The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
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"
37#include "mouse_handler_base.hpp" // for events::commands_disabled
38#include "resources.hpp"
39#include "synced_context.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"
49#include "deprecation.hpp"
50
51static 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
57static 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
61static 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
66static 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.
71namespace 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
78namespace { // 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 }
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 */
200wml_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 */
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;
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 */
324
326{
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 */
354WML_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.
373 unit_display::move_unit(path, dummy_unit.get_unit_ptr(), true, map_location::direction::indeterminate, force_scroll);
374 }
375}
376
377WML_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
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
578namespace {
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 */
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 */
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 */
688WML_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 }
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 */
726WML_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 }
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 }
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 }
825
826 unit_creator uc(tm,resources::gameboard->map().starting_position(side));
827
828 uc
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
std::string filename_
static lg::log_domain log_engine("engine")
#define LOG_WML
static lg::log_domain log_display("display")
static lg::log_domain log_wml("wml")
#define WML_HANDLER_FUNCTION(pname, pei, pcfg)
WML_HANDLER_FUNCTION macro handles auto registration for wml handlers.
static lg::log_domain log_config("config")
Define actions for the game's events mechanism.
map_location loc
Definition move.cpp:172
#define WRN_NG
#define ERR_NG
#define DBG_NG
#define LOG_NG
Managing the AIs lifecycle - headers TODO: Refactor history handling and internal commands.
double t
static manager & get_singleton()
Definition manager.hpp:140
Variant for storing WML attributes.
bool blank() const
Tests for an attribute that was never set.
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:433
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.
void place_on_fake_unit_manager(fake_unit_manager *d)
Place this on manager's fake_units_ dequeue.
unit * get()
Get a raw pointer to the underlying unit.
virtual const gamemap & map() const override
virtual const std::vector< team > & teams() const override
utils::optional< std::string > replace_map(const gamemap &r)
team & get_team(int i)
virtual const unit_map & units() const override
@ 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
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.
std::map< std::string, handler > map
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...
int w() const
Effective map width.
Definition map.hpp:50
location_map & special_locations()
Definition map.hpp:105
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
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
#define ERR_CF
Definition config.cpp:37
#define ERR_WML
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)
const config * cfg
Declarations for File-IO.
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)
Domain specific events.
std::stringstream & log_to_chat()
Use this to show WML errors in the ingame chat.
Definition log.cpp:550
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)
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:507
std::vector< std::string > split(const config_attribute_value &val)
This module contains various pathfinding functions and utilities.
static void clear_global_variable(persist_context &ctx, const vconfig &pcfg)
static void set_global_variable(persist_context &ctx, const vconfig &pcfg)
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)
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:149
std::unique_ptr< MIX_Audio, decltype(&MIX_DestroyAudio)> value
Definition sound.cpp:139
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:1499
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 i
#define e