The Battle for Wesnoth  1.15.0-dev
action_wml.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
15 /**
16  * @file
17  * Implementations of action WML tags, other than those implemented in Lua, and
18  * excluding conditional action WML.
19  */
20 
23 #include "game_events/pump.hpp"
24 
25 #include "actions/attack.hpp"
26 #include "actions/create.hpp"
27 #include "actions/move.hpp"
28 #include "actions/vision.hpp"
29 #include "ai/manager.hpp"
30 #include "fake_unit_ptr.hpp"
31 #include "filesystem.hpp"
32 #include "game_classification.hpp"
33 #include "game_display.hpp"
34 #include "preferences/game.hpp"
35 #include "gettext.hpp"
37 #include "gui/widgets/retval.hpp"
38 #include "log.hpp"
39 #include "map/map.hpp"
40 #include "map/exception.hpp"
41 #include "map/label.hpp"
42 #include "pathfind/teleport.hpp"
43 #include "pathfind/pathfind.hpp"
44 #include "persist_var.hpp"
45 #include "play_controller.hpp"
46 #include "recall_list_manager.hpp"
47 #include "replay.hpp"
48 #include "random.hpp"
49 #include "mouse_handler_base.hpp" // for events::commands_disabled
50 #include "resources.hpp"
52 #include "side_filter.hpp"
53 #include "soundsource.hpp"
54 #include "synced_context.hpp"
55 #include "synced_user_choice.hpp"
56 #include "team.hpp"
57 #include "terrain/filter.hpp"
58 #include "units/unit.hpp"
60 #include "units/udisplay.hpp"
61 #include "units/filter.hpp"
62 #include "wml_exception.hpp"
63 #include "whiteboard/manager.hpp"
64 #include "deprecation.hpp"
65 
66 #include <boost/regex.hpp>
67 
68 static lg::log_domain log_engine("engine");
69 #define DBG_NG LOG_STREAM(debug, log_engine)
70 #define LOG_NG LOG_STREAM(info, log_engine)
71 #define WRN_NG LOG_STREAM(warn, log_engine)
72 #define ERR_NG LOG_STREAM(err, log_engine)
73 
74 static lg::log_domain log_display("display");
75 #define DBG_DP LOG_STREAM(debug, log_display)
76 #define LOG_DP LOG_STREAM(info, log_display)
77 
78 static lg::log_domain log_wml("wml");
79 #define LOG_WML LOG_STREAM(info, log_wml)
80 #define WRN_WML LOG_STREAM(warn, log_wml)
81 #define ERR_WML LOG_STREAM(err, log_wml)
82 
83 static lg::log_domain log_config("config");
84 #define ERR_CF LOG_STREAM(err, log_config)
85 
86 
87 // This file is in the game_events namespace.
88 namespace game_events
89 {
90 
91 // This must be defined before any WML actions are.
92 // (So keep it at the rop of this file?)
94 
95 namespace { // Support functions
96 
97  /**
98  * Converts a vconfig to a location (based on x,y=).
99  * The default parameter values cause the default return value (if neither
100  * x nor y is specified) to equal map_location::null_location().
101  */
102  map_location cfg_to_loc(const vconfig& cfg, int defaultx = -999, int defaulty = -999)
103  {
104  return map_location(cfg["x"].to_int(defaultx), cfg["y"].to_int(defaulty), wml_loc());
105  }
106 
107  fake_unit_ptr create_fake_unit(const vconfig& cfg)
108  {
109  std::string type = cfg["type"];
110  std::string variation = cfg["variation"];
111  std::string img_mods = cfg["image_mods"];
112 
113  std::size_t side_num = cfg["side"].to_int(1);
114  if (!resources::gameboard->has_team(side_num)) {
115  side_num = 1;
116  }
117 
118  unit_race::GENDER gender = string_gender(cfg["gender"]);
119  const unit_type *ut = unit_types.find(type);
120  if (!ut) return fake_unit_ptr();
121  fake_unit_ptr fake = fake_unit_ptr(unit::create(*ut, side_num, false, gender));
122 
123  if(!variation.empty()) {
124  config mod;
125  config &effect = mod.add_child("effect");
126  effect["apply_to"] = "variation";
127  effect["name"] = variation;
128  fake->add_modification("variation",mod);
129  }
130 
131  if(!img_mods.empty()) {
132  config mod;
133  config &effect = mod.add_child("effect");
134  effect["apply_to"] = "image_mod";
135  effect["add"] = img_mods;
136  fake->add_modification("image_mod",mod);
137  }
138 
139  return fake;
140  }
141 
142  std::vector<map_location> fake_unit_path(const unit& fake_unit, const std::vector<std::string>& xvals, const std::vector<std::string>& yvals)
143  {
144  const gamemap *game_map = & resources::gameboard->map();
145  std::vector<map_location> path;
146  map_location src;
147  map_location dst;
148  for(std::size_t i = 0; i != std::min(xvals.size(),yvals.size()); ++i) {
149  if(i==0){
150  try {
151  src.set_wml_x(std::stoi(xvals[i]));
152  src.set_wml_y(std::stoi(yvals[i]));
153  } catch(std::invalid_argument&) {
154  ERR_CF << "Invalid move_unit_fake source: " << xvals[i] << ", " << yvals[i] << '\n';
155  continue;
156  }
157  if (!game_map->on_board(src)) {
158  ERR_CF << "Invalid move_unit_fake source: " << src << '\n';
159  break;
160  }
161  path.push_back(src);
162  continue;
163  }
164  pathfind::shortest_path_calculator calc(fake_unit,
165  resources::gameboard->get_team(fake_unit.side()),
166  resources::gameboard->teams(),
167  *game_map);
168 
169  try {
170  dst.set_wml_x(std::stoi(xvals[i]));
171  dst.set_wml_y(std::stoi(yvals[i]));
172  } catch(std::invalid_argument&) {
173  ERR_CF << "Invalid move_unit_fake destination: " << xvals[i] << ", " << yvals[i] << '\n';
174  }
175  if (!game_map->on_board(dst)) {
176  ERR_CF << "Invalid move_unit_fake destination: " << dst << '\n';
177  break;
178  }
179 
180  pathfind::plain_route route = pathfind::a_star_search(src, dst, 10000, calc,
181  game_map->w(), game_map->h());
182 
183  if (route.steps.empty()) {
184  WRN_NG << "Could not find move_unit_fake route from " << src << " to " << dst << ": ignoring complexities" << std::endl;
185  pathfind::emergency_path_calculator emergency_calc(fake_unit, *game_map);
186 
187  route = pathfind::a_star_search(src, dst, 10000, emergency_calc,
188  game_map->w(), game_map->h());
189  if(route.steps.empty()) {
190  // This would occur when trying to do a MUF of a unit
191  // over locations which are unreachable to it (infinite movement
192  // costs). This really cannot fail.
193  WRN_NG << "Could not find move_unit_fake route from " << src << " to " << dst << ": ignoring terrain" << std::endl;
194  pathfind::dummy_path_calculator dummy_calc(fake_unit, *game_map);
195  route = a_star_search(src, dst, 10000, dummy_calc, game_map->w(), game_map->h());
196  assert(!route.steps.empty());
197  }
198  }
199  // we add this section to the end of the complete path
200  // skipping section's head because already included
201  // by the previous iteration
202  path.insert(path.end(),
203  route.steps.begin()+1, route.steps.end());
204 
205  src = dst;
206  }
207  return path;
208  }
209 } // end anonymous namespace (support functions)
210 
211 /**
212  * Using this constructor for a static object outside action_wml.cpp
213  * will likely lead to a static initialization fiasco.
214  * @param[in] tag The WML tag for this action.
215  * @param[in] function The callback for this action.
216  */
217 wml_action::wml_action(const std::string & tag, handler function)
218 {
219  registry_[tag] = function;
220 }
221 
222 
223 /**
224  * WML_HANDLER_FUNCTION macro handles auto registration for wml handlers
225  *
226  * @param pname wml tag name
227  * @param pei the variable name of the queued_event object inside the function
228  * @param pcfg the variable name of the config object inside the function
229  *
230  * You are warned! This is evil macro magic!
231  *
232  * The following code registers a [foo] tag:
233  * \code
234  * // comment out unused parameters to prevent compiler warnings
235  * WML_HANDLER_FUNCTION(foo, event_info, cfg)
236  * {
237  * // code for foo
238  * }
239  * \endcode
240  *
241  * Generated code looks like this:
242  * \code
243  * void wml_func_foo(...);
244  * static wml_action wml_action_foo("foo", &wml_func_foo);
245  * void wml_func_foo(...)
246  * {
247  * // code for foo
248  * }
249  * \endcode
250  */
251 #define WML_HANDLER_FUNCTION(pname, pei, pcfg) \
252  static void wml_func_##pname(const queued_event &pei, const vconfig &pcfg); \
253  static wml_action wml_action_##pname(#pname, &wml_func_##pname); \
254  static void wml_func_##pname(const queued_event& pei, const vconfig& pcfg)
255 
256 
257 /// Experimental data persistence
258 /// @todo Finish experimenting.
260 {
261  if (!resources::controller->is_replay())
263 }
264 
265 static void on_replay_error(const std::string& message, bool /*b*/)
266 {
267  ERR_NG << "Error via [do_command]:" << std::endl;
268  ERR_NG << message << std::endl;
269 }
270 
271 // This tag exposes part of the code path used to handle [command]'s in replays
272 // This allows to perform scripting in WML that will use the same code path as player actions, for example.
273 WML_HANDLER_FUNCTION(do_command,, cfg)
274 {
275  // Doing this in a whiteboard applied context will cause bugs
276  // Note that even though game_events::wml_event_pump() will always apply the real unit map
277  // It is still possible get a wml commands to run in a whiteboard applied context
278  // With the theme_items lua callbacks
279  if(resources::whiteboard->has_planned_unit_map())
280  {
281  ERR_NG << "[do_command] called while whiteboard is applied, ignoring" << std::endl;
282  return;
283  }
284 
285  static const std::set<std::string> allowed_tags {"attack", "move", "recruit", "recall", "disband", "fire_event", "custom_command"};
286 
287  const bool is_too_early = resources::gamedata->phase() != game_data::START && resources::gamedata->phase() != game_data::PLAY;
288  const bool is_unsynced_too_early = resources::gamedata->phase() != game_data::PLAY;
289  const bool is_unsynced = synced_context::get_synced_state() == synced_context::UNSYNCED;
290  if(is_too_early)
291  {
292  ERR_NG << "[do_command] called too early, only allowed at START or later" << std::endl;
293  return;
294  }
295  if(is_unsynced && resources::controller->is_lingering())
296  {
297  ERR_NG << "[do_command] cannot be used in linger mode" << std::endl;
298  return;
299  }
300  if(is_unsynced && !resources::controller->gamestate().init_side_done())
301  {
302  ERR_NG << "[do_command] cannot be used before the turn has started" << std::endl;
303  return;
304  }
305  if(is_unsynced && is_unsynced_too_early)
306  {
307  ERR_NG << "[do_command] called too early" << std::endl;
308  return;
309  }
310  if(is_unsynced && events::commands_disabled)
311  {
312  ERR_NG << "[do_command] cannot invoke synced commands while commands are blocked" << std::endl;
313  return;
314  }
315  if(is_unsynced && !resources::controller->current_team().is_local())
316  {
317  ERR_NG << "[do_command] can only be used from clients that control the currently playing side" << std::endl;
318  return;
319  }
320  for(vconfig::all_children_iterator i = cfg.ordered_begin(); i != cfg.ordered_end(); ++i)
321  {
322  if(allowed_tags.find( i.get_key()) == allowed_tags.end()) {
323  ERR_NG << "unsupported tag [" << i.get_key() << "] in [do_command]" << std::endl;
324  std::stringstream o;
325  std::copy(allowed_tags.begin(), allowed_tags.end(), std::ostream_iterator<std::string>(o, " "));
326  ERR_NG << "allowed tags: " << o.str() << std::endl;
327  continue;
328  }
329  // TODO: afaik run_in_synced_context_if_not_already thows exceptions when the executed action end the scenario or the turn.
330  // This could cause problems, specially when its unclear whether that exception is caught by lua or not...
331 
332  //Note that this fires related events and everything else that also happens normally.
333  //have to watch out with the undo stack, therefore forbid [auto_shroud] and [update_shroud] here...
335  /*commandname*/ i.get_key(),
336  /*data*/ i.get_child().get_parsed_config(),
337  /*use_undo*/ true,
338  /*show*/ true,
339  /*error_handler*/ &on_replay_error
340  );
341  }
342 }
343 
344 /// Experimental data persistence
345 /// @todo Finish experimenting.
347 {
349 }
350 
351 WML_HANDLER_FUNCTION(modify_turns,, cfg)
352 {
353  config::attribute_value value = cfg["value"];
354  std::string add = cfg["add"];
355  config::attribute_value current = cfg["current"];
357  if(!add.empty()) {
358  tod_man.modify_turns_by_wml(add);
359  } else if(!value.empty()) {
360  tod_man.set_number_of_turns_by_wml(value.to_int(-1));
361  }
362  // change current turn only after applying mods
363  if(!current.empty()) {
364  const unsigned int current_turn_number = tod_man.turn();
365  int new_turn_number = current.to_int(current_turn_number);
366  const unsigned int new_turn_number_u = static_cast<unsigned int>(new_turn_number);
367  if(new_turn_number_u < 1 || (new_turn_number > tod_man.number_of_turns() && tod_man.number_of_turns() != -1)) {
368  ERR_NG << "attempted to change current turn number to one out of range (" << new_turn_number << ")" << std::endl;
369  } else if(new_turn_number_u != current_turn_number) {
370  tod_man.set_turn_by_wml(new_turn_number_u, resources::gamedata);
372  }
373  }
374 }
375 
376 /// Moving a 'unit' - i.e. a dummy unit
377 /// that is just moving for the visual effect
378 WML_HANDLER_FUNCTION(move_unit_fake,, cfg)
379 {
380  events::command_disabler command_disabler;
381  fake_unit_ptr dummy_unit(create_fake_unit(cfg));
382  if(!dummy_unit.get())
383  return;
384 
385  const bool force_scroll = cfg["force_scroll"].to_bool(true);
386 
387  const std::string x = cfg["x"];
388  const std::string y = cfg["y"];
389 
390  const std::vector<std::string> xvals = utils::split(x);
391  const std::vector<std::string> yvals = utils::split(y);
392 
393  const std::vector<map_location>& path = fake_unit_path(*dummy_unit, xvals, yvals);
394  if (!path.empty()) {
395  // Always scroll.
396  unit_display::move_unit(path, dummy_unit.get_unit_ptr(), true, map_location::NDIRECTIONS, force_scroll);
397  }
398 }
399 
400 WML_HANDLER_FUNCTION(move_units_fake,, cfg)
401 {
402  events::command_disabler command_disabler;
403  LOG_NG << "Processing [move_units_fake]\n";
404 
405  const bool force_scroll = cfg["force_scroll"].to_bool();
406  const vconfig::child_list unit_cfgs = cfg.get_children("fake_unit");
407  std::size_t num_units = unit_cfgs.size();
408  std::vector<fake_unit_ptr > units;
409  units.reserve(num_units);
410  std::vector<std::vector<map_location>> paths;
411  paths.reserve(num_units);
412 
413  LOG_NG << "Moving " << num_units << " units\n";
414 
415  std::size_t longest_path = 0;
416 
417  for (const vconfig& config : unit_cfgs) {
418  const std::vector<std::string> xvals = utils::split(config["x"]);
419  const std::vector<std::string> yvals = utils::split(config["y"]);
420  int skip_steps = config["skip_steps"];
421  fake_unit_ptr u = create_fake_unit(config);
422  units.push_back(u);
423  paths.push_back(fake_unit_path(*u, xvals, yvals));
424  if(skip_steps > 0)
425  paths.back().insert(paths.back().begin(), skip_steps, paths.back().front());
426  longest_path = std::max(longest_path, paths.back().size());
427  DBG_NG << "Path " << paths.size() - 1 << " has length " << paths.back().size() << '\n';
428 
429  u->set_location(paths.back().front());
430  units.back().place_on_fake_unit_manager(resources::fake_units);
431  }
432 
433  LOG_NG << "Units placed, longest path is " << longest_path << " long\n";
434 
435  std::vector<map_location> path_step(2);
436  path_step.resize(2);
437  for(std::size_t step = 1; step < longest_path; ++step) {
438  DBG_NG << "Doing step " << step << "...\n";
439  for(std::size_t un = 0; un < num_units; ++un) {
440  if(step >= paths[un].size() || paths[un][step - 1] == paths[un][step])
441  continue;
442  DBG_NG << "Moving unit " << un << ", doing step " << step << '\n';
443  path_step[0] = paths[un][step - 1];
444  path_step[1] = paths[un][step];
445  unit_display::move_unit(path_step, units[un].get_unit_ptr(), true, map_location::NDIRECTIONS, force_scroll);
446  units[un]->set_location(path_step[1]);
447  units[un]->anim_comp().set_standing(false);
448  }
449  }
450 
451  LOG_NG << "Units moved\n";
452 }
453 
454 /// If we should recall units that match a certain description.
455 // If you change attributes specific to [recall] (that is, not a Standard Unit Filter)
456 // be sure to update data/lua/wml_tag, auto_recall feature for [role] to reflect your changes.
457 WML_HANDLER_FUNCTION(recall,, cfg)
458 {
459  events::command_disabler command_disabler;
460  LOG_NG << "recalling unit...\n";
461  config temp_config(cfg.get_config());
462  // Prevent the recall unit filter from using the location as a criterion
463 
464  /**
465  * @todo FIXME: we should design the WML to avoid these types of
466  * collisions; filters should be named consistently and always have a
467  * distinct scope.
468  */
469  temp_config["x"] = "recall";
470  temp_config["y"] = "recall";
471  temp_config.remove_attribute("location_id");
472  vconfig unit_filter_cfg(temp_config);
473  const vconfig & leader_filter = cfg.child("secondary_unit");
474 
475  for(int index = 0; index < static_cast<int>(resources::gameboard->teams().size()); ++index) {
476  LOG_NG << "for side " << index + 1 << "...\n";
477  const std::string player_id = resources::gameboard->teams()[index].save_id_or_number();
478 
479  if(resources::gameboard->teams()[index].recall_list().size() < 1) {
480  DBG_NG << "recall list is empty when trying to recall!\n"
481  << "player_id: " << player_id << " side: " << index+1 << "\n";
482  continue;
483  }
484 
485  recall_list_manager & avail = resources::gameboard->teams()[index].recall_list();
486  std::vector<unit_map::unit_iterator> leaders = resources::gameboard->units().find_leaders(index + 1);
487 
488  const unit_filter ufilt(unit_filter_cfg);
489  const unit_filter lfilt(leader_filter); // Note that if leader_filter is null, this correctly gives a null filter that matches all units.
490  for(std::vector<unit_ptr>::iterator u = avail.begin(); u != avail.end(); ++u) {
491  DBG_NG << "checking unit against filter...\n";
492  scoped_recall_unit auto_store("this_unit", player_id, std::distance(avail.begin(), u));
493  if (ufilt(*(*u), map_location())) {
494  DBG_NG << (*u)->id() << " matched the filter...\n";
495  const unit_ptr to_recruit = *u;
496  const unit* pass_check = to_recruit.get();
497  if(!cfg["check_passability"].to_bool(true)) pass_check = nullptr;
498  map_location cfg_loc = cfg_to_loc(cfg);
499  if(cfg.has_attribute("location_id")) {
500  const auto& special_locs = resources::gameboard->map().special_locations().left;
501  const auto& iter = special_locs.find(cfg["location_id"]);
502  if(iter != special_locs.end()) {
503  cfg_loc = iter->second;
504  }
505  }
506 
507  /// @todo fendrin: comment this monster
508  for (unit_map::const_unit_iterator leader : leaders) {
509  DBG_NG << "...considering " + leader->id() + " as the recalling leader...\n";
510  map_location loc = cfg_loc;
511  if ( lfilt(*leader) &&
512  unit_filter(vconfig(leader->recall_filter())).matches( *(*u),map_location() ) ) {
513  DBG_NG << "...matched the leader filter and is able to recall the unit.\n";
514  if(!resources::gameboard->map().on_board(loc))
515  loc = leader->get_location();
516  if(pass_check || (resources::gameboard->units().count(loc) > 0))
517  loc = pathfind::find_vacant_tile(loc, pathfind::VACANT_ANY, pass_check);
518  if(resources::gameboard->map().on_board(loc)) {
519  DBG_NG << "...valid location for the recall found. Recalling.\n";
520  avail.erase(u); // Erase before recruiting, since recruiting can fire more events
521  actions::place_recruit(to_recruit, loc, leader->get_location(), 0, true,
522  map_location::parse_direction(cfg["facing"]),
523  cfg["show"].to_bool(true), cfg["fire_event"].to_bool(false),
524  true, true);
525  return;
526  }
527  }
528  }
529  if (resources::gameboard->map().on_board(cfg_loc)) {
530  map_location loc = cfg_loc;
531  if(pass_check || (resources::gameboard->units().count(loc) > 0))
532  loc = pathfind::find_vacant_tile(loc, pathfind::VACANT_ANY, pass_check);
533  // Check if we still have a valid location
534  if (resources::gameboard->map().on_board(loc)) {
535  DBG_NG << "No usable leader found, but found usable location. Recalling.\n";
536  avail.erase(u); // Erase before recruiting, since recruiting can fire more events
537  map_location null_location = map_location::null_location();
538  actions::place_recruit(to_recruit, loc, null_location, 0, true,
539  map_location::parse_direction(cfg["facing"]),
540  cfg["show"].to_bool(true), cfg["fire_event"].to_bool(false),
541  true, true);
542  return;
543  }
544  }
545  }
546  }
547  }
548  LOG_WML << "A [recall] tag with the following content failed:\n" << cfg.get_config().debug();
549 }
550 
551 namespace {
552  struct map_choice : public mp_sync::user_choice
553  {
554  map_choice(const std::string& filename) : filename_(filename) {}
555  std::string filename_;
556  virtual config query_user(int /*side*/) const
557  {
558  //Do a regex check for the file format to prevent sending arbitrary files to other clients.
559  //Note: this allows only the new format.
560  static const std::string s_simple_terrain = R"""([A-Za-z\\|/]{1,4})""";
561  static const std::string s_terrain = s_simple_terrain + R"""((\^)""" + s_simple_terrain + ")?";
562  static const std::string s_sep = "(, |\\n)";
563  static const std::string s_prefix = R"""((\d+ )?)""";
564  static const std::string s_all = "(" + s_prefix + s_terrain + s_sep + ")+";
565  static const boost::regex r_all(s_all);
566 
567  const std::string& mapfile = filesystem::get_wml_location(filename_);
568  std::string res = "";
569  if(filesystem::file_exists(mapfile)) {
570  res = filesystem::read_file(mapfile);
571  }
572  config retv;
573  if(boost::regex_match(res, r_all))
574  {
575  retv["map_data"] = res;
576  }
577  return retv;
578  }
579  virtual config random_choice(int /*side*/) const
580  {
581  return config();
582  }
583  virtual std::string description() const
584  {
585  return "Map Data";
586  }
587 
588  };
589 }
590 
591 /// Experimental map replace
592 /// @todo Finish experimenting.
593 WML_HANDLER_FUNCTION(replace_map,, cfg)
594 {
595  /*
596  * When a hex changes from a village terrain to a non-village terrain, and
597  * a team owned that village it loses that village. When a hex changes from
598  * a non-village terrain to a village terrain and there is a unit on that
599  * hex it does not automatically capture the village. The reason for not
600  * capturing villages it that there are too many choices to make; should a
601  * unit loose its movement points, should capture events be fired. It is
602  * easier to do this as wanted by the author in WML.
603  */
604 
605  const gamemap * game_map = & resources::gameboard->map();
606  gamemap map(*game_map);
607 
608  try {
609  if(!cfg["map_file"].empty()) {
610  config file_cfg = mp_sync::get_user_choice("map_data", map_choice(cfg["map_file"].str()));
611  map.read(file_cfg["map_data"].str(), false);
612  } else if(!cfg["map_data"].empty()) {
613  map.read(cfg["map_data"], false);
614  } else {
615  deprecated_message("[replace_map]map=", DEP_LEVEL::INDEFINITE, "1.16", "Use map_data= instead.");
616  map.read(cfg["map"], false);
617  }
618  } catch(const incorrect_map_format_error&) {
619  const std::string log_map_name = cfg["map"].empty() ? cfg["map_file"] : std::string("from inline data");
620  lg::wml_error() << "replace_map: Unable to load map " << log_map_name << std::endl;
621  return;
622  } catch(const wml_exception& e) {
623  e.show();
624  return;
625  }
626 
627  if (map.total_width() > game_map->total_width()
628  || map.total_height() > game_map->total_height()) {
629  if (!cfg["expand"].to_bool()) {
630  lg::wml_error() << "replace_map: Map dimension(s) increase but expand is not set" << std::endl;
631  return;
632  }
633  }
634 
635  if (map.total_width() < game_map->total_width()
636  || map.total_height() < game_map->total_height()) {
637  if (!cfg["shrink"].to_bool()) {
638  lg::wml_error() << "replace_map: Map dimension(s) decrease but shrink is not set" << std::endl;
639  return;
640  }
641  }
642 
643  boost::optional<std::string> errmsg = resources::gameboard->replace_map(map);
644 
645  if (errmsg) {
646  lg::wml_error() << *errmsg << std::endl;
647  }
648 
652 }
653 
654 /// Experimental data persistence
655 /// @todo Finish experimenting.
657 {
658  if (!resources::controller->is_replay())
660 }
661 
662 WML_HANDLER_FUNCTION(set_variables,, cfg)
663 {
664  const t_string& name = cfg["name"];
666  if(name.empty()) {
667  ERR_NG << "trying to set a variable with an empty name:\n" << cfg.get_config().debug();
668  return;
669  }
670 
671  std::vector<config> data;
672  if(cfg.has_attribute("to_variable"))
673  {
674  try
675  {
677  for (const config& c : tovar.as_array())
678  {
679  data.push_back(c);
680  }
681  }
682  catch(const invalid_variablename_exception&)
683  {
684  ERR_NG << "Cannot do [set_variables] with invalid to_variable variable: " << cfg["to_variable"] << " with " << cfg.get_config().debug() << std::endl;
685  }
686  } else {
687  typedef std::pair<std::string, vconfig> vchild;
688  for (const vchild& p : cfg.all_ordered()) {
689  if(p.first == "value") {
690  data.push_back(p.second.get_parsed_config());
691  } else if(p.first == "literal") {
692  data.push_back(p.second.get_config());
693  } else if(p.first == "split") {
694  const vconfig & split_element = p.second;
695 
696  std::string split_string=split_element["list"];
697  std::string separator_string=split_element["separator"];
698  std::string key_name=split_element["key"];
699  if(key_name.empty())
700  {
701  key_name="value";
702  }
703 
704  bool remove_empty = split_element["remove_empty"].to_bool();
705 
706  char* separator = separator_string.empty() ? nullptr : &separator_string[0];
707  if(separator_string.size() > 1){
708  ERR_NG << "[set_variables] [split] separator only supports 1 character, multiple passed: " << split_element["separator"] << " with " << cfg.get_config().debug() << std::endl;
709  }
710 
711  std::vector<std::string> split_vector;
712 
713  //if no separator is specified, explode the string
714  if(separator == nullptr)
715  {
716  for(std::string::iterator i=split_string.begin(); i!=split_string.end(); ++i)
717  {
718  split_vector.push_back(std::string(1, *i));
719  }
720  }
721  else {
722  split_vector=utils::split(split_string, *separator, remove_empty ? utils::REMOVE_EMPTY | utils::STRIP_SPACES : utils::STRIP_SPACES);
723  }
724 
725  for(std::vector<std::string>::iterator i=split_vector.begin(); i!=split_vector.end(); ++i)
726  {
727  data.emplace_back(key_name, *i);
728  }
729  }
730  }
731  }
732  try
733  {
734  const std::string& mode = cfg["mode"];
735  if(mode == "merge")
736  {
737  if(dest.explicit_index() && data.size() > 1)
738  {
739  //merge children into one
740  config merged_children;
741  for (const config &ch : data) {
742  merged_children.append(ch);
743  }
744  data = {merged_children};
745  }
746  dest.merge_array(data);
747  }
748  else if(mode == "insert")
749  {
750  dest.insert_array(data);
751  }
752  else if(mode == "append")
753  {
754  dest.append_array(data);
755  }
756  else /*default if(mode == "replace")*/
757  {
758  dest.replace_array(data);
759  }
760  }
761  catch(const invalid_variablename_exception&)
762  {
763  ERR_NG << "Cannot do [set_variables] with invalid destination variable: " << name << " with " << cfg.get_config().debug() << std::endl;
764  }
765 }
766 
767 /// Store the relative direction from one hex to another in a WML variable.
768 /// This is mainly useful as a diagnostic tool, but could be useful
769 /// for some kind of scenario.
770 WML_HANDLER_FUNCTION(store_relative_direction,, cfg)
771 {
772  if (!cfg.child("source")) {
773  WRN_NG << "No source in [store_relative_direction]" << std::endl;
774  return;
775  }
776  if (!cfg.child("destination")) {
777  WRN_NG << "No destination in [store_relative_direction]" << std::endl;
778  return;
779  }
780  if (!cfg.has_attribute("variable")) {
781  WRN_NG << "No variable in [store_relative_direction]" << std::endl;
782  return;
783  }
784 
785  const map_location src = cfg_to_loc(cfg.child("source"));
786  const map_location dst = cfg_to_loc(cfg.child("destination"));
787 
788  std::string variable = cfg["variable"];
789  map_location::RELATIVE_DIR_MODE mode = static_cast<map_location::RELATIVE_DIR_MODE> (cfg["mode"].to_int(0));
790  try
791  {
793 
795  }
796  catch(const invalid_variablename_exception&)
797  {
798  ERR_NG << "Cannot do [store_relative_direction] with invalid destination variable: " << variable << " with " << cfg.get_config().debug() << std::endl;
799  }
800 }
801 
802 /// Store the rotation of one hex around another in a WML variable.
803 /// In increments of 60 degrees, clockwise.
804 /// This is mainly useful as a diagnostic tool, but could be useful
805 /// for some kind of scenario.
806 WML_HANDLER_FUNCTION(store_rotate_map_location,, cfg)
807 {
808  if (!cfg.child("source")) {
809  WRN_NG << "No source in [store_rotate_map_location]" << std::endl;
810  return;
811  }
812  if (!cfg.child("destination")) {
813  WRN_NG << "No destination in [store_rotate_map_location]" << std::endl;
814  return;
815  }
816  if (!cfg.has_attribute("variable")) {
817  WRN_NG << "No variable in [store_rotate_map_location]" << std::endl;
818  return;
819  }
820 
821  const map_location src = cfg_to_loc(cfg.child("source"));
822  const map_location dst = cfg_to_loc(cfg.child("destination"));
823 
824  std::string variable = cfg["variable"];
825  int angle = cfg["angle"].to_int(1);
826 
827  try
828  {
830 
831  dst.rotate_right_around_center(src,angle).write(store.as_container());
832  }
833  catch(const invalid_variablename_exception&)
834  {
835  ERR_NG << "Cannot do [store_rotate_map_location] with invalid destination variable: " << variable << " with " << cfg.get_config().debug() << std::endl;
836  }
837 }
838 
839 
840 /// Store time of day config in a WML variable. This is useful for those who
841 /// are too lazy to calculate the corresponding time of day for a given turn,
842 /// or if the turn / time-of-day sequence mutates in a scenario.
843 WML_HANDLER_FUNCTION(store_time_of_day,, cfg)
844 {
845  const map_location loc = cfg_to_loc(cfg);
846  int turn = cfg["turn"];
847  // using 0 will use the current turn
848  const time_of_day& tod = resources::tod_manager->get_time_of_day(loc,turn);
849 
850  std::string variable = cfg["variable"];
851  if(variable.empty()) {
852  variable = "time_of_day";
853  }
854  try
855  {
857  tod.write(store.as_container());
858  }
859  catch(const invalid_variablename_exception&)
860  {
861  ERR_NG << "Found invalid variablename " << variable << " in [store_time_of_day] with " << cfg.get_config().debug() << "\n";
862  }
863 }
864 
865 WML_HANDLER_FUNCTION(tunnel,, cfg)
866 {
867  const bool remove = cfg["remove"].to_bool(false);
868  const bool delay = cfg["delayed_variable_substitution"].to_bool(true);
869  if (remove) {
870  const std::vector<std::string> ids = utils::split(cfg["id"]);
871  for (const std::string &id : ids) {
873  }
874  } else if (cfg.get_children("source").empty() ||
875  cfg.get_children("target").empty() ||
876  cfg.get_children("filter").empty()) {
877  ERR_WML << "[tunnel] is missing a mandatory tag:\n"
878  << cfg.get_config().debug();
879  } else {
880  pathfind::teleport_group tunnel(delay ? cfg : vconfig(cfg.get_parsed_config()), false);
881  resources::tunnels->add(tunnel);
882 
883  if(cfg["bidirectional"].to_bool(true)) {
884  tunnel = pathfind::teleport_group(delay ? cfg : vconfig(cfg.get_parsed_config()), true);
885  resources::tunnels->add(tunnel);
886  }
887  }
888 }
889 
890 /// If we should spawn a new unit on the map somewhere
892 {
893  events::command_disabler command_disabler;
894  config parsed_cfg = cfg.get_parsed_config();
895 
896  config::attribute_value to_variable = cfg["to_variable"];
897  if (!to_variable.blank())
898  {
899  parsed_cfg.remove_attribute("to_variable");
900  unit_ptr new_unit = unit::create(parsed_cfg, true, &cfg);
901  try
902  {
903  config &var = resources::gamedata->get_variable_cfg(to_variable);
904  var.clear();
905  new_unit->write(var);
906  if (const config::attribute_value *v = parsed_cfg.get("x")) var["x"] = *v;
907  if (const config::attribute_value *v = parsed_cfg.get("y")) var["y"] = *v;
908  }
909  catch(const invalid_variablename_exception&)
910  {
911  ERR_NG << "Cannot do [unit] with invalid to_variable: " << to_variable << " with " << cfg.get_config().debug() << std::endl;
912  }
913  return;
914 
915  }
916 
917  int side = parsed_cfg["side"].to_int(1);
918 
919 
920  if ((side<1)||(side > static_cast<int>(resources::gameboard->teams().size()))) {
921  ERR_NG << "wrong side in [unit] tag - no such side: "<<side<<" ( number of teams :"<<resources::gameboard->teams().size()<<")"<<std::endl;
922  DBG_NG << parsed_cfg.debug();
923  return;
924  }
925  team &tm = resources::gameboard->get_team(side);
926 
927  unit_creator uc(tm,resources::gameboard->map().starting_position(side));
928 
929  uc
930  .allow_add_to_recall(true)
931  .allow_discover(true)
932  .allow_get_village(true)
933  .allow_invalidate(true)
934  .allow_rename_side(true)
935  .allow_show(true);
936 
937  uc.add_unit(parsed_cfg, &cfg);
938 
939 }
940 
941 WML_HANDLER_FUNCTION(on_undo, event_info, cfg)
942 {
943  if(cfg["delayed_variable_substitution"].to_bool(false)) {
944  synced_context::add_undo_commands(cfg.get_config(), event_info);
945  } else {
946  synced_context::add_undo_commands(cfg.get_parsed_config(), event_info);
947  }
948 }
949 
950 } // end namespace game_events
void set_wml_y(int v)
Definition: location.hpp:161
void verify_and_get_global_variable(const vconfig &pcfg)
play_controller * controller
Definition: resources.cpp:21
static void add_undo_commands(const config &commands, const game_events::queued_event &ctx)
bool empty() const
Tests for an attribute that either was never set or was set to "".
std::map< std::string, handler > map
Definition: action_wml.hpp:50
unit_creator & allow_rename_side(bool b)
config get_user_choice(const std::string &name, const user_choice &uch, int side=0)
#define ERR_NG
Definition: action_wml.cpp:72
static void get_global_variable(persist_context &ctx, const vconfig &pcfg)
Definition: persist_var.cpp:59
static DIRECTION parse_direction(const std::string &str)
Definition: location.cpp:64
::tod_manager * tod_manager
Definition: resources.cpp:29
int h() const
Effective map height.
Definition: map.hpp:128
std::vector< unit_iterator > find_leaders(int side)
Definition: map.cpp:357
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:88
Function which doesn&#39;t take anything into account.
Definition: pathfind.hpp:254
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:1275
virtual const std::vector< team > & teams() const override
Definition: game_board.hpp:92
vconfig child(const std::string &key) const
Returns a child of *this whose key is key.
Definition: variable.cpp:252
virtual const unit_map & units() const override
Definition: game_board.hpp:114
DIRECTION get_relative_dir(const map_location &loc, map_location::RELATIVE_DIR_MODE mode) const
Definition: location.cpp:225
#define LOG_NG
Definition: action_wml.cpp:70
This class represents a single unit of a specific type.
Definition: unit.hpp:99
wml_action(const std::string &tag, handler function)
Using this constructor for a static object outside action_wml.cpp will likely lead to a static initia...
Definition: action_wml.cpp:217
void append(const config &cfg)
Append data from another config object to this one.
Definition: config.cpp:291
Various functions implementing vision (through fog of war and shroud).
static manager & get_singleton()
Definition: manager.hpp:151
unit_creator & allow_invalidate(bool b)
static void set_global_variable(persist_context &ctx, const vconfig &pcfg)
Definition: persist_var.cpp:94
unit_creator & allow_show(bool b)
bool explicit_index() const
Various functions that implement attacks and attack calculations.
void remove(const std::string &id)
Definition: teleport.cpp:298
Variant for storing WML attributes.
Add a special kind of assert to validate whether the input from WML doesn&#39;t contain any problems that...
internal_ptr get_unit_ptr()
Get a copy of the internal unit pointer.
config & get_variable_cfg(const std::string &varname)
throws invalid_variablename_exception if varname is no valid variable name.
Definition: game_data.cpp:79
void raise_map_changed()
Notifies all observers of &#39;ai_map_changed&#39; event.
Definition: manager.cpp:446
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...
static bool file_exists(const bfs::path &fpath)
Definition: filesystem.cpp:280
#define LOG_WML
Definition: action_wml.cpp:79
map_location find_vacant_tile(const map_location &loc, VACANT_TILE_TYPE vacancy, const unit *pass_check, const team *shroud_check, const game_board *board)
Function that will find a location on the board that is as near to loc as possible, but which is unoccupied by any units.
Definition: pathfind.cpp:54
unit_creator & allow_discover(bool b)
static void on_replay_error(const std::string &message, bool)
Definition: action_wml.cpp:265
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:744
virtual const gamemap & map() const override
Definition: game_board.hpp:109
WML_HANDLER_FUNCTION(clear_global_variable,, pcfg)
Experimental data persistence.
Definition: action_wml.cpp:259
void modify_turns_by_wml(const std::string &mod)
unit_type_data unit_types
Definition: types.cpp:1452
void set_wml_x(int v)
Definition: location.hpp:160
Define actions for the game&#39;s events mechanism.
config::child_itors insert_array(std::vector< config > children) const
Replay control code.
bool on_board(const map_location &loc) const
Tell if a location is on the map.
Definition: map.cpp:377
static lg::log_domain log_config("config")
void verify_and_set_global_variable(const vconfig &pcfg)
bool matches(const unit &u, const map_location &loc) const
Determine if *this matches filter at a specified location.
Definition: filter.hpp:129
Additional functionality for a non-const variable_info.
maybe_const_t< config, V > & as_container() const
If instantiated with vi_policy_const, the lifetime of the returned const attribute_value reference mi...
void clear()
Definition: config.cpp:865
void write(config &cfg) const
Definition: time_of_day.cpp:54
-file sdl_utils.hpp
void remove_attribute(config_key_type key)
Definition: config.cpp:239
bool empty() const
Definition: variable.hpp:100
unit_creator & allow_add_to_recall(bool b)
std::vector< std::string > split(const std::string &val, const char c, const int flags)
Splits a (comma-)separated string into a vector of pieces.
static synced_state get_synced_state()
void new_turn()
Update lighting settings.
static unit_ptr create(const config &cfg, bool use_traits=false, const vconfig *vcfg=nullptr)
Initializes a unit from a config.
Definition: unit.hpp:154
A single unit type that the player may recruit.
Definition: types.hpp:42
game_data * gamedata
Definition: resources.cpp:22
std::string filename_
Definition: action_wml.cpp:555
int total_width() const
Real width of the map, including borders.
Definition: map.hpp:134
#define WRN_NG
Definition: action_wml.cpp:71
Object which defines a time of day with associated bonuses, image, sounds etc.
Definition: time_of_day.hpp:57
void show() const
Shows the error in a dialog.
static const char * name(const std::vector< SDL_Joystick *> &joysticks, const std::size_t index)
Definition: joystick.cpp:48
This class stores all the data for a single &#39;side&#39; (in game nomenclature).
Definition: team.hpp:44
team & get_team(int i)
Definition: game_board.hpp:104
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:86
#define ERR_WML
Definition: action_wml.cpp:81
std::vector< map_location > steps
Definition: pathfind.hpp:134
#define DBG_NG
Definition: action_wml.cpp:69
void set_number_of_turns_by_wml(int num)
std::string deprecated_message(const std::string &elem_name, DEP_LEVEL level, const version_info &version, const std::string &detail)
Definition: deprecation.cpp:29
const starting_positions & special_locations() const
Definition: map.hpp:222
Structure which holds a single route between one location and another.
Definition: pathfind.hpp:131
void merge_array(std::vector< config > children) const
const time_of_day & get_time_of_day(int for_turn=0) const
Returns global time of day for the passed turn.
Definition: tod_manager.hpp:54
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:53
bool blank() const
Tests for an attribute that was never set.
game_board * gameboard
Definition: resources.cpp:20
REMOVE_EMPTY: remove empty elements.
Interface for querying local choices.
iterator begin()
begin iterator
Encapsulates the map of the game.
Definition: map.hpp:36
unit_race::GENDER string_gender(const std::string &str, unit_race::GENDER def)
Definition: race.cpp:141
fake_unit_manager * fake_units
Definition: resources.cpp:30
std::string path
Definition: game_config.cpp:39
config::child_itors append_array(std::vector< config > children) const
Managing the AIs lifecycle - headers.
Function which only uses terrain, ignoring shroud, enemies, etc.
Definition: pathfind.hpp:240
void read(const std::string &data, const bool allow_invalid=true)
Definition: map.cpp:121
iterator end()
end iterator
void needs_rebuild(bool b)
Sets whether the screen (map visuals) needs to be rebuilt. This is typically after the map has been c...
std::string read_file(const std::string &fname)
Basic disk I/O - read file.
Definition: filesystem.cpp:874
int number_of_turns() const
void set_turn_by_wml(const int num, game_data *vars=nullptr, const bool increase_limit_if_needed=true)
Dynamically change the current turn number.
Encapsulates the map of the game.
Definition: location.hpp:42
#define ERR_CF
Definition: action_wml.cpp:84
Various functions related to moving units.
Domain specific events.
Definition: action_wml.cpp:88
Helper class, don&#39;t construct this directly.
static map registry_
Tracks the known action handlers.
Definition: action_wml.hpp:63
std::shared_ptr< wb::manager > whiteboard
Definition: resources.cpp:33
Various functions related to the creation of units (recruits, recalls, and placed units)...
Define conditionals for the game&#39;s events mechanism, a.k.a.
unit * get()
Get a raw pointer to the underlying unit.
int w() const
Effective map width.
Definition: map.hpp:125
std::size_t i
Definition: function.cpp:933
std::stringstream & wml_error()
Use this logger to send errors due to deprecated WML.
Definition: log.cpp:269
std::string get_wml_location(const std::string &filename, const std::string &current_dir)
Returns a complete path to the actual WML file or directory or an empty string if the file isn&#39;t pres...
unit_creator & allow_get_village(bool b)
mock_party p
Define the game&#39;s event mechanism.
config::child_itors replace_array(std::vector< config > children) const
static lg::log_domain log_display("display")
Declarations for File-IO.
std::size_t index(const std::string &str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:71
int total_height() const
Real height of the map, including borders.
Definition: map.hpp:137
config & add_child(config_key_type key)
Definition: config.cpp:479
iterator erase(iterator it)
Erase an iterator to this object.
void add_unit(const config &cfg, const vconfig *vcfg=nullptr)
adds a unit on map without firing any events (so, usable during team construction in gamestatus) ...
void add(const teleport_group &group)
Definition: teleport.cpp:294
bool empty() const
Definition: tstring.hpp:182
boost::optional< std::string > replace_map(const gamemap &r)
Definition: game_board.cpp:252
boost::intrusive_ptr< unit > unit_ptr
Definition: ptr.hpp:29
Information on a WML variable.
static bool run_in_synced_context_if_not_already(const std::string &commandname, const config &data, bool use_undo=true, bool show=true, synced_command::error_handler_function error_handler=default_error_function)
checks whether we are currently running in a synced context, and if not we enters it...
void reload_map()
Updates internals that cache map size.
Definition: display.cpp:480
A variable-expanding proxy for the config class.
Definition: variable.hpp:42
const config & get_config() const
Definition: variable.hpp:75
Standard logging facilities (interface).
static const map_location & null_location()
Definition: location.hpp:85
static lg::log_domain log_engine("engine")
int turn() const
#define e
int side() const
The side this unit belongs to.
Definition: unit.hpp:303
static void clear_global_variable(persist_context &ctx, const vconfig &pcfg)
Definition: persist_var.cpp:88
maybe_const_t< config::child_itors, V > as_array() const
If instantiated with vi_policy_const, the lifetime of the returned const attribute_value reference mi...
place_recruit_result place_recruit(unit_ptr u, const map_location &recruit_location, const map_location &recruited_from, int cost, bool is_recall, map_location::DIRECTION facing, bool show, bool fire_event, bool full_movement, bool wml_triggered)
Place a unit into the game.
Definition: create.cpp:610
std::vector< vconfig > child_list
Definition: variable.hpp:78
static lg::log_domain log_wml("wml")
plain_route a_star_search(const map_location &src, const map_location &dst, double stop_at, const cost_calculator &calc, const std::size_t width, const std::size_t height, const teleport_map *teleports, bool border)
PHASE phase() const
Definition: game_data.hpp:76
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:92
mock_char c
This module contains various pathfinding functions and utilities.
static std::string write_direction(DIRECTION dir)
Definition: location.cpp:139
pathfind::manager * tunnels
Definition: resources.cpp:31
std::string::const_iterator iterator
Definition: tokenizer.hpp:24
Holds a temporary unit that can be drawn on the map without being placed in the unit_map.
Display units performing various actions: moving, attacking, and dying.
void move_unit(const std::vector< map_location > &path, unit_ptr u, bool animate, map_location::DIRECTION dir, bool force_scroll)
Display a unit moving along a given path.
Definition: udisplay.cpp:509
std::string debug() const
Definition: config.cpp:1279
void verify_and_clear_global_variable(const vconfig &pcfg)
static game_display * get_singleton()
variable_access_const get_variable_access_read(const std::string &varname) const
returns a variable_access that cannot be used to change the game variables
Definition: game_data.hpp:47