The Battle for Wesnoth  1.19.0-dev
callable_objects.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2009 - 2024
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 "game_board.hpp"
20 #include "resources.hpp"
21 #include "map/map.hpp"
22 #include "ai/game_info.hpp"
23 #include "ai/actions.hpp"
25 #include "units/unit.hpp"
26 #include "log.hpp"
27 #include "menu_events.hpp" // for fallback_ai_to_human_exception
28 
29 static lg::log_domain log_formula_ai("ai/engine/fai");
30 #define DBG_AI LOG_STREAM(debug, log_formula_ai)
31 #define LOG_AI LOG_STREAM(info, log_formula_ai)
32 #define WRN_AI LOG_STREAM(warn, log_formula_ai)
33 #define ERR_AI LOG_STREAM(err, log_formula_ai)
34 
35 namespace ai {
36 
38  auto fai = std::dynamic_pointer_cast<const formula_ai>(for_fai);
39  assert(fai != nullptr);
40  return *std::const_pointer_cast<formula_ai>(fai)->ai_ptr_;
41 }
42 
43 }
44 
45 namespace wfl {
46  using namespace ai;
47 
48 variant move_map_callable::get_value(const std::string& key) const
49 {
50  if(key == "moves") {
51  std::vector<variant> vars;
52  for(move_map::const_iterator i = srcdst_.begin(); i != srcdst_.end(); ++i) {
53  if( i->first == i->second || units_.count(i->second) == 0) {
54  auto item = std::make_shared<move_callable>(i->first, i->second);
55  vars.emplace_back(item);
56  }
57  }
58 
59  return variant(vars);
60  } else if(key == "has_moves") {
61  return variant(!srcdst_.empty());
62  } else {
63  return variant();
64  }
65 }
66 
68 {
69  add_input(inputs, "moves");
70 }
71 
72 int move_callable::do_compare(const formula_callable* callable) const
73 {
74  const move_callable* mv_callable = dynamic_cast<const move_callable*>(callable);
75  if(mv_callable == nullptr) {
76  return formula_callable::do_compare(callable);
77  }
78 
79  const map_location& other_src = mv_callable->src_;
80  const map_location& other_dst = mv_callable->dst_;
81 
82  if (int cmp = src_.do_compare(other_src)) {
83  return cmp;
84  }
85 
86  return dst_.do_compare(other_dst);
87 }
88 
91  move_result_ptr move_result = ai.execute_move_action(src_, dst_, true);
92 
93  if(!move_result->is_ok()) {
94  LOG_AI << "ERROR #" << move_result->get_status() << " while executing 'move' formula function";
95  return variant(std::make_shared<safe_call_result>(fake_ptr(), move_result->get_status(), move_result->get_unit_location()));
96  }
97 
99 }
100 
102 {
103  const move_partial_callable* mv_callable = dynamic_cast<const move_partial_callable*>(callable);
104  if(mv_callable == nullptr) {
105  return formula_callable::do_compare(callable);
106  }
107 
108  const map_location& other_src = mv_callable->src_;
109  const map_location& other_dst = mv_callable->dst_;
110 
111  if (int cmp = src_.do_compare(other_src)) {
112  return cmp;
113  }
114 
115  return dst_.do_compare(other_dst);
116 }
117 
120  move_result_ptr move_result = ai.execute_move_action(src_, dst_, false);
121 
122  if(!move_result->is_ok()) {
123  LOG_AI << "ERROR #" << move_result->get_status() << " while executing 'move_partial' formula function";
124  return variant(std::make_shared<safe_call_result>(fake_ptr(), move_result->get_status(), move_result->get_unit_location()));
125  }
126 
128 }
129 
130 variant position_callable::get_value(const std::string& key) const {
131  if(key == "chance") {
132  return variant(chance_);
133  } else {
134  return variant();
135  }
136 }
137 
139  add_input(inputs, "chance");
140 }
141 
142 variant outcome_callable::get_value(const std::string& key) const {
143  if(key == "hitpoints_left") {
144  return variant(hitLeft_);
145  } else if(key == "probability") {
146  return variant(prob_);
147  } else if(key == "possible_status") {
148  return variant(status_);
149  } else {
150  return variant();
151  }
152 }
153 
155  add_input(inputs, "hitpoints_left");
156  add_input(inputs, "probability");
157  add_input(inputs, "possible_status");
158 }
159 
161  const map_location& src, const map_location& dst, int weapon)
162  : move_from_(move_from), src_(src), dst_(dst),
163  bc_(resources::gameboard->units(), src, dst, weapon, -1, 1.0, nullptr,
164  resources::gameboard->units().find(move_from).get_shared_ptr())
165 {
166  type_ = ATTACK_C;
167 }
168 
169 variant attack_callable::get_value(const std::string& key) const {
170  if(key == "attack_from") {
171  return variant(std::make_shared<location_callable>(src_));
172  } else if(key == "defender") {
173  return variant(std::make_shared<location_callable>(dst_));
174  } else if(key == "move_from") {
175  return variant(std::make_shared<location_callable>(move_from_));
176  } else {
177  return variant();
178  }
179 }
180 
182  add_input(inputs, "attack_from");
183  add_input(inputs, "defender");
184  add_input(inputs, "move_from");
185 }
186 
188  const {
189  const attack_callable* a_callable = dynamic_cast<const attack_callable*>(callable);
190  if(a_callable == nullptr) {
191  return formula_callable::do_compare(callable);
192  }
193 
194  const map_location& other_from = a_callable->move_from();
195 
196  if (int cmp = move_from_.do_compare(other_from)) {
197  return cmp;
198  }
199  const map_location& other_src = a_callable->src();
200  if (int cmp = src_.do_compare(other_src)) {
201  return cmp;
202  }
203  const map_location& other_dst = a_callable->dst();
204  if (int cmp = dst_.do_compare(other_dst)) {
205  return cmp;
206  }
207  const int other_weapon = a_callable->weapon();
208  if (int cmp = (this->weapon() - other_weapon)) {
209  return cmp;
210  }
211  const int other_def_weapon = a_callable->defender_weapon();
212  return this->defender_weapon() - other_def_weapon;
213 }
214 
217  bool gamestate_changed = false;
219 
220  if(move_from_ != src_) {
221  move_result = ai.execute_move_action(move_from_, src_, false);
222  gamestate_changed |= move_result->is_gamestate_changed();
223 
224  if(!move_result->is_ok()) {
225  //move part failed
226  LOG_AI << "ERROR #" << move_result->get_status() << " while executing 'attack' formula function";
227  return variant(std::make_shared<safe_call_result>(fake_ptr(), move_result->get_status(), move_result->get_unit_location()));
228  }
229  }
230 
231  if(!move_result || move_result->is_ok()) {
232  //if move wasn't done at all or was done successfully
233  attack_result_ptr attack_result = ai.execute_attack_action(src_, dst_, weapon());
234  gamestate_changed |= attack_result->is_gamestate_changed();
235  if(!attack_result->is_ok()) {
236  //attack failed
237  LOG_AI << "ERROR #" << attack_result->get_status() << " while executing 'attack' formula function";
238  return variant(std::make_shared<safe_call_result>(fake_ptr(), attack_result->get_status()));
239  }
240  }
241 
242  return variant(gamestate_changed);
243 }
244 
245 variant attack_map_callable::get_value(const std::string& key) const {
246  if(key == "attacks") {
247  std::vector<variant> vars;
248  for(move_map::const_iterator i = ai_.get_srcdst().begin(); i != ai_.get_srcdst().end(); ++i) {
249  /* for each possible move check all adjacent tiles for enemies */
250  if(units_.count(i->second) == 0) {
251  collect_possible_attacks(vars, i->first, i->second);
252  }
253  }
254  /* special case, when unit moved toward enemy and can only attack */
255  for(unit_map::const_iterator i = resources::gameboard->units().begin(); i != resources::gameboard->units().end(); ++i) {
256  if (i->side() == ai_.get_side() && i->attacks_left() > 0) {
257  collect_possible_attacks(vars, i->get_location(), i->get_location());
258  }
259  }
260  return variant(vars);
261  } else {
262  return variant();
263  }
264 }
265 
267  add_input(inputs, "attacks");
268 }
269 
270 /* 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.*/
271 void attack_map_callable::collect_possible_attacks(std::vector<variant>& vars, map_location attacker_location, map_location attack_position) const {
272  for(const map_location& adj : get_adjacent_tiles(attack_position)) {
273  /* if adjacent tile is outside the board */
274  if (! resources::gameboard->map().on_board(adj))
275  continue;
277  /* if tile is empty */
278  if (unit == units_.end())
279  continue;
280  /* if tile is occupied by friendly or petrified/invisible unit */
281  if (!ai_.current_team().is_enemy(unit->side()) ||
282  unit->incapacitated() ||
284  continue;
285  /* add attacks with default weapon */
286  auto item = std::make_shared<attack_callable>(attacker_location, attack_position, adj, -1);
287  vars.emplace_back(item);
288  }
289 }
290 
291 variant recall_callable::get_value(const std::string& key) const {
292  if( key == "id")
293  return variant(id_);
294  if( key == "loc")
295  return variant(std::make_shared<location_callable>(loc_));
296  return variant();
297 }
298 
300  add_input(inputs, "id");
301  add_input(inputs, "loc");
302 }
303 
306  recall_result_ptr recall_result = ai.check_recall_action(id_, loc_);
307 
308  if(recall_result->is_ok()) {
310  } else {
311  LOG_AI << "ERROR #" << recall_result->get_status() << " while executing 'recall' formula function";
312  return variant(std::make_shared<safe_call_result>(fake_ptr(), recall_result->get_status()));
313  }
314 
316 }
317 
318 variant recruit_callable::get_value(const std::string& key) const {
319  if( key == "unit_type")
320  return variant(type_);
321  if( key == "recruit_loc")
322  return variant(std::make_shared<location_callable>(loc_));
323  return variant();
324 }
325 
327  add_input(inputs, "unit_type");
328  add_input(inputs, "recruit_loc");
329 }
330 
333  recruit_result_ptr recruit_result = ai.check_recruit_action(type_, loc_);
334 
335  //is_ok()==true means that the action is successful (eg. no unexpected events)
336  //is_ok() must be checked or the code will complain :)
337  if(recruit_result->is_ok()) {
339  } else {
340  LOG_AI << "ERROR #" << recruit_result->get_status() << " while executing 'recruit' formula function";
341  return variant(std::make_shared<safe_call_result>(fake_ptr(), recruit_result->get_status()));
342  }
343 
344  //is_gamestate_changed()==true means that the game state was somehow changed by action.
345  //it is believed that during a turn, a game state can change only a finite number of times
347 }
348 
349 variant set_unit_var_callable::get_value(const std::string& key) const {
350  if(key == "loc")
351  return variant(std::make_shared<location_callable>(loc_));
352 
353  if(key == "key")
354  return variant(key_);
355 
356  if(key == "value")
357  return value_;
358 
359  return variant();
360 }
361 
363  add_input(inputs, "loc");
364  add_input(inputs, "key");
365  add_input(inputs, "value");
366 }
367 
369  int status = 0;
371  unit_map& units = resources::gameboard->units();
372 
373 /* if(!infinite_loop_guardian_.set_unit_var_check()) {
374  status = 5001; //exceeded nmber of calls in a row - possible infinite loop
375  } else*/ if((unit = units.find(loc_)) == units.end()) {
376  status = 5002; //unit not found
377  } else if(unit->side() != get_ai_context(ctxt.as_callable()).get_side()) {
378  status = 5003;//unit does not belong to our side
379  }
380 
381  if(status == 0) {
382  LOG_AI << "Setting unit variable: " << key_ << " -> " << value_.to_debug_string();
384  return variant(true);
385  }
386 
387  ERR_AI << "ERROR #" << status << " while executing 'set_unit_var' formula function";
388  return variant(std::make_shared<safe_call_result>(fake_ptr(), status));
389 }
390 
392  // We want give control of the side to human for the rest of this turn
394 }
395 
396 }
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:148
bool is_gamestate_changed() const
Definition: actions.cpp:119
virtual const map_location & get_unit_location() const
Definition: actions.cpp:410
virtual const team & current_team() const override
Definition: contexts.hpp:450
virtual const move_map & get_srcdst() const override
Definition: contexts.hpp:716
virtual side_number get_side() const override
Get the side number.
Definition: contexts.hpp:396
virtual side_number get_side() const =0
Get the side number.
virtual const unit_map & units() const override
Definition: game_board.hpp:106
bool is_enemy(int n) const
Definition: team.hpp:229
void add_formula_var(std::string str, wfl::variant var)
Container associating units to locations.
Definition: map.hpp:98
unit_iterator end()
Definition: map.hpp:428
std::size_t count(const map_location &loc) const
Definition: map.hpp:413
unit_iterator find(std::size_t id)
Definition: map.cpp:302
This class represents a single unit of a specific type.
Definition: unit.hpp:133
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:643
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:2572
unit_formula_manager & formula_manager() const
Get the unit formula manager.
Definition: unit.hpp:1840
bool incapacitated() const
Check if the unit has been petrified.
Definition: unit.hpp:905
int side() const
The side this unit belongs to.
Definition: unit.hpp:343
const map_location & get_location() const
The current map location this unit is at.
Definition: unit.hpp:1357
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:474
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:59
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:412
game_board * gameboard
Definition: resources.cpp:20
Definition: contexts.hpp:43
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