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