The Battle for Wesnoth  1.17.0-dev
action_wml.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2021
3  by David White <dave@whitevine.net>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 /**
17  * @file
18  * Implementations of action WML tags, other than those implemented in Lua, and
19  * excluding conditional action WML.
20  */
21 
24 #include "game_events/pump.hpp"
25 
26 #include "actions/attack.hpp"
27 #include "actions/create.hpp"
28 #include "actions/move.hpp"
29 #include "actions/vision.hpp"
30 #include "ai/manager.hpp"
31 #include "fake_unit_ptr.hpp"
32 #include "filesystem.hpp"
33 #include "game_display.hpp"
34 #include "preferences/game.hpp"
35 #include "gettext.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"
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"
60 #include "units/udisplay.hpp"
61 #include "units/filter.hpp"
62 #include "units/types.hpp"
63 #include "wml_exception.hpp"
64 #include "whiteboard/manager.hpp"
65 #include "deprecation.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?)
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  */
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 
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  std::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 
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(std::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  */
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 /**
257  * Experimental data persistence
258  * @todo Finish experimenting.
259  */
261 {
262  if (!resources::controller->is_replay())
264 }
265 
266 static void on_replay_error(const std::string& message)
267 {
268  ERR_NG << "Error via [do_command]:" << std::endl;
269  ERR_NG << message << std::endl;
270 }
271 
272 // This tag exposes part of the code path used to handle [command]'s in replays
273 // This allows to perform scripting in WML that will use the same code path as player actions, for example.
274 WML_HANDLER_FUNCTION(do_command,, cfg)
275 {
276  // Doing this in a whiteboard applied context will cause bugs
277  // Note that even though game_events::wml_event_pump() will always apply the real unit map
278  // It is still possible get a wml commands to run in a whiteboard applied context
279  // With the theme_items lua callbacks
280  if(resources::whiteboard->has_planned_unit_map())
281  {
282  ERR_NG << "[do_command] called while whiteboard is applied, ignoring" << std::endl;
283  return;
284  }
285 
286  static const std::set<std::string> allowed_tags {"attack", "move", "recruit", "recall", "disband", "fire_event", "custom_command"};
287 
288  const bool is_too_early = resources::gamedata->phase() != game_data::START && resources::gamedata->phase() != game_data::PLAY;
289  const bool is_unsynced_too_early = resources::gamedata->phase() != game_data::PLAY;
290  const bool is_unsynced = synced_context::get_synced_state() == synced_context::UNSYNCED;
291  if(is_too_early)
292  {
293  ERR_NG << "[do_command] called too early, only allowed at START or later" << std::endl;
294  return;
295  }
296  if(is_unsynced && resources::controller->is_lingering())
297  {
298  ERR_NG << "[do_command] cannot be used in linger mode" << std::endl;
299  return;
300  }
301  if(is_unsynced && !resources::controller->gamestate().init_side_done())
302  {
303  ERR_NG << "[do_command] cannot be used before the turn has started" << std::endl;
304  return;
305  }
306  if(is_unsynced && is_unsynced_too_early)
307  {
308  ERR_NG << "[do_command] called too early" << std::endl;
309  return;
310  }
311  if(is_unsynced && events::commands_disabled)
312  {
313  ERR_NG << "[do_command] cannot invoke synced commands while commands are blocked" << std::endl;
314  return;
315  }
316  if(is_unsynced && !resources::controller->current_team().is_local())
317  {
318  ERR_NG << "[do_command] can only be used from clients that control the currently playing side" << std::endl;
319  return;
320  }
321  for(const auto& [key, child] : cfg.all_ordered())
322  {
323  if(allowed_tags.find(key) == allowed_tags.end()) {
324  ERR_NG << "unsupported tag [" << key << "] in [do_command]" << std::endl;
325  std::stringstream o;
326  std::copy(allowed_tags.begin(), allowed_tags.end(), std::ostream_iterator<std::string>(o, " "));
327  ERR_NG << "allowed tags: " << o.str() << std::endl;
328  continue;
329  }
330  // TODO: afaik run_in_synced_context_if_not_already thows exceptions when the executed action end the scenario or the turn.
331  // This could cause problems, specially when its unclear whether that exception is caught by lua or not...
332 
333  //Note that this fires related events and everything else that also happens normally.
334  //have to watch out with the undo stack, therefore forbid [auto_shroud] and [update_shroud] here...
336  /*commandname*/ key,
337  /*data*/ child.get_parsed_config(),
338  /*use_undo*/ true,
339  /*show*/ true,
340  /*error_handler*/ &on_replay_error
341  );
343  }
344 }
345 
346 /**
347  * Experimental data persistence
348  * @todo Finish experimenting.
349  */
351 {
353 }
354 
355 WML_HANDLER_FUNCTION(modify_turns,, cfg)
356 {
357  config::attribute_value value = cfg["value"];
358  std::string add = cfg["add"];
359  config::attribute_value current = cfg["current"];
361  if(!add.empty()) {
362  tod_man.modify_turns_by_wml(add);
363  } else if(!value.empty()) {
364  tod_man.set_number_of_turns_by_wml(value.to_int(-1));
365  }
366  // change current turn only after applying mods
367  if(!current.empty()) {
368  const unsigned int current_turn_number = tod_man.turn();
369  int new_turn_number = current.to_int(current_turn_number);
370  const unsigned int new_turn_number_u = static_cast<unsigned int>(new_turn_number);
371  if(new_turn_number_u < 1 || (new_turn_number > tod_man.number_of_turns() && tod_man.number_of_turns() != -1)) {
372  ERR_NG << "attempted to change current turn number to one out of range (" << new_turn_number << ")" << std::endl;
373  } else if(new_turn_number_u != current_turn_number) {
374  tod_man.set_turn_by_wml(new_turn_number_u, resources::gamedata);
376  }
377  }
378 }
379 
380 /**
381  * Moving a 'unit' - i.e. a dummy unit
382  * that is just moving for the visual effect
383  */
384 WML_HANDLER_FUNCTION(move_unit_fake,, cfg)
385 {
387  events::command_disabler command_disabler;
388  fake_unit_ptr dummy_unit(create_fake_unit(cfg));
389  if(!dummy_unit.get())
390  return;
391 
392  const bool force_scroll = cfg["force_scroll"].to_bool(true);
393 
394  const std::string x = cfg["x"];
395  const std::string y = cfg["y"];
396 
397  const std::vector<std::string> xvals = utils::split(x);
398  const std::vector<std::string> yvals = utils::split(y);
399 
400  const std::vector<map_location>& path = fake_unit_path(*dummy_unit, xvals, yvals);
401  if (!path.empty()) {
402  // Always scroll.
403  unit_display::move_unit(path, dummy_unit.get_unit_ptr(), true, map_location::NDIRECTIONS, force_scroll);
404  }
405 }
406 
407 WML_HANDLER_FUNCTION(move_units_fake,, cfg)
408 {
410  events::command_disabler command_disabler;
411  LOG_NG << "Processing [move_units_fake]\n";
412 
413  const bool force_scroll = cfg["force_scroll"].to_bool();
414  const vconfig::child_list unit_cfgs = cfg.get_children("fake_unit");
415  std::size_t num_units = unit_cfgs.size();
416  std::vector<fake_unit_ptr > units;
417  units.reserve(num_units);
418  std::vector<std::vector<map_location>> paths;
419  paths.reserve(num_units);
420 
421  LOG_NG << "Moving " << num_units << " units\n";
422 
423  std::size_t longest_path = 0;
424 
425  for (const vconfig& config : unit_cfgs) {
426  const std::vector<std::string> xvals = utils::split(config["x"]);
427  const std::vector<std::string> yvals = utils::split(config["y"]);
428  int skip_steps = config["skip_steps"];
429  fake_unit_ptr u = create_fake_unit(config);
430  units.push_back(u);
431  paths.push_back(fake_unit_path(*u, xvals, yvals));
432  if(skip_steps > 0)
433  paths.back().insert(paths.back().begin(), skip_steps, paths.back().front());
434  longest_path = std::max(longest_path, paths.back().size());
435  DBG_NG << "Path " << paths.size() - 1 << " has length " << paths.back().size() << '\n';
436 
437  u->set_location(paths.back().front());
438  units.back().place_on_fake_unit_manager(resources::fake_units);
439  }
440 
441  LOG_NG << "Units placed, longest path is " << longest_path << " long\n";
442 
443  std::vector<map_location> path_step(2);
444  path_step.resize(2);
445  for(std::size_t step = 1; step < longest_path; ++step) {
446  DBG_NG << "Doing step " << step << "...\n";
447  for(std::size_t un = 0; un < num_units; ++un) {
448  if(step >= paths[un].size() || paths[un][step - 1] == paths[un][step])
449  continue;
450  DBG_NG << "Moving unit " << un << ", doing step " << step << '\n';
451  path_step[0] = paths[un][step - 1];
452  path_step[1] = paths[un][step];
453  unit_display::move_unit(path_step, units[un].get_unit_ptr(), true, map_location::NDIRECTIONS, force_scroll);
454  units[un]->set_location(path_step[1]);
455  units[un]->anim_comp().set_standing(false);
456  }
457  }
458 
459  LOG_NG << "Units moved\n";
460 }
461 
462 /**
463  * If we should recall units that match a certain description.
464  * If you change attributes specific to [recall] (that is, not a Standard Unit Filter)
465  * be sure to update data/lua/wml_tag, auto_recall feature for [role] to reflect your changes.
466  */
467 WML_HANDLER_FUNCTION(recall,, cfg)
468 {
469  events::command_disabler command_disabler;
470  LOG_NG << "recalling unit...\n";
471  config temp_config(cfg.get_config());
472  // Prevent the recall unit filter from using the location as a criterion
473 
474  /**
475  * FIXME: we should design the WML to avoid these types of
476  * collisions; filters should be named consistently and always have a
477  * distinct scope.
478  */
479  temp_config["x"] = "recall";
480  temp_config["y"] = "recall";
481  temp_config.remove_attribute("location_id");
482  vconfig unit_filter_cfg(temp_config);
483  const vconfig & leader_filter = cfg.child("secondary_unit");
484 
485  for(int index = 0; index < static_cast<int>(resources::gameboard->teams().size()); ++index) {
486  LOG_NG << "for side " << index + 1 << "...\n";
487  const std::string player_id = resources::gameboard->teams()[index].save_id_or_number();
488 
489  if(resources::gameboard->teams()[index].recall_list().size() < 1) {
490  DBG_NG << "recall list is empty when trying to recall!\n"
491  << "player_id: " << player_id << " side: " << index+1 << "\n";
492  continue;
493  }
494 
495  recall_list_manager & avail = resources::gameboard->teams()[index].recall_list();
496  std::vector<unit_map::unit_iterator> leaders = resources::gameboard->units().find_leaders(index + 1);
497 
498  const unit_filter ufilt(unit_filter_cfg);
499  const unit_filter lfilt(leader_filter); // Note that if leader_filter is null, this correctly gives a null filter that matches all units.
500  for(std::vector<unit_ptr>::iterator u = avail.begin(); u != avail.end(); ++u) {
501  DBG_NG << "checking unit against filter...\n";
502  scoped_recall_unit auto_store("this_unit", player_id, std::distance(avail.begin(), u));
503  if (ufilt(*(*u), map_location())) {
504  DBG_NG << (*u)->id() << " matched the filter...\n";
505  const unit_ptr to_recruit = *u;
506  const unit* pass_check = to_recruit.get();
507  if(!cfg["check_passability"].to_bool(true)) pass_check = nullptr;
508  map_location cfg_loc = cfg_to_loc(cfg);
509  if(cfg.has_attribute("location_id")) {
510  const auto& special_locs = resources::gameboard->map().special_locations().left;
511  const auto& iter = special_locs.find(cfg["location_id"]);
512  if(iter != special_locs.end()) {
513  cfg_loc = iter->second;
514  }
515  }
516 
517  for (unit_map::const_unit_iterator leader : leaders) {
518  DBG_NG << "...considering " + leader->id() + " as the recalling leader...\n";
519  map_location loc = cfg_loc;
520  if ( lfilt(*leader) &&
521  unit_filter(vconfig(leader->recall_filter())).matches( *(*u),map_location() ) ) {
522  DBG_NG << "...matched the leader filter and is able to recall the unit.\n";
523  if(!resources::gameboard->map().on_board(loc))
524  loc = leader->get_location();
525  if(pass_check || (resources::gameboard->units().count(loc) > 0))
526  loc = pathfind::find_vacant_tile(loc, pathfind::VACANT_ANY, pass_check);
527  if(resources::gameboard->map().on_board(loc)) {
528  DBG_NG << "...valid location for the recall found. Recalling.\n";
529  avail.erase(u); // Erase before recruiting, since recruiting can fire more events
530  actions::place_recruit(to_recruit, loc, leader->get_location(), 0, true,
531  map_location::parse_direction(cfg["facing"]),
532  cfg["show"].to_bool(true), cfg["fire_event"].to_bool(false),
533  true, true);
534  return;
535  }
536  }
537  }
538  if (resources::gameboard->map().on_board(cfg_loc)) {
539  map_location loc = cfg_loc;
540  if(pass_check || (resources::gameboard->units().count(loc) > 0))
541  loc = pathfind::find_vacant_tile(loc, pathfind::VACANT_ANY, pass_check);
542  // Check if we still have a valid location
543  if (resources::gameboard->map().on_board(loc)) {
544  DBG_NG << "No usable leader found, but found usable location. Recalling.\n";
545  avail.erase(u); // Erase before recruiting, since recruiting can fire more events
546  map_location null_location = map_location::null_location();
547  actions::place_recruit(to_recruit, loc, null_location, 0, true,
548  map_location::parse_direction(cfg["facing"]),
549  cfg["show"].to_bool(true), cfg["fire_event"].to_bool(false),
550  true, true);
551  return;
552  }
553  }
554  }
555  }
556  }
557  LOG_WML << "A [recall] tag with the following content failed:\n" << cfg.get_config().debug();
558 }
559 
560 namespace {
561  struct map_choice : public mp_sync::user_choice
562  {
563  map_choice(const std::string& filename) : filename_(filename) {}
564  std::string filename_;
565  virtual config query_user(int /*side*/) const override
566  {
567  std::string res = filesystem::read_map(filename_);
568  return config {"map_data", res};
569  }
570  virtual config random_choice(int /*side*/) const override
571  {
572  return config();
573  }
574  virtual std::string description() const override
575  {
576  return "Map Data";
577  }
578  virtual bool is_visible() const override
579  {
580  // Allow query_user() to be called during prestart events, as it doesn't show any UI.
581  return false;
582  }
583  };
584 }
585 
586 /**
587  * Experimental map replace
588  * @todo Finish experimenting.
589  */
590 WML_HANDLER_FUNCTION(replace_map,, cfg)
591 {
592  /*
593  * When a hex changes from a village terrain to a non-village terrain, and
594  * a team owned that village it loses that village. When a hex changes from
595  * a non-village terrain to a village terrain and there is a unit on that
596  * hex it does not automatically capture the village. The reason for not
597  * capturing villages it that there are too many choices to make; should a
598  * unit loose its movement points, should capture events be fired. It is
599  * easier to do this as wanted by the author in WML.
600  */
601 
602  const gamemap * game_map = & resources::gameboard->map();
603  gamemap map(*game_map);
604 
605  try {
606  if(!cfg["map_file"].empty()) {
607  config file_cfg = mp_sync::get_user_choice("map_data", map_choice(cfg["map_file"].str()));
608  map.read(file_cfg["map_data"].str(), false);
609  } else if(!cfg["map_data"].empty()) {
610  map.read(cfg["map_data"], false);
611  } else {
612  deprecated_message("[replace_map]map=", DEP_LEVEL::INDEFINITE, "1.16", "Use map_data= instead.");
613  map.read(cfg["map"], false);
614  }
615  } catch(const incorrect_map_format_error&) {
616  const std::string log_map_name = cfg["map"].empty() ? cfg["map_file"] : std::string("from inline data");
617  lg::log_to_chat() << "replace_map: Unable to load map " << log_map_name << '\n';
618  ERR_WML << "replace_map: Unable to load map " << log_map_name;
619  return;
620  } catch(const wml_exception& e) {
621  e.show();
622  return;
623  }
624 
625  if (map.total_width() > game_map->total_width()
626  || map.total_height() > game_map->total_height()) {
627  if (!cfg["expand"].to_bool()) {
628  lg::log_to_chat() << "replace_map: Map dimension(s) increase but expand is not set\n";
629  ERR_WML << "replace_map: Map dimension(s) increase but expand is not set";
630  return;
631  }
632  }
633 
634  if (map.total_width() < game_map->total_width()
635  || map.total_height() < game_map->total_height()) {
636  if (!cfg["shrink"].to_bool()) {
637  lg::log_to_chat() << "replace_map: Map dimension(s) decrease but shrink is not set\n";
638  ERR_WML << "replace_map: Map dimension(s) decrease but shrink is not set";
639  return;
640  }
641  }
642 
643  std::optional<std::string> errmsg = resources::gameboard->replace_map(map);
644 
645  if (errmsg) {
646  lg::log_to_chat() << *errmsg << '\n';
647  ERR_WML << *errmsg;
648  }
649 
653 }
654 
655 /**
656  * Experimental data persistence
657  * @todo Finish experimenting.
658  */
660 {
661  if (!resources::controller->is_replay())
663 }
664 
665 WML_HANDLER_FUNCTION(set_variables,, cfg)
666 {
667  const t_string& name = cfg["name"];
669  if(name.empty()) {
670  ERR_NG << "trying to set a variable with an empty name:\n" << cfg.get_config().debug();
671  return;
672  }
673 
674  std::vector<config> data;
675  if(cfg.has_attribute("to_variable"))
676  {
677  try
678  {
680  for (const config& c : tovar.as_array())
681  {
682  data.push_back(c);
683  }
684  }
685  catch(const invalid_variablename_exception&)
686  {
687  ERR_NG << "Cannot do [set_variables] with invalid to_variable variable: " << cfg["to_variable"] << " with " << cfg.get_config().debug() << std::endl;
688  }
689  } else {
690  typedef std::pair<std::string, vconfig> vchild;
691  for (const vchild& p : cfg.all_ordered()) {
692  if(p.first == "value") {
693  data.push_back(p.second.get_parsed_config());
694  } else if(p.first == "literal") {
695  data.push_back(p.second.get_config());
696  } else if(p.first == "split") {
697  const vconfig & split_element = p.second;
698 
699  std::string split_string=split_element["list"];
700  std::string separator_string=split_element["separator"];
701  std::string key_name=split_element["key"];
702  if(key_name.empty())
703  {
704  key_name="value";
705  }
706 
707  bool remove_empty = split_element["remove_empty"].to_bool();
708 
709  char* separator = separator_string.empty() ? nullptr : &separator_string[0];
710  if(separator_string.size() > 1){
711  ERR_NG << "[set_variables] [split] separator only supports 1 character, multiple passed: " << split_element["separator"] << " with " << cfg.get_config().debug() << std::endl;
712  }
713 
714  std::vector<std::string> split_vector;
715 
716  //if no separator is specified, explode the string
717  if(separator == nullptr)
718  {
719  for(std::string::iterator i=split_string.begin(); i!=split_string.end(); ++i)
720  {
721  split_vector.push_back(std::string(1, *i));
722  }
723  }
724  else {
725  split_vector=utils::split(split_string, *separator, remove_empty ? utils::REMOVE_EMPTY | utils::STRIP_SPACES : utils::STRIP_SPACES);
726  }
727 
728  for(std::vector<std::string>::iterator i=split_vector.begin(); i!=split_vector.end(); ++i)
729  {
730  data.emplace_back(key_name, *i);
731  }
732  }
733  }
734  }
735  try
736  {
737  const std::string& mode = cfg["mode"];
738  if(mode == "merge")
739  {
740  if(dest.explicit_index() && data.size() > 1)
741  {
742  //merge children into one
743  config merged_children;
744  for (const config &ch : data) {
745  merged_children.append(ch);
746  }
747  data = {merged_children};
748  }
749  dest.merge_array(data);
750  }
751  else if(mode == "insert")
752  {
753  dest.insert_array(data);
754  }
755  else if(mode == "append")
756  {
757  dest.append_array(data);
758  }
759  else /*default if(mode == "replace")*/
760  {
761  dest.replace_array(data);
762  }
763  }
764  catch(const invalid_variablename_exception&)
765  {
766  ERR_NG << "Cannot do [set_variables] with invalid destination variable: " << name << " with " << cfg.get_config().debug() << std::endl;
767  }
768 }
769 
770 /**
771  * Store the relative direction from one hex to another in a WML variable.
772  * This is mainly useful as a diagnostic tool, but could be useful
773  * for some kind of scenario.
774  */
775 WML_HANDLER_FUNCTION(store_relative_direction,, cfg)
776 {
777  if (!cfg.child("source")) {
778  WRN_NG << "No source in [store_relative_direction]" << std::endl;
779  return;
780  }
781  if (!cfg.child("destination")) {
782  WRN_NG << "No destination in [store_relative_direction]" << std::endl;
783  return;
784  }
785  if (!cfg.has_attribute("variable")) {
786  WRN_NG << "No variable in [store_relative_direction]" << std::endl;
787  return;
788  }
789 
790  const map_location src = cfg_to_loc(cfg.child("source"));
791  const map_location dst = cfg_to_loc(cfg.child("destination"));
792 
793  std::string variable = cfg["variable"];
794  map_location::RELATIVE_DIR_MODE mode = static_cast<map_location::RELATIVE_DIR_MODE> (cfg["mode"].to_int(0));
795  try
796  {
798 
800  }
801  catch(const invalid_variablename_exception&)
802  {
803  ERR_NG << "Cannot do [store_relative_direction] with invalid destination variable: " << variable << " with " << cfg.get_config().debug() << std::endl;
804  }
805 }
806 
807 /**
808  * Store the rotation of one hex around another in a WML variable.
809  * In increments of 60 degrees, clockwise.
810  * This is mainly useful as a diagnostic tool, but could be useful
811  * for some kind of scenario.
812  */
813 WML_HANDLER_FUNCTION(store_rotate_map_location,, cfg)
814 {
815  if (!cfg.child("source")) {
816  WRN_NG << "No source in [store_rotate_map_location]" << std::endl;
817  return;
818  }
819  if (!cfg.child("destination")) {
820  WRN_NG << "No destination in [store_rotate_map_location]" << std::endl;
821  return;
822  }
823  if (!cfg.has_attribute("variable")) {
824  WRN_NG << "No variable in [store_rotate_map_location]" << std::endl;
825  return;
826  }
827 
828  const map_location src = cfg_to_loc(cfg.child("source"));
829  const map_location dst = cfg_to_loc(cfg.child("destination"));
830 
831  std::string variable = cfg["variable"];
832  int angle = cfg["angle"].to_int(1);
833 
834  try
835  {
837 
838  dst.rotate_right_around_center(src,angle).write(store.as_container());
839  }
840  catch(const invalid_variablename_exception&)
841  {
842  ERR_NG << "Cannot do [store_rotate_map_location] with invalid destination variable: " << variable << " with " << cfg.get_config().debug() << std::endl;
843  }
844 }
845 
846 WML_HANDLER_FUNCTION(tunnel,, cfg)
847 {
848  const bool remove = cfg["remove"].to_bool(false);
849  const bool delay = cfg["delayed_variable_substitution"].to_bool(true);
850  if (remove) {
851  const std::vector<std::string> ids = utils::split(cfg["id"]);
852  for (const std::string &id : ids) {
854  }
855  } else if (cfg.get_children("source").empty() ||
856  cfg.get_children("target").empty() ||
857  cfg.get_children("filter").empty()) {
858  ERR_WML << "[tunnel] is missing a mandatory tag:\n"
859  << cfg.get_config().debug();
860  } else {
861  pathfind::teleport_group tunnel(delay ? cfg : vconfig(cfg.get_parsed_config()), false);
862  resources::tunnels->add(tunnel);
863 
864  if(cfg["bidirectional"].to_bool(true)) {
865  tunnel = pathfind::teleport_group(delay ? cfg : vconfig(cfg.get_parsed_config()), true);
866  resources::tunnels->add(tunnel);
867  }
868  }
869 }
870 
871 /** If we should spawn a new unit on the map somewhere */
873 {
874  events::command_disabler command_disabler;
875  config parsed_cfg = cfg.get_parsed_config();
876 
877  config::attribute_value to_variable = cfg["to_variable"];
878  if (!to_variable.blank())
879  {
880  parsed_cfg.remove_attribute("to_variable");
881  unit_ptr new_unit = unit::create(parsed_cfg, true, &cfg);
882  try
883  {
884  config &var = resources::gamedata->get_variable_cfg(to_variable);
885  var.clear();
886  new_unit->write(var);
887  if (const config::attribute_value *v = parsed_cfg.get("x")) var["x"] = *v;
888  if (const config::attribute_value *v = parsed_cfg.get("y")) var["y"] = *v;
889  }
890  catch(const invalid_variablename_exception&)
891  {
892  ERR_NG << "Cannot do [unit] with invalid to_variable: " << to_variable << " with " << cfg.get_config().debug() << std::endl;
893  }
894  return;
895 
896  }
897 
898  int side = parsed_cfg["side"].to_int(1);
899 
900 
901  if ((side<1)||(side > static_cast<int>(resources::gameboard->teams().size()))) {
902  ERR_NG << "wrong side in [unit] tag - no such side: "<<side<<" ( number of teams :"<<resources::gameboard->teams().size()<<")"<<std::endl;
903  DBG_NG << parsed_cfg.debug();
904  return;
905  }
906  team &tm = resources::gameboard->get_team(side);
907 
908  unit_creator uc(tm,resources::gameboard->map().starting_position(side));
909 
910  uc
911  .allow_add_to_recall(true)
912  .allow_discover(true)
913  .allow_get_village(true)
914  .allow_invalidate(true)
915  .allow_rename_side(true)
916  .allow_show(true);
917 
918  uc.add_unit(parsed_cfg, &cfg);
919 
920 }
921 
922 WML_HANDLER_FUNCTION(on_undo, event_info, cfg)
923 {
924  if(cfg["delayed_variable_substitution"].to_bool(false)) {
925  synced_context::add_undo_commands(cfg.get_config(), event_info);
926  } else {
927  synced_context::add_undo_commands(cfg.get_parsed_config(), event_info);
928  }
929 }
930 
931 } // end namespace game_events
void set_wml_y(int v)
Definition: location.hpp:157
std::optional< std::string > replace_map(const gamemap &r)
Definition: game_board.cpp:290
void verify_and_get_global_variable(const vconfig &pcfg)
play_controller * controller
Definition: resources.cpp:22
static void add_undo_commands(const config &commands, const game_events::queued_event &ctx)
bool empty() const
Tests for an attribute that either was never set or was set to "".
std::map< std::string, handler > map
Definition: action_wml.hpp:51
unit_creator & allow_rename_side(bool b)
config get_user_choice(const std::string &name, const user_choice &uch, int side=0)
static synced_state get_synced_state()
#define ERR_NG
Definition: action_wml.cpp:71
static void get_global_variable(persist_context &ctx, const vconfig &pcfg)
Definition: persist_var.cpp:64
static DIRECTION parse_direction(const std::string &str)
Definition: location.cpp:66
::tod_manager * tod_manager
Definition: resources.cpp:30
std::vector< unit_iterator > find_leaders(int side)
Definition: map.cpp:356
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:92
Function which doesn&#39;t take anything into account.
Definition: pathfind.hpp:255
const unit_type * find(const std::string &key, unit_type::BUILD_STATUS status=unit_type::FULL) const
Finds a unit_type by its id() and makes sure it is built to the specified level.
Definition: types.cpp:1262
virtual const std::vector< team > & teams() const override
Definition: game_board.hpp:85
location_map & special_locations()
Definition: map.hpp:90
vconfig child(const std::string &key) const
Returns a child of *this whose key is key.
Definition: variable.cpp:288
virtual const unit_map & units() const override
Definition: game_board.hpp:112
DIRECTION get_relative_dir(const map_location &loc, map_location::RELATIVE_DIR_MODE mode) const
Definition: location.cpp:227
#define LOG_NG
Definition: action_wml.cpp:69
This class represents a single unit of a specific type.
Definition: unit.hpp:121
wml_action(const std::string &tag, handler function)
Using this constructor for a static object outside action_wml.cpp will likely lead to a static initia...
Definition: action_wml.cpp:216
void append(const config &cfg)
Append data from another config object to this one.
Definition: config.cpp:269
Various functions implementing vision (through fog of war and shroud).
static manager & get_singleton()
Definition: manager.hpp:145
void raise_gamestate_changed()
Notifies all observers of &#39;ai_gamestate_changed&#39; event.
Definition: manager.cpp:441
unit_creator & allow_invalidate(bool b)
static void set_global_variable(persist_context &ctx, const vconfig &pcfg)
Definition: persist_var.cpp:99
unit_creator & allow_show(bool b)
bool explicit_index() const
Various functions that implement attacks and attack calculations.
void remove(const std::string &id)
Definition: teleport.cpp:308
Variant for storing WML attributes.
Add a special kind of assert to validate whether the input from WML doesn&#39;t contain any problems that...
internal_ptr get_unit_ptr()
Get a copy of the internal unit pointer.
config & get_variable_cfg(const std::string &varname)
throws invalid_variablename_exception if varname is no valid variable name.
Definition: game_data.cpp:80
void raise_map_changed()
Notifies all observers of &#39;ai_map_changed&#39; event.
Definition: manager.cpp:457
maybe_const_t< config::attribute_value, V > & as_scalar() const
If instantiated with vi_policy_const, the lifetime of the returned const attribute_value reference mi...
#define LOG_WML
Definition: action_wml.cpp:78
map_location find_vacant_tile(const map_location &loc, VACANT_TILE_TYPE vacancy, const unit *pass_check, const team *shroud_check, const game_board *board)
Function that will find a location on the board that is as near to loc as possible, but which is unoccupied by any units.
Definition: pathfind.cpp:55
unit_creator & allow_discover(bool b)
const attribute_value * get(config_key_type key) const
Returns a pointer to the attribute with the given key or nullptr if it does not exist.
Definition: config.cpp:780
virtual const gamemap & map() const override
Definition: game_board.hpp:102
WML_HANDLER_FUNCTION(clear_global_variable,, pcfg)
Experimental data persistence.
Definition: action_wml.cpp:260
void modify_turns_by_wml(const std::string &mod)
unit_type_data unit_types
Definition: types.cpp:1481
void set_wml_x(int v)
Definition: location.hpp:156
Define actions for the game&#39;s events mechanism.
config::child_itors insert_array(std::vector< config > children) const
Replay control code.
This class encapsulates the recall list of a team.
static lg::log_domain log_config("config")
void verify_and_set_global_variable(const vconfig &pcfg)
bool matches(const unit &u, const map_location &loc) const
Determine if *this matches filter at a specified location.
Definition: filter.hpp:132
Additional functionality for a non-const variable_info.
maybe_const_t< config, V > & as_container() const
If instantiated with vi_policy_const, the lifetime of the returned const attribute_value reference mi...
void clear()
Definition: config.cpp:920
An object to leave the synced context during draw or unsynced wml items when we don’t know whether w...
void remove_attribute(config_key_type key)
Definition: config.cpp:217
bool empty() const
Definition: variable.hpp:100
unit_creator & allow_add_to_recall(bool b)
std::shared_ptr< unit > unit_ptr
Definition: ptr.hpp:26
void new_turn()
Update lighting settings.
static unit_ptr create(const config &cfg, bool use_traits=false, const vconfig *vcfg=nullptr)
Initializes a unit from a config.
Definition: unit.hpp:190
A single unit type that the player may recruit.
Definition: types.hpp:45
game_data * gamedata
Definition: resources.cpp:23
std::string filename_
Definition: action_wml.cpp:564
#define WRN_NG
Definition: action_wml.cpp:70
void show() const
Shows the error in a dialog.
This class stores all the data for a single &#39;side&#39; (in game nomenclature).
Definition: team.hpp:72
team & get_team(int i)
Definition: game_board.hpp:97
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:87
#define ERR_WML
Definition: action_wml.cpp:80
std::vector< map_location > steps
Definition: pathfind.hpp:135
#define DBG_NG
Definition: action_wml.cpp:68
void set_number_of_turns_by_wml(int num)
std::string deprecated_message(const std::string &elem_name, DEP_LEVEL level, const version_info &version, const std::string &detail)
Definition: deprecation.cpp:30
int w() const
Effective map width.
Definition: map.hpp:50
Structure which holds a single route between one location and another.
Definition: pathfind.hpp:132
void merge_array(std::vector< config > children) const
variable_access_create get_variable_access_write(const std::string &varname)
returns a variable_access that can be used to change the game variables
Definition: game_data.hpp:52
bool blank() const
Tests for an attribute that was never set.
game_board * gameboard
Definition: resources.cpp:21
Interface for querying local choices.
iterator begin()
begin iterator
Encapsulates the map of the game.
Definition: map.hpp:171
unit_race::GENDER string_gender(const std::string &str, unit_race::GENDER def)
Definition: race.cpp:149
fake_unit_manager * fake_units
Definition: resources.cpp:31
std::string path
Definition: game_config.cpp:39
config::child_itors append_array(std::vector< config > children) const
Managing the AIs lifecycle - headers TODO: Refactor history handling and internal commands...
Function which only uses terrain, ignoring shroud, enemies, etc.
Definition: pathfind.hpp:241
void read(const std::string &data, const bool allow_invalid=true)
Definition: map.cpp:133
iterator end()
end iterator
void needs_rebuild(bool b)
Sets whether the screen (map visuals) needs to be rebuilt.
int number_of_turns() const
std::string read_map(const std::string &name)
void set_turn_by_wml(const int num, game_data *vars=nullptr, const bool increase_limit_if_needed=true)
Dynamically change the current turn number.
Encapsulates the map of the game.
Definition: location.hpp:38
#define ERR_CF
Definition: action_wml.cpp:83
Various functions related to moving units.
Domain specific events.
Definition: action_wml.cpp:87
Helper class, don&#39;t construct this directly.
static map registry_
Tracks the known action handlers.
Definition: action_wml.hpp:66
std::shared_ptr< wb::manager > whiteboard
Definition: resources.cpp:34
Various functions related to the creation of units (recruits, recalls, and placed units)...
Define conditionals for the game&#39;s events mechanism, a.k.a.
int total_width() const
Real width of the map, including borders.
Definition: map.hpp:59
unit * get()
Get a raw pointer to the underlying unit.
std::size_t i
Definition: function.cpp:967
unit_creator & allow_get_village(bool b)
mock_party p
Define the game&#39;s event mechanism.
config::child_itors replace_array(std::vector< config > children) const
bool on_board(const map_location &loc) const
Tell if a location is on the map.
Definition: map.cpp:385
static lg::log_domain log_display("display")
Declarations for File-IO.
std::size_t index(const std::string &str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:72
int total_height() const
Real height of the map, including borders.
Definition: map.hpp:62
config & add_child(config_key_type key)
Definition: config.cpp:514
REMOVE_EMPTY: remove empty elements.
iterator erase(iterator it)
Erase an iterator to this object.
void add_unit(const config &cfg, const vconfig *vcfg=nullptr)
adds a unit on map without firing any events (so, usable during team construction in gamestatus) ...
static void on_replay_error(const std::string &message)
Definition: action_wml.cpp:266
void add(const teleport_group &group)
Definition: teleport.cpp:304
bool empty() const
Definition: tstring.hpp:187
Information on a WML variable.
static bool run_in_synced_context_if_not_already(const std::string &commandname, const config &data, bool use_undo=true, bool show=true, synced_command::error_handler_function error_handler=default_error_function)
Checks whether we are currently running in a synced context, and if not we enters it...
void reload_map()
Updates internals that cache map size.
Definition: display.cpp:509
std::vector< std::string > split(const config_attribute_value &val)
A variable-expanding proxy for the config class.
Definition: variable.hpp:44
const config & get_config() const
Definition: variable.hpp:75
Standard logging facilities (interface).
static const map_location & null_location()
Definition: location.hpp:81
static lg::log_domain log_engine("engine")
A RAII object to temporary leave the synced context like in wesnoth.synchronize_choice.
int turn() const
#define e
int side() const
The side this unit belongs to.
Definition: unit.hpp:334
static void clear_global_variable(persist_context &ctx, const vconfig &pcfg)
Definition: persist_var.cpp:93
maybe_const_t< config::child_itors, V > as_array() const
If instantiated with vi_policy_const, the lifetime of the returned const attribute_value reference mi...
place_recruit_result place_recruit(unit_ptr u, const map_location &recruit_location, const map_location &recruited_from, int cost, bool is_recall, map_location::DIRECTION facing, bool show, bool fire_event, bool full_movement, bool wml_triggered)
Place a unit into the game.
Definition: create.cpp:613
std::vector< vconfig > child_list
Definition: variable.hpp:78
static lg::log_domain log_wml("wml")
plain_route a_star_search(const map_location &src, const map_location &dst, double stop_at, const cost_calculator &calc, const std::size_t width, const std::size_t height, const teleport_map *teleports, bool border)
PHASE phase() const
Definition: game_data.hpp:79
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:61
mock_char c
int h() const
Effective map height.
Definition: map.hpp:53
This module contains various pathfinding functions and utilities.
static std::string write_direction(DIRECTION dir)
Definition: location.cpp:141
pathfind::manager * tunnels
Definition: resources.cpp:32
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
Holds a temporary unit that can be drawn on the map without being placed in the unit_map.
Display units performing various actions: moving, attacking, and dying.
void move_unit(const std::vector< map_location > &path, unit_ptr u, bool animate, map_location::DIRECTION dir, bool force_scroll)
Display a unit moving along a given path.
Definition: udisplay.cpp:510
std::string debug() const
Definition: config.cpp:1347
void verify_and_clear_global_variable(const vconfig &pcfg)
std::stringstream & log_to_chat()
Use this to show WML errors in the ingame chat.
Definition: log.cpp:289
static game_display * get_singleton()
variable_access_const get_variable_access_read(const std::string &varname) const
returns a variable_access that cannot be used to change the game variables
Definition: game_data.hpp:46