1 /*
2    Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
3    Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2 of the License, or
8    (at your option) any later version.
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY.
11 
12    See the COPYING file for more details.
13 */
14 
15 /**
16  * @file
17  * Implementations of action WML tags, other than those implemented in Lua, and
18  * excluding conditional action WML.
19  */
20 
21 #include "game_events/action_wml.hpp"
22 #include "game_events/conditional_wml.hpp"
23 #include "game_events/pump.hpp"
24 
25 #include "actions/attack.hpp"
26 #include "actions/create.hpp"
27 #include "actions/move.hpp"
28 #include "actions/vision.hpp"
29 #include "ai/manager.hpp"
30 #include "fake_unit_ptr.hpp"
31 #include "filesystem.hpp"
32 #include "game_classification.hpp"
33 #include "game_display.hpp"
34 #include "preferences/game.hpp"
35 #include "gettext.hpp"
36 #include "gui/dialogs/transient_message.hpp"
37 #include "gui/widgets/retval.hpp"
38 #include "log.hpp"
39 #include "map/map.hpp"
40 #include "map/exception.hpp"
41 #include "map/label.hpp"
42 #include "pathfind/teleport.hpp"
43 #include "pathfind/pathfind.hpp"
44 #include "persist_var.hpp"
45 #include "play_controller.hpp"
46 #include "recall_list_manager.hpp"
47 #include "replay.hpp"
48 #include "random.hpp"
49 #include "mouse_handler_base.hpp" // for events::commands_disabled
50 #include "resources.hpp"
51 #include "scripting/game_lua_kernel.hpp"
52 #include "side_filter.hpp"
53 #include "soundsource.hpp"
54 #include "synced_context.hpp"
55 #include "synced_user_choice.hpp"
56 #include "team.hpp"
57 #include "terrain/filter.hpp"
58 #include "units/unit.hpp"
59 #include "units/animation_component.hpp"
60 #include "units/udisplay.hpp"
61 #include "units/filter.hpp"
62 #include "wml_exception.hpp"
63 #include "whiteboard/manager.hpp"
64 
65 #include <boost/regex.hpp>
66 
67 static lg::log_domain log_engine("engine");
68 #define DBG_NG LOG_STREAM(debug, log_engine)
69 #define LOG_NG LOG_STREAM(info, log_engine)
70 #define WRN_NG LOG_STREAM(warn, log_engine)
71 #define ERR_NG LOG_STREAM(err, log_engine)
72 
73 static lg::log_domain log_display("display");
74 #define DBG_DP LOG_STREAM(debug, log_display)
75 #define LOG_DP LOG_STREAM(info, log_display)
76 
77 static lg::log_domain log_wml("wml");
78 #define LOG_WML LOG_STREAM(info, log_wml)
79 #define WRN_WML LOG_STREAM(warn, log_wml)
80 #define ERR_WML LOG_STREAM(err, log_wml)
81 
82 static lg::log_domain log_config("config");
83 #define ERR_CF LOG_STREAM(err, log_config)
84 
85 
86 // This file is in the game_events namespace.
87 namespace game_events
88 {
89 
90 // This must be defined before any WML actions are.
91 // (So keep it at the rop of this file?)
92 wml_action::map wml_action::registry_;
93 
94 namespace { // Support functions
95 
96 	/**
97 	 * Converts a vconfig to a location (based on x,y=).
98 	 * The default parameter values cause the default return value (if neither
99 	 * x nor y is specified) to equal map_location::null_location().
100 	 */
cfg_to_loc(const vconfig & cfg,int defaultx=-999,int defaulty=-999)101 	map_location cfg_to_loc(const vconfig& cfg, int defaultx = -999, int defaulty = -999)
102 	{
103 		return map_location(cfg["x"].to_int(defaultx), cfg["y"].to_int(defaulty), wml_loc());
104 	}
105 
create_fake_unit(const vconfig & cfg)106 	fake_unit_ptr create_fake_unit(const vconfig& cfg)
107 	{
108 		std::string type = cfg["type"];
109 		std::string variation = cfg["variation"];
110 		std::string img_mods = cfg["image_mods"];
111 
112 		size_t side_num = cfg["side"].to_int(1);
113 		if (!resources::gameboard->has_team(side_num)) {
114 			side_num = 1;
115 		}
116 
117 		unit_race::GENDER gender = string_gender(cfg["gender"]);
118 		const unit_type *ut = unit_types.find(type);
119 		if (!ut) return fake_unit_ptr();
120 		fake_unit_ptr fake = fake_unit_ptr(unit::create(*ut, side_num, false, gender));
121 
122 		if(!variation.empty()) {
123 			config mod;
124 			config &effect = mod.add_child("effect");
125 			effect["apply_to"] = "variation";
126 			effect["name"] = variation;
127 			fake->add_modification("variation",mod);
128 		}
129 
130 		if(!img_mods.empty()) {
131 			config mod;
132 			config &effect = mod.add_child("effect");
133 			effect["apply_to"] = "image_mod";
134 			effect["add"] = img_mods;
135 			fake->add_modification("image_mod",mod);
136 		}
137 
138 		return fake;
139 	}
140 
fake_unit_path(const unit & fake_unit,const std::vector<std::string> & xvals,const std::vector<std::string> & yvals)141 	std::vector<map_location> fake_unit_path(const unit& fake_unit, const std::vector<std::string>& xvals, const std::vector<std::string>& yvals)
142 	{
143 		const gamemap *game_map = & resources::gameboard->map();
144 		std::vector<map_location> path;
145 		map_location src;
146 		map_location dst;
147 		for(size_t i = 0; i != std::min(xvals.size(),yvals.size()); ++i) {
148 			if(i==0){
149 				try {
150 					src.set_wml_x(std::stoi(xvals[i]));
151 					src.set_wml_y(std::stoi(yvals[i]));
152 				} catch(std::invalid_argument&) {
153 					ERR_CF << "Invalid move_unit_fake source: " << xvals[i] << ", " << yvals[i] << '\n';
154 					continue;
155 				}
156 				if (!game_map->on_board(src)) {
157 					ERR_CF << "Invalid move_unit_fake source: " << src << '\n';
158 					break;
159 				}
160 				path.push_back(src);
161 				continue;
162 			}
163 			pathfind::shortest_path_calculator calc(fake_unit,
164 					resources::gameboard->get_team(fake_unit.side()),
165 					resources::gameboard->teams(),
166 					*game_map);
167 
168 			try {
169 				dst.set_wml_x(std::stoi(xvals[i]));
170 				dst.set_wml_y(std::stoi(yvals[i]));
171 			} catch(std::invalid_argument&) {
172 				ERR_CF << "Invalid move_unit_fake destination: " << xvals[i] << ", " << yvals[i] << '\n';
173 			}
174 			if (!game_map->on_board(dst)) {
175 				ERR_CF << "Invalid move_unit_fake destination: " << dst << '\n';
176 				break;
177 			}
178 
179 			pathfind::plain_route route = pathfind::a_star_search(src, dst, 10000, calc,
180 				game_map->w(), game_map->h());
181 
182 			if (route.steps.empty()) {
183 				WRN_NG << "Could not find move_unit_fake route from " << src << " to " << dst << ": ignoring complexities" << std::endl;
184 				pathfind::emergency_path_calculator emergency_calc(fake_unit, *game_map);
185 
186 				route = pathfind::a_star_search(src, dst, 10000, emergency_calc,
187 						game_map->w(), game_map->h());
188 				if(route.steps.empty()) {
189 					// This would occur when trying to do a MUF of a unit
190 					// over locations which are unreachable to it (infinite movement
191 					// costs). This really cannot fail.
192 					WRN_NG << "Could not find move_unit_fake route from " << src << " to " << dst << ": ignoring terrain" << std::endl;
193 					pathfind::dummy_path_calculator dummy_calc(fake_unit, *game_map);
194 					route = a_star_search(src, dst, 10000, dummy_calc, game_map->w(), game_map->h());
195 					assert(!route.steps.empty());
196 				}
197 			}
198 			// we add this section to the end of the complete path
199 			// skipping section's head because already included
200 			// by the previous iteration
201 			path.insert(path.end(),
202 					route.steps.begin()+1, route.steps.end());
203 
204 			src = dst;
205 		}
206 		return path;
207 	}
208 } // end anonymous namespace (support functions)
209 
210 /**
211  * Using this constructor for a static object outside action_wml.cpp
212  * will likely lead to a static initialization fiasco.
213  * @param[in]  tag       The WML tag for this action.
214  * @param[in]  function  The callback for this action.
215  */
wml_action(const std::string & tag,handler function)216 wml_action::wml_action(const std::string & tag, handler function)
217 {
218 	registry_[tag] = function;
219 }
220 
221 
222 /**
223  * WML_HANDLER_FUNCTION macro handles auto registration for wml handlers
224  *
225  * @param pname wml tag name
226  * @param pei the variable name of the queued_event object inside the function
227  * @param pcfg the variable name of the config object inside the function
228  *
229  * You are warned! This is evil macro magic!
230  *
231  * The following code registers a [foo] tag:
232  * \code
233  * // comment out unused parameters to prevent compiler warnings
234  * WML_HANDLER_FUNCTION(foo, event_info, cfg)
235  * {
236  *    // code for foo
237  * }
238  * \endcode
239  *
240  * Generated code looks like this:
241  * \code
242  * void wml_func_foo(...);
243  * static wml_action wml_action_foo("foo", &wml_func_foo);
244  * void wml_func_foo(...)
245  * {
246  *    // code for foo
247  * }
248  * \endcode
249  */
250 #define WML_HANDLER_FUNCTION(pname, pei, pcfg) \
251 	static void wml_func_##pname(const queued_event &pei, const vconfig &pcfg); \
252 	static wml_action wml_action_##pname(#pname, &wml_func_##pname);  \
253 	static void wml_func_##pname(const queued_event& pei, const vconfig& pcfg)
254 
255 
256 /// Experimental data persistence
257 /// @todo Finish experimenting.
258 WML_HANDLER_FUNCTION(clear_global_variable,,pcfg)
259 {
260 	if (!resources::controller->is_replay())
261 		verify_and_clear_global_variable(pcfg);
262 }
263 
on_replay_error(const std::string & message,bool)264 static void on_replay_error(const std::string& message, bool /*b*/)
265 {
266 	ERR_NG << "Error via [do_command]:" << std::endl;
267 	ERR_NG << message << std::endl;
268 }
269 
270 // This tag exposes part of the code path used to handle [command]'s in replays
271 // This allows to perform scripting in WML that will use the same code path as player actions, for example.
272 WML_HANDLER_FUNCTION(do_command,, cfg)
273 {
274 	// Doing this in a whiteboard applied context will cause bugs
275 	// Note that even though game_events::wml_event_pump() will always apply the real unit map
276 	// It is still possible get a wml commands to run in a whiteboard applied context
277 	// With the theme_items lua callbacks
278 	if(resources::whiteboard->has_planned_unit_map())
279 	{
280 		ERR_NG << "[do_command] called while whiteboard is applied, ignoring" << std::endl;
281 		return;
282 	}
283 
284 	static const std::set<std::string> allowed_tags {"attack", "move", "recruit", "recall", "disband", "fire_event", "custom_command"};
285 
286 	const bool is_too_early = resources::gamedata->phase() != game_data::START && resources::gamedata->phase() != game_data::PLAY;
287 	const bool is_unsynced_too_early = resources::gamedata->phase() != game_data::PLAY;
288 	const bool is_unsynced = synced_context::get_synced_state() == synced_context::UNSYNCED;
289 	if(is_too_early)
290 	{
291 		ERR_NG << "[do_command] called too early, only allowed at START or later" << std::endl;
292 		return;
293 	}
294 	if(is_unsynced && resources::controller->is_lingering())
295 	{
296 		ERR_NG << "[do_command] cannot be used in linger mode" << std::endl;
297 		return;
298 	}
299 	if(is_unsynced && !resources::controller->gamestate().init_side_done())
300 	{
301 		ERR_NG << "[do_command] cannot be used before the turn has started" << std::endl;
302 		return;
303 	}
304 	if(is_unsynced && is_unsynced_too_early)
305 	{
306 		ERR_NG << "[do_command] called too early" << std::endl;
307 		return;
308 	}
309 	if(is_unsynced && events::commands_disabled)
310 	{
311 		ERR_NG << "[do_command] cannot invoke synced commands while commands are blocked" << std::endl;
312 		return;
313 	}
314 	if(is_unsynced && !resources::controller->current_team().is_local())
315 	{
316 		ERR_NG << "[do_command] can only be used from clients that control the currently playing side" << std::endl;
317 		return;
318 	}
319 	for(vconfig::all_children_iterator i = cfg.ordered_begin(); i != cfg.ordered_end(); ++i)
320 	{
321 		if(allowed_tags.find( i.get_key()) == allowed_tags.end()) {
322 			ERR_NG << "unsupported tag [" << i.get_key() << "] in [do_command]" << std::endl;
323 			std::stringstream o;
324 			std::copy(allowed_tags.begin(), allowed_tags.end(), std::ostream_iterator<std::string>(o, " "));
325 			ERR_NG << "allowed tags: " << o.str() << std::endl;
326 			continue;
327 		}
328 		// TODO: afaik run_in_synced_context_if_not_already thows exceptions when the executed action end the scenario or the turn.
329 		//       This could cause problems, specially when its unclear whether that exception is caught by lua or not...
330 
331 		//Note that this fires related events and everything else that also happens normally.
332 		//have to watch out with the undo stack, therefore forbid [auto_shroud] and [update_shroud] here...
333 		synced_context::run_in_synced_context_if_not_already(
334 			/*commandname*/ i.get_key(),
335 			/*data*/ i.get_child().get_parsed_config(),
336 			/*use_undo*/ true,
337 			/*show*/ true,
338 			/*error_handler*/ &on_replay_error
339 		);
340 	}
341 }
342 
343 /// Experimental data persistence
344 /// @todo Finish experimenting.
345 WML_HANDLER_FUNCTION(get_global_variable,,pcfg)
346 {
347 	verify_and_get_global_variable(pcfg);
348 }
349 
350 WML_HANDLER_FUNCTION(modify_turns,, cfg)
351 {
352 	config::attribute_value value = cfg["value"];
353 	std::string add = cfg["add"];
354 	config::attribute_value current = cfg["current"];
355 	tod_manager& tod_man = *resources::tod_manager;
356 	if(!add.empty()) {
357 		tod_man.modify_turns_by_wml(add);
358 	} else if(!value.empty()) {
359 		tod_man.set_number_of_turns_by_wml(value.to_int(-1));
360 	}
361 	// change current turn only after applying mods
362 	if(!current.empty()) {
363 		const unsigned int current_turn_number = tod_man.turn();
364 		int new_turn_number = current.to_int(current_turn_number);
365 		const unsigned int new_turn_number_u = static_cast<unsigned int>(new_turn_number);
366 		if(new_turn_number_u < 1 || (new_turn_number > tod_man.number_of_turns() && tod_man.number_of_turns() != -1)) {
367 			ERR_NG << "attempted to change current turn number to one out of range (" << new_turn_number << ")" << std::endl;
368 		} else if(new_turn_number_u != current_turn_number) {
369 			tod_man.set_turn_by_wml(new_turn_number_u, resources::gamedata);
370 			game_display::get_singleton()->new_turn();
371 		}
372 	}
373 }
374 
375 /// Moving a 'unit' - i.e. a dummy unit
376 /// that is just moving for the visual effect
377 WML_HANDLER_FUNCTION(move_unit_fake,, cfg)
378 {
379 	events::command_disabler command_disabler;
380 	fake_unit_ptr dummy_unit(create_fake_unit(cfg));
381 	if(!dummy_unit.get())
382 		return;
383 
384 	const bool force_scroll = cfg["force_scroll"].to_bool(true);
385 
386 	const std::string x = cfg["x"];
387 	const std::string y = cfg["y"];
388 
389 	const std::vector<std::string> xvals = utils::split(x);
390 	const std::vector<std::string> yvals = utils::split(y);
391 
392 	const std::vector<map_location>& path = fake_unit_path(*dummy_unit, xvals, yvals);
393 	if (!path.empty()) {
394 		// Always scroll.
395 		unit_display::move_unit(path, dummy_unit.get_unit_ptr(), true, map_location::NDIRECTIONS, force_scroll);
396 	}
397 }
398 
399 WML_HANDLER_FUNCTION(move_units_fake,, cfg)
400 {
401 	events::command_disabler command_disabler;
402 	LOG_NG << "Processing [move_units_fake]\n";
403 
404 	const vconfig::child_list unit_cfgs = cfg.get_children("fake_unit");
405 	size_t num_units = unit_cfgs.size();
406 	std::vector<fake_unit_ptr > units;
407 	units.reserve(num_units);
408 	std::vector<std::vector<map_location>> paths;
409 	paths.reserve(num_units);
410 
411 	LOG_NG << "Moving " << num_units << " units\n";
412 
413 	size_t longest_path = 0;
414 
415 	for (const vconfig& config : unit_cfgs) {
416 		const std::vector<std::string> xvals = utils::split(config["x"]);
417 		const std::vector<std::string> yvals = utils::split(config["y"]);
418 		int skip_steps = config["skip_steps"];
419 		fake_unit_ptr u = create_fake_unit(config);
420 		units.push_back(u);
421 		paths.push_back(fake_unit_path(*u, xvals, yvals));
422 		if(skip_steps > 0)
423 			paths.back().insert(paths.back().begin(), skip_steps, paths.back().front());
424 		longest_path = std::max(longest_path, paths.back().size());
425 		DBG_NG << "Path " << paths.size() - 1 << " has length " << paths.back().size() << '\n';
426 
427 		u->set_location(paths.back().front());
428 		units.back().place_on_fake_unit_manager(resources::fake_units);
429 	}
430 
431 	LOG_NG << "Units placed, longest path is " << longest_path << " long\n";
432 
433 	std::vector<map_location> path_step(2);
434 	path_step.resize(2);
435 	for(size_t step = 1; step < longest_path; ++step) {
436 		DBG_NG << "Doing step " << step << "...\n";
437 		for(size_t un = 0; un < num_units; ++un) {
438 			if(step >= paths[un].size() || paths[un][step - 1] == paths[un][step])
439 				continue;
440 			DBG_NG << "Moving unit " << un << ", doing step " << step << '\n';
441 			path_step[0] = paths[un][step - 1];
442 			path_step[1] = paths[un][step];
443 			unit_display::move_unit(path_step, units[un].get_unit_ptr());
444 			units[un]->set_location(path_step[1]);
445 			units[un]->anim_comp().set_standing(false);
446 		}
447 	}
448 
449 	LOG_NG << "Units moved\n";
450 }
451 
452 /// If we should recall units that match a certain description.
453 // If you change attributes specific to [recall] (that is, not a Standard Unit Filter)
454 // be sure to update data/lua/wml_tag, auto_recall feature for [role] to reflect your changes.
455 WML_HANDLER_FUNCTION(recall,, cfg)
456 {
457 	events::command_disabler command_disabler;
458 	LOG_NG << "recalling unit...\n";
459 	config temp_config(cfg.get_config());
460 	// Prevent the recall unit filter from using the location as a criterion
461 
462 	/**
463 	 * @todo FIXME: we should design the WML to avoid these types of
464 	 * collisions; filters should be named consistently and always have a
465 	 * distinct scope.
466 	 */
467 	temp_config["x"] = "recall";
468 	temp_config["y"] = "recall";
469 	vconfig unit_filter_cfg(temp_config);
470 	const vconfig & leader_filter = cfg.child("secondary_unit");
471 
472 	for(int index = 0; index < static_cast<int>(resources::gameboard->teams().size()); ++index) {
473 		LOG_NG << "for side " << index + 1 << "...\n";
474 		const std::string player_id = resources::gameboard->teams()[index].save_id_or_number();
475 
476 		if(resources::gameboard->teams()[index].recall_list().size() < 1) {
477 			DBG_NG << "recall list is empty when trying to recall!\n"
478 				   << "player_id: " << player_id << " side: " << index+1 << "\n";
479 			continue;
480 		}
481 
482 		recall_list_manager & avail = resources::gameboard->teams()[index].recall_list();
483 		std::vector<unit_map::unit_iterator> leaders = resources::gameboard->units().find_leaders(index + 1);
484 
485 		const unit_filter ufilt(unit_filter_cfg);
486 		const unit_filter lfilt(leader_filter); // Note that if leader_filter is null, this correctly gives a null filter that matches all units.
487 		for(std::vector<unit_ptr>::iterator u = avail.begin(); u != avail.end(); ++u) {
488 			DBG_NG << "checking unit against filter...\n";
489 			scoped_recall_unit auto_store("this_unit", player_id, std::distance(avail.begin(), u));
490 			if (ufilt(*(*u), map_location())) {
491 				DBG_NG << (*u)->id() << " matched the filter...\n";
492 				const unit_ptr to_recruit = *u;
493 				const unit* pass_check = to_recruit.get();
494 				if(!cfg["check_passability"].to_bool(true)) pass_check = nullptr;
495 				const map_location cfg_loc = cfg_to_loc(cfg);
496 
497 				/// @todo fendrin: comment this monster
498 				for (unit_map::const_unit_iterator leader : leaders) {
499 					DBG_NG << "...considering " + leader->id() + " as the recalling leader...\n";
500 					map_location loc = cfg_loc;
501 					if ( lfilt(*leader)  &&
502 					     unit_filter(vconfig(leader->recall_filter())).matches( *(*u),map_location() ) ) {
503 						DBG_NG << "...matched the leader filter and is able to recall the unit.\n";
504 						if(!resources::gameboard->map().on_board(loc))
505 							loc = leader->get_location();
506 						if(pass_check || (resources::gameboard->units().count(loc) > 0))
507 							loc = pathfind::find_vacant_tile(loc, pathfind::VACANT_ANY, pass_check);
508 						if(resources::gameboard->map().on_board(loc)) {
509 							DBG_NG << "...valid location for the recall found. Recalling.\n";
510 							avail.erase(u);	// Erase before recruiting, since recruiting can fire more events
511 							actions::place_recruit(to_recruit, loc, leader->get_location(), 0, true,
512 							                       map_location::parse_direction(cfg["facing"]),
513 							                       cfg["show"].to_bool(true), cfg["fire_event"].to_bool(false),
514 							                       true, true);
515 							return;
516 						}
517 					}
518 				}
519 				if (resources::gameboard->map().on_board(cfg_loc)) {
520 					map_location loc = cfg_loc;
521 					if(pass_check || (resources::gameboard->units().count(loc) > 0))
522 						loc = pathfind::find_vacant_tile(loc, pathfind::VACANT_ANY, pass_check);
523 					// Check if we still have a valid location
524 					if (resources::gameboard->map().on_board(loc)) {
525 						DBG_NG << "No usable leader found, but found usable location. Recalling.\n";
526 						avail.erase(u);	// Erase before recruiting, since recruiting can fire more events
527 						map_location null_location = map_location::null_location();
528 						actions::place_recruit(to_recruit, loc, null_location, 0, true,
529 						                       map_location::parse_direction(cfg["facing"]),
530 						                       cfg["show"].to_bool(true), cfg["fire_event"].to_bool(false),
531 						                       true, true);
532 						return;
533 					}
534 				}
535 			}
536 		}
537 	}
538 	LOG_WML << "A [recall] tag with the following content failed:\n" << cfg.get_config().debug();
539 }
540 
541 namespace {
542 	struct map_choice : public mp_sync::user_choice
543 	{
map_choicegame_events::__anona619c90d0211::map_choice544 		map_choice(const std::string& filename) : filename_(filename) {}
545 		std::string filename_;
query_usergame_events::__anona619c90d0211::map_choice546 		virtual config query_user(int /*side*/) const
547 		{
548 			//Do a regex check for the file format to prevent sending arbitrary files to other clients.
549 			//Note: this allows only the new format.
550 			static const std::string s_simple_terrain = R"""([A-Za-z\\|/]{1,4})""";
551 			static const std::string s_terrain = s_simple_terrain + R"""((\^)""" + s_simple_terrain + ")?";
552 			static const std::string s_sep = "(, |\\n)";
553 			static const std::string s_prefix = R"""((\d+ )?)""";
554 			static const std::string s_all = "(" + s_prefix + s_terrain + s_sep + ")+";
555 			static const boost::regex r_all(s_all);
556 
557 			const std::string& mapfile = filesystem::get_wml_location(filename_);
558 			std::string res = "";
559 			if(filesystem::file_exists(mapfile)) {
560 				res = filesystem::read_file(mapfile);
561 			}
562 			config retv;
563 			if(boost::regex_match(res, r_all))
564 			{
565 				retv["map_data"] = res;
566 			}
567 			return retv;
568 		}
random_choicegame_events::__anona619c90d0211::map_choice569 		virtual config random_choice(int /*side*/) const
570 		{
571 			return config();
572 		}
descriptiongame_events::__anona619c90d0211::map_choice573 		virtual std::string description() const
574 		{
575 			return "Map Data";
576 		}
577 
578 	};
579 }
580 
581 /// Experimental map replace
582 /// @todo Finish experimenting.
583 WML_HANDLER_FUNCTION(replace_map,, cfg)
584 {
585 	/*
586 	 * When a hex changes from a village terrain to a non-village terrain, and
587 	 * a team owned that village it loses that village. When a hex changes from
588 	 * a non-village terrain to a village terrain and there is a unit on that
589 	 * hex it does not automatically capture the village. The reason for not
590 	 * capturing villages it that there are too many choices to make; should a
591 	 * unit loose its movement points, should capture events be fired. It is
592 	 * easier to do this as wanted by the author in WML.
593 	 */
594 
595 	const gamemap * game_map = & resources::gameboard->map();
596 	gamemap map(*game_map);
597 
598 	try {
599 		if(!cfg["map_file"].empty()) {
600 			config file_cfg = mp_sync::get_user_choice("map_data", map_choice(cfg["map_file"].str()));
601 			map.read(file_cfg["map_data"].str(), false);
602 		} else {
603 			map.read(cfg["map"], false);
604 		}
605 	} catch(const incorrect_map_format_error&) {
606 		const std::string log_map_name = cfg["map"].empty() ? cfg["file"] : std::string("from inline data");
607 		lg::wml_error() << "replace_map: Unable to load map " << log_map_name << std::endl;
608 		return;
609 	} catch(const wml_exception& e) {
610 		e.show();
611 		return;
612 	}
613 
614 	if (map.total_width() > game_map->total_width()
615 	|| map.total_height() > game_map->total_height()) {
616 		if (!cfg["expand"].to_bool()) {
617 			lg::wml_error() << "replace_map: Map dimension(s) increase but expand is not set" << std::endl;
618 			return;
619 		}
620 	}
621 
622 	if (map.total_width() < game_map->total_width()
623 	|| map.total_height() < game_map->total_height()) {
624 		if (!cfg["shrink"].to_bool()) {
625 			lg::wml_error() << "replace_map: Map dimension(s) decrease but shrink is not set" << std::endl;
626 			return;
627 		}
628 	}
629 
630 	boost::optional<std::string> errmsg = resources::gameboard->replace_map(map);
631 
632 	if (errmsg) {
633 		lg::wml_error() << *errmsg << std::endl;
634 	}
635 
636 	display::get_singleton()->reload_map();
637 	game_display::get_singleton()->needs_rebuild(true);
638 	ai::manager::get_singleton().raise_map_changed();
639 }
640 
641 /// Experimental data persistence
642 /// @todo Finish experimenting.
643 WML_HANDLER_FUNCTION(set_global_variable,,pcfg)
644 {
645 	if (!resources::controller->is_replay())
646 		verify_and_set_global_variable(pcfg);
647 }
648 
649 WML_HANDLER_FUNCTION(set_variables,, cfg)
650 {
651 	const t_string& name = cfg["name"];
652 	variable_access_create dest = resources::gamedata->get_variable_access_write(name);
653 	if(name.empty()) {
654 		ERR_NG << "trying to set a variable with an empty name:\n" << cfg.get_config().debug();
655 		return;
656 	}
657 
658 	std::vector<config> data;
659 	if(cfg.has_attribute("to_variable"))
660 	{
661 		try
662 		{
663 			variable_access_const tovar = resources::gamedata->get_variable_access_read(cfg["to_variable"]);
664 			for (const config& c : tovar.as_array())
665 			{
666 				data.push_back(c);
667 			}
668 		}
669 		catch(const invalid_variablename_exception&)
670 		{
671 			ERR_NG << "Cannot do [set_variables] with invalid to_variable variable: " << cfg["to_variable"] << " with " << cfg.get_config().debug() << std::endl;
672 		}
673 	} else {
674 		typedef std::pair<std::string, vconfig> vchild;
675 		for (const vchild& p : cfg.all_ordered()) {
676 			if(p.first == "value") {
677 				data.push_back(p.second.get_parsed_config());
678 			} else if(p.first == "literal") {
679 				data.push_back(p.second.get_config());
680 			} else if(p.first == "split") {
681 				const vconfig & split_element = p.second;
682 
683 				std::string split_string=split_element["list"];
684 				std::string separator_string=split_element["separator"];
685 				std::string key_name=split_element["key"];
686 				if(key_name.empty())
687 				{
688 					key_name="value";
689 				}
690 
691 				bool remove_empty = split_element["remove_empty"].to_bool();
692 
693 				char* separator = separator_string.empty() ? nullptr : &separator_string[0];
694 				if(separator_string.size() > 1){
695 					ERR_NG << "[set_variables] [split] separator only supports 1 character, multiple passed: " << split_element["separator"] << " with " << cfg.get_config().debug() << std::endl;
696 				}
697 
698 				std::vector<std::string> split_vector;
699 
700 				//if no separator is specified, explode the string
701 				if(separator == nullptr)
702 				{
703 					for(std::string::iterator i=split_string.begin(); i!=split_string.end(); ++i)
704 					{
705 						split_vector.push_back(std::string(1, *i));
706 					}
707 				}
708 				else {
709 					split_vector=utils::split(split_string, *separator, remove_empty ? utils::REMOVE_EMPTY | utils::STRIP_SPACES : utils::STRIP_SPACES);
710 				}
711 
712 				for(std::vector<std::string>::iterator i=split_vector.begin(); i!=split_vector.end(); ++i)
713 				{
714 					data.emplace_back(key_name, *i);
715 				}
716 			}
717 		}
718 	}
719 	try
720 	{
721 		const std::string& mode = cfg["mode"];
722 		if(mode == "merge")
723 		{
724 			if(dest.explicit_index() && data.size() > 1)
725 			{
726 				//merge children into one
727 				config merged_children;
728 				for (const config &ch : data) {
729 					merged_children.append(ch);
730 				}
731 				data = {merged_children};
732 			}
733 			dest.merge_array(data);
734 		}
735 		else if(mode == "insert")
736 		{
737 			dest.insert_array(data);
738 		}
739 		else if(mode == "append")
740 		{
741 			dest.append_array(data);
742 		}
743 		else /*default if(mode == "replace")*/
744 		{
745 			dest.replace_array(data);
746 		}
747 	}
748 	catch(const invalid_variablename_exception&)
749 	{
750 		ERR_NG << "Cannot do [set_variables] with invalid destination variable: " << name << " with " << cfg.get_config().debug() << std::endl;
751 	}
752 }
753 
754 /// Store the relative direction from one hex to another in a WML variable.
755 /// This is mainly useful as a diagnostic tool, but could be useful
756 /// for some kind of scenario.
757 WML_HANDLER_FUNCTION(store_relative_direction,, cfg)
758 {
759 	if (!cfg.child("source")) {
760 		WRN_NG << "No source in [store_relative_direction]" << std::endl;
761 		return;
762 	}
763 	if (!cfg.child("destination")) {
764 		WRN_NG << "No destination in [store_relative_direction]" << std::endl;
765 		return;
766 	}
767 	if (!cfg.has_attribute("variable")) {
768 		WRN_NG << "No variable in [store_relative_direction]" << std::endl;
769 		return;
770 	}
771 
772 	const map_location src = cfg_to_loc(cfg.child("source"));
773 	const map_location dst = cfg_to_loc(cfg.child("destination"));
774 
775 	std::string variable = cfg["variable"];
776 	map_location::RELATIVE_DIR_MODE mode = static_cast<map_location::RELATIVE_DIR_MODE> (cfg["mode"].to_int(0));
777 	try
778 	{
779 		variable_access_create store = resources::gamedata->get_variable_access_write(variable);
780 
781 		store.as_scalar() = map_location::write_direction(src.get_relative_dir(dst,mode));
782 	}
783 	catch(const invalid_variablename_exception&)
784 	{
785 		ERR_NG << "Cannot do [store_relative_direction] with invalid destination variable: " << variable << " with " << cfg.get_config().debug() << std::endl;
786 	}
787 }
788 
789 /// Store the rotation of one hex around another in a WML variable.
790 /// In increments of 60 degrees, clockwise.
791 /// This is mainly useful as a diagnostic tool, but could be useful
792 /// for some kind of scenario.
793 WML_HANDLER_FUNCTION(store_rotate_map_location,, cfg)
794 {
795 	if (!cfg.child("source")) {
796 		WRN_NG << "No source in [store_rotate_map_location]" << std::endl;
797 		return;
798 	}
799 	if (!cfg.child("destination")) {
800 		WRN_NG << "No destination in [store_rotate_map_location]" << std::endl;
801 		return;
802 	}
803 	if (!cfg.has_attribute("variable")) {
804 		WRN_NG << "No variable in [store_rotate_map_location]" << std::endl;
805 		return;
806 	}
807 
808 	const map_location src = cfg_to_loc(cfg.child("source"));
809 	const map_location dst = cfg_to_loc(cfg.child("destination"));
810 
811 	std::string variable = cfg["variable"];
812 	int angle = cfg["angle"].to_int(1);
813 
814 	try
815 	{
816 		variable_access_create store = resources::gamedata->get_variable_access_write(variable);
817 
818 		dst.rotate_right_around_center(src,angle).write(store.as_container());
819 	}
820 	catch(const invalid_variablename_exception&)
821 	{
822 		ERR_NG << "Cannot do [store_rotate_map_location] with invalid destination variable: " << variable << " with " << cfg.get_config().debug() << std::endl;
823 	}
824 }
825 
826 
827 /// Store time of day config in a WML variable. This is useful for those who
828 /// are too lazy to calculate the corresponding time of day for a given turn,
829 /// or if the turn / time-of-day sequence mutates in a scenario.
830 WML_HANDLER_FUNCTION(store_time_of_day,, cfg)
831 {
832 	const map_location loc = cfg_to_loc(cfg);
833 	int turn = cfg["turn"];
834 	// using 0 will use the current turn
835 	const time_of_day& tod = resources::tod_manager->get_time_of_day(loc,turn);
836 
837 	std::string variable = cfg["variable"];
838 	if(variable.empty()) {
839 		variable = "time_of_day";
840 	}
841 	try
842 	{
843 		variable_access_create store = resources::gamedata->get_variable_access_write(variable);
844 		tod.write(store.as_container());
845 	}
846 	catch(const invalid_variablename_exception&)
847 	{
848 		ERR_NG << "Found invalid variablename " << variable << " in [store_time_of_day] with " << cfg.get_config().debug() << "\n";
849 	}
850 }
851 
852 /// Creating a mask of the terrain
853 WML_HANDLER_FUNCTION(terrain_mask,, cfg)
854 {
855 	map_location loc = cfg_to_loc(cfg, 1, 1);
856 
857 	gamemap mask_map(resources::gameboard->map().tdata(), "");
858 
859 	try {
860 		if(!cfg["mask_file"].empty()) {
861 			const std::string& maskfile = filesystem::get_wml_location(cfg["mask_file"].str());
862 
863 			if(filesystem::file_exists(maskfile)) {
864 				mask_map.read(filesystem::read_file(maskfile), false);
865 			} else {
866 				throw incorrect_map_format_error("Invalid file path");
867 			}
868 		} else {
869 			mask_map.read(cfg["mask"], false);
870 		}
871 	} catch(const incorrect_map_format_error&) {
872 		ERR_NG << "terrain mask is in the incorrect format, and couldn't be applied" << std::endl;
873 		return;
874 	} catch(const wml_exception& e) {
875 		e.show();
876 		return;
877 	}
878 
879 	if (!cfg["border"].to_bool(true)) {
880 		mask_map.add_fog_border();
881 	}
882 
883 	resources::gameboard->overlay_map(mask_map, cfg.get_parsed_config(), loc);
884 	game_display::get_singleton()->needs_rebuild(true);
885 }
886 
887 WML_HANDLER_FUNCTION(tunnel,, cfg)
888 {
889 	const bool remove = cfg["remove"].to_bool(false);
890 	const bool delay = cfg["delayed_variable_substitution"].to_bool(true);
891 	if (remove) {
892 		const std::vector<std::string> ids = utils::split(cfg["id"]);
893 		for (const std::string &id : ids) {
894 			resources::tunnels->remove(id);
895 		}
896 	} else if (cfg.get_children("source").empty() ||
897 		cfg.get_children("target").empty() ||
898 		cfg.get_children("filter").empty()) {
899 		ERR_WML << "[tunnel] is missing a mandatory tag:\n"
900 			 << cfg.get_config().debug();
901 	} else {
902 		pathfind::teleport_group tunnel(delay ? cfg : vconfig(cfg.get_parsed_config()), false);
903 		resources::tunnels->add(tunnel);
904 
905 		if(cfg["bidirectional"].to_bool(true)) {
906 			tunnel = pathfind::teleport_group(delay ? cfg : vconfig(cfg.get_parsed_config()), true);
907 			resources::tunnels->add(tunnel);
908 		}
909 	}
910 }
911 
912 /// If we should spawn a new unit on the map somewhere
913 WML_HANDLER_FUNCTION(unit,, cfg)
914 {
915 	events::command_disabler command_disabler;
916 	config parsed_cfg = cfg.get_parsed_config();
917 
918 	config::attribute_value to_variable = cfg["to_variable"];
919 	if (!to_variable.blank())
920 	{
921 		parsed_cfg.remove_attribute("to_variable");
922 		unit_ptr new_unit = unit::create(parsed_cfg, true, &cfg);
923 		try
924 		{
925 			config &var = resources::gamedata->get_variable_cfg(to_variable);
926 			var.clear();
927 			new_unit->write(var);
928 			if (const config::attribute_value *v = parsed_cfg.get("x")) var["x"] = *v;
929 			if (const config::attribute_value *v = parsed_cfg.get("y")) var["y"] = *v;
930 		}
931 		catch(const invalid_variablename_exception&)
932 		{
933 			ERR_NG << "Cannot do [unit] with invalid to_variable:  " << to_variable << " with " << cfg.get_config().debug() << std::endl;
934 		}
935 		return;
936 
937 	}
938 
939 	int side = parsed_cfg["side"].to_int(1);
940 
941 
942 	if ((side<1)||(side > static_cast<int>(resources::gameboard->teams().size()))) {
943 		ERR_NG << "wrong side in [unit] tag - no such side: "<<side<<" ( number of teams :"<<resources::gameboard->teams().size()<<")"<<std::endl;
944 		DBG_NG << parsed_cfg.debug();
945 		return;
946 	}
947 	team &tm = resources::gameboard->get_team(side);
948 
949 	unit_creator uc(tm,resources::gameboard->map().starting_position(side));
950 
951 	uc
952 		.allow_add_to_recall(true)
953 		.allow_discover(true)
954 		.allow_get_village(true)
955 		.allow_invalidate(true)
956 		.allow_rename_side(true)
957 		.allow_show(true);
958 
959 	uc.add_unit(parsed_cfg, &cfg);
960 
961 }
962 
WML_HANDLER_FUNCTION(on_undo,event_info,cfg)963 WML_HANDLER_FUNCTION(on_undo, event_info, cfg)
964 {
965 	if(cfg["delayed_variable_substitution"].to_bool(false)) {
966 		synced_context::add_undo_commands(cfg.get_config(), event_info);
967 	} else {
968 		synced_context::add_undo_commands(cfg.get_parsed_config(), event_info);
969 	}
970 }
971 
972 } // end namespace game_events
973