The Battle for Wesnoth  1.15.1+dev
attack.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
15 /**
16  * @file
17  * Calculate & analyze attacks of the default ai
18  */
19 
20 #include "ai/manager.hpp"
21 #include "ai/default/contexts.hpp"
22 #include "ai/actions.hpp"
23 #include "ai/formula/ai.hpp"
25 
26 #include "actions/attack.hpp"
27 #include "attack_prediction.hpp"
28 #include "game_config.hpp"
29 #include "log.hpp"
30 #include "map/map.hpp"
31 #include "team.hpp"
32 #include "units/unit.hpp"
33 #include "formula/callable_objects.hpp" // for location_callable
34 #include "resources.hpp"
35 #include "game_board.hpp"
36 
37 static lg::log_domain log_ai("ai/attack");
38 #define LOG_AI LOG_STREAM(info, log_ai)
39 #define ERR_AI LOG_STREAM(err, log_ai)
40 
41 namespace ai {
42 
43 extern ai_context& get_ai_context(wfl::const_formula_callable_ptr for_fai);
44 
45 void attack_analysis::analyze(const gamemap& map, unit_map& units,
46  const readonly_context& ai_obj,
47  const move_map& dstsrc, const move_map& srcdst,
48  const move_map& enemy_dstsrc, double aggression)
49 {
50  const unit_map::const_iterator defend_it = units.find(target);
51  assert(defend_it != units.end());
52 
53  // See if the target is a threat to our leader or an ally's leader.
55  get_adjacent_tiles(target,adj.data());
56  std::size_t tile;
57  for(tile = 0; tile < adj.size(); ++tile) {
58  const unit_map::const_iterator leader = units.find(adj[tile]);
59  if(leader != units.end() && leader->can_recruit() && !ai_obj.current_team().is_enemy(leader->side())) {
60  break;
61  }
62  }
63 
64  leader_threat = (tile != 6);
65  uses_leader = false;
66 
67  target_value = defend_it->cost();
68  target_value += (static_cast<double>(defend_it->experience())/
69  static_cast<double>(defend_it->max_experience()))*target_value;
70  target_starting_damage = defend_it->max_hitpoints() -
71  defend_it->hitpoints();
72 
73  // Calculate the 'alternative_terrain_quality' -- the best possible defensive values
74  // the attacking units could hope to achieve if they didn't attack and moved somewhere.
75  // This is used for comparative purposes, to see just how vulnerable the AI is
76  // making itself.
78  double cost_sum = 0.0;
79  for(std::size_t i = 0; i != movements.size(); ++i) {
80  const unit_map::const_iterator att = units.find(movements[i].first);
81  const double cost = att->cost();
82  cost_sum += cost;
83  alternative_terrain_quality += cost*ai_obj.best_defensive_position(movements[i].first,dstsrc,srcdst,enemy_dstsrc).chance_to_hit;
84  }
85  alternative_terrain_quality /= cost_sum*100;
86 
88  avg_damage_taken = 0.0;
89  resources_used = 0.0;
90  terrain_quality = 0.0;
91  avg_losses = 0.0;
92  chance_to_kill = 0.0;
93 
94  double def_avg_experience = 0.0;
95  double first_chance_kill = 0.0;
96 
97  double prob_dead_already = 0.0;
98  assert(!movements.empty());
99  std::vector<std::pair<map_location,map_location>>::const_iterator m;
100 
101  std::unique_ptr<battle_context> bc(nullptr);
102  std::unique_ptr<battle_context> old_bc(nullptr);
103 
104  const combatant *prev_def = nullptr;
105 
106  for (m = movements.begin(); m != movements.end(); ++m) {
107  // We fix up units map to reflect what this would look like.
108  unit_ptr up = units.extract(m->first);
109  up->set_location(m->second);
110  units.insert(up);
111  double m_aggression = aggression;
112 
113  if (up->can_recruit()) {
114  uses_leader = true;
115  // FIXME: suokko's r29531 omitted this line
116  leader_threat = false;
117  m_aggression = ai_obj.get_leader_aggression();
118  }
119 
120  bool from_cache = false;
121 
122  // Swap the two context pointers. old_bc should be null at this point, so bc is cleared
123  // and old_bc takes ownership of the context pointer. This allows prev_def to remain
124  // valid until it's reassigned.
125  old_bc.swap(bc);
126 
127  // This cache is only about 99% correct, but speeds up evaluation by about 1000 times.
128  // We recalculate when we actually attack.
129  const readonly_context::unit_stats_cache_t::key_type cache_key = std::make_pair(target, &up->type());
130  const readonly_context::unit_stats_cache_t::iterator usc = ai_obj.unit_stats_cache().find(cache_key);
131  // Just check this attack is valid for this attacking unit (may be modified)
132  if (usc != ai_obj.unit_stats_cache().end() &&
133  usc->second.first.attack_num <
134  static_cast<int>(up->attacks().size())) {
135 
136  from_cache = true;
137  bc.reset(new battle_context(usc->second.first, usc->second.second));
138  } else {
139  bc.reset(new battle_context(units, m->second, target, -1, -1, m_aggression, prev_def));
140  }
141  const combatant &att = bc->get_attacker_combatant(prev_def);
142  const combatant &def = bc->get_defender_combatant(prev_def);
143 
144  prev_def = &bc->get_defender_combatant(prev_def);
145 
146  // We no longer need the old context since prev_def has been reassigned.
147  old_bc.reset(nullptr);
148 
149  if ( !from_cache ) {
150  ai_obj.unit_stats_cache().emplace(cache_key, std::make_pair(
151  bc->get_attacker_stats(),
152  bc->get_defender_stats()
153  ));
154  }
155 
156  // Note we didn't fight at all if defender is already dead.
157  double prob_fought = (1.0 - prob_dead_already);
158 
159  double prob_killed = def.hp_dist[0] - prob_dead_already;
160  prob_dead_already = def.hp_dist[0];
161 
162  double prob_died = att.hp_dist[0];
163  double prob_survived = (1.0 - prob_died) * prob_fought;
164 
165  double cost = up->cost();
166  const bool on_village = map.is_village(m->second);
167  // Up to double the value of a unit based on experience
168  cost += (static_cast<double>(up->experience()) / up->max_experience())*cost;
169  resources_used += cost;
170  avg_losses += cost * prob_died;
171 
172  // add half of cost for poisoned unit so it might get chance to heal
173  avg_losses += cost * up->get_state(unit::STATE_POISONED) /2;
174 
175  if (!bc->get_defender_stats().is_poisoned) {
177  }
178 
179  // Double reward to emphasize getting onto villages if they survive.
180  if (on_village) {
181  avg_damage_taken -= game_config::poison_amount*2 * prob_survived;
182  }
183 
184  terrain_quality += (static_cast<double>(bc->get_defender_stats().chance_to_hit)/100.0)*cost * (on_village ? 0.5 : 1.0);
185 
186  double advance_prob = 0.0;
187  // The reward for advancing a unit is to get a 'negative' loss of that unit
188  if (!up->advances_to().empty()) {
189  int xp_for_advance = up->experience_to_advance();
190 
191  // See bug #6272... in some cases, unit already has got enough xp to advance,
192  // but hasn't (bug elsewhere?). Can cause divide by zero.
193  if (xp_for_advance == 0)
194  xp_for_advance = 1;
195 
196  int fight_xp = game_config::combat_xp(defend_it->level());
197  int kill_xp = game_config::kill_xp(fight_xp);
198 
199  if (fight_xp >= xp_for_advance) {
200  advance_prob = prob_fought;
201  avg_losses -= up->cost() * prob_fought;
202  } else if (kill_xp >= xp_for_advance) {
203  advance_prob = prob_killed;
204  avg_losses -= up->cost() * prob_killed;
205  // The reward for getting a unit closer to advancement
206  // (if it didn't advance) is to get the proportion of
207  // remaining experience needed, and multiply it by
208  // a quarter of the unit cost.
209  // This will cause the AI to heavily favor
210  // getting xp for close-to-advance units.
211  avg_losses -= up->cost() * 0.25 *
212  fight_xp * (prob_fought - prob_killed)
213  / xp_for_advance;
214  } else {
215  avg_losses -= up->cost() * 0.25 *
216  (kill_xp * prob_killed + fight_xp * (prob_fought - prob_killed))
217  / xp_for_advance;
218  }
219 
220  // The reward for killing with a unit that plagues
221  // is to get a 'negative' loss of that unit.
222  if (bc->get_attacker_stats().plagues) {
223  avg_losses -= prob_killed * up->cost();
224  }
225  }
226 
227  // If we didn't advance, we took this damage.
228  avg_damage_taken += (up->hitpoints() - att.average_hp()) * (1.0 - advance_prob);
229 
230  int fight_xp = game_config::combat_xp(up->level());
231  int kill_xp = game_config::kill_xp(fight_xp);
232  def_avg_experience += fight_xp * (1.0 - att.hp_dist[0]) + kill_xp * att.hp_dist[0];
233  if (m == movements.begin()) {
234  first_chance_kill = def.hp_dist[0];
235  }
236  }
237 
238  if (!defend_it->advances_to().empty() &&
239  def_avg_experience >= defend_it->experience_to_advance()) {
240  // It's likely to advance: only if we can kill with first blow.
241  chance_to_kill = first_chance_kill;
242  // Negative average damage (it will advance).
243  avg_damage_inflicted += defend_it->hitpoints() - defend_it->max_hitpoints();
244  } else {
245  chance_to_kill = prev_def->hp_dist[0];
246  avg_damage_inflicted += defend_it->hitpoints() - prev_def->average_hp(map.gives_healing(defend_it->get_location()));
247  }
248 
250 
251  // Restore the units to their original positions.
252  for (m = movements.begin(); m != movements.end(); ++m) {
253  units.move(m->second, m->first);
254  }
255 }
256 
258 {
259  std::set<map_location> &attacks = manager::get_singleton().get_ai_info().recent_attacks;
260  for(std::set<map_location>::const_iterator i = attacks.begin(); i != attacks.end(); ++i) {
261  if(distance_between(*i,loc) < 4) {
262  return true;
263  }
264  }
265 
266  return false;
267 }
268 
269 
270 double attack_analysis::rating(double aggression, const readonly_context& ai_obj) const
271 {
272  if(leader_threat) {
273  aggression = 1.0;
274  }
275 
276  if(uses_leader) {
277  aggression = ai_obj.get_leader_aggression();
278  }
279 
280  double value = chance_to_kill*target_value - avg_losses*(1.0-aggression);
281 
283  // This situation looks like it might be a bad move:
284  // we are moving our attackers out of their optimal terrain
285  // into sub-optimal terrain.
286  // Calculate the 'exposure' of our units to risk.
287 
288  const double exposure_mod = uses_leader ? 2.0 : ai_obj.get_caution();
289  const double exposure = exposure_mod*resources_used*(terrain_quality - alternative_terrain_quality)*vulnerability/std::max<double>(0.01,support);
290  LOG_AI << "attack option has base value " << value << " with exposure " << exposure << ": "
291  << vulnerability << "/" << support << " = " << (vulnerability/std::max<double>(support,0.1)) << "\n";
292  value -= exposure*(1.0-aggression);
293  }
294 
295  // Prefer to attack already damaged targets.
296  value += ((target_starting_damage/3 + avg_damage_inflicted) - (1.0-aggression)*avg_damage_taken)/10.0;
297 
298  // If the unit is surrounded and there is no support,
299  // or if the unit is surrounded and the average damage is 0,
300  // the unit skips its sanity check and tries to break free as good as possible.
301  if(!is_surrounded || (support != 0 && avg_damage_taken != 0))
302  {
303  // Sanity check: if we're putting ourselves at major risk,
304  // and have no chance to kill, and we're not aiding our allies
305  // who are also attacking, then don't do it.
306  if(vulnerability > 50.0 && vulnerability > support*2.0
307  && chance_to_kill < 0.02 && aggression < 0.75
308  && !attack_close(target)) {
309  return -1.0;
310  }
311  }
312 
313  if(!leader_threat && vulnerability*terrain_quality > 0.0 && support != 0) {
315  }
316 
317  value /= ((resources_used/2) + (resources_used/2)*terrain_quality);
318 
319  if(leader_threat) {
320  value *= 5.0;
321  }
322 
323  LOG_AI << "attack on " << target << ": attackers: " << movements.size()
324  << " value: " << value << " chance to kill: " << chance_to_kill
325  << " damage inflicted: " << avg_damage_inflicted
326  << " damage taken: " << avg_damage_taken
327  << " vulnerability: " << vulnerability
328  << " support: " << support
329  << " quality: " << terrain_quality
330  << " alternative quality: " << alternative_terrain_quality << "\n";
331 
332  return value;
333 }
334 
335 wfl::variant attack_analysis::get_value(const std::string& key) const
336 {
337  using namespace wfl;
338  if(key == "target") {
339  return variant(std::make_shared<location_callable>(target));
340  } else if(key == "movements") {
341  std::vector<variant> res;
342  for(std::size_t n = 0; n != movements.size(); ++n) {
343  auto item = std::make_shared<map_formula_callable>(nullptr);
344  item->add("src", variant(std::make_shared<location_callable>(movements[n].first)));
345  item->add("dst", variant(std::make_shared<location_callable>(movements[n].second)));
346  res.emplace_back(item);
347  }
348 
349  return variant(res);
350  } else if(key == "units") {
351  std::vector<variant> res;
352  for(std::size_t n = 0; n != movements.size(); ++n) {
353  res.emplace_back(std::make_shared<location_callable>(movements[n].first));
354  }
355 
356  return variant(res);
357  } else if(key == "target_value") {
358  return variant(static_cast<int>(target_value*1000));
359  } else if(key == "avg_losses") {
360  return variant(static_cast<int>(avg_losses*1000));
361  } else if(key == "chance_to_kill") {
362  return variant(static_cast<int>(chance_to_kill*100));
363  } else if(key == "avg_damage_inflicted") {
364  return variant(static_cast<int>(avg_damage_inflicted));
365  } else if(key == "target_starting_damage") {
367  } else if(key == "avg_damage_taken") {
368  return variant(static_cast<int>(avg_damage_taken));
369  } else if(key == "resources_used") {
370  return variant(static_cast<int>(resources_used));
371  } else if(key == "terrain_quality") {
372  return variant(static_cast<int>(terrain_quality));
373  } else if(key == "alternative_terrain_quality") {
374  return variant(static_cast<int>(alternative_terrain_quality));
375  } else if(key == "vulnerability") {
376  return variant(static_cast<int>(vulnerability));
377  } else if(key == "support") {
378  return variant(static_cast<int>(support));
379  } else if(key == "leader_threat") {
380  return variant(leader_threat);
381  } else if(key == "uses_leader") {
382  return variant(uses_leader);
383  } else if(key == "is_surrounded") {
384  return variant(is_surrounded);
385  } else {
386  return variant();
387  }
388 }
389 
391 {
392  add_input(inputs, "target");
393  add_input(inputs, "movements");
394  add_input(inputs, "units");
395  add_input(inputs, "target_value");
396  add_input(inputs, "avg_losses");
397  add_input(inputs, "chance_to_kill");
398  add_input(inputs, "avg_damage_inflicted");
399  add_input(inputs, "target_starting_damage");
400  add_input(inputs, "avg_damage_taken");
401  add_input(inputs, "resources_used");
402  add_input(inputs, "terrain_quality");
403  add_input(inputs, "alternative_terrain_quality");
404  add_input(inputs, "vulnerability");
405  add_input(inputs, "support");
406  add_input(inputs, "leader_threat");
407  add_input(inputs, "uses_leader");
408  add_input(inputs, "is_surrounded");
409 }
410 
412  //If we get an attack analysis back we will do the first attack.
413  //Then the AI can get run again and re-choose.
414  if(movements.empty()) {
415  return wfl::variant(false);
416  }
417 
418  unit_map& units = resources::gameboard->units();
419 
420  //make sure that unit which has to attack is at given position and is able to attack
421  unit_map::const_iterator unit = units.find(movements.front().first);
422  if(!unit.valid() || unit->attacks_left() == 0) {
423  return wfl::variant(false);
424  }
425 
426  const map_location& move_from = movements.front().first;
427  const map_location& att_src = movements.front().second;
428  const map_location& att_dst = target;
429 
430  //check if target is still valid
431  unit = units.find(att_dst);
432  if(unit == units.end()) {
433  return wfl::variant(std::make_shared<wfl::safe_call_result>(fake_ptr(), attack_result::E_EMPTY_DEFENDER, move_from));
434  }
435 
436  //check if we need to move
437  if(move_from != att_src) {
438  //now check if location to which we want to move is still unoccupied
439  unit = units.find(att_src);
440  if(unit != units.end()) {
441  return wfl::variant(std::make_shared<wfl::safe_call_result>(fake_ptr(), move_result::E_NO_UNIT, move_from));
442  }
443 
444  ai::move_result_ptr result = get_ai_context(ctxt.as_callable()).execute_move_action(move_from, att_src);
445  if(!result->is_ok()) {
446  //move part failed
447  LOG_AI << "ERROR #" << result->get_status() << " while executing 'attack' formula function\n" << std::endl;
448  return wfl::variant(std::make_shared<wfl::safe_call_result>(fake_ptr(), result->get_status(), result->get_unit_location()));
449  }
450  }
451 
452  if(units.count(att_src)) {
453  ai::attack_result_ptr result = get_ai_context(ctxt.as_callable()).execute_attack_action(movements.front().second, target, -1);
454  if(!result->is_ok()) {
455  //attack failed
456  LOG_AI << "ERROR #" << result->get_status() << " while executing 'attack' formula function\n" << std::endl;
457  return wfl::variant(std::make_shared<wfl::safe_call_result>(fake_ptr(), result->get_status()));
458  }
459  }
460  return wfl::variant(true);
461 }
462 
463 } //end of namespace ai
Defines formula ai.
int kill_xp(int level)
Definition: game_config.hpp:46
bool leader_threat
Is true if the unit is a threat to our leader.
Definition: contexts.hpp:125
double avg_damage_taken
The average hitpoints damage taken.
Definition: contexts.hpp:103
int combat_xp(int level)
Definition: game_config.hpp:51
std::vector< double > hp_dist
Resulting probability distribution (might be not as large as max_hp)
unit_iterator end()
Definition: map.hpp:415
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:517
virtual const unit_map & units() const override
Definition: game_board.hpp:114
This class represents a single unit of a specific type.
Definition: unit.hpp:99
const battle_context_unit_stats & get_defender_stats() const
This method returns the statistics of the defender.
Definition: attack.hpp:204
static manager & get_singleton()
Definition: manager.hpp:150
umap_retval_pair_t insert(unit_ptr p)
Inserts the unit pointed to by p into the map.
Definition: map.cpp:135
Various functions that implement attacks and attack calculations.
const combatant & get_attacker_combatant(const combatant *prev_def=nullptr)
Get the simulation results.
Definition: attack.cpp:456
double avg_damage_inflicted
The average hitpoints damage inflicted.
Definition: contexts.hpp:98
Managing the AI-Game interaction - AI actions and their results.
double average_hp(unsigned int healing=0) const
What&#39;s the average hp (weighted average of hp_dist).
std::shared_ptr< move_result > move_result_ptr
Definition: game_info.hpp:84
map_location target
Definition: contexts.hpp:85
double vulnerability
The vulnerability is the power projection of enemy units onto the hex we&#39;re standing on...
Definition: contexts.hpp:122
std::vector< formula_input > formula_input_vector
The unit is poisoned - it loses health each turn.
Definition: unit.hpp:812
double resources_used
The sum of the values of units used in the attack.
Definition: contexts.hpp:106
double chance_to_kill
Estimated % chance to kill the unit.
Definition: contexts.hpp:95
-file sdl_utils.hpp
std::shared_ptr< attack_result > attack_result_ptr
Definition: game_info.hpp:81
ai_context & get_ai_context(wfl::const_formula_callable_ptr for_fai)
unsigned int chance_to_hit
Effective chance to hit as a percentage (all factors accounted for).
Definition: attack.hpp:73
double terrain_quality
The weighted average of the % chance to hit each attacking unit.
Definition: contexts.hpp:109
double target_value
The value of the unit being targeted.
Definition: contexts.hpp:89
std::multimap< map_location, map_location > move_map
The standard way in which a map of possible moves is recorded.
Definition: game_info.hpp:42
const combatant & get_defender_combatant(const combatant *prev_def=nullptr)
Definition: attack.cpp:463
A small explanation about what&#39;s going on here: Each action has access to two game_info objects First...
Definition: actions.cpp:58
int gives_healing(const map_location &loc) const
Definition: map.cpp:67
void analyze(const gamemap &map, unit_map &units, const readonly_context &ai_obj, const move_map &dstsrc, const move_map &srcdst, const move_map &enemy_dstsrc, double aggression)
Definition: attack.cpp:45
virtual unit_stats_cache_t & unit_stats_cache() const =0
double alternative_terrain_quality
The weighted average of the % defense of the best possible terrain that the attacking units could rea...
Definition: contexts.hpp:116
TYPE type
Definition: contexts.hpp:46
game_board * gameboard
Definition: resources.cpp:20
bool plagues
Attack turns opponent into a zombie when fatal.
Definition: attack.hpp:58
Encapsulates the map of the game.
Definition: map.hpp:36
Computes the statistics of a battle between an attacker and a defender unit.
Definition: attack.hpp:171
bool is_enemy(int n) const
Definition: team.hpp:243
umap_retval_pair_t move(const map_location &src, const map_location &dst)
Moves a unit from location src to location dst.
Definition: map.cpp:94
Managing the AIs lifecycle - headers TODO: Refactor history handling and internal commands...
if(win_locale=="af") win_locale
std::size_t count(const map_location &loc) const
Definition: map.hpp:400
std::array< map_location, 6 > adjacent_loc_array_t
Definition: location.hpp:170
static lg::log_domain log_ai("ai/attack")
wfl::variant get_value(const std::string &key) const override
Definition: attack.cpp:335
#define LOG_AI
Definition: attack.cpp:38
Encapsulates the map of the game.
Definition: location.hpp:42
const_formula_callable_ptr as_callable() const
Definition: variant.hpp:82
bool uses_leader
Is true if this attack sequence makes use of the leader.
Definition: contexts.hpp:128
unit_iterator find(std::size_t id)
Definition: map.cpp:311
All combat-related info.
std::size_t i
Definition: function.cpp:933
formula_callable_ptr fake_ptr()
Definition: callable.hpp:41
virtual const team & current_team() const =0
bool attack_close(const map_location &loc) const
Definition: attack.cpp:257
std::vector< std::pair< map_location, map_location > > movements
Definition: contexts.hpp:86
std::set< map_location > recent_attacks
Definition: game_info.hpp:114
void get_inputs(wfl::formula_input_vector &inputs) const override
Definition: attack.cpp:390
Default AI contexts.
double avg_losses
The value on average, of units lost in the combat.
Definition: contexts.hpp:92
bool is_poisoned
True if the unit is poisoned at the beginning of the battle.
Definition: attack.hpp:53
double rating(double aggression, const readonly_context &ai_obj) const
Definition: attack.cpp:270
std::shared_ptr< const formula_callable > const_formula_callable_ptr
formula_input_vector inputs() const
Definition: callable.hpp:62
bool is_village(const map_location &loc) const
Definition: map.cpp:65
std::size_t distance_between(const map_location &a, const map_location &b)
Function which gives the number of hexes between two tiles (i.e.
Definition: location.cpp:600
boost::intrusive_ptr< unit > unit_ptr
Definition: ptr.hpp:29
Definition: contexts.hpp:42
game_info & get_ai_info()
Gets global AI-game info.
Definition: manager.cpp:713
virtual const defensive_position & best_defensive_position(const map_location &unit, const move_map &dstsrc, const move_map &srcdst, const move_map &enemy_dstsrc) const =0
Composite AI contexts.
unit_ptr extract(const map_location &loc)
Extracts a unit from the map.
Definition: map.cpp:268
Standard logging facilities (interface).
virtual double get_leader_aggression() const =0
Container associating units to locations.
Definition: map.hpp:99
bool is_surrounded
Is true if the units involved in this attack sequence are surrounded.
Definition: contexts.hpp:131
static void add_input(formula_input_vector &inputs, const std::string &key, FORMULA_ACCESS_TYPE access_type=FORMULA_READ_ONLY)
Definition: callable.hpp:135
bool valid() const
Definition: map.hpp:276
static map_location::DIRECTION n
const battle_context_unit_stats & get_attacker_stats() const
This method returns the statistics of the attacker.
Definition: attack.hpp:198
std::string::const_iterator iterator
Definition: tokenizer.hpp:24
virtual double get_caution() const =0
double poisoned
Resulting chance we are poisoned.
std::pair< std::string, unsigned > item
Definition: help_impl.hpp:371
wfl::variant execute_self(wfl::variant ctxt) override
Definition: attack.cpp:411