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