The Battle for Wesnoth  1.19.18+dev
function_gamestate.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 
18 
19 #include "actions/attack.hpp"
20 #include "filesystem.hpp"
21 #include "game_board.hpp"
22 #include "map/label.hpp"
23 #include "map/map.hpp"
24 #include "pathutils.hpp"
25 #include "play_controller.hpp"
26 #include "resources.hpp"
27 #include "tod_manager.hpp"
28 #include "units/types.hpp"
29 #include "units/unit.hpp"
30 
31 #include "utils/optional_fwd.hpp"
32 #include <utility>
33 
34 namespace wfl {
35 
36 namespace gamestate {
37 
38 namespace
39 {
40 /**
41  * Extracts a terrain_code from the given variant.
42  *
43  * @param loc_var Variant containing either terrain_callable or location_callable.
44  * A plain terrain code string is also accepted.
45  *
46  * @returns The given location's terrain_code for the given terrain, or nullopt
47  * if an invalid variant was given.
48  */
49 utils::optional<t_translation::terrain_code> as_terrain_code(const variant& loc_var)
50 {
51  if(loc_var.is_string()) {
53  }
54 
55  else if(auto tc = loc_var.try_convert<terrain_callable>()) {
56  return tc->get_terrain_type().number();
57  }
58 
59  else if(auto loc = loc_var.try_convert<location_callable>()) {
60  const gamemap& map = resources::gameboard->map();
61  if(map.on_board(loc->loc())) {
62  return map.get_terrain(loc->loc());
63  }
64  }
65 
66  return utils::nullopt;
67 }
68 
69 /**
70  * Gets arbitrary movement type info for a given terrain from either a unit or unit type.
71  *
72  * @param loc_var Variant containing either terrain_callable or location_callable.
73  * A plain terrain code string is also accepted.
74  * @param unit_var Variant containing either unit_callable or unit_type_callable.
75  * @param getter Functor of the signature `[](const terrain_code&, const auto&) -> variant`
76  * If @a unit_var resolves to a unit_callable, the second argument will be a
77  * const @ref unit reference. For unit_type_callable, the argument will be a
78  * const @ref movetype reference for that unit type.
79  */
80 template<typename F>
81 variant get_movement_property(const variant& loc_var, const variant& unit_var, const F& getter)
82 {
83  const utils::optional terrain_code = as_terrain_code(loc_var);
84  if(!terrain_code) {
85  return variant();
86  }
87 
88  if(auto u_call = unit_var.try_convert<unit_callable>()) {
89  return std::invoke(getter, terrain_code.value(), u_call->get_unit());
90  }
91 
92  if(auto u_type = unit_var.try_convert<unit_type_callable>()) {
93  return std::invoke(getter, terrain_code.value(), u_type->get_unit_type().movement_type());
94  }
95 
96  return variant();
97 }
98 
99 } // namespace
100 
101 DEFINE_WFL_FUNCTION(run_file, 1, 1)
102 {
103  variant var = args()[0]->evaluate(variables, add_debug_info(fdb, 0, "run_file:file"));
104 
105  // NOTE: get_wml_location also filters file path to ensure it doesn't contain things like "../../top/secret"
107  if(!path) {
108  return variant();
109  }
110 
111  std::string formula_string = filesystem::read_file(path.value());
113 
114  auto parsed_formula = formula::create_optional_formula(formula_string, &symbols);
115  return formula::evaluate(parsed_formula, variables, add_debug_info(fdb, -1, "run_file:formula_from_file"));
116 }
117 
118 DEFINE_WFL_FUNCTION(debug_label, 2, 2)
119 {
120  variant var0 = args()[0]->evaluate(variables, fdb);
121  variant var1 = args()[1]->evaluate(variables, fdb);
122 
123  if(game_config::debug) {
125  const map_location loc = var0.convert_to<location_callable>()->loc();
126  const std::string text = var1.is_string() ? var1.as_string() : var1.to_debug_string();
127 
130  }
131 
132  return variant(std::vector{var0, var1});
133 }
134 
135 DEFINE_WFL_FUNCTION(adjacent_locs, 1, 1)
136 {
137  const map_location loc = args()[0]->evaluate(variables, add_debug_info(fdb, 0, "adjacent_locs:location")).convert_to<location_callable>()->loc();
138 
139  std::vector<variant> v;
140  for(const map_location& adj : get_adjacent_tiles(loc)) {
141  if(resources::gameboard->map().on_board(adj)) {
142  v.emplace_back(std::make_shared<location_callable>(adj));
143  }
144  }
145 
146  return variant(v);
147 }
148 
149 DEFINE_WFL_FUNCTION(locations_in_radius, 2, 2)
150 {
151  const map_location loc = args()[0]->evaluate(variables, fdb).convert_to<location_callable>()->loc();
152 
153  int range = args()[1]->evaluate(variables, fdb).as_int();
154 
155  if(range < 0) {
156  return variant();
157  }
158 
159  if(!range) {
160  return variant(std::make_shared<location_callable>(loc));
161  }
162 
163  std::vector<map_location> res;
164 
165  get_tiles_in_radius(loc, range, res);
166 
167  std::vector<variant> v;
168  v.reserve(res.size() + 1);
169  v.emplace_back(std::make_shared<location_callable>(loc));
170 
171  for(std::size_t n = 0; n != res.size(); ++n) {
172  if(resources::gameboard->map().on_board(res[n])) {
173  v.emplace_back(std::make_shared<location_callable>(res[n]));
174  }
175  }
176 
177  return variant(v);
178 }
179 
181 {
182  const std::string type = args()[0]->evaluate(variables, add_debug_info(fdb, 0, "get_unit_type:name")).as_string();
183 
184  const unit_type *ut = unit_types.find(type);
185  if(ut) {
186  return variant(std::make_shared<unit_type_callable>(*ut));
187  }
188 
189  return variant();
190 }
191 
192 DEFINE_WFL_FUNCTION(unit_at, 1, 1)
193 {
194  variant loc_var = args()[0]->evaluate(variables, add_debug_info(fdb, 0, "unit_at:location"));
195  if(loc_var.is_null()) {
196  return variant();
197  }
198  auto loc = loc_var.convert_to<location_callable>();
200  if(i != resources::gameboard->units().end()) {
201  return variant(std::make_shared<unit_callable>(*i));
202  } else {
203  return variant();
204  }
205 }
206 
207 DEFINE_WFL_FUNCTION(defense_on, 2, 2)
208 {
209  variant u = args()[0]->evaluate(variables, add_debug_info(fdb, 0, "defense_on:unit"));
210  variant loc_var = args()[1]->evaluate(variables, add_debug_info(fdb, 1, "defense_on:location"));
211  if(u.is_null() || loc_var.is_null()) {
212  return variant();
213  }
214 
215  return get_movement_property(loc_var, u,
216  [](const t_translation::terrain_code& terrain, const auto& datum) {
217  return variant(100 - datum.defense_modifier(terrain));
218  });
219 }
220 
221 DEFINE_WFL_FUNCTION(chance_to_hit, 2, 2)
222 {
223  variant u = args()[0]->evaluate(variables, add_debug_info(fdb, 0, "chance_to_hit:unit"));
224  variant loc_var = args()[1]->evaluate(variables, add_debug_info(fdb, 1, "chance_to_hit:location"));
225  if(u.is_null() || loc_var.is_null()) {
226  return variant();
227  }
228 
229  return get_movement_property(loc_var, u,
230  [](const t_translation::terrain_code& terrain, const auto& datum) {
231  return variant(datum.defense_modifier(terrain));
232  });
233 }
234 
235 DEFINE_WFL_FUNCTION(movement_cost, 2, 2)
236 {
237  variant u = args()[0]->evaluate(variables, add_debug_info(fdb, 0, "movement_cost:unit"));
238  variant loc_var = args()[1]->evaluate(variables, add_debug_info(fdb, 0, "movement_cost:location"));
239  if(u.is_null() || loc_var.is_null()) {
240  return variant();
241  }
242 
243  return get_movement_property(loc_var, u,
244  [](const t_translation::terrain_code& terrain, const auto& datum) {
245  return variant(datum.movement_cost(terrain));
246  });
247 }
248 
249 DEFINE_WFL_FUNCTION(vision_cost, 2, 2)
250 {
251  variant u = args()[0]->evaluate(variables, add_debug_info(fdb, 0, "vision_cost:unit"));
252  variant loc_var = args()[1]->evaluate(variables, add_debug_info(fdb, 0, "vision_cost:location"));
253  if(u.is_null() || loc_var.is_null()) {
254  return variant();
255  }
256 
257  return get_movement_property(loc_var, u,
258  [](const t_translation::terrain_code& terrain, const auto& datum) {
259  return variant(datum.vision_cost(terrain));
260  });
261 }
262 
263 DEFINE_WFL_FUNCTION(jamming_cost, 2, 2)
264 {
265  variant u = args()[0]->evaluate(variables, add_debug_info(fdb, 0, "jamming_cost:unit"));
266  variant loc_var = args()[1]->evaluate(variables, add_debug_info(fdb, 0, "jamming_cost:location"));
267  if(u.is_null() || loc_var.is_null()) {
268  return variant();
269  }
270 
271  return get_movement_property(loc_var, u,
272  [](const t_translation::terrain_code& terrain, const auto& datum) {
273  return variant(datum.jamming_cost(terrain));
274  });
275 }
276 
277 DEFINE_WFL_FUNCTION(enemy_of, 2, 2)
278 {
279  variant self_v = args()[0]->evaluate(variables, add_debug_info(fdb, 0, "enemy_of:self"));
280  variant other_v = args()[1]->evaluate(variables, add_debug_info(fdb, 1, "enemy_of:other"));
281  int self, other;
282 
283  if(auto uc = self_v.try_convert<unit_callable>()) {
284  self = uc->get_value("side_number").as_int();
285  } else if(auto tc = self_v.try_convert<team_callable>()) {
286  self = tc->get_value("side_number").as_int();
287  } else {
288  self = self_v.as_int();
289  }
290 
291  if(auto uc = other_v.try_convert<unit_callable>()) {
292  other = uc->get_value("side_number").as_int();
293  } else if(auto tc = other_v.try_convert<team_callable>()) {
294  other = tc->get_value("side_number").as_int();
295  } else {
296  other = other_v.as_int();
297  }
298 
299  int num_teams = resources::gameboard->teams().size();
300  if(self < 1 || self > num_teams || other < 1 || other > num_teams) {
301  return variant(0);
302  }
303  return variant(resources::gameboard->get_team(self).is_enemy(other) ? 1 : 0);
304 }
305 
306 DEFINE_WFL_FUNCTION(resistance_on, 3, 4)
307 {
308  variant u = args()[0]->evaluate(variables, add_debug_info(fdb, 0, "resistance_on:unit"));
309  variant loc_var = args()[1]->evaluate(variables, add_debug_info(fdb, 1, "resistance_on:location"));
310  if(u.is_null() || loc_var.is_null()) {
311  return variant();
312  }
313  std::string type = args()[2]->evaluate(variables, add_debug_info(fdb, 2, "resistance_on:type")).as_string();
314  bool attacker = args().size() > 3 ? args()[3]->evaluate(variables, add_debug_info(fdb, 3, "resistance_on:attacker")).as_bool() : false;
315  const map_location& loc = loc_var.convert_to<location_callable>()->loc();
316 
317  if(auto u_call = u.try_convert<unit_callable>()) {
318  const unit& un = u_call->get_unit();
319 
320  return variant(100 - un.resistance_against(type, attacker, loc));
321  }
322 
323  return variant();
324 }
325 
326 DEFINE_WFL_FUNCTION(tod_bonus, 0, 2)
327 {
329  int turn = resources::controller->turn();
330  if(args().size() > 0) {
331  variant loc_arg = args()[0]->evaluate(variables, add_debug_info(fdb, 0, "tod_bonus:loc"));
332  if(auto p = loc_arg.try_convert<location_callable>()) {
333  loc = p->loc();
334  } else return variant();
335 
336  if(args().size() > 1) {
337  variant turn_arg = args()[1]->evaluate(variables, add_debug_info(fdb, 0, "tod_bonus:turn"));
338  if(turn_arg.is_int()) {
339  turn = turn_arg.as_int();
340  } else if(!turn_arg.is_null()) {
341  return variant();
342  }
343  }
344  }
346  return variant(bonus);
347 }
348 
349 DEFINE_WFL_FUNCTION(base_tod_bonus, 0, 2)
350 {
352  int turn = resources::controller->turn();
353  if(args().size() > 0) {
354  variant loc_arg = args()[0]->evaluate(variables, add_debug_info(fdb, 0, "tod_bonus:loc"));
355  if(auto p = loc_arg.try_convert<location_callable>()) {
356  loc = p->loc();
357  } else return variant();
358 
359  if(args().size() > 1) {
360  variant turn_arg = args()[1]->evaluate(variables, add_debug_info(fdb, 0, "tod_bonus:turn"));
361  if(turn_arg.is_int()) {
362  turn = turn_arg.as_int();
363  } else if(!turn_arg.is_null()) {
364  return variant();
365  }
366  }
367  }
369  return variant(bonus);
370 }
371 
372 DEFINE_WFL_FUNCTION(unit_tod_modifier, 1, 2)
373 {
374  const unit& unit = args()[0]
375  ->evaluate(variables, add_debug_info(fdb, 0, "unit_tod_modifier:unit"))
376  .convert_to<unit_callable>()
377  ->get_unit();
378 
379  const map_location loc = args().size() == 2
380  ? args()[1]
381  ->evaluate(variables, add_debug_info(fdb, 1, "unit_tod_modifier:location"))
382  .convert_to<location_callable>()
383  ->loc()
384  : unit.get_location();
385 
386  return variant(combat_modifier(
388 }
389 
391 {
392  variant var0 = args()[0]->evaluate(variables, fdb);
393  variant var1 = args()[1]->evaluate(variables, fdb);
394 
395  try {
396  const map_location loc = var0.convert_to<location_callable>()->loc();
397  return variant(resources::gameboard->get_team(var1.as_int()).shrouded(loc));
398 
399  } catch(const std::out_of_range&) {
400  return variant();
401  }
402 }
403 
405 {
406  variant var0 = args()[0]->evaluate(variables, fdb);
407  variant var1 = args()[1]->evaluate(variables, fdb);
408 
409  try {
410  const map_location loc = var0.convert_to<location_callable>()->loc();
411  return variant(resources::gameboard->get_team(var1.as_int()).fogged(loc));
412 
413  } catch(const std::out_of_range&) {
414  return variant();
415  }
416 }
417 
418 } // namespace gamestate
419 
420 gamestate_function_symbol_table::gamestate_function_symbol_table(const std::shared_ptr<function_symbol_table>& parent) : function_symbol_table(parent) {
421  using namespace gamestate;
422  function_symbol_table& functions_table = *this;
423  DECLARE_WFL_FUNCTION(run_file);
424  DECLARE_WFL_FUNCTION(debug_label);
426  DECLARE_WFL_FUNCTION(unit_at);
427  DECLARE_WFL_FUNCTION(resistance_on);
428  DECLARE_WFL_FUNCTION(defense_on);
429  DECLARE_WFL_FUNCTION(chance_to_hit);
430  DECLARE_WFL_FUNCTION(movement_cost);
431  DECLARE_WFL_FUNCTION(vision_cost);
432  DECLARE_WFL_FUNCTION(jamming_cost);
433  DECLARE_WFL_FUNCTION(adjacent_locs); // This is deliberately duplicated here; this form excludes off-map locations, while the core form does not
434  DECLARE_WFL_FUNCTION(locations_in_radius);
435  DECLARE_WFL_FUNCTION(enemy_of);
436  DECLARE_WFL_FUNCTION(tod_bonus);
437  DECLARE_WFL_FUNCTION(base_tod_bonus);
438  DECLARE_WFL_FUNCTION(unit_tod_modifier);
441 }
442 
443 } // namespace wfl
int combat_modifier(const unit_map &units, const gamemap &map, const map_location &loc, unit_alignments::type alignment, bool is_fearless)
Returns the amount that a unit's damage should be multiplied by due to the current time of day.
Definition: attack.cpp:1498
Various functions that implement attacks and attack calculations.
map_location loc
Definition: move.cpp:172
map_labels & labels()
Definition: display.cpp:2426
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:102
virtual const std::vector< team > & teams() const override
Definition: game_board.hpp:80
virtual const unit_map & units() const override
Definition: game_board.hpp:107
virtual const gamemap & map() const override
Definition: game_board.hpp:97
terrain_code get_terrain(const map_location &loc) const
Looks up terrain at a particular location.
Definition: map.cpp:271
bool on_board(const map_location &loc) const
Tell if a location is on the map.
Definition: map.cpp:353
Encapsulates the map of the game.
Definition: map.hpp:173
const terrain_label * set_label(const map_location &loc, const t_string &text, const int creator=-1, const std::string &team="", const color_t color=font::NORMAL_COLOR, const bool visible_in_fog=true, const bool visible_in_shroud=false, const bool immutable=false, const std::string &category="", const t_string &tooltip="")
Definition: label.cpp:148
std::size_t turn() const
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:74
int side() const
Definition: team.hpp:179
const std::string & team_name() const
Definition: team.hpp:320
static color_t get_side_color(int side)
Definition: team.cpp:943
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:57
time_of_day get_illuminated_time_of_day(const unit_map &units, const gamemap &map, const map_location &loc, int for_turn=0) const
Returns time of day object for the passed turn at a location.
unit_iterator find(std::size_t id)
Definition: map.cpp:302
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:1273
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 variant evaluate(const const_formula_ptr &f, const formula_callable &variables, formula_debugger *fdb=nullptr, variant default_res=variant(0))
Definition: formula.hpp:48
static formula_ptr create_optional_formula(const std::string &str, function_symbol_table *symbols=nullptr)
Definition: formula.cpp:231
gamestate_function_symbol_table(const std::shared_ptr< function_symbol_table > &parent=nullptr)
std::shared_ptr< T > try_convert() const
Definition: variant.hpp:97
int as_int(int fallback=0) const
Returns the variant's value as an integer.
Definition: variant.cpp:291
std::shared_ptr< T > convert_to() const
Definition: variant.hpp:107
const std::string & as_string() const
Definition: variant.cpp:318
bool is_string() const
Definition: variant.hpp:67
std::string to_debug_string(bool verbose=false, formula_seen_stack *seen=nullptr) const
Definition: variant.cpp:643
bool is_null() const
Functions to test the type of the internal value.
Definition: variant.hpp:62
bool is_int() const
Definition: variant.hpp:63
Declarations for File-IO.
std::size_t i
Definition: function.cpp:1032
#define DECLARE_WFL_FUNCTION(name)
Declares a function name in the local function table functions_table.
Definition: function.hpp:47
unit_alignments::type alignment() const
The alignment of this unit.
Definition: unit.hpp:397
int resistance_against(const std::string &damage_name, bool attacker, const map_location &loc) const
The unit's resistance against a given damage type.
Definition: unit.cpp:1806
const map_location & get_location() const
The current map location this unit is at.
Definition: unit.hpp:1327
bool is_fearless() const
Gets whether this unit is fearless - ie, unaffected by time of day.
Definition: unit.hpp:1186
void get_adjacent_tiles(const map_location &a, utils::span< map_location, 6 > res)
Function which, given a location, will place all adjacent locations in res.
Definition: location.cpp:513
bool is_shrouded(const display *disp, const map_location &loc)
Our definition of map labels being obscured is if the tile is obscured, or the tile below is obscured...
Definition: label.cpp:33
bool is_fogged(const display *disp, const map_location &loc)
Rather simple test for a hex being fogged.
Definition: label.cpp:43
utils::optional< std::string > get_wml_location(const std::string &path, const utils::optional< std::string > &current_dir)
Returns a translated path to the actual file or directory, if it exists.
std::string read_file(const std::string &fname)
Basic disk I/O - read file.
std::string path
Definition: filesystem.cpp:106
const bool & debug
Definition: game_config.cpp:95
::tod_manager * tod_manager
Definition: resources.cpp:29
game_board * gameboard
Definition: resources.cpp:20
play_controller * controller
Definition: resources.cpp:21
terrain_code read_terrain_code(std::string_view str, const ter_layer filler)
Reads a single terrain from a string.
std::size_t size(std::string_view str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:81
DEFINE_WFL_FUNCTION(run_file, 1, 1)
Definition: callable.hpp:26
formula_debugger * add_debug_info(formula_debugger *fdb, int arg_number, const std::string &f_name)
void get_tiles_in_radius(const map_location &center, const int radius, std::vector< map_location > &result)
Function that will add to result all locations within radius tiles of center (excluding center itself...
Definition: pathutils.cpp:56
Encapsulates the map of the game.
Definition: location.hpp:46
A terrain string which is converted to a terrain is a string with 1 or 2 layers the layers are separa...
Definition: translation.hpp:49
int lawful_bonus
The % bonus lawful units receive.
Definition: time_of_day.hpp:83
mock_party p
static map_location::direction n
unit_type_data unit_types
Definition: types.cpp:1514
static const unit_type & get_unit_type(const std::string &type_id)
Converts a string ID to a unit_type.
Definition: unit.cpp:216