The Battle for Wesnoth  1.19.7+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;
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.
252 WML_HANDLER_FUNCTION(do_command,, cfg)
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 vconfig::child_list unit_cfgs = cfg.get_children("fake_unit");
385  std::size_t num_units = unit_cfgs.size();
386  std::vector<fake_unit_ptr > units;
387  units.reserve(num_units);
388  std::vector<std::vector<map_location>> paths;
389  paths.reserve(num_units);
390 
391  LOG_NG << "Moving " << num_units << " units";
392 
393  std::size_t longest_path = 0;
394 
395  for (const vconfig& config : unit_cfgs) {
396  const std::vector<std::string> xvals = utils::split(config["x"]);
397  const std::vector<std::string> yvals = utils::split(config["y"]);
398  int skip_steps = config["skip_steps"].to_int();
399  fake_unit_ptr u = create_fake_unit(config);
400  units.push_back(u);
401  paths.push_back(fake_unit_path(*u, xvals, yvals));
402  if(skip_steps > 0)
403  paths.back().insert(paths.back().begin(), skip_steps, paths.back().front());
404  longest_path = std::max(longest_path, paths.back().size());
405  DBG_NG << "Path " << paths.size() - 1 << " has length " << paths.back().size();
406 
407  u->set_location(paths.back().front());
409  }
410 
411  LOG_NG << "Units placed, longest path is " << longest_path << " long";
412 
413  std::vector<map_location> path_step(2);
414  path_step.resize(2);
415  for(std::size_t step = 1; step < longest_path; ++step) {
416  DBG_NG << "Doing step " << step << "...";
417  for(std::size_t un = 0; un < num_units; ++un) {
418  if(step >= paths[un].size() || paths[un][step - 1] == paths[un][step])
419  continue;
420  DBG_NG << "Moving unit " << un << ", doing step " << step;
421  path_step[0] = paths[un][step - 1];
422  path_step[1] = paths[un][step];
423  unit_display::move_unit(path_step, units[un].get_unit_ptr(), true, map_location::direction::indeterminate, force_scroll);
424  units[un]->set_location(path_step[1]);
425  units[un]->anim_comp().set_standing(false);
426  }
427  }
428 
429  LOG_NG << "Units moved";
430 }
431 
432 /**
433  * If we should recall units that match a certain description.
434  * If you change attributes specific to [recall] (that is, not a Standard Unit Filter)
435  * be sure to update data/lua/wml_tag, auto_recall feature for [role] to reflect your changes.
436  */
437 WML_HANDLER_FUNCTION(recall,, cfg)
438 {
439  events::command_disabler command_disabler;
440  LOG_NG << "recalling unit...";
441  config temp_config(cfg.get_config());
442  // Prevent the recall unit filter from using the location as a criterion
443 
444  /**
445  * FIXME: we should design the WML to avoid these types of
446  * collisions; filters should be named consistently and always have a
447  * distinct scope.
448  */
449  temp_config["x"] = "recall";
450  temp_config["y"] = "recall";
451  temp_config.remove_attribute("location_id");
452  vconfig unit_filter_cfg(temp_config);
453  const vconfig & leader_filter = cfg.child("secondary_unit");
454 
455  for(team& t : resources::gameboard->teams()) {
456  LOG_NG << "for side " << t.side() << "...";
457  const std::string player_id = t.save_id_or_number();
458 
459  if(t.recall_list().size() < 1) {
460  DBG_NG << "recall list is empty when trying to recall!";
461  DBG_NG << "player_id: " << player_id << " side: " << t.side();
462  continue;
463  }
464 
465  recall_list_manager & avail = t.recall_list();
466  std::vector<unit_map::unit_iterator> leaders = resources::gameboard->units().find_leaders(t.side());
467 
468  const unit_filter ufilt(unit_filter_cfg);
469  const unit_filter lfilt(leader_filter); // Note that if leader_filter is null, this correctly gives a null filter that matches all units.
470  for(std::vector<unit_ptr>::iterator u = avail.begin(); u != avail.end(); ++u) {
471  DBG_NG << "checking unit against filter...";
472  scoped_recall_unit auto_store("this_unit", player_id, std::distance(avail.begin(), u));
473  if (ufilt(*(*u), map_location())) {
474  DBG_NG << (*u)->id() << " matched the filter...";
475  const unit_ptr to_recruit = *u;
476  const unit* pass_check = to_recruit.get();
477  if(!cfg["check_passability"].to_bool(true)) pass_check = nullptr;
478  map_location cfg_loc = cfg_to_loc(cfg);
479  if(cfg.has_attribute("location_id")) {
480  const auto& special_locs = resources::gameboard->map().special_locations().left;
481  const auto& iter = special_locs.find(cfg["location_id"]);
482  if(iter != special_locs.end()) {
483  cfg_loc = iter->second;
484  }
485  }
486 
487  for (unit_map::const_unit_iterator leader : leaders) {
488  DBG_NG << "...considering " + leader->id() + " as the recalling leader...";
489  map_location loc = cfg_loc;
490  if ( lfilt(*leader) &&
491  unit_filter(vconfig(leader->recall_filter())).matches( *(*u),map_location() ) ) {
492  DBG_NG << "...matched the leader filter and is able to recall the unit.";
493  if(!resources::gameboard->map().on_board(loc))
494  loc = leader->get_location();
495  if(pass_check || (resources::gameboard->units().count(loc) > 0))
497  if(resources::gameboard->map().on_board(loc)) {
498  DBG_NG << "...valid location for the recall found. Recalling.";
499  avail.erase(u); // Erase before recruiting, since recruiting can fire more events
500  actions::place_recruit(to_recruit, loc, leader->get_location(), 0, true,
501  map_location::parse_direction(cfg["facing"]),
502  cfg["show"].to_bool(true), cfg["fire_event"].to_bool(false),
503  true, true);
504  return;
505  }
506  }
507  }
508  if (resources::gameboard->map().on_board(cfg_loc)) {
509  map_location loc = cfg_loc;
510  if(pass_check || (resources::gameboard->units().count(loc) > 0))
512  // Check if we still have a valid location
513  if (resources::gameboard->map().on_board(loc)) {
514  DBG_NG << "No usable leader found, but found usable location. Recalling.";
515  avail.erase(u); // Erase before recruiting, since recruiting can fire more events
516  map_location null_location = map_location::null_location();
517  actions::place_recruit(to_recruit, loc, null_location, 0, true,
518  map_location::parse_direction(cfg["facing"]),
519  cfg["show"].to_bool(true), cfg["fire_event"].to_bool(false),
520  true, true);
521  return;
522  }
523  }
524  }
525  }
526  }
527  LOG_WML << "A [recall] tag with the following content failed:\n" << cfg.get_config().debug();
528 }
529 
530 namespace {
531  struct map_choice : public mp_sync::user_choice
532  {
533  map_choice(const std::string& filename) : filename_(filename) {}
534  std::string filename_;
535  virtual config query_user(int /*side*/) const override
536  {
537  std::string res = filesystem::read_map(filename_);
538  return config {"map_data", res};
539  }
540  virtual config random_choice(int /*side*/) const override
541  {
542  return config();
543  }
544  virtual std::string description() const override
545  {
546  return "Map Data";
547  }
548  virtual bool is_visible() const override
549  {
550  // Allow query_user() to be called during prestart events, as it doesn't show any UI.
551  return false;
552  }
553  };
554 }
555 
556 /**
557  * Experimental map replace
558  * @todo Finish experimenting.
559  */
560 WML_HANDLER_FUNCTION(replace_map,, cfg)
561 {
562  /*
563  * When a hex changes from a village terrain to a non-village terrain, and
564  * a team owned that village it loses that village. When a hex changes from
565  * a non-village terrain to a village terrain and there is a unit on that
566  * hex it does not automatically capture the village. The reason for not
567  * capturing villages it that there are too many choices to make; should a
568  * unit loose its movement points, should capture events be fired. It is
569  * easier to do this as wanted by the author in WML.
570  */
571 
572  const gamemap * game_map = & resources::gameboard->map();
573  gamemap map(*game_map);
574 
575  try {
576  if(!cfg["map_file"].empty()) {
577  config file_cfg = mp_sync::get_user_choice("map_data", map_choice(cfg["map_file"].str()));
578  map.read(file_cfg["map_data"].str(), false);
579  } else if(!cfg["map_data"].empty()) {
580  map.read(cfg["map_data"], false);
581  } else {
582  deprecated_message("[replace_map]map=", DEP_LEVEL::INDEFINITE, "1.16", "Use map_data= instead.");
583  map.read(cfg["map"], false);
584  }
585  } catch(const incorrect_map_format_error&) {
586  const std::string log_map_name = cfg["map"].empty() ? cfg["map_file"] : std::string("from inline data");
587  lg::log_to_chat() << "replace_map: Unable to load map " << log_map_name << '\n';
588  ERR_WML << "replace_map: Unable to load map " << log_map_name;
589  return;
590  } catch(const wml_exception& e) {
591  e.show();
592  return;
593  }
594 
595  if (map.total_width() > game_map->total_width()
596  || map.total_height() > game_map->total_height()) {
597  if (!cfg["expand"].to_bool()) {
598  lg::log_to_chat() << "replace_map: Map dimension(s) increase but expand is not set\n";
599  ERR_WML << "replace_map: Map dimension(s) increase but expand is not set";
600  return;
601  }
602  }
603 
604  if (map.total_width() < game_map->total_width()
605  || map.total_height() < game_map->total_height()) {
606  if (!cfg["shrink"].to_bool()) {
607  lg::log_to_chat() << "replace_map: Map dimension(s) decrease but shrink is not set\n";
608  ERR_WML << "replace_map: Map dimension(s) decrease but shrink is not set";
609  return;
610  }
611  }
612 
613  utils::optional<std::string> errmsg = resources::gameboard->replace_map(map);
614 
615  if (errmsg) {
616  lg::log_to_chat() << *errmsg << '\n';
617  ERR_WML << *errmsg;
618  }
619 
622  ai::manager::get_singleton().raise_map_changed();
623 }
624 
625 /**
626  * Experimental data persistence
627  * @todo Finish experimenting.
628  */
630 {
631  if (!resources::controller->is_replay())
633 }
634 
635 WML_HANDLER_FUNCTION(set_variables,, cfg)
636 {
637  const std::string name = cfg["name"];
639  if(name.empty()) {
640  ERR_NG << "trying to set a variable with an empty name:\n" << cfg.get_config().debug();
641  return;
642  }
643 
644  std::vector<config> data;
645  if(cfg.has_attribute("to_variable"))
646  {
647  try
648  {
650  for (const config& c : tovar.as_array())
651  {
652  data.push_back(c);
653  }
654  }
655  catch(const invalid_variablename_exception&)
656  {
657  ERR_NG << "Cannot do [set_variables] with invalid to_variable variable: " << cfg["to_variable"] << " with " << cfg.get_config().debug();
658  }
659  } else {
660  typedef std::pair<std::string, vconfig> vchild;
661  for (const vchild& p : cfg.all_ordered()) {
662  if(p.first == "value") {
663  data.push_back(p.second.get_parsed_config());
664  } else if(p.first == "literal") {
665  data.push_back(p.second.get_config());
666  } else if(p.first == "split") {
667  const vconfig & split_element = p.second;
668 
669  std::string split_string=split_element["list"];
670  std::string separator_string=split_element["separator"];
671  std::string key_name=split_element["key"];
672  if(key_name.empty())
673  {
674  key_name="value";
675  }
676 
677  bool remove_empty = split_element["remove_empty"].to_bool();
678 
679  char* separator = separator_string.empty() ? nullptr : &separator_string[0];
680  if(separator_string.size() > 1){
681  ERR_NG << "[set_variables] [split] separator only supports 1 character, multiple passed: " << split_element["separator"] << " with " << cfg.get_config().debug();
682  }
683 
684  std::vector<std::string> split_vector;
685 
686  //if no separator is specified, explode the string
687  if(separator == nullptr)
688  {
689  for(std::string::iterator i=split_string.begin(); i!=split_string.end(); ++i)
690  {
691  split_vector.push_back(std::string(1, *i));
692  }
693  }
694  else {
695  split_vector=utils::split(split_string, *separator, remove_empty ? utils::REMOVE_EMPTY | utils::STRIP_SPACES : utils::STRIP_SPACES);
696  }
697 
698  for(std::vector<std::string>::iterator i=split_vector.begin(); i!=split_vector.end(); ++i)
699  {
700  data.emplace_back(key_name, *i);
701  }
702  }
703  }
704  }
705  try
706  {
707  const std::string& mode = cfg["mode"];
708  if(mode == "merge")
709  {
710  if(dest.explicit_index() && data.size() > 1)
711  {
712  //merge children into one
713  config merged_children;
714  for (const config &ch : data) {
715  merged_children.append(ch);
716  }
717  data = {merged_children};
718  }
719  dest.merge_array(data);
720  }
721  else if(mode == "insert")
722  {
723  dest.insert_array(data);
724  }
725  else if(mode == "append")
726  {
727  dest.append_array(data);
728  }
729  else /*default if(mode == "replace")*/
730  {
731  dest.replace_array(data);
732  }
733  }
734  catch(const invalid_variablename_exception&)
735  {
736  ERR_NG << "Cannot do [set_variables] with invalid destination variable: " << name << " with " << cfg.get_config().debug();
737  }
738 }
739 
740 /**
741  * Store the relative direction from one hex to another in a WML variable.
742  * This is mainly useful as a diagnostic tool, but could be useful
743  * for some kind of scenario.
744  */
745 WML_HANDLER_FUNCTION(store_relative_direction,, cfg)
746 {
747  if (!cfg.child("source")) {
748  WRN_NG << "No source in [store_relative_direction]";
749  return;
750  }
751  if (!cfg.child("destination")) {
752  WRN_NG << "No destination in [store_relative_direction]";
753  return;
754  }
755  if (!cfg.has_attribute("variable")) {
756  WRN_NG << "No variable in [store_relative_direction]";
757  return;
758  }
759 
760  const map_location src = cfg_to_loc(cfg.child("source"));
761  const map_location dst = cfg_to_loc(cfg.child("destination"));
762 
763  std::string variable = cfg["variable"];
764  map_location::RELATIVE_DIR_MODE mode = static_cast<map_location::RELATIVE_DIR_MODE> (cfg["mode"].to_int(0));
765  try
766  {
768 
769  store.as_scalar() = map_location::write_direction(src.get_relative_dir(dst,mode));
770  }
771  catch(const invalid_variablename_exception&)
772  {
773  ERR_NG << "Cannot do [store_relative_direction] with invalid destination variable: " << variable << " with " << cfg.get_config().debug();
774  }
775 }
776 
777 /**
778  * Store the rotation of one hex around another in a WML variable.
779  * In increments of 60 degrees, clockwise.
780  * This is mainly useful as a diagnostic tool, but could be useful
781  * for some kind of scenario.
782  */
783 WML_HANDLER_FUNCTION(store_rotate_map_location,, cfg)
784 {
785  if (!cfg.child("source")) {
786  WRN_NG << "No source in [store_rotate_map_location]";
787  return;
788  }
789  if (!cfg.child("destination")) {
790  WRN_NG << "No destination in [store_rotate_map_location]";
791  return;
792  }
793  if (!cfg.has_attribute("variable")) {
794  WRN_NG << "No variable in [store_rotate_map_location]";
795  return;
796  }
797 
798  const map_location src = cfg_to_loc(cfg.child("source"));
799  const map_location dst = cfg_to_loc(cfg.child("destination"));
800 
801  std::string variable = cfg["variable"];
802  int angle = cfg["angle"].to_int(1);
803 
804  try
805  {
807 
808  dst.rotate_right_around_center(src,angle).write(store.as_container());
809  }
810  catch(const invalid_variablename_exception&)
811  {
812  ERR_NG << "Cannot do [store_rotate_map_location] with invalid destination variable: " << variable << " with " << cfg.get_config().debug();
813  }
814 }
815 
816 WML_HANDLER_FUNCTION(tunnel,, cfg)
817 {
818  const bool remove = cfg["remove"].to_bool(false);
819  const bool delay = cfg["delayed_variable_substitution"].to_bool(true);
820  if (remove) {
821  const std::vector<std::string> ids = utils::split(cfg["id"]);
822  for (const std::string &id : ids) {
824  }
825  } else if (cfg.get_children("source").empty() ||
826  cfg.get_children("target").empty() ||
827  cfg.get_children("filter").empty()) {
828  ERR_WML << "[tunnel] is missing a mandatory tag:\n"
829  << cfg.get_config().debug();
830  } else if (cfg.get_children("source").size() > 1 ||
831  cfg.get_children("target").size() > 1 ||
832  cfg.get_children("filter").size() > 1) {
833  ERR_WML << "[tunnel] should have exactly one of each mandatory tag:\n"
834  << cfg.get_config().debug();
835  } else {
836  pathfind::teleport_group tunnel(delay ? cfg : vconfig(cfg.get_parsed_config()), false);
837  resources::tunnels->add(tunnel);
838 
839  if(cfg["bidirectional"].to_bool(true)) {
840  tunnel = pathfind::teleport_group(delay ? cfg : vconfig(cfg.get_parsed_config()), true);
841  resources::tunnels->add(tunnel);
842  }
843  }
844 }
845 
846 /** If we should spawn a new unit on the map somewhere */
848 {
849  events::command_disabler command_disabler;
850  config parsed_cfg = cfg.get_parsed_config();
851 
852  config::attribute_value to_variable = cfg["to_variable"];
853  if (!to_variable.blank())
854  {
855  parsed_cfg.remove_attribute("to_variable");
856  unit_ptr new_unit = unit::create(parsed_cfg, true, &cfg);
857  try
858  {
859  config &var = resources::gamedata->get_variable_cfg(to_variable);
860  var.clear();
861  new_unit->write(var);
862  if (const config::attribute_value *v = parsed_cfg.get("x")) var["x"] = *v;
863  if (const config::attribute_value *v = parsed_cfg.get("y")) var["y"] = *v;
864  }
865  catch(const invalid_variablename_exception&)
866  {
867  ERR_NG << "Cannot do [unit] with invalid to_variable: " << to_variable << " with " << cfg.get_config().debug();
868  }
869  return;
870 
871  }
872 
873  int side = parsed_cfg["side"].to_int(1);
874 
875 
876  if ((side<1)||(side > static_cast<int>(resources::gameboard->teams().size()))) {
877  ERR_NG << "wrong side in [unit] tag - no such side: "<<side<<" ( number of teams :"<<resources::gameboard->teams().size()<<")";
878  DBG_NG << parsed_cfg.debug();
879  return;
880  }
881  team &tm = resources::gameboard->get_team(side);
882 
883  unit_creator uc(tm,resources::gameboard->map().starting_position(side));
884 
885  uc
886  .allow_add_to_recall(true)
887  .allow_discover(true)
888  .allow_get_village(true)
889  .allow_invalidate(true)
890  .allow_rename_side(true)
891  .allow_show(true);
892 
893  try
894  {
895  uc.add_unit(parsed_cfg, &cfg);
896  }
897  catch(const unit_type::error& e)
898  {
899  ERR_WML << "Error occured inside [unit]: " << e.what();
900 
901  throw;
902  }
903 }
904 
905 } // end namespace game_events
#define WRN_NG
Definition: action_wml.cpp:54
std::string filename_
Definition: action_wml.cpp:534
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:158
void append(const config &cfg)
Append data from another config object to this one.
Definition: config.cpp:203
void remove_attribute(config_key_type key)
Definition: config.cpp:162
std::string debug() const
Definition: config.cpp:1240
void clear()
Definition: config.cpp:828
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:685
config & add_child(config_key_type key)
Definition: config.cpp:440
void reload_map()
Updates internals that cache map size.
Definition: display.cpp:438
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:111
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
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: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: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:75
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:1265
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:1029
int side() const
The side this unit belongs to.
Definition: unit.hpp:343
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:92
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:520
std::string tag(const std::string &tag_name, Args &&... contents)
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:505
std::size_t size(std::string_view str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:85
@ STRIP_SPACES
REMOVE_EMPTY: remove empty elements.
@ REMOVE_EMPTY
int stoi(std::string_view str)
Same interface as std::stoi and meant as a drop in replacement, except:
Definition: charconv.hpp:154
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:178
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:149
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:45
static std::string write_direction(direction dir)
Definition: location.cpp:154
static const map_location & null_location()
Definition: location.hpp:102
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.
mock_char c
mock_party p
unit_type_data unit_types
Definition: types.cpp:1504
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