The Battle for Wesnoth  1.17.23+dev
callable_objects.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2009 - 2023
3  by Bartosz Waresiak <dragonking@o2.pl>
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 #include "ai/formula/ai.hpp"
17 #include "attack_prediction.hpp"
18 #include "game_board.hpp"
21 #include "resources.hpp"
22 #include "map/map.hpp"
23 #include "ai/game_info.hpp"
24 #include "ai/actions.hpp"
26 #include "units/unit.hpp"
27 #include "log.hpp"
28 #include "menu_events.hpp" // for fallback_ai_to_human_exception
29 
30 static lg::log_domain log_formula_ai("ai/engine/fai");
31 #define DBG_AI LOG_STREAM(debug, log_formula_ai)
32 #define LOG_AI LOG_STREAM(info, log_formula_ai)
33 #define WRN_AI LOG_STREAM(warn, log_formula_ai)
34 #define ERR_AI LOG_STREAM(err, log_formula_ai)
35 
36 namespace ai {
37 
39  auto fai = std::dynamic_pointer_cast<const formula_ai>(for_fai);
40  assert(fai != nullptr);
41  return *std::const_pointer_cast<formula_ai>(fai)->ai_ptr_;
42 }
43 
44 }
45 
46 namespace wfl {
47  using namespace ai;
48 
49 variant move_map_callable::get_value(const std::string& key) const
50 {
51  if(key == "moves") {
52  std::vector<variant> vars;
53  for(move_map::const_iterator i = srcdst_.begin(); i != srcdst_.end(); ++i) {
54  if( i->first == i->second || units_.count(i->second) == 0) {
55  auto item = std::make_shared<move_callable>(i->first, i->second);
56  vars.emplace_back(item);
57  }
58  }
59 
60  return variant(vars);
61  } else if(key == "has_moves") {
62  return variant(!srcdst_.empty());
63  } else {
64  return variant();
65  }
66 }
67 
69 {
70  add_input(inputs, "moves");
71 }
72 
73 int move_callable::do_compare(const formula_callable* callable) const
74 {
75  const move_callable* mv_callable = dynamic_cast<const move_callable*>(callable);
76  if(mv_callable == nullptr) {
77  return formula_callable::do_compare(callable);
78  }
79 
80  const map_location& other_src = mv_callable->src_;
81  const map_location& other_dst = mv_callable->dst_;
82 
83  if (int cmp = src_.do_compare(other_src)) {
84  return cmp;
85  }
86 
87  return dst_.do_compare(other_dst);
88 }
89 
92  move_result_ptr move_result = ai.execute_move_action(src_, dst_, true);
93 
94  if(!move_result->is_ok()) {
95  LOG_AI << "ERROR #" << move_result->get_status() << " while executing 'move' formula function";
96  return variant(std::make_shared<safe_call_result>(fake_ptr(), move_result->get_status(), move_result->get_unit_location()));
97  }
98 
100 }
101 
103 {
104  const move_partial_callable* mv_callable = dynamic_cast<const move_partial_callable*>(callable);
105  if(mv_callable == nullptr) {
106  return formula_callable::do_compare(callable);
107  }
108 
109  const map_location& other_src = mv_callable->src_;
110  const map_location& other_dst = mv_callable->dst_;
111 
112  if (int cmp = src_.do_compare(other_src)) {
113  return cmp;
114  }
115 
116  return dst_.do_compare(other_dst);
117 }
118 
121  move_result_ptr move_result = ai.execute_move_action(src_, dst_, false);
122 
123  if(!move_result->is_ok()) {
124  LOG_AI << "ERROR #" << move_result->get_status() << " while executing 'move_partial' formula function";
125  return variant(std::make_shared<safe_call_result>(fake_ptr(), move_result->get_status(), move_result->get_unit_location()));
126  }
127 
129 }
130 
131 variant position_callable::get_value(const std::string& key) const {
132  if(key == "chance") {
133  return variant(chance_);
134  } else {
135  return variant();
136  }
137 }
138 
140  add_input(inputs, "chance");
141 }
142 
143 variant outcome_callable::get_value(const std::string& key) const {
144  if(key == "hitpoints_left") {
145  return variant(hitLeft_);
146  } else if(key == "probability") {
147  return variant(prob_);
148  } else if(key == "possible_status") {
149  return variant(status_);
150  } else {
151  return variant();
152  }
153 }
154 
156  add_input(inputs, "hitpoints_left");
157  add_input(inputs, "probability");
158  add_input(inputs, "possible_status");
159 }
160 
162  const map_location& src, const map_location& dst, int weapon)
163  : move_from_(move_from), src_(src), dst_(dst),
164  bc_(resources::gameboard->units(), src, dst, weapon, -1, 1.0, nullptr,
165  resources::gameboard->units().find(move_from).get_shared_ptr())
166 {
167  type_ = ATTACK_C;
168 }
169 
170 variant attack_callable::get_value(const std::string& key) const {
171  if(key == "attack_from") {
172  return variant(std::make_shared<location_callable>(src_));
173  } else if(key == "defender") {
174  return variant(std::make_shared<location_callable>(dst_));
175  } else if(key == "move_from") {
176  return variant(std::make_shared<location_callable>(move_from_));
177  } else {
178  return variant();
179  }
180 }
181 
183  add_input(inputs, "attack_from");
184  add_input(inputs, "defender");
185  add_input(inputs, "move_from");
186 }
187 
189  const {
190  const attack_callable* a_callable = dynamic_cast<const attack_callable*>(callable);
191  if(a_callable == nullptr) {
192  return formula_callable::do_compare(callable);
193  }
194 
195  const map_location& other_from = a_callable->move_from();
196 
197  if (int cmp = move_from_.do_compare(other_from)) {
198  return cmp;
199  }
200  const map_location& other_src = a_callable->src();
201  if (int cmp = src_.do_compare(other_src)) {
202  return cmp;
203  }
204  const map_location& other_dst = a_callable->dst();
205  if (int cmp = dst_.do_compare(other_dst)) {
206  return cmp;
207  }
208  const int other_weapon = a_callable->weapon();
209  if (int cmp = (this->weapon() - other_weapon)) {
210  return cmp;
211  }
212  const int other_def_weapon = a_callable->defender_weapon();
213  return this->defender_weapon() - other_def_weapon;
214 }
215 
218  bool gamestate_changed = false;
220 
221  if(move_from_ != src_) {
222  move_result = ai.execute_move_action(move_from_, src_, false);
223  gamestate_changed |= move_result->is_gamestate_changed();
224 
225  if(!move_result->is_ok()) {
226  //move part failed
227  LOG_AI << "ERROR #" << move_result->get_status() << " while executing 'attack' formula function";
228  return variant(std::make_shared<safe_call_result>(fake_ptr(), move_result->get_status(), move_result->get_unit_location()));
229  }
230  }
231 
232  if(!move_result || move_result->is_ok()) {
233  //if move wasn't done at all or was done successfully
234  attack_result_ptr attack_result = ai.execute_attack_action(src_, dst_, weapon());
235  gamestate_changed |= attack_result->is_gamestate_changed();
236  if(!attack_result->is_ok()) {
237  //attack failed
238  LOG_AI << "ERROR #" << attack_result->get_status() << " while executing 'attack' formula function";
239  return variant(std::make_shared<safe_call_result>(fake_ptr(), attack_result->get_status()));
240  }
241  }
242 
243  return variant(gamestate_changed);
244 }
245 
246 variant attack_map_callable::get_value(const std::string& key) const {
247  if(key == "attacks") {
248  std::vector<variant> vars;
249  for(move_map::const_iterator i = ai_.get_srcdst().begin(); i != ai_.get_srcdst().end(); ++i) {
250  /* for each possible move check all adjacent tiles for enemies */
251  if(units_.count(i->second) == 0) {
252  collect_possible_attacks(vars, i->first, i->second);
253  }
254  }
255  /* special case, when unit moved toward enemy and can only attack */
256  for(unit_map::const_iterator i = resources::gameboard->units().begin(); i != resources::gameboard->units().end(); ++i) {
257  if (i->side() == ai_.get_side() && i->attacks_left() > 0) {
258  collect_possible_attacks(vars, i->get_location(), i->get_location());
259  }
260  }
261  return variant(vars);
262  } else {
263  return variant();
264  }
265 }
266 
268  add_input(inputs, "attacks");
269 }
270 
271 /* add to vars all attacks on enemy units around <attack_position> tile. attacker_location is tile where unit is currently standing. It's moved to attack_position first and then performs attack.*/
272 void attack_map_callable::collect_possible_attacks(std::vector<variant>& vars, map_location attacker_location, map_location attack_position) const {
273  for(const map_location& adj : get_adjacent_tiles(attack_position)) {
274  /* if adjacent tile is outside the board */
275  if (! resources::gameboard->map().on_board(adj))
276  continue;
278  /* if tile is empty */
279  if (unit == units_.end())
280  continue;
281  /* if tile is occupied by friendly or petrified/invisible unit */
282  if (!ai_.current_team().is_enemy(unit->side()) ||
283  unit->incapacitated() ||
285  continue;
286  /* add attacks with default weapon */
287  auto item = std::make_shared<attack_callable>(attacker_location, attack_position, adj, -1);
288  vars.emplace_back(item);
289  }
290 }
291 
292 variant recall_callable::get_value(const std::string& key) const {
293  if( key == "id")
294  return variant(id_);
295  if( key == "loc")
296  return variant(std::make_shared<location_callable>(loc_));
297  return variant();
298 }
299 
301  add_input(inputs, "id");
302  add_input(inputs, "loc");
303 }
304 
307  recall_result_ptr recall_result = ai.check_recall_action(id_, loc_);
308 
309  if(recall_result->is_ok()) {
311  } else {
312  LOG_AI << "ERROR #" << recall_result->get_status() << " while executing 'recall' formula function";
313  return variant(std::make_shared<safe_call_result>(fake_ptr(), recall_result->get_status()));
314  }
315 
317 }
318 
319 variant recruit_callable::get_value(const std::string& key) const {
320  if( key == "unit_type")
321  return variant(type_);
322  if( key == "recruit_loc")
323  return variant(std::make_shared<location_callable>(loc_));
324  return variant();
325 }
326 
328  add_input(inputs, "unit_type");
329  add_input(inputs, "recruit_loc");
330 }
331 
334  recruit_result_ptr recruit_result = ai.check_recruit_action(type_, loc_);
335 
336  //is_ok()==true means that the action is successful (eg. no unexpected events)
337  //is_ok() must be checked or the code will complain :)
338  if(recruit_result->is_ok()) {
340  } else {
341  LOG_AI << "ERROR #" << recruit_result->get_status() << " while executing 'recruit' formula function";
342  return variant(std::make_shared<safe_call_result>(fake_ptr(), recruit_result->get_status()));
343  }
344 
345  //is_gamestate_changed()==true means that the game state was somehow changed by action.
346  //it is believed that during a turn, a game state can change only a finite number of times
348 }
349 
350 variant set_unit_var_callable::get_value(const std::string& key) const {
351  if(key == "loc")
352  return variant(std::make_shared<location_callable>(loc_));
353 
354  if(key == "key")
355  return variant(key_);
356 
357  if(key == "value")
358  return value_;
359 
360  return variant();
361 }
362 
364  add_input(inputs, "loc");
365  add_input(inputs, "key");
366  add_input(inputs, "value");
367 }
368 
370  int status = 0;
372  unit_map& units = resources::gameboard->units();
373 
374 /* if(!infinite_loop_guardian_.set_unit_var_check()) {
375  status = 5001; //exceeded nmber of calls in a row - possible infinite loop
376  } else*/ if((unit = units.find(loc_)) == units.end()) {
377  status = 5002; //unit not found
378  } else if(unit->side() != get_ai_context(ctxt.as_callable()).get_side()) {
379  status = 5003;//unit does not belong to our side
380  }
381 
382  if(status == 0) {
383  LOG_AI << "Setting unit variable: " << key_ << " -> " << value_.to_debug_string();
385  return variant(true);
386  }
387 
388  ERR_AI << "ERROR #" << status << " while executing 'set_unit_var' formula function";
389  return variant(std::make_shared<safe_call_result>(fake_ptr(), status));
390 }
391 
393  // We want give control of the side to human for the rest of this turn
395 }
396 
397 }
Managing the AI-Game interaction - AI actions and their results.
#define ERR_AI
static lg::log_domain log_formula_ai("ai/engine/fai")
#define LOG_AI
int get_status() const
Definition: actions.cpp:150
bool is_gamestate_changed() const
Definition: actions.cpp:121
virtual const map_location & get_unit_location() const
Definition: actions.cpp:412
virtual const team & current_team() const override
Definition: contexts.hpp:457
virtual const move_map & get_srcdst() const override
Definition: contexts.hpp:723
virtual side_number get_side() const override
Get the side number.
Definition: contexts.hpp:403
virtual side_number get_side() const =0
Get the side number.
virtual const unit_map & units() const override
Definition: game_board.hpp:113
bool is_enemy(int n) const
Definition: team.hpp:231
void add_formula_var(std::string str, wfl::variant var)
Container associating units to locations.
Definition: map.hpp:99
unit_iterator end()
Definition: map.hpp:429
std::size_t count(const map_location &loc) const
Definition: map.hpp:414
unit_iterator find(std::size_t id)
Definition: map.cpp:301
This class represents a single unit of a specific type.
Definition: unit.hpp:135
const map_location & src() const
const map_location & move_from() const
void get_inputs(formula_input_vector &inputs) const override
attack_callable(const map_location &move_from, const map_location &src, const map_location &dst, int weapon)
variant execute_self(variant ctxt) override
const map_location & dst() const
variant get_value(const std::string &key) const override
int do_compare(const formula_callable *callable) const override
Compare two attacks in deterministic way or compare pointers (nondeterministic in consequent game run...
variant get_value(const std::string &key) const override
const ai::formula_ai & ai_
void collect_possible_attacks(std::vector< variant > &vars, map_location attacker_location, map_location attack_position) const
void get_inputs(formula_input_vector &inputs) const override
variant execute_self(variant ctxt) override
formula_callable_ptr fake_ptr()
Definition: callable.hpp:42
formula_input_vector inputs() const
Definition: callable.hpp:63
static void add_input(formula_input_vector &inputs, const std::string &key, formula_access access_type=formula_access::read_only)
Definition: callable.hpp:136
virtual int do_compare(const formula_callable *callable) const
Definition: callable.hpp:146
variant execute_self(variant ctxt) override
int do_compare(const formula_callable *callable) const override
void get_inputs(formula_input_vector &inputs) const override
variant get_value(const std::string &key) const override
int do_compare(const formula_callable *callable) const override
variant execute_self(variant ctxt) override
variant get_value(const std::string &key) const override
void get_inputs(formula_input_vector &inputs) const override
void get_inputs(formula_input_vector &inputs) const override
variant get_value(const std::string &key) const override
void get_inputs(formula_input_vector &inputs) const override
variant execute_self(variant ctxt) override
variant get_value(const std::string &key) const override
void get_inputs(formula_input_vector &inputs) const override
variant execute_self(variant ctxt) override
variant get_value(const std::string &key) const override
const std::string & key() const
void get_inputs(formula_input_vector &inputs) const override
variant execute_self(variant ctxt) override
variant get_value(const std::string &key) const override
const_formula_callable_ptr as_callable() const
Definition: variant.hpp:83
std::string to_debug_string(bool verbose=false, formula_seen_stack *seen=nullptr) const
Definition: variant.cpp:646
Composite AI contexts.
Defines formula ai.
std::size_t i
Definition: function.cpp:968
Game information for the AI.
bool invisible(const map_location &loc, bool see_all=true) const
Definition: unit.cpp:2598
unit_formula_manager & formula_manager() const
Get the unit formula manager.
Definition: unit.hpp:1842
bool incapacitated() const
Check if the unit has been petrified.
Definition: unit.hpp:907
int side() const
The side this unit belongs to.
Definition: unit.hpp:345
const map_location & get_location() const
The current map location this unit is at.
Definition: unit.hpp:1359
void get_adjacent_tiles(const map_location &a, map_location *res)
Function which, given a location, will place all adjacent locations in res.
Definition: location.cpp:475
Standard logging facilities (interface).
A small explanation about what's going on here: Each action has access to two game_info objects First...
Definition: actions.cpp:61
std::shared_ptr< recruit_result > recruit_result_ptr
Definition: game_info.hpp:84
ai_context & get_ai_context(wfl::const_formula_callable_ptr for_fai)
std::shared_ptr< attack_result > attack_result_ptr
Definition: game_info.hpp:82
std::shared_ptr< move_result > move_result_ptr
Definition: game_info.hpp:85
std::shared_ptr< recall_result > recall_result_ptr
Definition: game_info.hpp:83
std::pair< std::string, unsigned > item
Definition: help_impl.hpp:414
game_board * gameboard
Definition: resources.cpp:21
Definition: contexts.hpp:44
std::vector< formula_input > formula_input_vector
std::shared_ptr< const formula_callable > const_formula_callable_ptr
Encapsulates the map of the game.
Definition: location.hpp:38
int do_compare(const map_location &a) const
three-way comparator
Definition: location.hpp:105