The Battle for Wesnoth  1.19.25+dev
action_wml.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2025
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;
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 
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 // This tag exposes part of the code path used to handle [command]'s in replays
251 // This allows to perform scripting in WML that will use the same code path as player actions, for example.
253 {
254  // Doing this in a whiteboard applied context will cause bugs
255  // Note that even though game_events::wml_event_pump() will always apply the real unit map
256  // It is still possible get a wml commands to run in a whiteboard applied context
257  // With the theme_items lua callbacks
258  if(resources::whiteboard->has_planned_unit_map())
259  {
260  ERR_NG << "[do_command] called while whiteboard is applied, ignoring";
261  return;
262  }
263 
264  static const std::set<std::string> allowed_tags {"attack", "move", "recruit", "recall", "disband", "fire_event", "custom_command"};
265 
267  const bool is_during_turn = resources::gamedata->phase() == game_data::TURN_PLAYING;
268  const bool is_unsynced = synced_context::get_synced_state() == synced_context::UNSYNCED;
269  if(is_too_early)
270  {
271  ERR_NG << "[do_command] called too early, only allowed at START or later";
272  return;
273  }
274  if(is_unsynced && !is_during_turn)
275  {
276  ERR_NG << "[do_command] can only be used during a turn when a user woudl also be able to invoke commands";
277  return;
278  }
279  if(is_unsynced && events::commands_disabled)
280  {
281  ERR_NG << "[do_command] cannot invoke synced commands while commands are blocked";
282  return;
283  }
284  if(is_unsynced && !resources::controller->current_team().is_local())
285  {
286  ERR_NG << "[do_command] can only be used from clients that control the currently playing side";
287  return;
288  }
289  for(const auto& [key, child] : cfg.all_ordered())
290  {
291  if(allowed_tags.find(key) == allowed_tags.end()) {
292  ERR_NG << "unsupported tag [" << key << "] in [do_command]";
293  std::stringstream o;
294  std::copy(allowed_tags.begin(), allowed_tags.end(), std::ostream_iterator<std::string>(o, " "));
295  ERR_NG << "allowed tags: " << o.str();
296  continue;
297  }
298  // TODO: afaik run_in_synced_context_if_not_already thows exceptions when the executed action end the scenario or the turn.
299  // This could cause problems, specially when its unclear whether that exception is caught by lua or not...
300 
301  //Note that this fires related events and everything else that also happens normally.
302  //have to watch out with the undo stack, therefore forbid [auto_shroud] and [update_shroud] here...
303  auto spectator = action_spectator([](const std::string& message) {
304  ERR_NG << "Error via [do_command]:";
305  ERR_NG << message;
306  });
308  key,
309  child.get_parsed_config(),
310  spectator
311  );
312  ai::manager::get_singleton().raise_gamestate_changed();
313  }
314 }
315 
316 /**
317  * Experimental data persistence
318  * @todo Finish experimenting.
319  */
321 {
323 }
324 
325 WML_HANDLER_FUNCTION(modify_turns,, cfg)
326 {
327  config::attribute_value value = cfg["value"];
328  std::string add = cfg["add"];
329  config::attribute_value current = cfg["current"];
331  if(!add.empty()) {
332  tod_man.modify_turns_by_wml(add);
333  } else if(!value.empty()) {
334  tod_man.set_number_of_turns_by_wml(value.to_int(-1));
335  }
336  // change current turn only after applying mods
337  if(!current.empty()) {
338  const unsigned int current_turn_number = tod_man.turn();
339  int new_turn_number = current.to_int(current_turn_number);
340  const unsigned int new_turn_number_u = static_cast<unsigned int>(new_turn_number);
341  if(new_turn_number_u < 1 || (new_turn_number > tod_man.number_of_turns() && tod_man.number_of_turns() != -1)) {
342  ERR_NG << "attempted to change current turn number to one out of range (" << new_turn_number << ")";
343  } else if(new_turn_number_u != current_turn_number) {
344  tod_man.set_turn_by_wml(new_turn_number_u, resources::gamedata);
346  }
347  }
348 }
349 
350 /**
351  * Moving a 'unit' - i.e. a dummy unit
352  * that is just moving for the visual effect
353  */
354 WML_HANDLER_FUNCTION(move_unit_fake,, cfg)
355 {
357  events::command_disabler command_disabler;
358  fake_unit_ptr dummy_unit(create_fake_unit(cfg));
359  if(!dummy_unit.get())
360  return;
361 
362  const bool force_scroll = cfg["force_scroll"].to_bool(true);
363 
364  const std::string x = cfg["x"];
365  const std::string y = cfg["y"];
366 
367  const std::vector<std::string> xvals = utils::split(x);
368  const std::vector<std::string> yvals = utils::split(y);
369 
370  const std::vector<map_location>& path = fake_unit_path(*dummy_unit, xvals, yvals);
371  if (!path.empty()) {
372  // Always scroll.
374  }
375 }
376 
377 WML_HANDLER_FUNCTION(move_units_fake,, cfg)
378 {
380  events::command_disabler command_disabler;
381  LOG_NG << "Processing [move_units_fake]";
382 
383  const bool force_scroll = cfg["force_scroll"].to_bool();
384  const bool parallel = cfg["parallel"].to_bool(false);
385  const vconfig::child_list unit_cfgs = cfg.get_children("fake_unit");
386  std::size_t num_units = unit_cfgs.size();
387  std::vector<fake_unit_ptr > units;
388  units.reserve(num_units);
389  std::vector<std::vector<map_location>> paths;
390  paths.reserve(num_units);
391 
392  LOG_NG << "Moving " << num_units << " units (parallel=" << (parallel ? "yes" : "no") << ")";
393 
394  std::size_t longest_path = 0;
395 
396  for (const vconfig& config : unit_cfgs) {
397  const std::vector<std::string> xvals = utils::split(config["x"]);
398  const std::vector<std::string> yvals = utils::split(config["y"]);
399  int skip_steps = config["skip_steps"].to_int();
400  fake_unit_ptr u = create_fake_unit(config);
401  units.push_back(u);
402  paths.push_back(fake_unit_path(*u, xvals, yvals));
403  if(skip_steps > 0)
404  paths.back().insert(paths.back().begin(), skip_steps, paths.back().front());
405  longest_path = std::max(longest_path, paths.back().size());
406  DBG_NG << "Path " << paths.size() - 1 << " has length " << paths.back().size();
407 
408  u->set_location(paths.back().front());
410  }
411 
412  LOG_NG << "Units placed, longest path is " << longest_path << " long";
413 
414  if(!parallel) {
415  // Serially move each unit step by step, waiting for each to finish before starting the next.
416  std::vector<map_location> path_step(2);
417  path_step.resize(2);
418  for(std::size_t step = 1; step < longest_path; ++step) {
419  DBG_NG << "Doing step " << step << "...";
420  for(std::size_t un = 0; un < num_units; ++un) {
421  if (step >= paths[un].size() || paths[un][step - 1] == paths[un][step]) {
422  continue;
423  }
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::direction::indeterminate, force_scroll);
428  units[un]->set_location(path_step[1]);
429  units[un]->anim_comp().set_standing(false);
430  }
431  }
432  } else {
433  // Parallel movement: all units move at the same time, at the same speed, hex by hex.
434  DBG_NG << "Parallel movement: initializing " << num_units << " animators.";
435  std::vector<std::unique_ptr<unit_display::unit_movement_animator>> animators;
436  animators.reserve(num_units);
437  std::size_t max_steps = 0;
438  for(std::size_t i = 0; i < num_units; ++i) {
439  max_steps = std::max(max_steps, paths[i].size() > 1 ? paths[i].size() - 1 : std::size_t(0));
440  }
441 
442  // 1. Create animators and kick off the first movement for all units simultaneously.
443  for(std::size_t i = 0; i < num_units; ++i) {
444  animators.push_back(std::make_unique<unit_display::unit_movement_animator>(paths[i], true, force_scroll));
445  animators[i]->start(units[i].get_unit_ptr());
446  if(paths[i].size() > 1) {
447  animators[i]->proceed_to(units[i].get_unit_ptr(), 1, false, false);
448  DBG_NG << "Unit " << i << " started movement (path length " << paths[i].size()-1 << ")";
449  }
450  }
451 
452  // 2. Lockstep loop: wait for all units to reach step N, update them, then kick off step N+1.
453  for(std::size_t step = 1; step <= max_steps; ++step) {
454  for(std::size_t i = 0; i < num_units; ++i) {
455  if(step >= paths[i].size()) {
456  continue; // This unit has reached its final destination, skip it.
457  }
458  animators[i]->wait_for_anims();
459  DBG_NG << "Unit " << i << " arrived at hex " << step << " (" << paths[i][step] << ")";
460  units[i]->set_location(paths[i][step]);
461  if(step + 1 < paths[i].size()) {
462  animators[i]->proceed_to(units[i].get_unit_ptr(), step + 1, false, false);
463  } else {
464  DBG_NG << "Unit " << i << " reached its final destination.";
465  units[i]->anim_comp().set_standing(true);
466  }
467  }
468  }
469 
470  // 3. Finalization: Perform heavy cleanup only after all units have stopped.
471  DBG_NG << "All units stopped. Finalizing states.";
472  for(std::size_t i = 0; i < num_units; ++i) {
473  animators[i]->finish(units[i].get_unit_ptr());
474  }
475  }
476 
477  LOG_NG << "Units moved";
478 }
479 
480 /**
481  * If we should recall units that match a certain description.
482  * If you change attributes specific to [recall] (that is, not a Standard Unit Filter)
483  * be sure to update data/lua/wml_tag, auto_recall feature for [role] to reflect your changes.
484  */
486 {
487  events::command_disabler command_disabler;
488  LOG_NG << "recalling unit...";
489  config temp_config(cfg.get_config());
490  // Prevent the recall unit filter from using the location as a criterion
491 
492  /**
493  * FIXME: we should design the WML to avoid these types of
494  * collisions; filters should be named consistently and always have a
495  * distinct scope.
496  */
497  temp_config["x"] = "recall";
498  temp_config["y"] = "recall";
499  temp_config.remove_attribute("location_id");
500  vconfig unit_filter_cfg(temp_config);
501  const vconfig & leader_filter = cfg.child("secondary_unit");
502 
503  for(team& t : resources::gameboard->teams()) {
504  LOG_NG << "for side " << t.side() << "...";
505  const std::string player_id = t.save_id_or_number();
506 
507  if(t.recall_list().size() < 1) {
508  DBG_NG << "recall list is empty when trying to recall!";
509  DBG_NG << "player_id: " << player_id << " side: " << t.side();
510  continue;
511  }
512 
513  recall_list_manager & avail = t.recall_list();
514  std::vector<unit_map::unit_iterator> leaders = resources::gameboard->units().find_leaders(t.side());
515 
516  const unit_filter ufilt(unit_filter_cfg);
517  const unit_filter lfilt(leader_filter); // Note that if leader_filter is null, this correctly gives a null filter that matches all units.
518  for(std::vector<unit_ptr>::iterator u = avail.begin(); u != avail.end(); ++u) {
519  DBG_NG << "checking unit against filter...";
520  scoped_recall_unit auto_store("this_unit", player_id, std::distance(avail.begin(), u));
521  if (ufilt(*(*u), map_location())) {
522  DBG_NG << (*u)->id() << " matched the filter...";
523  const unit_ptr to_recruit = *u;
524  const unit* pass_check = to_recruit.get();
525  if(!cfg["check_passability"].to_bool(true)) pass_check = nullptr;
526  map_location cfg_loc = cfg_to_loc(cfg);
527  if(cfg.has_attribute("location_id")) {
528  const auto& special_locs = resources::gameboard->map().special_locations().left;
529  const auto& iter = special_locs.find(cfg["location_id"]);
530  if(iter != special_locs.end()) {
531  cfg_loc = iter->second;
532  }
533  }
534 
535  for (unit_map::const_unit_iterator leader : leaders) {
536  DBG_NG << "...considering " + leader->id() + " as the recalling leader...";
537  map_location loc = cfg_loc;
538  if ( lfilt(*leader) &&
539  unit_filter(vconfig(leader->recall_filter())).matches( *(*u),map_location() ) ) {
540  DBG_NG << "...matched the leader filter and is able to recall the unit.";
541  if(!resources::gameboard->map().on_board(loc))
542  loc = leader->get_location();
543  if(pass_check || (resources::gameboard->units().count(loc) > 0))
545  if(resources::gameboard->map().on_board(loc)) {
546  DBG_NG << "...valid location for the recall found. Recalling.";
547  avail.erase(u); // Erase before recruiting, since recruiting can fire more events
548  actions::place_recruit(to_recruit, loc, leader->get_location(), 0, true,
550  cfg["show"].to_bool(true), cfg["fire_event"].to_bool(false),
551  true, true);
552  return;
553  }
554  }
555  }
556  if (resources::gameboard->map().on_board(cfg_loc)) {
557  map_location loc = cfg_loc;
558  if(pass_check || (resources::gameboard->units().count(loc) > 0))
560  // Check if we still have a valid location
561  if (resources::gameboard->map().on_board(loc)) {
562  DBG_NG << "No usable leader found, but found usable location. Recalling.";
563  avail.erase(u); // Erase before recruiting, since recruiting can fire more events
564  map_location null_location = map_location::null_location();
565  actions::place_recruit(to_recruit, loc, null_location, 0, true,
567  cfg["show"].to_bool(true), cfg["fire_event"].to_bool(false),
568  true, true);
569  return;
570  }
571  }
572  }
573  }
574  }
575  LOG_WML << "A [recall] tag with the following content failed:\n" << cfg.get_config().debug();
576 }
577 
578 namespace {
579  struct map_choice : public mp_sync::user_choice
580  {
581  map_choice(const std::string& filename) : filename_(filename) {}
582  std::string filename_;
583  virtual config query_user(int /*side*/) const override
584  {
585  std::string res = filesystem::read_map(filename_);
586  return config {"map_data", res};
587  }
588  virtual config random_choice(int /*side*/) const override
589  {
590  return config();
591  }
592  virtual std::string description() const override
593  {
594  return "Map Data";
595  }
596  virtual bool is_visible() const override
597  {
598  // Allow query_user() to be called during prestart events, as it doesn't show any UI.
599  return false;
600  }
601  };
602 }
603 
604 /**
605  * Experimental map replace
606  * @todo Finish experimenting.
607  */
608 WML_HANDLER_FUNCTION(replace_map,, cfg)
609 {
610  /*
611  * When a hex changes from a village terrain to a non-village terrain, and
612  * a team owned that village it loses that village. When a hex changes from
613  * a non-village terrain to a village terrain and there is a unit on that
614  * hex it does not automatically capture the village. The reason for not
615  * capturing villages it that there are too many choices to make; should a
616  * unit loose its movement points, should capture events be fired. It is
617  * easier to do this as wanted by the author in WML.
618  */
619 
620  const gamemap * game_map = & resources::gameboard->map();
621  gamemap map(*game_map);
622 
623  try {
624  if(!cfg["map_file"].empty()) {
625  config file_cfg = mp_sync::get_user_choice("map_data", map_choice(cfg["map_file"].str()));
626  map.read(file_cfg["map_data"].str(), false);
627  } else if(!cfg["map_data"].empty()) {
628  map.read(cfg["map_data"].str(), false);
629  } else {
630  deprecated_message("[replace_map]map=", DEP_LEVEL::INDEFINITE, "1.16", "Use map_data= instead.");
631  map.read(cfg["map"].str(), false);
632  }
633  } catch(const incorrect_map_format_error&) {
634  const std::string log_map_name = cfg["map"].empty() ? cfg["map_file"] : std::string("from inline data");
635  lg::log_to_chat() << "replace_map: Unable to load map " << log_map_name << '\n';
636  ERR_WML << "replace_map: Unable to load map " << log_map_name;
637  return;
638  } catch(const wml_exception& e) {
639  e.show();
640  return;
641  }
642 
643  if (map.total_width() > game_map->total_width()
644  || map.total_height() > game_map->total_height()) {
645  if (!cfg["expand"].to_bool()) {
646  lg::log_to_chat() << "replace_map: Map dimension(s) increase but expand is not set\n";
647  ERR_WML << "replace_map: Map dimension(s) increase but expand is not set";
648  return;
649  }
650  }
651 
652  if (map.total_width() < game_map->total_width()
653  || map.total_height() < game_map->total_height()) {
654  if (!cfg["shrink"].to_bool()) {
655  lg::log_to_chat() << "replace_map: Map dimension(s) decrease but shrink is not set\n";
656  ERR_WML << "replace_map: Map dimension(s) decrease but shrink is not set";
657  return;
658  }
659  }
660 
661  utils::optional<std::string> errmsg = resources::gameboard->replace_map(map);
662 
663  if (errmsg) {
664  lg::log_to_chat() << *errmsg << '\n';
665  ERR_WML << *errmsg;
666  }
667 
670  ai::manager::get_singleton().raise_map_changed();
671 }
672 
673 /**
674  * Experimental data persistence
675  * @todo Finish experimenting.
676  */
678 {
679  if (!resources::controller->is_replay())
681 }
682 
683 /**
684  * Store the relative direction from one hex to another in a WML variable.
685  * This is mainly useful as a diagnostic tool, but could be useful
686  * for some kind of scenario.
687  */
688 WML_HANDLER_FUNCTION(store_relative_direction,, cfg)
689 {
690  if (!cfg.child("source")) {
691  WRN_NG << "No source in [store_relative_direction]";
692  return;
693  }
694  if (!cfg.child("destination")) {
695  WRN_NG << "No destination in [store_relative_direction]";
696  return;
697  }
698  if (!cfg.has_attribute("variable")) {
699  WRN_NG << "No variable in [store_relative_direction]";
700  return;
701  }
702 
703  const map_location src = cfg_to_loc(cfg.child("source"));
704  const map_location dst = cfg_to_loc(cfg.child("destination"));
705 
706  std::string variable = cfg["variable"];
707  map_location::RELATIVE_DIR_MODE mode = static_cast<map_location::RELATIVE_DIR_MODE> (cfg["mode"].to_int(0));
708  try
709  {
711 
712  store.as_scalar() = map_location::write_direction(src.get_relative_dir(dst,mode));
713  }
714  catch(const invalid_variablename_exception&)
715  {
716  ERR_NG << "Cannot do [store_relative_direction] with invalid destination variable: " << variable << " with " << cfg.get_config().debug();
717  }
718 }
719 
720 /**
721  * Store the rotation of one hex around another in a WML variable.
722  * In increments of 60 degrees, clockwise.
723  * This is mainly useful as a diagnostic tool, but could be useful
724  * for some kind of scenario.
725  */
726 WML_HANDLER_FUNCTION(store_rotate_map_location,, cfg)
727 {
728  if (!cfg.child("source")) {
729  WRN_NG << "No source in [store_rotate_map_location]";
730  return;
731  }
732  if (!cfg.child("destination")) {
733  WRN_NG << "No destination in [store_rotate_map_location]";
734  return;
735  }
736  if (!cfg.has_attribute("variable")) {
737  WRN_NG << "No variable in [store_rotate_map_location]";
738  return;
739  }
740 
741  const map_location src = cfg_to_loc(cfg.child("source"));
742  const map_location dst = cfg_to_loc(cfg.child("destination"));
743 
744  std::string variable = cfg["variable"];
745  int angle = cfg["angle"].to_int(1);
746 
747  try
748  {
750 
751  dst.rotate_right_around_center(src,angle).write(store.as_container());
752  }
753  catch(const invalid_variablename_exception&)
754  {
755  ERR_NG << "Cannot do [store_rotate_map_location] with invalid destination variable: " << variable << " with " << cfg.get_config().debug();
756  }
757 }
758 
760 {
761  const bool remove = cfg["remove"].to_bool(false);
762  const bool delay = cfg["delayed_variable_substitution"].to_bool(true);
763  if (remove) {
764  const std::vector<std::string> ids = utils::split(cfg["id"]);
765  for (const std::string &id : ids) {
767  }
768  } else if (cfg.get_children("source").empty() ||
769  cfg.get_children("target").empty() ||
770  cfg.get_children("filter").empty()) {
771  ERR_WML << "[tunnel] is missing a mandatory tag:\n"
772  << cfg.get_config().debug();
773  } else if (cfg.get_children("source").size() > 1 ||
774  cfg.get_children("target").size() > 1 ||
775  cfg.get_children("filter").size() > 1) {
776  ERR_WML << "[tunnel] should have exactly one of each mandatory tag:\n"
777  << cfg.get_config().debug();
778  } else {
779  pathfind::teleport_group tunnel(delay ? cfg : vconfig(cfg.get_parsed_config()), false);
780  resources::tunnels->add(tunnel);
781 
782  if(cfg["bidirectional"].to_bool(true)) {
783  tunnel = pathfind::teleport_group(delay ? cfg : vconfig(cfg.get_parsed_config()), true);
784  resources::tunnels->add(tunnel);
785  }
786  }
787 }
788 
789 /** If we should spawn a new unit on the map somewhere */
791 {
792  events::command_disabler command_disabler;
793  config parsed_cfg = cfg.get_parsed_config();
794 
795  config::attribute_value to_variable = cfg["to_variable"];
796  if (!to_variable.blank())
797  {
798  parsed_cfg.remove_attribute("to_variable");
799  unit_ptr new_unit = unit::create(parsed_cfg, true, &cfg);
800  try
801  {
802  config &var = resources::gamedata->get_variable_cfg(to_variable);
803  var.clear();
804  new_unit->write(var);
805  if (const config::attribute_value *v = parsed_cfg.get("x")) var["x"] = *v;
806  if (const config::attribute_value *v = parsed_cfg.get("y")) var["y"] = *v;
807  }
808  catch(const invalid_variablename_exception&)
809  {
810  ERR_NG << "Cannot do [unit] with invalid to_variable: " << to_variable << " with " << cfg.get_config().debug();
811  }
812  return;
813 
814  }
815 
816  int side = parsed_cfg["side"].to_int(1);
817 
818 
819  if ((side<1)||(side > static_cast<int>(resources::gameboard->teams().size()))) {
820  ERR_NG << "wrong side in [unit] tag - no such side: "<<side<<" ( number of teams :"<<resources::gameboard->teams().size()<<")";
821  DBG_NG << parsed_cfg.debug();
822  return;
823  }
824  team &tm = resources::gameboard->get_team(side);
825 
826  unit_creator uc(tm,resources::gameboard->map().starting_position(side));
827 
828  uc
829  .allow_add_to_recall(true)
830  .allow_discover(true)
831  .allow_get_village(true)
832  .allow_invalidate(true)
833  .allow_rename_side(true)
834  .allow_show(true);
835 
836  try
837  {
838  uc.add_unit(parsed_cfg, &cfg);
839  }
840  catch(const unit_type::error& e)
841  {
842  ERR_WML << "Error occured inside [unit]: " << e.what();
843 
844  throw;
845  }
846 }
847 
848 } // end namespace game_events
#define WRN_NG
Definition: action_wml.cpp:54
std::string filename_
Definition: action_wml.cpp:582
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.
map_location loc
Definition: move.cpp:172
Managing the AIs lifecycle - headers TODO: Refactor history handling and internal commands.
double t
Definition: astarsearch.cpp:63
static manager & get_singleton()
Definition: manager.hpp:140
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:157
void remove_attribute(std::string_view key)
Definition: config.cpp:162
config & add_child(std::string_view key)
Definition: config.cpp:436
const attribute_value * get(std::string_view key) const
Returns a pointer to the attribute with the given key or nullptr if it does not exist.
Definition: config.cpp:665
bool has_attribute(std::string_view key) const
Definition: config.cpp:157
std::string debug() const
Definition: config.cpp:1214
bool empty() const
Definition: config.cpp:823
void clear()
Definition: config.cpp:802
void reload_map()
Updates internals that cache map size.
Definition: display.cpp:427
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:102
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:80
utils::optional< std::string > replace_map(const gamemap &r)
Definition: game_board.cpp:282
team & get_team(int i)
Definition: game_board.hpp:92
virtual const unit_map & units() const override
Definition: game_board.hpp:107
virtual const gamemap & map() const override
Definition: game_board.hpp:97
@ 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
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:347
location_map & special_locations()
Definition: map.hpp:105
Encapsulates the map of the game.
Definition: map.hpp:176
void read(std::string_view data, const bool allow_invalid=true)
Definition: map.cpp:110
A RAII object to temporary leave the synced context like in wesnoth.synchronize_choice.
void add(const teleport_group &group)
Definition: teleport.cpp:285
void remove(const std::string &id)
Definition: teleport.cpp:289
This class encapsulates the recall list of a team.
iterator end()
end iterator
iterator erase(const iterator &it)
Erase an iterator to this object.
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 bool run_in_synced_context_if_not_already(const std::string &commandname, const config &data, action_spectator &spectator=get_default_spectator())
Checks whether we are currently running in a synced context, and if not we enters it.
static synced_state get_synced_state()
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:1253
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:39
static unit_ptr create(const config &cfg, bool use_traits=false, const vconfig *vcfg=nullptr)
Initializes a unit from a config.
Definition: unit.hpp:107
Additional functionality for a non-const variable_info.
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...
A variable-expanding proxy for the config class.
Definition: variable.hpp:45
std::vector< vconfig > child_list
Definition: variable.hpp:78
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
const config * cfg
Declarations for File-IO.
std::size_t i
Definition: function.cpp:1031
int side() const
The side this unit belongs to.
Definition: unit.hpp:249
Standard logging facilities (interface).
place_recruit_result place_recruit(const 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:106
Domain specific events.
WML_HANDLER_FUNCTION(clear_global_variable,, pcfg)
Experimental data persistence.
Definition: action_wml.cpp:244
void remove()
Removes a tip.
Definition: tooltip.cpp:94
std::stringstream & log_to_chat()
Use this to show WML errors in the ingame chat.
Definition: log.cpp:550
std::string tag(std::string_view tag, Args &&... data)
Wraps the given data in the specified tag.
Definition: markup.hpp:45
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, const unit_ptr &u, bool animate, map_location::direction dir, bool force_scroll)
Display a unit moving along a given path.
Definition: udisplay.cpp:510
std::size_t size(std::string_view str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:81
int stoi(std::string_view str)
Same interface as std::stoi and meant as a drop in replacement, except:
Definition: charconv.hpp:156
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::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:147
rect dst
Location on the final composed sheet.
rect src
Non-transparent portion of the surface to compose.
std::string filename
Filename.
Encapsulates the map of the game.
Definition: location.hpp:46
static std::string write_direction(direction dir)
Definition: location.cpp:154
static const map_location & null_location()
Definition: location.hpp:103
static direction parse_direction(const std::string &str)
Definition: location.cpp:79
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.
unit_type_data unit_types
Definition: types.cpp:1494
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