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  * Fighting.
18  */
19 
20 #include "actions/attack.hpp"
21 
22 #include "actions/advancement.hpp"
23 #include "actions/vision.hpp"
24 
26 #include "game_config.hpp"
27 #include "game_data.hpp"
28 #include "game_events/pump.hpp"
29 #include "gettext.hpp"
30 #include "log.hpp"
31 #include "map/map.hpp"
32 #include "mouse_handler_base.hpp"
33 #include "play_controller.hpp"
34 #include "preferences/game.hpp"
35 #include "random.hpp"
36 #include "replay.hpp"
37 #include "resources.hpp"
38 #include "statistics.hpp"
39 #include "synced_checkup.hpp"
40 #include "synced_user_choice.hpp"
41 #include "team.hpp"
42 #include "tod_manager.hpp"
43 #include "units/abilities.hpp"
45 #include "units/helper.hpp"
46 #include "units/map.hpp"
47 #include "units/udisplay.hpp"
48 #include "units/unit.hpp"
49 #include "whiteboard/manager.hpp"
50 #include "wml_exception.hpp"
51 
52 static lg::log_domain log_engine("engine");
53 #define DBG_NG LOG_STREAM(debug, log_engine)
54 #define LOG_NG LOG_STREAM(info, log_engine)
55 #define WRN_NG LOG_STREAM(err, log_engine)
56 #define ERR_NG LOG_STREAM(err, log_engine)
57 
58 static lg::log_domain log_config("config");
59 #define LOG_CF LOG_STREAM(info, log_config)
60 
61 // ==================================================================================
62 // BATTLE CONTEXT UNIT STATS
63 // ==================================================================================
64 
66  const map_location& u_loc,
67  int u_attack_num,
68  bool attacking,
69  const unit& opp,
70  const map_location& opp_loc,
71  const_attack_ptr opp_weapon,
72  const unit_map& units)
73  : weapon(nullptr)
74  , attack_num(u_attack_num)
75  , is_attacker(attacking)
76  , is_poisoned(u.get_state(unit::STATE_POISONED))
77  , is_slowed(u.get_state(unit::STATE_SLOWED))
78  , slows(false)
79  , drains(false)
80  , petrifies(false)
81  , plagues(false)
82  , poisons(false)
83  , backstab_pos(false)
84  , swarm(false)
85  , firststrike(false)
86  , disable(false)
87  , experience(u.experience())
88  , max_experience(u.max_experience())
89  , level(u.level())
90  , rounds(1)
91  , hp(0)
92  , max_hp(u.max_hitpoints())
93  , chance_to_hit(0)
94  , damage(0)
95  , slow_damage(0)
96  , drain_percent(0)
97  , drain_constant(0)
98  , num_blows(0)
99  , swarm_min(0)
100  , swarm_max(0)
101  , plague_type()
102 {
103  // Get the current state of the unit.
104  if(attack_num >= 0) {
105  weapon = u.attacks()[attack_num].shared_from_this();
106  }
107 
108  if(u.hitpoints() < 0) {
109  LOG_CF << "Unit with " << u.hitpoints() << " hitpoints found, set to 0 for damage calculations\n";
110  hp = 0;
111  } else if(u.hitpoints() > u.max_hitpoints()) {
112  // If a unit has more hp than its maximum, the engine will fail with an
113  // assertion failure due to accessing the prob_matrix out of bounds.
114  hp = u.max_hitpoints();
115  } else {
116  hp = u.hitpoints();
117  }
118 
119  // Exit if no weapon.
120  if(!weapon) {
121  return;
122  }
123 
124  // Get the weapon characteristics as appropriate.
125  weapon->set_specials_context(u_loc, opp_loc, attacking, opp_weapon);
126 
127  if(opp_weapon) {
128  opp_weapon->set_specials_context(opp_loc, u_loc, !attacking, weapon);
129  }
130 
131  slows = weapon->get_special_bool("slow");
132  drains = !opp.get_state("undrainable") && weapon->get_special_bool("drains");
133  petrifies = weapon->get_special_bool("petrifies");
134  poisons = !opp.get_state("unpoisonable") && weapon->get_special_bool("poison") && !opp.get_state(unit::STATE_POISONED);
135  backstab_pos = is_attacker && backstab_check(u_loc, opp_loc, units, resources::gameboard->teams());
136  rounds = weapon->get_specials("berserk").highest("value", 1).first;
137  firststrike = weapon->get_special_bool("firststrike");
138 
139  {
140  const int distance = distance_between(u_loc, opp_loc);
141  const bool out_of_range = distance > weapon->max_range() || distance < weapon->min_range();
142  disable = weapon->get_special_bool("disable") || out_of_range;
143  }
144 
145  // Handle plague.
146  unit_ability_list plague_specials = weapon->get_specials("plague");
147  plagues = !opp.get_state("unplagueable") && !plague_specials.empty() &&
148  opp.undead_variation() != "null" && !resources::gameboard->map().is_village(opp_loc);
149 
150  if(plagues) {
151  plague_type = (*plague_specials.front().first)["type"].str();
152 
153  if(plague_type.empty()) {
154  plague_type = u.type().base_id();
155  }
156  }
157 
158  // Compute chance to hit.
159  chance_to_hit = opp.defense_modifier(resources::gameboard->map().get_terrain(opp_loc)) + weapon->accuracy()
160  - (opp_weapon ? opp_weapon->parry() : 0);
161 
162  if(chance_to_hit > 100) {
163  chance_to_hit = 100;
164  }
165 
166  unit_ability_list cth_specials = weapon->get_specials("chance_to_hit");
167  unit_abilities::effect cth_effects(cth_specials, chance_to_hit, backstab_pos);
168  chance_to_hit = cth_effects.get_composite_value();
169 
170  if(opp.get_state("invulnerable")) {
171  chance_to_hit = 0;
172  }
173 
174  // Compute base damage done with the weapon.
175  int base_damage = weapon->modified_damage(backstab_pos);
176 
177  // Get the damage multiplier applied to the base damage of the weapon.
178  int damage_multiplier = 100;
179 
180  // Time of day bonus.
181  damage_multiplier += combat_modifier(
183 
184  // Leadership bonus.
185  int leader_bonus = under_leadership(units, u_loc).first;
186  if(leader_bonus != 0) {
187  damage_multiplier += leader_bonus;
188  }
189 
190  // Resistance modifier.
191  damage_multiplier *= opp.damage_from(*weapon, !attacking, opp_loc);
192 
193  // Compute both the normal and slowed damage.
194  damage = round_damage(base_damage, damage_multiplier, 10000);
195  slow_damage = round_damage(base_damage, damage_multiplier, 20000);
196 
197  if(is_slowed) {
199  }
200 
201  // Compute drain amounts only if draining is possible.
202  if(drains) {
203  unit_ability_list drain_specials = weapon->get_specials("drains");
204 
205  // Compute the drain percent (with 50% as the base for backward compatibility)
206  unit_abilities::effect drain_percent_effects(drain_specials, 50, backstab_pos);
207  drain_percent = drain_percent_effects.get_composite_value();
208  }
209 
210  // Add heal_on_hit (the drain constant)
211  unit_ability_list heal_on_hit_specials = weapon->get_specials("heal_on_hit");
212  unit_abilities::effect heal_on_hit_effects(heal_on_hit_specials, 0, backstab_pos);
213  drain_constant += heal_on_hit_effects.get_composite_value();
214 
216 
217  // Compute the number of blows and handle swarm.
218  weapon->modified_attacks(backstab_pos, swarm_min, swarm_max);
221 }
222 
224  const_attack_ptr att_weapon,
225  bool attacking,
226  const unit_type* opp_type,
227  const_attack_ptr opp_weapon,
228  unsigned int opp_terrain_defense,
229  int lawful_bonus)
230  : weapon(att_weapon)
231  , attack_num(-2) // This is and stays invalid. Always use weapon when using this constructor.
232  , is_attacker(attacking)
233  , is_poisoned(false)
234  , is_slowed(false)
235  , slows(false)
236  , drains(false)
237  , petrifies(false)
238  , plagues(false)
239  , poisons(false)
240  , backstab_pos(false)
241  , swarm(false)
242  , firststrike(false)
243  , disable(false)
244  , experience(0)
245  , max_experience(0)
246  , level(0)
247  , rounds(1)
248  , hp(0)
249  , max_hp(0)
250  , chance_to_hit(0)
251  , damage(0)
252  , slow_damage(0)
253  , drain_percent(0)
254  , drain_constant(0)
255  , num_blows(0)
256  , swarm_min(0)
257  , swarm_max(0)
258  , plague_type()
259 {
260  if(!u_type || !opp_type) {
261  return;
262  }
263 
264  // Get the current state of the unit.
265  if(u_type->hitpoints() < 0) {
266  hp = 0;
267  } else {
268  hp = u_type->hitpoints();
269  }
270 
271  max_experience = u_type->experience_needed();
272  level = (u_type->level());
273  max_hp = (u_type->hitpoints());
274 
275  // Exit if no weapon.
276  if(!weapon) {
277  return;
278  }
279 
280  // Get the weapon characteristics as appropriate.
281  weapon->set_specials_context(map_location::null_location(), attacking);
282 
283  if(opp_weapon) {
284  opp_weapon->set_specials_context(map_location::null_location(), !attacking);
285  }
286 
287  slows = weapon->get_special_bool("slow");
288  drains = !opp_type->musthave_status("undrainable") && weapon->get_special_bool("drains");
289  petrifies = weapon->get_special_bool("petrifies");
290  poisons = !opp_type->musthave_status("unpoisonable") && weapon->get_special_bool("poison");
291  rounds = weapon->get_specials("berserk").highest("value", 1).first;
292  firststrike = weapon->get_special_bool("firststrike");
293  disable = weapon->get_special_bool("disable");
294 
295  unit_ability_list plague_specials = weapon->get_specials("plague");
296  plagues = !opp_type->musthave_status("unplagueable") && !plague_specials.empty() &&
297  opp_type->undead_variation() != "null";
298 
299  if(plagues) {
300  plague_type = (*plague_specials.front().first)["type"].str();
301  if(plague_type.empty()) {
302  plague_type = u_type->base_id();
303  }
304  }
305 
306  signed int cth = 100 - opp_terrain_defense + weapon->accuracy() - (opp_weapon ? opp_weapon->parry() : 0);
307  cth = std::min(100, cth);
308  cth = std::max(0, cth);
309  chance_to_hit = cth;
310 
311  unit_ability_list cth_specials = weapon->get_specials("chance_to_hit");
312  unit_abilities::effect cth_effects(cth_specials, chance_to_hit, backstab_pos);
313  chance_to_hit = cth_effects.get_composite_value();
314 
315  int base_damage = weapon->modified_damage(backstab_pos);
316  int damage_multiplier = 100;
317  damage_multiplier
318  += generic_combat_modifier(lawful_bonus, u_type->alignment(), u_type->musthave_status("fearless"));
319  damage_multiplier *= opp_type->resistance_against(weapon->type(), !attacking);
320 
321  damage = round_damage(base_damage, damage_multiplier, 10000);
322  slow_damage = round_damage(base_damage, damage_multiplier, 20000);
323 
324  if(drains) {
325  unit_ability_list drain_specials = weapon->get_specials("drains");
326 
327  // Compute the drain percent (with 50% as the base for backward compatibility)
328  unit_abilities::effect drain_percent_effects(drain_specials, 50, backstab_pos);
329  drain_percent = drain_percent_effects.get_composite_value();
330  }
331 
332  // Add heal_on_hit (the drain constant)
333  unit_ability_list heal_on_hit_specials = weapon->get_specials("heal_on_hit");
334  unit_abilities::effect heal_on_hit_effects(heal_on_hit_specials, 0, backstab_pos);
335  drain_constant += heal_on_hit_effects.get_composite_value();
336 
338 
339  // Compute the number of blows and handle swarm.
340  weapon->modified_attacks(backstab_pos, swarm_min, swarm_max);
343 }
344 
345 
346 // ==================================================================================
347 // BATTLE CONTEXT
348 // ==================================================================================
349 
351  const map_location& attacker_loc,
352  const map_location& defender_loc,
353  int attacker_weapon,
354  int defender_weapon,
355  double aggression,
356  const combatant* prev_def,
357  const unit* attacker_ptr)
358  : attacker_stats_(nullptr)
359  , defender_stats_(nullptr)
360  , attacker_combatant_(nullptr)
361  , defender_combatant_(nullptr)
362 {
363  const unit& attacker = attacker_ptr ? *attacker_ptr : *units.find(attacker_loc);
364  const unit& defender = *units.find(defender_loc);
365  const double harm_weight = 1.0 - aggression;
366 
367  if(attacker_weapon == -1 &&
368  attacker.attacks().size() == 1 &&
369  attacker.attacks()[0].attack_weight() > 0 &&
370  !attacker.attacks()[0].get_special_bool("disable", true)
371  ) {
372  attacker_weapon = 0;
373  }
374 
375  if(attacker_weapon == -1) {
376  attacker_weapon = choose_attacker_weapon(
377  attacker, defender, units, attacker_loc, defender_loc, harm_weight, &defender_weapon, prev_def
378  );
379  } else if(defender_weapon == -1) {
380  defender_weapon = choose_defender_weapon(
381  attacker, defender, attacker_weapon, units, attacker_loc, defender_loc, prev_def
382  );
383  }
384 
385  // If those didn't have to generate statistics, do so now.
386  if(!attacker_stats_) {
387  const_attack_ptr adef = nullptr;
388  const_attack_ptr ddef = nullptr;
389 
390  if(attacker_weapon >= 0) {
391  VALIDATE(attacker_weapon < static_cast<int>(attacker.attacks().size()),
392  _("An invalid attacker weapon got selected."));
393 
394  adef = attacker.attacks()[attacker_weapon].shared_from_this();
395  }
396 
397  if(defender_weapon >= 0) {
398  VALIDATE(defender_weapon < static_cast<int>(defender.attacks().size()),
399  _("An invalid defender weapon got selected."));
400 
401  ddef = defender.attacks()[defender_weapon].shared_from_this();
402  }
403 
405 
407  attacker, attacker_loc, attacker_weapon, true, defender, defender_loc, ddef, units));
409  defender, defender_loc, defender_weapon, false, attacker, attacker_loc, adef, units));
410  }
411 
412  // There have been various bugs where only one of these was set
413  assert(attacker_stats_);
414  assert(defender_stats_);
415 }
416 
418  : attacker_stats_(new battle_context_unit_stats(att))
419  , defender_stats_(new battle_context_unit_stats(def))
420  , attacker_combatant_(nullptr)
421  , defender_combatant_(nullptr)
422 {
423 }
424 
426  : attacker_stats_(nullptr)
427  , defender_stats_(nullptr)
428  , attacker_combatant_(nullptr)
429  , defender_combatant_(nullptr)
430 {
431  *this = other;
432 }
433 
435 {
436  if(&other != this) {
439 
441  ? new combatant(*other.attacker_combatant_, *attacker_stats_) : nullptr);
442 
444  ? new combatant(*other.defender_combatant_, *defender_stats_) : nullptr);
445  }
446 
447  return *this;
448 }
449 
450 /** @todo FIXME: better to initialize combatant initially (move into
451  battle_context_unit_stats?), just do fight() when required. */
453 {
454  // We calculate this lazily, since AI doesn't always need it.
455  if(!attacker_combatant_) {
456  assert(!defender_combatant_);
457 
459  defender_combatant_.reset(new combatant(*defender_stats_, prev_def));
460 
462  }
463 
464  return *attacker_combatant_;
465 }
466 
468 {
469  // We calculate this lazily, since AI doesn't always need it.
470  if(!defender_combatant_) {
471  assert(!attacker_combatant_);
472 
474  defender_combatant_.reset(new combatant(*defender_stats_, prev_def));
475 
477  }
478 
479  return *defender_combatant_;
480 }
481 
482 // Given this harm_weight, are we better than this other context?
483 bool battle_context::better_attack(class battle_context& that, double harm_weight)
484 {
485  return better_combat(
488  that.get_attacker_combatant(),
489  that.get_defender_combatant(),
490  harm_weight
491  );
492 }
493 
494 // Does combat A give us a better result than combat B?
496  const combatant& them_a,
497  const combatant& us_b,
498  const combatant& them_b,
499  double harm_weight)
500 {
501  double a, b;
502 
503  // Compare: P(we kill them) - P(they kill us).
504  a = them_a.hp_dist[0] - us_a.hp_dist[0] * harm_weight;
505  b = them_b.hp_dist[0] - us_b.hp_dist[0] * harm_weight;
506 
507  if(a - b < -0.01) {
508  return false;
509  }
510 
511  if(a - b > 0.01) {
512  return true;
513  }
514 
515  // Add poison to calculations
516  double poison_a_us = (us_a.poisoned) * game_config::poison_amount;
517  double poison_a_them = (them_a.poisoned) * game_config::poison_amount;
518  double poison_b_us = (us_b.poisoned) * game_config::poison_amount;
519  double poison_b_them = (them_b.poisoned) * game_config::poison_amount;
520 
521  // Compare: damage to them - damage to us (average_hp replaces -damage)
522  a = (us_a.average_hp() - poison_a_us) * harm_weight - (them_a.average_hp() - poison_a_them);
523  b = (us_b.average_hp() - poison_b_us) * harm_weight - (them_b.average_hp() - poison_b_them);
524 
525  if(a - b < -0.01) {
526  return false;
527  }
528 
529  if(a - b > 0.01) {
530  return true;
531  }
532 
533  // All else equal: go for most damage.
534  return them_a.average_hp() < them_b.average_hp();
535 }
536 
538  const unit& defender,
539  const unit_map& units,
540  const map_location& attacker_loc,
541  const map_location& defender_loc,
542  double harm_weight,
543  int* defender_weapon,
544  const combatant* prev_def)
545 {
546  std::vector<unsigned int> choices;
547 
548  // What options does attacker have?
549  unsigned int i;
550 
551  for(i = 0; i < attacker.attacks().size(); ++i) {
552  const attack_type& att = attacker.attacks()[i];
553 
554  if(att.attack_weight() > 0) {
555  choices.push_back(i);
556  }
557  }
558 
559  if(choices.empty()) {
560  return -1;
561  }
562 
563  if(choices.size() == 1) {
564  *defender_weapon
565  = choose_defender_weapon(attacker, defender, choices[0], units, attacker_loc, defender_loc, prev_def);
566  const_attack_ptr def_weapon
567  = *defender_weapon >= 0 ? defender.attacks()[*defender_weapon].shared_from_this() : nullptr;
569  attacker, attacker_loc, choices[0], true, defender, defender_loc, def_weapon, units));
570 
571  if(attacker_stats_->disable) {
572  return -1;
573  }
574 
575  const attack_type& att = attacker.attacks()[choices[0]];
577  defender, defender_loc, *defender_weapon, false, attacker, attacker_loc, att.shared_from_this(), units));
578 
579  return choices[0];
580  }
581 
582  // Multiple options: simulate them, save best.
583  std::unique_ptr<battle_context_unit_stats> best_att_stats(nullptr);
584  std::unique_ptr<battle_context_unit_stats> best_def_stats(nullptr);
585 
586  std::unique_ptr<combatant> best_att_comb(nullptr);
587  std::unique_ptr<combatant> best_def_comb(nullptr);
588 
589  for(i = 0; i < choices.size(); ++i) {
590  const attack_type& att = attacker.attacks()[choices[i]];
591 
592  int def_weapon =
593  choose_defender_weapon(attacker, defender, choices[i], units, attacker_loc, defender_loc, prev_def);
594 
595  // If that didn't simulate, do so now.
596  if(!attacker_combatant_) {
597  const_attack_ptr def = nullptr;
598 
599  if(def_weapon >= 0) {
600  def = defender.attacks()[def_weapon].shared_from_this();
601  }
602 
604  attacker, attacker_loc, choices[i], true, defender, defender_loc, def, units));
605 
606  if(attacker_stats_->disable) {
607  continue;
608  }
609 
611  defender, defender_loc, def_weapon, false, attacker, attacker_loc, att.shared_from_this(), units));
612 
614  defender_combatant_.reset(new combatant(*defender_stats_, prev_def));
615 
617  } else {
618  if(attacker_stats_ != nullptr && attacker_stats_->disable) {
619  continue;
620  }
621  }
622 
623  if(!best_att_comb ||
624  better_combat(*attacker_combatant_, *defender_combatant_, *best_att_comb, *best_def_comb, harm_weight)
625  ) {
626  best_att_comb = std::move(attacker_combatant_);
627  best_def_comb = std::move(defender_combatant_);
628  best_att_stats = std::move(attacker_stats_);
629  best_def_stats = std::move(defender_stats_);
630  }
631 
632  attacker_combatant_.reset();
633  defender_combatant_.reset();
634  attacker_stats_.reset();
635  defender_stats_.reset();
636  }
637 
638  attacker_combatant_ = std::move(best_att_comb);
639  defender_combatant_ = std::move(best_def_comb);
640  attacker_stats_ = std::move(best_att_stats);
641  defender_stats_ = std::move(best_def_stats);
642 
643  // These currently mean the same thing, but assumptions like that have been broken before
645  return -1;
646  }
647 
648  *defender_weapon = defender_stats_->attack_num;
649  return attacker_stats_->attack_num;
650 }
651 
652 /** @todo FIXME: Hand previous defender unit in here. */
654  const unit& defender,
655  unsigned attacker_weapon,
656  const unit_map& units,
657  const map_location& attacker_loc,
658  const map_location& defender_loc,
659  const combatant* prev_def)
660 {
661  VALIDATE(attacker_weapon < attacker.attacks().size(), _("An invalid attacker weapon got selected."));
662 
663  const attack_type& att = attacker.attacks()[attacker_weapon];
664  std::vector<unsigned int> choices;
665 
666  // What options does defender have?
667  unsigned int i;
668 
669  for(i = 0; i < defender.attacks().size(); ++i) {
670  const attack_type& def = defender.attacks()[i];
671 
672  if(def.range() == att.range() && def.defense_weight() > 0) {
673  choices.push_back(i);
674  }
675  }
676 
677  if(choices.empty()) {
678  return -1;
679  }
680 
681  if(choices.size() == 1) {
682  const battle_context_unit_stats def_stats(
683  defender, defender_loc, choices[0], false, attacker, attacker_loc, att.shared_from_this(), units);
684 
685  return (def_stats.disable) ? -1 : choices[0];
686  }
687 
688  // Multiple options:
689  // First pass : get the best weight and the minimum simple rating for this weight.
690  // simple rating = number of blows * damage per blows (resistance taken in account) * cth * weight
691  // Eligible attacks for defense should have a simple rating greater or equal to this weight.
692 
693  int min_rating = 0;
694  {
695  double max_weight = 0.0;
696 
697  for(i = 0; i < choices.size(); ++i) {
698  const attack_type& def = defender.attacks()[choices[i]];
699 
700  if(def.defense_weight() >= max_weight) {
701  const battle_context_unit_stats def_stats(defender, defender_loc, choices[i], false, attacker,
702  attacker_loc, att.shared_from_this(), units);
703 
704  if(def_stats.disable) {
705  continue;
706  }
707 
708  max_weight = def.defense_weight();
709  int rating = static_cast<int>(
710  def_stats.num_blows * def_stats.damage * def_stats.chance_to_hit * def.defense_weight());
711 
712  if(def.defense_weight() > max_weight || rating < min_rating) {
713  min_rating = rating;
714  }
715  }
716  }
717  }
718 
719  // Multiple options: simulate them, save best.
720  for(i = 0; i < choices.size(); ++i) {
721  const attack_type& def = defender.attacks()[choices[i]];
722 
723  std::unique_ptr<battle_context_unit_stats> att_stats(new battle_context_unit_stats(
724  attacker, attacker_loc, attacker_weapon, true, defender, defender_loc, def.shared_from_this(), units));
725 
726  std::unique_ptr<battle_context_unit_stats> def_stats(new battle_context_unit_stats(
727  defender, defender_loc, choices[i], false, attacker, attacker_loc, att.shared_from_this(), units));
728 
729  if(def_stats->disable) {
730  continue;
731  }
732 
733  std::unique_ptr<combatant> att_comb(new combatant(*att_stats));
734  std::unique_ptr<combatant> def_comb(new combatant(*def_stats, prev_def));
735 
736  att_comb->fight(*def_comb);
737 
738  int simple_rating = static_cast<int>(
739  def_stats->num_blows * def_stats->damage * def_stats->chance_to_hit * def.defense_weight());
740 
741  if(simple_rating >= min_rating &&
742  (!attacker_combatant_ || better_combat(*def_comb, *att_comb, *defender_combatant_, *attacker_combatant_, 1.0))
743  ) {
744  attacker_combatant_ = std::move(att_comb);
745  defender_combatant_ = std::move(def_comb);
746  attacker_stats_ = std::move(att_stats);
747  defender_stats_ = std::move(def_stats);
748  }
749  }
750 
751  return defender_stats_ ? defender_stats_->attack_num : -1;
752 }
753 
754 
755 // ==================================================================================
756 // HELPERS
757 // ==================================================================================
758 
759 namespace
760 {
761 void refresh_weapon_index(int& weap_index, const std::string& weap_id, attack_itors attacks)
762 {
763  // No attacks to choose from.
764  if(attacks.empty()) {
765  weap_index = -1;
766  return;
767  }
768 
769  // The currently selected attack fits.
770  if(weap_index >= 0 && weap_index < static_cast<int>(attacks.size()) && attacks[weap_index].id() == weap_id) {
771  return;
772  }
773 
774  // Look up the weapon by id.
775  if(!weap_id.empty()) {
776  for(int i = 0; i < static_cast<int>(attacks.size()); ++i) {
777  if(attacks[i].id() == weap_id) {
778  weap_index = i;
779  return;
780  }
781  }
782  }
783 
784  // Lookup has failed.
785  weap_index = -1;
786  return;
787 }
788 
789 /** Helper class for performing an attack. */
790 class attack
791 {
792 public:
793  attack(const map_location& attacker,
794  const map_location& defender,
795  int attack_with,
796  int defend_with,
797  bool update_display = true);
798 
799  void perform();
800 
801 private:
802  class attack_end_exception
803  {
804  };
805 
806  bool perform_hit(bool, statistics::attack_context&);
807  void fire_event(const std::string& n);
808  void refresh_bc();
809 
810  /** Structure holding unit info used in the attack action. */
811  struct unit_info
812  {
813  const map_location loc_;
814  int weapon_;
815  unit_map& units_;
816  size_t id_; /**< unit.underlying_id() */
817  std::string weap_id_;
818  int orig_attacks_;
819  int n_attacks_; /**< Number of attacks left. */
820  int cth_;
821  int damage_;
822  int xp_;
823 
824  unit_info(const map_location& loc, int weapon, unit_map& units);
825  unit& get_unit();
826  bool valid();
827 
828  std::string dump();
829  };
830 
831  /**
832  * Used in perform_hit to confirm a replay is in sync.
833  * Check OOS_error_ after this method, true if error detected.
834  */
835  void check_replay_attack_result(bool&, int, int&, config, unit_info&);
836 
837  void unit_killed(
838  unit_info&, unit_info&, const battle_context_unit_stats*&, const battle_context_unit_stats*&, bool);
839 
840  std::unique_ptr<battle_context> bc_;
841 
842  const battle_context_unit_stats* a_stats_;
843  const battle_context_unit_stats* d_stats_;
844 
845  int abs_n_attack_, abs_n_defend_;
846  // update_att_fog_ is not used, other than making some code simpler.
847  bool update_att_fog_, update_def_fog_, update_minimap_;
848 
849  unit_info a_, d_;
850  unit_map& units_;
851  std::ostringstream errbuf_;
852 
853  bool update_display_;
854  bool OOS_error_;
855 };
856 
857 attack::unit_info::unit_info(const map_location& loc, int weapon, unit_map& units)
858  : loc_(loc)
859  , weapon_(weapon)
860  , units_(units)
861  , id_()
862  , weap_id_()
863  , orig_attacks_(0)
864  , n_attacks_(0)
865  , cth_(0)
866  , damage_(0)
867  , xp_(0)
868 {
869  unit_map::iterator i = units_.find(loc_);
870  if(!i.valid()) {
871  return;
872  }
873 
874  id_ = i->underlying_id();
875 }
876 
877 unit& attack::unit_info::get_unit()
878 {
879  unit_map::iterator i = units_.find(loc_);
880  assert(i.valid() && i->underlying_id() == id_);
881  return *i;
882 }
883 
884 bool attack::unit_info::valid()
885 {
886  unit_map::iterator i = units_.find(loc_);
887  return i.valid() && i->underlying_id() == id_;
888 }
889 
890 std::string attack::unit_info::dump()
891 {
892  std::stringstream s;
893  s << get_unit().type_id() << " (" << loc_.wml_x() << ',' << loc_.wml_y() << ')';
894  return s.str();
895 }
896 
897 attack::attack(const map_location& attacker,
898  const map_location& defender,
899  int attack_with,
900  int defend_with,
901  bool update_display)
902  : bc_(nullptr)
903  , a_stats_(nullptr)
904  , d_stats_(nullptr)
905  , abs_n_attack_(0)
906  , abs_n_defend_(0)
907  , update_att_fog_(false)
908  , update_def_fog_(false)
909  , update_minimap_(false)
910  , a_(attacker, attack_with, resources::gameboard->units())
911  , d_(defender, defend_with, resources::gameboard->units())
912  , units_(resources::gameboard->units())
913  , errbuf_()
914  , update_display_(update_display)
915  , OOS_error_(false)
916 {
917 }
918 
919 void attack::fire_event(const std::string& n)
920 {
921  LOG_NG << "firing " << n << " event\n";
922 
923  // prepare the event data for weapon filtering
924  config ev_data;
925  config& a_weapon_cfg = ev_data.add_child("first");
926  config& d_weapon_cfg = ev_data.add_child("second");
927 
928  if(a_stats_->weapon != nullptr && a_.valid()) {
929  a_stats_->weapon->write(a_weapon_cfg);
930  }
931 
932  if(d_stats_->weapon != nullptr && d_.valid()) {
933  d_stats_->weapon->write(d_weapon_cfg);
934  }
935 
936  if(a_weapon_cfg["name"].empty()) {
937  a_weapon_cfg["name"] = "none";
938  }
939 
940  if(d_weapon_cfg["name"].empty()) {
941  d_weapon_cfg["name"] = "none";
942  }
943 
944  if(n == "attack_end") {
945  // We want to fire attack_end event in any case! Even if one of units was removed by WML.
946  resources::game_events->pump().fire(n, a_.loc_, d_.loc_, ev_data);
947  return;
948  }
949 
950  const int defender_side = d_.get_unit().side();
951 
954  game_events::entity_location(d_.loc_, d_.id_), ev_data);
955 
956  // The event could have killed either the attacker or
957  // defender, so we have to make sure they still exist.
958  refresh_bc();
959 
960  if(!a_.valid() || !d_.valid()
961  || !resources::gameboard->get_team(a_.get_unit().side()).is_enemy(d_.get_unit().side())
962  ) {
963  actions::recalculate_fog(defender_side);
964 
965  if(update_display_) {
967  resources::screen->draw(true, true);
968  }
969 
970  fire_event("attack_end");
971  throw attack_end_exception();
972  }
973 }
974 
975 void attack::refresh_bc()
976 {
977  // Fix index of weapons.
978  if(a_.valid()) {
979  refresh_weapon_index(a_.weapon_, a_.weap_id_, a_.get_unit().attacks());
980  }
981 
982  if(d_.valid()) {
983  refresh_weapon_index(d_.weapon_, d_.weap_id_, d_.get_unit().attacks());
984  }
985 
986  if(!a_.valid() || !d_.valid()) {
987  // Fix pointer to weapons.
988  const_cast<battle_context_unit_stats*>(a_stats_)->weapon
989  = a_.valid() && a_.weapon_ >= 0 ? a_.get_unit().attacks()[a_.weapon_].shared_from_this() : nullptr;
990 
991  const_cast<battle_context_unit_stats*>(d_stats_)->weapon
992  = d_.valid() && d_.weapon_ >= 0 ? d_.get_unit().attacks()[d_.weapon_].shared_from_this() : nullptr;
993 
994  return;
995  }
996 
997  bc_.reset(new battle_context(units_, a_.loc_, d_.loc_, a_.weapon_, d_.weapon_));
998 
999  a_stats_ = &bc_->get_attacker_stats();
1000  d_stats_ = &bc_->get_defender_stats();
1001 
1002  a_.cth_ = a_stats_->chance_to_hit;
1003  d_.cth_ = d_stats_->chance_to_hit;
1004  a_.damage_ = a_stats_->damage;
1005  d_.damage_ = d_stats_->damage;
1006 }
1007 
1008 bool attack::perform_hit(bool attacker_turn, statistics::attack_context& stats)
1009 {
1010  unit_info& attacker = attacker_turn ? a_ : d_;
1011  unit_info& defender = attacker_turn ? d_ : a_;
1012 
1013  // NOTE: we need to use a reference-to-pointer here so a_stats_ and d_stats_ can be
1014  // modified without. Using a pointer directly would render them invalid when that happened.
1015  const battle_context_unit_stats*& attacker_stats = attacker_turn ? a_stats_ : d_stats_;
1016  const battle_context_unit_stats*& defender_stats = attacker_turn ? d_stats_ : a_stats_;
1017 
1018  int& abs_n = attacker_turn ? abs_n_attack_ : abs_n_defend_;
1019  bool& update_fog = attacker_turn ? update_def_fog_ : update_att_fog_;
1020 
1021  int ran_num = randomness::generator->get_random_int(0, 99);
1022  bool hits = (ran_num < attacker.cth_);
1023 
1024  int damage = 0;
1025  if(hits) {
1026  damage = attacker.damage_;
1027  resources::gamedata->get_variable("damage_inflicted") = damage;
1028  }
1029 
1030  // Make sure that if we're serializing a game here,
1031  // we got the same results as the game did originally.
1032  const config local_results {"chance", attacker.cth_, "hits", hits, "damage", damage};
1033 
1034  config replay_results;
1035  bool equals_replay = checkup_instance->local_checkup(local_results, replay_results);
1036 
1037  if(!equals_replay) {
1038  check_replay_attack_result(hits, ran_num, damage, replay_results, attacker);
1039  }
1040 
1041  // can do no more damage than the defender has hitpoints
1042  int damage_done = std::min<int>(defender.get_unit().hitpoints(), attacker.damage_);
1043 
1044  // expected damage = damage potential * chance to hit (as a percentage)
1045  double expected_damage = damage_done * attacker.cth_ * 0.01;
1046 
1047  if(attacker_turn) {
1048  stats.attack_expected_damage(expected_damage, 0);
1049  } else {
1050  stats.attack_expected_damage(0, expected_damage);
1051  }
1052 
1053  int drains_damage = 0;
1054  if(hits && attacker_stats->drains) {
1055  drains_damage = damage_done * attacker_stats->drain_percent / 100 + attacker_stats->drain_constant;
1056 
1057  // don't drain so much that the attacker gets more than his maximum hitpoints
1058  drains_damage =
1059  std::min<int>(drains_damage, attacker.get_unit().max_hitpoints() - attacker.get_unit().hitpoints());
1060 
1061  // if drain is negative, don't allow drain to kill the attacker
1062  drains_damage = std::max<int>(drains_damage, 1 - attacker.get_unit().hitpoints());
1063  }
1064 
1065  if(update_display_) {
1066  std::ostringstream float_text;
1067  std::vector<std::string> extra_hit_sounds;
1068 
1069  if(hits) {
1070  const unit& defender_unit = defender.get_unit();
1071  if(attacker_stats->poisons && !defender_unit.get_state(unit::STATE_POISONED)) {
1072  float_text << (defender_unit.gender() == unit_race::FEMALE ? _("female^poisoned") : _("poisoned"))
1073  << '\n';
1074 
1075  extra_hit_sounds.push_back(game_config::sounds::status::poisoned);
1076  }
1077 
1078  if(attacker_stats->slows && !defender_unit.get_state(unit::STATE_SLOWED)) {
1079  float_text << (defender_unit.gender() == unit_race::FEMALE ? _("female^slowed") : _("slowed")) << '\n';
1080 
1081  extra_hit_sounds.push_back(game_config::sounds::status::slowed);
1082  }
1083 
1084  if(attacker_stats->petrifies) {
1085  float_text << (defender_unit.gender() == unit_race::FEMALE ? _("female^petrified") : _("petrified"))
1086  << '\n';
1087 
1088  extra_hit_sounds.push_back(game_config::sounds::status::petrified);
1089  }
1090  }
1091 
1095  attacker.loc_, defender.loc_,
1096  damage,
1097  *attacker_stats->weapon, defender_stats->weapon,
1098  abs_n, float_text.str(), drains_damage, "",
1099  &extra_hit_sounds
1100  );
1101  }
1102 
1103  bool dies = defender.get_unit().take_hit(damage);
1104  LOG_NG << "defender took " << damage << (dies ? " and died\n" : "\n");
1105 
1106  if(attacker_turn) {
1107  stats.attack_result(hits
1108  ? (dies
1111  : statistics::attack_context::MISSES, damage_done, drains_damage
1112  );
1113  } else {
1114  stats.defend_result(hits
1115  ? (dies
1118  : statistics::attack_context::MISSES, damage_done, drains_damage
1119  );
1120  }
1121 
1122  replay_results.clear();
1123 
1124  // There was also a attribute cfg["unit_hit"] which was never used so i deleted.
1125  equals_replay = checkup_instance->local_checkup(config{"dies", dies}, replay_results);
1126 
1127  if(!equals_replay) {
1128  bool results_dies = replay_results["dies"].to_bool();
1129 
1130  errbuf_ << "SYNC: In attack " << a_.dump() << " vs " << d_.dump() << ": the data source says the "
1131  << (attacker_turn ? "defender" : "attacker") << ' ' << (results_dies ? "perished" : "survived")
1132  << " while in-game calculations show it " << (dies ? "perished" : "survived")
1133  << " (over-riding game calculations with data source results)\n";
1134 
1135  dies = results_dies;
1136 
1137  // Set hitpoints to 0 so later checks don't invalidate the death.
1138  if(results_dies) {
1139  defender.get_unit().set_hitpoints(0);
1140  }
1141 
1142  OOS_error_ = true;
1143  }
1144 
1145  if(hits) {
1146  try {
1147  fire_event(attacker_turn ? "attacker_hits" : "defender_hits");
1148  } catch(attack_end_exception) {
1149  refresh_bc();
1150  return false;
1151  }
1152  } else {
1153  try {
1154  fire_event(attacker_turn ? "attacker_misses" : "defender_misses");
1155  } catch(attack_end_exception) {
1156  refresh_bc();
1157  return false;
1158  }
1159  }
1160 
1161  refresh_bc();
1162 
1163  bool attacker_dies = false;
1164 
1165  if(drains_damage > 0) {
1166  attacker.get_unit().heal(drains_damage);
1167  } else if(drains_damage < 0) {
1168  attacker_dies = attacker.get_unit().take_hit(-drains_damage);
1169  }
1170 
1171  if(dies) {
1172  unit_killed(attacker, defender, attacker_stats, defender_stats, false);
1173  update_fog = true;
1174  }
1175 
1176  if(attacker_dies) {
1177  unit_killed(defender, attacker, defender_stats, attacker_stats, true);
1178  (attacker_turn ? update_att_fog_ : update_def_fog_) = true;
1179  }
1180 
1181  if(dies) {
1182  update_minimap_ = true;
1183  return false;
1184  }
1185 
1186  if(hits) {
1187  unit& defender_unit = defender.get_unit();
1188 
1189  if(attacker_stats->poisons && !defender_unit.get_state(unit::STATE_POISONED)) {
1190  defender_unit.set_state(unit::STATE_POISONED, true);
1191  LOG_NG << "defender poisoned\n";
1192  }
1193 
1194  if(attacker_stats->slows && !defender_unit.get_state(unit::STATE_SLOWED)) {
1195  defender_unit.set_state(unit::STATE_SLOWED, true);
1196  update_fog = true;
1197  defender.damage_ = defender_stats->slow_damage;
1198  LOG_NG << "defender slowed\n";
1199  }
1200 
1201  // If the defender is petrified, the fight stops immediately
1202  if(attacker_stats->petrifies) {
1203  defender_unit.set_state(unit::STATE_PETRIFIED, true);
1204  update_fog = true;
1205  attacker.n_attacks_ = 0;
1206  defender.n_attacks_ = -1; // Petrified.
1207  resources::game_events->pump().fire("petrified", defender.loc_, attacker.loc_);
1208  refresh_bc();
1209  }
1210  }
1211 
1212  // Delay until here so that poison and slow go through
1213  if(attacker_dies) {
1214  update_minimap_ = true;
1215  return false;
1216  }
1217 
1218  --attacker.n_attacks_;
1219  return true;
1220 }
1221 
1222 void attack::unit_killed(unit_info& attacker,
1223  unit_info& defender,
1224  const battle_context_unit_stats*& attacker_stats,
1225  const battle_context_unit_stats*& defender_stats,
1226  bool drain_killed)
1227 {
1228  attacker.xp_ = game_config::kill_xp(defender.get_unit().level());
1229  defender.xp_ = 0;
1230 
1231  resources::screen->invalidate(attacker.loc_);
1232 
1233  game_events::entity_location death_loc(defender.loc_, defender.id_);
1234  game_events::entity_location attacker_loc(attacker.loc_, attacker.id_);
1235 
1236  std::string undead_variation = defender.get_unit().undead_variation();
1237 
1238  fire_event("attack_end");
1239  refresh_bc();
1240 
1241  // Get weapon info for last_breath and die events.
1242  config dat;
1243  config a_weapon_cfg = attacker_stats->weapon && attacker.valid() ? attacker_stats->weapon->to_config() : config();
1244  config d_weapon_cfg = defender_stats->weapon && defender.valid() ? defender_stats->weapon->to_config() : config();
1245 
1246  if(a_weapon_cfg["name"].empty()) {
1247  a_weapon_cfg["name"] = "none";
1248  }
1249 
1250  if(d_weapon_cfg["name"].empty()) {
1251  d_weapon_cfg["name"] = "none";
1252  }
1253 
1254  dat.add_child("first", d_weapon_cfg);
1255  dat.add_child("second", a_weapon_cfg);
1256 
1257  resources::game_events->pump().fire("last_breath", death_loc, attacker_loc, dat);
1258  refresh_bc();
1259 
1260  // WML has invalidated the dying unit, abort.
1261  if(!defender.valid() || defender.get_unit().hitpoints() > 0) {
1262  return;
1263  }
1264 
1265  if(!attacker.valid()) {
1267  defender.loc_,
1268  defender.get_unit(),
1269  nullptr,
1270  defender_stats->weapon
1271  );
1272  } else {
1274  defender.loc_,
1275  defender.get_unit(),
1276  attacker_stats->weapon,
1277  defender_stats->weapon,
1278  attacker.loc_,
1279  &attacker.get_unit()
1280  );
1281  }
1282 
1283  resources::game_events->pump().fire("die", death_loc, attacker_loc, dat);
1284  refresh_bc();
1285 
1286  if(!defender.valid() || defender.get_unit().hitpoints() > 0) {
1287  // WML has invalidated the dying unit, abort
1288  return;
1289  }
1290 
1291  units_.erase(defender.loc_);
1292  resources::whiteboard->on_kill_unit();
1293 
1294  // Plague units make new units on the target hex.
1295  if(attacker.valid() && attacker_stats->plagues && !drain_killed) {
1296  LOG_NG << "trying to reanimate " << attacker_stats->plague_type << '\n';
1297 
1298  if(const unit_type* reanimator = unit_types.find(attacker_stats->plague_type)) {
1299  LOG_NG << "found unit type:" << reanimator->id() << '\n';
1300 
1301  unit_ptr newunit(new unit(*reanimator, attacker.get_unit().side(), true, unit_race::MALE));
1302  newunit->set_attacks(0);
1303  newunit->set_movement(0, true);
1304  newunit->set_facing(map_location::get_opposite_dir(attacker.get_unit().facing()));
1305 
1306  // Apply variation
1307  if(undead_variation != "null") {
1308  config mod;
1309  config& variation = mod.add_child("effect");
1310  variation["apply_to"] = "variation";
1311  variation["name"] = undead_variation;
1312  newunit->add_modification("variation", mod);
1313  newunit->heal_fully();
1314  }
1315 
1316  newunit->set_location(death_loc);
1317  units_.insert(newunit);
1318 
1319  game_events::entity_location reanim_loc(defender.loc_, newunit->underlying_id());
1320  resources::game_events->pump().fire("unit_placed", reanim_loc);
1321 
1322  preferences::encountered_units().insert(newunit->type_id());
1323 
1324  if(update_display_) {
1325  resources::screen->invalidate(death_loc);
1326  }
1327  }
1328  } else {
1329  LOG_NG << "unit not reanimated\n";
1330  }
1331 }
1332 
1333 void attack::perform()
1334 {
1335  // Stop the user from issuing any commands while the units are fighting.
1336  const events::command_disabler disable_commands;
1337 
1338  if(!a_.valid() || !d_.valid()) {
1339  return;
1340  }
1341 
1342  // no attack weapon => stop here and don't attack
1343  if(a_.weapon_ < 0) {
1344  a_.get_unit().set_attacks(a_.get_unit().attacks_left() - 1);
1345  a_.get_unit().set_movement(-1, true);
1346  return;
1347  }
1348 
1349  a_.get_unit().set_facing(a_.loc_.get_relative_dir(d_.loc_));
1350  d_.get_unit().set_facing(d_.loc_.get_relative_dir(a_.loc_));
1351 
1352  a_.get_unit().set_attacks(a_.get_unit().attacks_left() - 1);
1353 
1354  VALIDATE(a_.weapon_ < static_cast<int>(a_.get_unit().attacks().size()),
1355  _("An invalid attacker weapon got selected."));
1356 
1357  a_.get_unit().set_movement(a_.get_unit().movement_left() - a_.get_unit().attacks()[a_.weapon_].movement_used(), true);
1358  a_.get_unit().set_state(unit::STATE_NOT_MOVED, false);
1359  a_.get_unit().set_resting(false);
1360  d_.get_unit().set_resting(false);
1361 
1362  // If the attacker was invisible, she isn't anymore!
1363  a_.get_unit().set_state(unit::STATE_UNCOVERED, true);
1364 
1365  bc_.reset(new battle_context(units_, a_.loc_, d_.loc_, a_.weapon_, d_.weapon_));
1366 
1367  a_stats_ = &bc_->get_attacker_stats();
1368  d_stats_ = &bc_->get_defender_stats();
1369 
1370  if(a_stats_->weapon) {
1371  a_.weap_id_ = a_stats_->weapon->id();
1372  }
1373 
1374  if(d_stats_->weapon) {
1375  d_.weap_id_ = d_stats_->weapon->id();
1376  }
1377 
1378  try {
1379  fire_event("attack");
1380  } catch(attack_end_exception) {
1381  return;
1382  }
1383 
1384  refresh_bc();
1385 
1386  DBG_NG << "getting attack statistics\n";
1387  statistics::attack_context attack_stats(
1388  a_.get_unit(), d_.get_unit(), a_stats_->chance_to_hit, d_stats_->chance_to_hit);
1389 
1390  a_.orig_attacks_ = a_stats_->num_blows;
1391  d_.orig_attacks_ = d_stats_->num_blows;
1392  a_.n_attacks_ = a_.orig_attacks_;
1393  d_.n_attacks_ = d_.orig_attacks_;
1394  a_.xp_ = d_.get_unit().level();
1395  d_.xp_ = a_.get_unit().level();
1396 
1397  bool defender_strikes_first = (d_stats_->firststrike && !a_stats_->firststrike);
1398  unsigned int rounds = std::max<unsigned int>(a_stats_->rounds, d_stats_->rounds) - 1;
1399  const int defender_side = d_.get_unit().side();
1400 
1401  LOG_NG << "Fight: (" << a_.loc_ << ") vs (" << d_.loc_ << ") ATT: " << a_stats_->weapon->name() << " "
1402  << a_stats_->damage << "-" << a_stats_->num_blows << "(" << a_stats_->chance_to_hit
1403  << "%) vs DEF: " << (d_stats_->weapon ? d_stats_->weapon->name() : "none") << " " << d_stats_->damage << "-"
1404  << d_stats_->num_blows << "(" << d_stats_->chance_to_hit << "%)"
1405  << (defender_strikes_first ? " defender first-strike" : "") << "\n";
1406 
1407  // Play the pre-fight animation
1408  unit_display::unit_draw_weapon(a_.loc_, a_.get_unit(), a_stats_->weapon, d_stats_->weapon, d_.loc_, &d_.get_unit());
1409 
1410  for(;;) {
1411  DBG_NG << "start of attack loop...\n";
1412  ++abs_n_attack_;
1413 
1414  if(a_.n_attacks_ > 0 && !defender_strikes_first) {
1415  if(!perform_hit(true, attack_stats)) {
1416  DBG_NG << "broke from attack loop on attacker turn\n";
1417  break;
1418  }
1419  }
1420 
1421  // If the defender got to strike first, they use it up here.
1422  defender_strikes_first = false;
1423  ++abs_n_defend_;
1424 
1425  if(d_.n_attacks_ > 0) {
1426  if(!perform_hit(false, attack_stats)) {
1427  DBG_NG << "broke from attack loop on defender turn\n";
1428  break;
1429  }
1430  }
1431 
1432  // Continue the fight to death; if one of the units got petrified,
1433  // either n_attacks or n_defends is -1
1434  if(rounds > 0 && d_.n_attacks_ == 0 && a_.n_attacks_ == 0) {
1435  a_.n_attacks_ = a_.orig_attacks_;
1436  d_.n_attacks_ = d_.orig_attacks_;
1437  --rounds;
1438  defender_strikes_first = (d_stats_->firststrike && !a_stats_->firststrike);
1439  }
1440 
1441  if(a_.n_attacks_ <= 0 && d_.n_attacks_ <= 0) {
1442  fire_event("attack_end");
1443  refresh_bc();
1444  break;
1445  }
1446  }
1447 
1448  if(update_def_fog_) {
1449  actions::recalculate_fog(defender_side);
1450  }
1451 
1452  // TODO: if we knew the viewing team, we could skip this display update
1453  if(update_minimap_ && update_display_) {
1455  }
1456 
1457  if(a_.valid()) {
1458  unit& u = a_.get_unit();
1459  u.anim_comp().set_standing();
1460  u.set_experience(u.experience() + a_.xp_);
1461  }
1462 
1463  if(d_.valid()) {
1464  unit& u = d_.get_unit();
1465  u.anim_comp().set_standing();
1466  u.set_experience(u.experience() + d_.xp_);
1467  }
1468 
1469  unit_display::unit_sheath_weapon(a_.loc_, a_.valid() ? &a_.get_unit() : nullptr, a_stats_->weapon, d_stats_->weapon,
1470  d_.loc_, d_.valid() ? &d_.get_unit() : nullptr);
1471 
1472  if(update_display_) {
1475  resources::screen->invalidate(d_.loc_);
1476  resources::screen->draw(true, true);
1477  }
1478 
1479  if(OOS_error_) {
1480  replay::process_error(errbuf_.str());
1481  }
1482 }
1483 
1484 void attack::check_replay_attack_result(
1485  bool& hits, int ran_num, int& damage, config replay_results, unit_info& attacker)
1486 {
1487  int results_chance = replay_results["chance"];
1488  bool results_hits = replay_results["hits"].to_bool();
1489  int results_damage = replay_results["damage"];
1490 
1491 #if 0
1492  errbuf_ << "SYNC: In attack " << a_.dump() << " vs " << d_.dump()
1493  << " replay data differs from local calculated data:"
1494  << " chance to hit in data source: " << results_chance
1495  << " chance to hit in calculated: " << attacker.cth_
1496  << " chance to hit in data source: " << results_chance
1497  << " chance to hit in calculated: " << attacker.cth_
1498  ;
1499 
1500  attacker.cth_ = results_chance;
1501  hits = results_hits;
1502  damage = results_damage;
1503 
1504  OOS_error_ = true;
1505 #endif
1506 
1507  if(results_chance != attacker.cth_) {
1508  errbuf_ << "SYNC: In attack " << a_.dump() << " vs " << d_.dump()
1509  << ": chance to hit is inconsistent. Data source: " << results_chance
1510  << "; Calculation: " << attacker.cth_ << " (over-riding game calculations with data source results)\n";
1511  attacker.cth_ = results_chance;
1512  OOS_error_ = true;
1513  }
1514 
1515  if(results_hits != hits) {
1516  errbuf_ << "SYNC: In attack " << a_.dump() << " vs " << d_.dump() << ": the data source says the hit was "
1517  << (results_hits ? "successful" : "unsuccessful") << ", while in-game calculations say the hit was "
1518  << (hits ? "successful" : "unsuccessful") << " random number: " << ran_num << " = " << (ran_num % 100)
1519  << "/" << results_chance << " (over-riding game calculations with data source results)\n";
1520  hits = results_hits;
1521  OOS_error_ = true;
1522  }
1523 
1524  if(results_damage != damage) {
1525  errbuf_ << "SYNC: In attack " << a_.dump() << " vs " << d_.dump() << ": the data source says the hit did "
1526  << results_damage << " damage, while in-game calculations show the hit doing " << damage
1527  << " damage (over-riding game calculations with data source results)\n";
1528  damage = results_damage;
1529  OOS_error_ = true;
1530  }
1531 }
1532 } // end anonymous namespace
1533 
1534 
1535 // ==================================================================================
1536 // FREE-STANDING FUNCTIONS
1537 // ==================================================================================
1538 
1539 void attack_unit(const map_location& attacker,
1540  const map_location& defender,
1541  int attack_with,
1542  int defend_with,
1543  bool update_display)
1544 {
1545  attack dummy(attacker, defender, attack_with, defend_with, update_display);
1546  dummy.perform();
1547 }
1548 
1550  const map_location& defender,
1551  int attack_with,
1552  int defend_with,
1553  bool update_display,
1554  const ai::unit_advancements_aspect& ai_advancement)
1555 {
1556  attack_unit(attacker, defender, attack_with, defend_with, update_display);
1557 
1559  if(atku != resources::gameboard->units().end()) {
1560  advance_unit_at(advance_unit_params(attacker).ai_advancements(ai_advancement));
1561  }
1562 
1564  if(defu != resources::gameboard->units().end()) {
1565  advance_unit_at(advance_unit_params(defender).ai_advancements(ai_advancement));
1566  }
1567 }
1568 
1569 std::pair<int, map_location> under_leadership(const unit_map& units, const map_location& loc)
1570 {
1571  const unit_map::const_iterator un = units.find(loc);
1572  if(un == units.end()) {
1573  return {0, map_location::null_location()};
1574  }
1575 
1576  unit_ability_list abil = un->get_abilities("leadership");
1577  return abil.highest("value");
1578 }
1579 
1580 int combat_modifier(const unit_map& units,
1581  const gamemap& map,
1582  const map_location& loc,
1583  unit_type::ALIGNMENT alignment,
1584  bool is_fearless)
1585 {
1586  const tod_manager& tod_m = *resources::tod_manager;
1587  int lawful_bonus = tod_m.get_illuminated_time_of_day(units, map, loc).lawful_bonus;
1588  return generic_combat_modifier(lawful_bonus, alignment, is_fearless);
1589 }
1590 
1591 int generic_combat_modifier(int lawful_bonus, unit_type::ALIGNMENT alignment, bool is_fearless)
1592 {
1593  int bonus;
1594 
1595  switch(alignment.v) {
1596  case unit_type::ALIGNMENT::LAWFUL:
1597  bonus = lawful_bonus;
1598  break;
1599  case unit_type::ALIGNMENT::NEUTRAL:
1600  bonus = 0;
1601  break;
1602  case unit_type::ALIGNMENT::CHAOTIC:
1603  bonus = -lawful_bonus;
1604  break;
1605  case unit_type::ALIGNMENT::LIMINAL:
1606  bonus = -std::abs(lawful_bonus);
1607  break;
1608  default:
1609  bonus = 0;
1610  }
1611 
1612  if(is_fearless) {
1613  bonus = std::max<int>(bonus, 0);
1614  }
1615 
1616  return bonus;
1617 }
1618 
1619 bool backstab_check(const map_location& attacker_loc,
1620  const map_location& defender_loc,
1621  const unit_map& units,
1622  const std::vector<team>& teams)
1623 {
1624  const unit_map::const_iterator defender = units.find(defender_loc);
1625  if(defender == units.end()) {
1626  return false; // No defender
1627  }
1628 
1629  map_location adj[6];
1630  get_adjacent_tiles(defender_loc, adj);
1631 
1632  int i;
1633 
1634  for(i = 0; i != 6; ++i) {
1635  if(adj[i] == attacker_loc) {
1636  break;
1637  }
1638  }
1639 
1640  if(i >= 6) {
1641  return false; // Attack not from adjacent location
1642  }
1643 
1644  const unit_map::const_iterator opp = units.find(adj[(i + 3) % 6]);
1645 
1646  // No opposite unit.
1647  if(opp == units.end()) {
1648  return false;
1649  }
1650 
1651  if(opp->incapacitated()) {
1652  return false;
1653  }
1654 
1655  // If sides aren't valid teams, then they are enemies.
1656  if(size_t(defender->side() - 1) >= teams.size() || size_t(opp->side() - 1) >= teams.size()) {
1657  return true;
1658  }
1659 
1660  // Defender and opposite are enemies.
1661  if(teams[defender->side() - 1].is_enemy(opp->side())) {
1662  return true;
1663  }
1664 
1665  // Defender and opposite are friends.
1666  return false;
1667 }
const_attack_ptr weapon
The weapon used by the unit to attack the opponent, or nullptr if there is none.
Definition: attack.hpp:49
int kill_xp(int level)
Definition: game_config.hpp:48
void set_experience(int xp)
Sets the current experience point amount.
Definition: unit.hpp:436
std::unique_ptr< combatant > attacker_combatant_
Outcome of simulated fight.
Definition: attack.hpp:243
bool fire_event(const ui_event event, std::vector< std::pair< widget *, ui_event >> &event_chain, widget *dispatcher, widget *w, F &&...params)
Helper function for fire_event.
unsigned int calc_blows(unsigned new_hp) const
Calculates the number of blows we would have if we had new_hp.
Definition: attack.hpp:107
#define LOG_NG
Definition: attack.cpp:54
::tod_manager * tod_manager
Definition: resources.cpp:30
virtual const unit_map & units() const
Definition: game_board.hpp:97
unit_iterator end()
Definition: map.hpp:415
std::vector< double > hp_dist
Resulting probability distribution (might be not as large as max_hp)
std::vector< char_t > string
bool invalidate(const map_location &loc)
Function to invalidate a specific tile for redrawing.
Definition: display.cpp:2981
void unit_draw_weapon(const map_location &loc, unit &attacker, const_attack_ptr attack, const_attack_ptr secondary_attack, const map_location &defender_loc, unit *defender)
Play a pre-fight animation First unit is the attacker, second unit the defender.
Definition: udisplay.cpp:509
std::string plague_type
The plague type used by the attack, if any.
Definition: attack.hpp:81
static lg::log_domain log_config("config")
This class represents a single unit of a specific type.
Definition: unit.hpp:101
int dummy
Definition: lstrlib.cpp:1125
unsigned int hp
Hitpoints of the unit at the beginning of the battle.
Definition: attack.hpp:70
Various functions implementing vision (through fog of war and shroud).
The unit is uncovered - it was hiding but has been spotted.
Definition: unit.hpp:724
double attack_weight() const
Definition: attack_type.hpp:51
int choose_defender_weapon(const unit &attacker, const unit &defender, unsigned attacker_weapon, const unit_map &units, const map_location &attacker_loc, const map_location &defender_loc, const combatant *prev_def)
Definition: attack.cpp:653
Various functions that implement attacks and attack calculations.
Add a special kind of assert to validate whether the input from WML doesn't contain any problems that...
static bool better_combat(const combatant &us_a, const combatant &them_a, const combatant &us_b, const combatant &them_b, double harm_weight)
Definition: attack.cpp:495
bool musthave_status(const std::string &status) const
Definition: types.cpp:694
const combatant & get_attacker_combatant(const combatant *prev_def=nullptr)
Get the simulation results.
Definition: attack.cpp:452
#define a
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
game_display * screen
Definition: resources.cpp:27
void invalidate_unit()
Function to invalidate that unit status displayed on the sidebar.
const std::string & undead_variation() const
Info on the type of unit that the unit reanimates as.
Definition: types.hpp:132
int lawful_bonus
The % bonus lawful units receive.
Definition: time_of_day.hpp:84
The unit is poisoned - it loses health each turn.
Definition: unit.hpp:722
unit_type_data unit_types
Definition: types.cpp:1455
The unit is petrified - it cannot move or be attacked.
Definition: unit.hpp:723
bool is_slowed
True if the unit is slowed at the beginning of the battle.
Definition: attack.hpp:53
void insert(config_key_type key, T &&value)
Returns a reference to the first child with the given key.
Definition: config.hpp:467
Replay control code.
int wml_y() const
Definition: location.hpp:116
battle_context(const unit_map &units, const map_location &attacker_loc, const map_location &defender_loc, int attacker_weapon=-1, int defender_weapon=-1, double aggression=0.0, const combatant *prev_def=nullptr, const unit *attacker_ptr=nullptr)
If no attacker_weapon is given, we select the best one, based on harm_weight (1.0 means 1 hp lost cou...
Definition: attack.cpp:350
bool slows
Attack slows opponent when it hits.
Definition: attack.hpp:54
void clear()
Definition: config.cpp:742
void defend_result(hit_result res, int damage, int drain)
Definition: statistics.cpp:482
virtual void draw()
Draws invalidated items.
Definition: display.cpp:2432
int drain_constant
Base HP drained regardless of damage dealt.
Definition: attack.hpp:76
-file sdl_utils.hpp
int level() const
Definition: types.hpp:151
unit_ability & front()
Definition: unit.hpp:83
void redraw_minimap()
Schedule the minimap to be redrawn.
Definition: display.hpp:600
unsigned int chance_to_hit
Effective chance to hit as a percentage (all factors accounted for).
Definition: attack.hpp:72
A single unit type that the player may recruit.
Definition: types.hpp:43
game_data * gamedata
Definition: resources.cpp:22
config::attribute_value & get_variable(const std::string &varname)
throws invalid_variablename_exception if varname is no valid variable name.
Definition: game_data.cpp:62
map_location loc_
#define b
bool poisons
Attack poisons opponent when it hits.
Definition: attack.hpp:58
const combatant & get_defender_combatant(const combatant *prev_def=nullptr)
Definition: attack.cpp:467
bool backstab_pos
True if the attacker is in position to backstab the defender (this is used to determine whether to ap...
Definition: attack.hpp:59
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:89
team & get_team(int i)
Definition: game_board.hpp:94
int damage
Effective damage of the weapon (all factors accounted for).
Definition: attack.hpp:73
Various functions that implement advancements of units.
int wml_x() const
Definition: location.hpp:115
void set_state(const std::string &state, bool value)
Set whether the unit is affected by a status effect.
Definition: unit.cpp:1334
#define VALIDATE(cond, message)
The macro to use for the validation of WML.
unsigned int level
Definition: attack.hpp:67
The unit is slowed - it moves slower and does less damage.
Definition: unit.hpp:721
unsigned int rounds
Berserk special can force us to fight more than one round.
Definition: attack.hpp:69
unsigned int swarm_min
Minimum number of blows with swarm (equal to num_blows if swarm isn't used).
Definition: attack.hpp:78
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
int hitpoints() const
The current number of hitpoints this unit has.
Definition: unit.hpp:394
game_board * gameboard
Definition: resources.cpp:20
std::unique_ptr< battle_context_unit_stats > defender_stats_
Definition: attack.hpp:240
bool plagues
Attack turns opponent into a zombie when fatal.
Definition: attack.hpp:57
void unit_die(const map_location &loc, unit &loser, const_attack_ptr attack, const_attack_ptr secondary_attack, const map_location &winner_loc, unit *winner)
Show a unit fading out.
Definition: udisplay.cpp:557
unit_race::GENDER gender() const
The gender of this unit.
Definition: unit.hpp:361
bool better_attack(class battle_context &that, double harm_weight)
Given this harm_weight, is this attack better than that?
Definition: attack.cpp:483
Encapsulates the map of the game.
Definition: map.hpp:34
const std::string & undead_variation() const
The ID of the undead variation (ie, dwarf, swimmer) of this unit.
Definition: unit.hpp:460
void recalculate_fog(int side)
Function that recalculates the fog of war.
Definition: vision.cpp:705
Computes the statistics of a battle between an attacker and a defender unit.
Definition: attack.hpp:170
static DIRECTION get_opposite_dir(DIRECTION d)
Definition: location.hpp:188
int max_hitpoints() const
The max number of hitpoints this unit can have.
Definition: unit.hpp:400
int defense_modifier(const t_translation::terrain_code &terrain) const
The unit's defense on a given terrain.
Definition: unit.cpp:1535
void fight(combatant &opponent, bool levelup_considered=true)
Simulate a fight! Can be called multiple times for cumulative calculations.
checkup * checkup_instance
void unit_attack(display *disp, game_board &board, const map_location &a, const map_location &b, int damage, const attack_type &attack, const_attack_ptr secondary_attack, int swing, const std::string &hit_text, int drain_amount, const std::string &att_text, const std::vector< std::string > *extra_hit_sounds)
Make the unit on tile 'a' attack the unit on tile 'b'.
Definition: udisplay.cpp:581
int attack_num
Index into unit->attacks() or -1 for none.
Definition: attack.hpp:50
Structure describing the statistics of a unit involved in the battle.
Definition: attack.hpp:47
The unit has not moved.
Definition: unit.hpp:725
static const map_location & null_location()
Definition: location.hpp:220
int experience() const
The current number of experience points this unit has.
Definition: unit.hpp:412
int choose_attacker_weapon(const unit &attacker, const unit &defender, const unit_map &units, const map_location &attacker_loc, const map_location &defender_loc, double harm_weight, int *defender_weapon, const combatant *prev_def)
Definition: attack.cpp:537
std::unique_ptr< battle_context_unit_stats > attacker_stats_
Statistics of the units.
Definition: attack.hpp:239
game_events::manager * game_events
Definition: resources.cpp:24
int generic_combat_modifier(int lawful_bonus, unit_type::ALIGNMENT alignment, bool is_fearless)
Returns the amount that a unit's damage should be multiplied by due to a given lawful_bonus.
Definition: attack.cpp:1591
advances the unit at loc if it has enough experience, maximum 20 times.
Definition: advancement.hpp:40
Encapsulates the map of the game.
Definition: location.hpp:40
int round_damage(int base_damage, int bonus, int divisor)
round (base_damage * bonus / divisor) to the closest integer, but up or down towards base_damage ...
Definition: math.hpp:57
std::shared_ptr< wb::manager > whiteboard
Definition: resources.cpp:35
static void process_error(const std::string &msg)
Definition: replay.cpp:194
bool empty() const
Definition: unit.hpp:82
All combat-related info.
unit_animation_component & anim_comp() const
Definition: unit.hpp:1329
battle_context & operator=(const battle_context &other)
Definition: attack.cpp:434
std::pair< int, map_location > highest(const std::string &key, int def=0) const
Definition: unit.hpp:60
bool backstab_check(const map_location &attacker_loc, const map_location &defender_loc, const unit_map &units, const std::vector< team > &teams)
Function to check if an attack will satisfy the requirements for backstab.
Definition: attack.cpp:1619
bool swarm
Attack has swarm special.
Definition: attack.hpp:63
int slow_damage
Effective damage if unit becomes slowed (== damage, if already slowed)
Definition: attack.hpp:74
std::unique_ptr< combatant > defender_combatant_
Definition: attack.hpp:244
const unit_type & type() const
This unit's type, accounting for gender and variation.
Definition: unit.hpp:257
void unit_sheath_weapon(const map_location &primary_loc, unit *primary_unit, const_attack_ptr primary_attack, const_attack_ptr secondary_attack, const map_location &secondary_loc, unit *secondary_unit)
Play a post-fight animation Both unit can be set to null, only valid units will play their animation...
Definition: udisplay.cpp:527
#define LOG_CF
Definition: attack.cpp:59
static map_location::DIRECTION s
bool get_state(const std::string &state) const
Check if the unit is affected by a status effect.
Definition: unit.cpp:1286
const std::string & range() const
Definition: attack_type.hpp:43
Define the game's event mechanism.
attack_itors attacks()
Gets an iterator over this unit's attacks.
Definition: unit.hpp:786
bool disable
Attack has disable special.
Definition: attack.hpp:65
bool firststrike
Attack has firststrike special.
Definition: attack.hpp:64
#define i
std::set< std::string > & encountered_units()
Definition: game.cpp:942
int get_random_int(int min, int max)
This helper method provides a random int from the underlying generator, using results of next_random...
Definition: random.hpp:51
void advance_unit_at(const advance_unit_params &params)
variant a_
Definition: function.cpp:851
rng * generator
This generator is automatically synced during synced context.
Definition: random.cpp:57
config & add_child(config_key_type key)
Definition: config.cpp:408
pump_result_t fire(const std::string &event, const entity_location &loc1=entity_location::null_entity, const entity_location &loc2=entity_location::null_entity, const config &data=config())
Function to fire an event.
Definition: pump.cpp:493
int drain_percent
Percentage of damage recovered as health.
Definition: attack.hpp:75
int hitpoints() const
Definition: types.hpp:148
virtual const gamemap & map() const
Definition: game_board.hpp:96
int experience_needed(bool with_acceleration=true) const
Definition: types.cpp:526
const time_of_day get_illuminated_time_of_day(const unit_map &units, const gamemap &map, const map_location &loc, int for_turn=0) const
Returns time of day object for the passed turn at a location.
const std::string & base_id() const
The id of the original type from which this (variation) descended.
Definition: types.hpp:144
boost::intrusive_ptr< unit > unit_ptr
Definition: ptr.hpp:29
battle_context_unit_stats(const unit &u, const map_location &u_loc, int u_attack_num, bool attacking, const unit &opp, const map_location &opp_loc, const_attack_ptr opp_weapon, const unit_map &units)
Definition: attack.cpp:65
void attack_expected_damage(double attacker_inflict, double defender_inflict)
Definition: statistics.cpp:442
void attack_unit(const map_location &attacker, const map_location &defender, int attack_with, int defend_with, bool update_display)
Performs an attack.
Definition: attack.cpp:1539
bool is_fearless() const
Gets whether this unit is fearless - ie, unaffected by time of day.
Definition: unit.hpp:1039
Standard logging facilities (interface).
unit_type::ALIGNMENT alignment() const
The alignment of this unit.
Definition: unit.hpp:371
void attack_result(hit_result res, int damage, int drain)
Definition: statistics.cpp:458
Container associating units to locations.
Definition: map.hpp:99
double defense_weight() const
Definition: attack_type.hpp:52
unsigned int num_blows
Effective number of blows, takes swarm into account.
Definition: attack.hpp:77
std::pair< int, map_location > under_leadership(const unit_map &units, const map_location &loc)
Tests if the unit at loc is currently affected by leadership.
Definition: attack.cpp:1569
const unit_type * find(const std::string &key, unit_type::BUILD_STATUS status=unit_type::FULL) const
Finds a unit_type by its id() and makes sure it is built to the specified level.
Definition: types.cpp:1273
unsigned int max_hp
Maximum hitpoints of the unit.
Definition: attack.hpp:71
double average_hp(unsigned int healing=0) const
What's the average hp (weighted average of hp_dist).
bool is_attacker
True if the unit is the attacker.
Definition: attack.hpp:51
game_events::wml_event_pump & pump()
Definition: manager.cpp:217
void set_standing(bool with_bars=true)
Sets the animation state to standing.
unsigned int max_experience
Definition: attack.hpp:66
int get_composite_value() const
Definition: abilities.hpp:49
unit_iterator find(size_t id)
Definition: map.cpp:311
bool valid() const
Definition: map.hpp:276
virtual bool local_checkup(const config &expected_data, config &real_data)=0
Compares data to the results calculated during the original game.
#define DBG_NG
Definition: attack.cpp:53
int damage_from(const attack_type &attack, bool attacker, const map_location &loc) const
Calculates the damage this unit would take from a certain attack.
Definition: unit.hpp:829
bool petrifies
Attack petrifies opponent when it hits.
Definition: attack.hpp:56
static lg::log_domain log_engine("engine")
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:93
std::shared_ptr< const attack_type > const_attack_ptr
Definition: ptr.hpp:37
static map_location::DIRECTION n
bool drains
Attack drains opponent when it hits.
Definition: attack.hpp:55
unit_map * units
Definition: resources.cpp:34
Display units performing various actions: moving, attacking, and dying.
int resistance_against(const std::string &damage_name, bool attacker) const
Gets resistance while considering custom WML abilities.
Definition: types.cpp:841
void attack_unit_and_advance(const map_location &attacker, const map_location &defender, int attack_with, int defend_with, bool update_display, const ai::unit_advancements_aspect &ai_advancement)
Performs an attack, and advanced the units afterwards.
Definition: attack.cpp:1549
double poisoned
Resulting chance we are poisoned.
boost::iterator_range< boost::indirect_iterator< attack_list::iterator >> attack_itors
unsigned int swarm_max
Maximum number of blows with swarm (equal to num_blows if swarm isn't used).
Definition: attack.hpp:79
static game_display * get_singleton()
int combat_modifier(const unit_map &units, const gamemap &map, const map_location &loc, unit_type::ALIGNMENT alignment, bool is_fearless)
Returns the amount that a unit's damage should be multiplied by due to the current time of day...
Definition: attack.cpp:1580