The Battle for Wesnoth  1.13.10+dev
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
attack.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2017 by David White <dave@whitevine.net>
3  Part of the Battle for Wesnoth Project http://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 
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  map_location adj[6];
56  size_t tile;
57  for(tile = 0; tile != 6; ++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 += (double(defend_it->experience())/
69  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(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  /** @todo 1.9 add combatant.prob_killed */
160  double prob_killed = def.hp_dist[0] - prob_dead_already;
161  prob_dead_already = def.hp_dist[0];
162 
163  double prob_died = att.hp_dist[0];
164  double prob_survived = (1.0 - prob_died) * prob_fought;
165 
166  double cost = up->cost();
167  const bool on_village = map.is_village(m->second);
168  // Up to double the value of a unit based on experience
169  cost += (double(up->experience()) / up->max_experience())*cost;
170  resources_used += cost;
171  avg_losses += cost * prob_died;
172 
173  // add half of cost for poisoned unit so it might get chance to heal
174  avg_losses += cost * up->get_state(unit::STATE_POISONED) /2;
175 
176  if (!bc->get_defender_stats().is_poisoned) {
178  }
179 
180  // Double reward to emphasize getting onto villages if they survive.
181  if (on_village) {
182  avg_damage_taken -= game_config::poison_amount*2 * prob_survived;
183  }
184 
185  terrain_quality += (double(bc->get_defender_stats().chance_to_hit)/100.0)*cost * (on_village ? 0.5 : 1.0);
186 
187  double advance_prob = 0.0;
188  // The reward for advancing a unit is to get a 'negative' loss of that unit
189  if (!up->advances_to().empty()) {
190  int xp_for_advance = up->experience_to_advance();
191 
192  // See bug #6272... in some cases, unit already has got enough xp to advance,
193  // but hasn't (bug elsewhere?). Can cause divide by zero.
194  if (xp_for_advance == 0)
195  xp_for_advance = 1;
196 
197  int fight_xp = defend_it->level();
198  int kill_xp = game_config::kill_xp(fight_xp);
199 
200  if (fight_xp >= xp_for_advance) {
201  advance_prob = prob_fought;
202  avg_losses -= up->cost() * prob_fought;
203  } else if (kill_xp >= xp_for_advance) {
204  advance_prob = prob_killed;
205  avg_losses -= up->cost() * prob_killed;
206  // The reward for getting a unit closer to advancement
207  // (if it didn't advance) is to get the proportion of
208  // remaining experience needed, and multiply it by
209  // a quarter of the unit cost.
210  // This will cause the AI to heavily favor
211  // getting xp for close-to-advance units.
212  avg_losses -= up->cost() * 0.25 *
213  fight_xp * (prob_fought - prob_killed)
214  / xp_for_advance;
215  } else {
216  avg_losses -= up->cost() * 0.25 *
217  (kill_xp * prob_killed + fight_xp * (prob_fought - prob_killed))
218  / xp_for_advance;
219  }
220 
221  // The reward for killing with a unit that plagues
222  // is to get a 'negative' loss of that unit.
223  if (bc->get_attacker_stats().plagues) {
224  avg_losses -= prob_killed * up->cost();
225  }
226  }
227 
228  // If we didn't advance, we took this damage.
229  avg_damage_taken += (up->hitpoints() - att.average_hp()) * (1.0 - advance_prob);
230 
231  /**
232  * @todo 1.9: attack_prediction.cpp should understand advancement
233  * directly. For each level of attacker def gets 1 xp or
234  * kill_experience.
235  */
236  int fight_xp = up->level();
237  int kill_xp = game_config::kill_xp(fight_xp);
238  def_avg_experience += fight_xp * (1.0 - att.hp_dist[0]) + kill_xp * att.hp_dist[0];
239  if (m == movements.begin()) {
240  first_chance_kill = def.hp_dist[0];
241  }
242  }
243 
244  if (!defend_it->advances_to().empty() &&
245  def_avg_experience >= defend_it->experience_to_advance()) {
246  // It's likely to advance: only if we can kill with first blow.
247  chance_to_kill = first_chance_kill;
248  // Negative average damage (it will advance).
249  avg_damage_inflicted += defend_it->hitpoints() - defend_it->max_hitpoints();
250  } else {
251  chance_to_kill = prev_def->hp_dist[0];
252  avg_damage_inflicted += defend_it->hitpoints() - prev_def->average_hp(map.gives_healing(defend_it->get_location()));
253  }
254 
256 
257  // Restore the units to their original positions.
258  for (m = movements.begin(); m != movements.end(); ++m) {
259  units.move(m->second, m->first);
260  }
261 }
262 
264 {
265  std::set<map_location> &attacks = manager::get_ai_info().recent_attacks;
266  for(std::set<map_location>::const_iterator i = attacks.begin(); i != attacks.end(); ++i) {
267  if(distance_between(*i,loc) < 4) {
268  return true;
269  }
270  }
271 
272  return false;
273 }
274 
275 
276 double attack_analysis::rating(double aggression, const readonly_context& ai_obj) const
277 {
278  if(leader_threat) {
279  aggression = 1.0;
280  }
281 
282  if(uses_leader) {
283  aggression = ai_obj.get_leader_aggression();
284  }
285 
286  double value = chance_to_kill*target_value - avg_losses*(1.0-aggression);
287 
289  // This situation looks like it might be a bad move:
290  // we are moving our attackers out of their optimal terrain
291  // into sub-optimal terrain.
292  // Calculate the 'exposure' of our units to risk.
293 
294  const double exposure_mod = uses_leader ? 2.0 : ai_obj.get_caution();
295  const double exposure = exposure_mod*resources_used*(terrain_quality - alternative_terrain_quality)*vulnerability/std::max<double>(0.01,support);
296  LOG_AI << "attack option has base value " << value << " with exposure " << exposure << ": "
297  << vulnerability << "/" << support << " = " << (vulnerability/std::max<double>(support,0.1)) << "\n";
298  value -= exposure*(1.0-aggression);
299  }
300 
301  // Prefer to attack already damaged targets.
302  value += ((target_starting_damage/3 + avg_damage_inflicted) - (1.0-aggression)*avg_damage_taken)/10.0;
303 
304  // If the unit is surrounded and there is no support,
305  // or if the unit is surrounded and the average damage is 0,
306  // the unit skips its sanity check and tries to break free as good as possible.
307  if(!is_surrounded || (support != 0 && avg_damage_taken != 0))
308  {
309  // Sanity check: if we're putting ourselves at major risk,
310  // and have no chance to kill, and we're not aiding our allies
311  // who are also attacking, then don't do it.
312  if(vulnerability > 50.0 && vulnerability > support*2.0
313  && chance_to_kill < 0.02 && aggression < 0.75
314  && !attack_close(target)) {
315  return -1.0;
316  }
317  }
318 
319  if(!leader_threat && vulnerability*terrain_quality > 0.0 && support != 0) {
321  }
322 
323  value /= ((resources_used/2) + (resources_used/2)*terrain_quality);
324 
325  if(leader_threat) {
326  value *= 5.0;
327  }
328 
329  LOG_AI << "attack on " << target << ": attackers: " << movements.size()
330  << " value: " << value << " chance to kill: " << chance_to_kill
331  << " damage inflicted: " << avg_damage_inflicted
332  << " damage taken: " << avg_damage_taken
333  << " vulnerability: " << vulnerability
334  << " support: " << support
335  << " quality: " << terrain_quality
336  << " alternative quality: " << alternative_terrain_quality << "\n";
337 
338  return value;
339 }
340 
342 {
343  using namespace wfl;
344  if(key == "target") {
345  return variant(std::make_shared<location_callable>(target));
346  } else if(key == "movements") {
347  std::vector<variant> res;
348  for(size_t n = 0; n != movements.size(); ++n) {
349  auto item = std::make_shared<map_formula_callable>(nullptr);
350  item->add("src", variant(std::make_shared<location_callable>(movements[n].first)));
351  item->add("dst", variant(std::make_shared<location_callable>(movements[n].second)));
352  res.emplace_back(item);
353  }
354 
355  return variant(res);
356  } else if(key == "units") {
357  std::vector<variant> res;
358  for(size_t n = 0; n != movements.size(); ++n) {
359  res.emplace_back(std::make_shared<location_callable>(movements[n].first));
360  }
361 
362  return variant(res);
363  } else if(key == "target_value") {
364  return variant(static_cast<int>(target_value*1000));
365  } else if(key == "avg_losses") {
366  return variant(static_cast<int>(avg_losses*1000));
367  } else if(key == "chance_to_kill") {
368  return variant(static_cast<int>(chance_to_kill*100));
369  } else if(key == "avg_damage_inflicted") {
370  return variant(static_cast<int>(avg_damage_inflicted));
371  } else if(key == "target_starting_damage") {
373  } else if(key == "avg_damage_taken") {
374  return variant(static_cast<int>(avg_damage_taken));
375  } else if(key == "resources_used") {
376  return variant(static_cast<int>(resources_used));
377  } else if(key == "terrain_quality") {
378  return variant(static_cast<int>(terrain_quality));
379  } else if(key == "alternative_terrain_quality") {
380  return variant(static_cast<int>(alternative_terrain_quality));
381  } else if(key == "vulnerability") {
382  return variant(static_cast<int>(vulnerability));
383  } else if(key == "support") {
384  return variant(static_cast<int>(support));
385  } else if(key == "leader_threat") {
386  return variant(leader_threat);
387  } else if(key == "uses_leader") {
388  return variant(uses_leader);
389  } else if(key == "is_surrounded") {
390  return variant(is_surrounded);
391  } else {
392  return variant();
393  }
394 }
395 
397 {
398  add_input(inputs, "target");
399  add_input(inputs, "movements");
400  add_input(inputs, "units");
401  add_input(inputs, "target_value");
402  add_input(inputs, "avg_losses");
403  add_input(inputs, "chance_to_kill");
404  add_input(inputs, "avg_damage_inflicted");
405  add_input(inputs, "target_starting_damage");
406  add_input(inputs, "avg_damage_taken");
407  add_input(inputs, "resources_used");
408  add_input(inputs, "terrain_quality");
409  add_input(inputs, "alternative_terrain_quality");
410  add_input(inputs, "vulnerability");
411  add_input(inputs, "support");
412  add_input(inputs, "leader_threat");
413  add_input(inputs, "uses_leader");
414  add_input(inputs, "is_surrounded");
415 }
416 
418  //If we get an attack analysis back we will do the first attack.
419  //Then the AI can get run again and re-choose.
420  if(movements.empty()) {
421  return wfl::variant(false);
422  }
423 
425 
426  //make sure that unit which has to attack is at given position and is able to attack
427  unit_map::const_iterator unit = units.find(movements.front().first);
428  if(!unit.valid() || unit->attacks_left() == 0) {
429  return wfl::variant(false);
430  }
431 
432  const map_location& move_from = movements.front().first;
433  const map_location& att_src = movements.front().second;
434  const map_location& att_dst = target;
435 
436  //check if target is still valid
437  unit = units.find(att_dst);
438  if(unit == units.end()) {
439  return wfl::variant(std::make_shared<wfl::safe_call_result>(fake_ptr(), attack_result::E_EMPTY_DEFENDER, move_from));
440  }
441 
442  //check if we need to move
443  if(move_from != att_src) {
444  //now check if location to which we want to move is still unoccupied
445  unit = units.find(att_src);
446  if(unit != units.end()) {
447  return wfl::variant(std::make_shared<wfl::safe_call_result>(fake_ptr(), move_result::E_NO_UNIT, move_from));
448  }
449 
450  ai::move_result_ptr result = get_ai_context(ctxt.as_callable()).execute_move_action(move_from, att_src);
451  if(!result->is_ok()) {
452  //move part failed
453  LOG_AI << "ERROR #" << result->get_status() << " while executing 'attack' formula function\n" << std::endl;
454  return wfl::variant(std::make_shared<wfl::safe_call_result>(fake_ptr(), result->get_status(), result->get_unit_location()));
455  }
456  }
457 
458  if(units.count(att_src)) {
459  ai::attack_result_ptr result = get_ai_context(ctxt.as_callable()).execute_attack_action(movements.front().second, target, -1);
460  if(!result->is_ok()) {
461  //attack failed
462  LOG_AI << "ERROR #" << result->get_status() << " while executing 'attack' formula function\n" << std::endl;
463  return wfl::variant(std::make_shared<wfl::safe_call_result>(fake_ptr(), result->get_status()));
464  }
465  }
466  return wfl::variant(true);
467 }
468 
469 } //end of namespace ai
Defines formula ai.
int kill_xp(int level)
Definition: game_config.hpp:48
bool leader_threat
Is true if the unit is a threat to our leader.
Definition: contexts.hpp:132
double avg_damage_taken
The average hitpoints damage taken.
Definition: contexts.hpp:110
const_formula_callable_ptr as_callable() const
Definition: variant.hpp:79
virtual const unit_map & units() const
Definition: game_board.hpp:97
std::vector< double > hp_dist
Resulting probability distribution (might be not as large as max_hp)
unit_iterator end()
Definition: map.hpp:415
std::vector< char_t > string
This class represents a single unit of a specific type.
Definition: unit.hpp:100
size_t count(const map_location &loc) const
Definition: map.hpp:400
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:452
double avg_damage_inflicted
The average hitpoints damage inflicted.
Definition: contexts.hpp:105
Managing the AI-Game interaction - AI actions and their results.
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.hpp:299
bool is_village(const map_location &loc) const
Definition: map.cpp:66
bool is_enemy(int n) const
Definition: team.hpp:241
std::shared_ptr< move_result > move_result_ptr
Definition: game_info.hpp:94
map_location target
Definition: contexts.hpp:92
double vulnerability
The vulnerability is the power projection of enemy units onto the hex we're standing on...
Definition: contexts.hpp:129
std::vector< formula_input > formula_input_vector
The unit is poisoned - it loses health each turn.
Definition: unit.hpp:721
formula_input_vector inputs() const
Definition: callable.hpp:62
double resources_used
The sum of the values of units used in the attack.
Definition: contexts.hpp:113
double chance_to_kill
Estimated % chance to kill the unit.
Definition: contexts.hpp:102
-file sdl_utils.hpp
std::shared_ptr< attack_result > attack_result_ptr
Definition: game_info.hpp:91
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:72
double terrain_quality
The weighted average of the % chance to hit each attacking unit.
Definition: contexts.hpp:116
double target_value
The value of the unit being targeted.
Definition: contexts.hpp:96
const combatant & get_defender_combatant(const combatant *prev_def=nullptr)
Definition: attack.cpp:467
std::multimap< map_location, map_location > move_map
The standard way in which a map of possible moves is recorded.
Definition: game_info.hpp:39
A small explanation about what's going on here: Each action has access to two game_info objects First...
Definition: actions.cpp:58
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
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.hpp:382
double alternative_terrain_quality
The weighted average of the % defense of the best possible terrain that the attacking units could rea...
Definition: contexts.hpp:123
TYPE type
Definition: contexts.hpp:52
bool attack_close(const map_location &loc) const
Definition: attack.cpp:263
game_board * gameboard
Definition: resources.cpp:20
const battle_context_unit_stats & get_defender_stats() const
This method returns the statistics of the defender.
Definition: attack.hpp:202
bool plagues
Attack turns opponent into a zombie when fatal.
Definition: attack.hpp:57
Encapsulates the map of the game.
Definition: map.hpp:34
Computes the statistics of a battle between an attacker and a defender unit.
Definition: attack.hpp:170
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.
if(win_locale=="af") win_locale
static lg::log_domain log_ai("ai/attack")
wfl::variant get_value(const std::string &key) const override
Definition: attack.cpp:341
#define LOG_AI
Definition: attack.cpp:38
Encapsulates the map of the game.
Definition: location.hpp:40
bool uses_leader
Is true if this attack sequence makes use of the leader.
Definition: contexts.hpp:135
All combat-related info.
formula_callable_ptr fake_ptr()
Definition: callable.hpp:41
virtual const team & current_team() const =0
std::vector< std::pair< map_location, map_location > > movements
Definition: contexts.hpp:93
std::set< map_location > recent_attacks
hack.
Definition: game_info.hpp:117
void get_inputs(wfl::formula_input_vector &inputs) const override
Definition: attack.cpp:396
Default AI contexts.
double avg_losses
The value on average, of units lost in the combat.
Definition: contexts.hpp:99
size_t i
Definition: function.cpp:933
bool is_poisoned
True if the unit is poisoned at the beginning of the battle.
Definition: attack.hpp:52
std::shared_ptr< const formula_callable > const_formula_callable_ptr
boost::intrusive_ptr< unit > unit_ptr
Definition: ptr.hpp:29
Definition: contexts.hpp:42
virtual defensive_position const & best_defensive_position(const map_location &unit, const move_map &dstsrc, const move_map &srcdst, const move_map &enemy_dstsrc) const =0
static game_info & get_ai_info()
Gets global AI-game info.
Definition: manager.cpp:746
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:138
double average_hp(unsigned int healing=0) const
What's the average hp (weighted average of hp_dist).
const battle_context_unit_stats & get_attacker_stats() const
This method returns the statistics of the attacker.
Definition: attack.hpp:196
static void add_input(formula_input_vector &inputs, const std::string &key, FORMULA_ACCESS_TYPE access_type=FORMULA_READ_ONLY)
Definition: callable.hpp:135
unit_iterator find(size_t id)
Definition: map.cpp:311
bool valid() const
Definition: map.hpp:276
static map_location::DIRECTION n
std::string::const_iterator iterator
Definition: tokenizer.hpp:24
unit_map * units
Definition: resources.cpp:34
virtual double get_caution() const =0
double poisoned
Resulting chance we are poisoned.
double rating(double aggression, const readonly_context &ai_obj) const
Definition: attack.cpp:276
int gives_healing(const map_location &loc) const
Definition: map.cpp:68
wfl::variant execute_self(wfl::variant ctxt) override
Definition: attack.cpp:417