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