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