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  }
968 
969  fire_event("attack_end");
970  throw attack_end_exception();
971  }
972 }
973 
974 void attack::refresh_bc()
975 {
976  // Fix index of weapons.
977  if(a_.valid()) {
978  refresh_weapon_index(a_.weapon_, a_.weap_id_, a_.get_unit().attacks());
979  }
980 
981  if(d_.valid()) {
982  refresh_weapon_index(d_.weapon_, d_.weap_id_, d_.get_unit().attacks());
983  }
984 
985  if(!a_.valid() || !d_.valid()) {
986  // Fix pointer to weapons.
987  const_cast<battle_context_unit_stats*>(a_stats_)->weapon
988  = a_.valid() && a_.weapon_ >= 0 ? a_.get_unit().attacks()[a_.weapon_].shared_from_this() : nullptr;
989 
990  const_cast<battle_context_unit_stats*>(d_stats_)->weapon
991  = d_.valid() && d_.weapon_ >= 0 ? d_.get_unit().attacks()[d_.weapon_].shared_from_this() : nullptr;
992 
993  return;
994  }
995 
996  bc_.reset(new battle_context(units_, a_.loc_, d_.loc_, a_.weapon_, d_.weapon_));
997 
998  a_stats_ = &bc_->get_attacker_stats();
999  d_stats_ = &bc_->get_defender_stats();
1000 
1001  a_.cth_ = a_stats_->chance_to_hit;
1002  d_.cth_ = d_stats_->chance_to_hit;
1003  a_.damage_ = a_stats_->damage;
1004  d_.damage_ = d_stats_->damage;
1005 }
1006 
1007 bool attack::perform_hit(bool attacker_turn, statistics::attack_context& stats)
1008 {
1009  unit_info& attacker = attacker_turn ? a_ : d_;
1010  unit_info& defender = attacker_turn ? d_ : a_;
1011 
1012  // NOTE: we need to use a reference-to-pointer here so a_stats_ and d_stats_ can be
1013  // modified without. Using a pointer directly would render them invalid when that happened.
1014  const battle_context_unit_stats*& attacker_stats = attacker_turn ? a_stats_ : d_stats_;
1015  const battle_context_unit_stats*& defender_stats = attacker_turn ? d_stats_ : a_stats_;
1016 
1017  int& abs_n = attacker_turn ? abs_n_attack_ : abs_n_defend_;
1018  bool& update_fog = attacker_turn ? update_def_fog_ : update_att_fog_;
1019 
1020  int ran_num = randomness::generator->get_random_int(0, 99);
1021  bool hits = (ran_num < attacker.cth_);
1022 
1023  int damage = 0;
1024  if(hits) {
1025  damage = attacker.damage_;
1026  resources::gamedata->get_variable("damage_inflicted") = damage;
1027  }
1028 
1029  // Make sure that if we're serializing a game here,
1030  // we got the same results as the game did originally.
1031  const config local_results {"chance", attacker.cth_, "hits", hits, "damage", damage};
1032 
1033  config replay_results;
1034  bool equals_replay = checkup_instance->local_checkup(local_results, replay_results);
1035 
1036  if(!equals_replay) {
1037  check_replay_attack_result(hits, ran_num, damage, replay_results, attacker);
1038  }
1039 
1040  // can do no more damage than the defender has hitpoints
1041  int damage_done = std::min<int>(defender.get_unit().hitpoints(), attacker.damage_);
1042 
1043  // expected damage = damage potential * chance to hit (as a percentage)
1044  double expected_damage = damage_done * attacker.cth_ * 0.01;
1045 
1046  if(attacker_turn) {
1047  stats.attack_expected_damage(expected_damage, 0);
1048  } else {
1049  stats.attack_expected_damage(0, expected_damage);
1050  }
1051 
1052  int drains_damage = 0;
1053  if(hits && attacker_stats->drains) {
1054  drains_damage = damage_done * attacker_stats->drain_percent / 100 + attacker_stats->drain_constant;
1055 
1056  // don't drain so much that the attacker gets more than his maximum hitpoints
1057  drains_damage =
1058  std::min<int>(drains_damage, attacker.get_unit().max_hitpoints() - attacker.get_unit().hitpoints());
1059 
1060  // if drain is negative, don't allow drain to kill the attacker
1061  drains_damage = std::max<int>(drains_damage, 1 - attacker.get_unit().hitpoints());
1062  }
1063 
1064  if(update_display_) {
1065  std::ostringstream float_text;
1066  std::vector<std::string> extra_hit_sounds;
1067 
1068  if(hits) {
1069  const unit& defender_unit = defender.get_unit();
1070  if(attacker_stats->poisons && !defender_unit.get_state(unit::STATE_POISONED)) {
1071  float_text << (defender_unit.gender() == unit_race::FEMALE ? _("female^poisoned") : _("poisoned"))
1072  << '\n';
1073 
1074  extra_hit_sounds.push_back(game_config::sounds::status::poisoned);
1075  }
1076 
1077  if(attacker_stats->slows && !defender_unit.get_state(unit::STATE_SLOWED)) {
1078  float_text << (defender_unit.gender() == unit_race::FEMALE ? _("female^slowed") : _("slowed")) << '\n';
1079 
1080  extra_hit_sounds.push_back(game_config::sounds::status::slowed);
1081  }
1082 
1083  if(attacker_stats->petrifies) {
1084  float_text << (defender_unit.gender() == unit_race::FEMALE ? _("female^petrified") : _("petrified"))
1085  << '\n';
1086 
1087  extra_hit_sounds.push_back(game_config::sounds::status::petrified);
1088  }
1089  }
1090 
1094  attacker.loc_, defender.loc_,
1095  damage,
1096  *attacker_stats->weapon, defender_stats->weapon,
1097  abs_n, float_text.str(), drains_damage, "",
1098  &extra_hit_sounds
1099  );
1100  }
1101 
1102  bool dies = defender.get_unit().take_hit(damage);
1103  LOG_NG << "defender took " << damage << (dies ? " and died\n" : "\n");
1104 
1105  if(attacker_turn) {
1106  stats.attack_result(hits
1107  ? (dies
1110  : statistics::attack_context::MISSES, damage_done, drains_damage
1111  );
1112  } else {
1113  stats.defend_result(hits
1114  ? (dies
1117  : statistics::attack_context::MISSES, damage_done, drains_damage
1118  );
1119  }
1120 
1121  replay_results.clear();
1122 
1123  // There was also a attribute cfg["unit_hit"] which was never used so i deleted.
1124  equals_replay = checkup_instance->local_checkup(config{"dies", dies}, replay_results);
1125 
1126  if(!equals_replay) {
1127  bool results_dies = replay_results["dies"].to_bool();
1128 
1129  errbuf_ << "SYNC: In attack " << a_.dump() << " vs " << d_.dump() << ": the data source says the "
1130  << (attacker_turn ? "defender" : "attacker") << ' ' << (results_dies ? "perished" : "survived")
1131  << " while in-game calculations show it " << (dies ? "perished" : "survived")
1132  << " (over-riding game calculations with data source results)\n";
1133 
1134  dies = results_dies;
1135 
1136  // Set hitpoints to 0 so later checks don't invalidate the death.
1137  if(results_dies) {
1138  defender.get_unit().set_hitpoints(0);
1139  }
1140 
1141  OOS_error_ = true;
1142  }
1143 
1144  if(hits) {
1145  try {
1146  fire_event(attacker_turn ? "attacker_hits" : "defender_hits");
1147  } catch(attack_end_exception) {
1148  refresh_bc();
1149  return false;
1150  }
1151  } else {
1152  try {
1153  fire_event(attacker_turn ? "attacker_misses" : "defender_misses");
1154  } catch(attack_end_exception) {
1155  refresh_bc();
1156  return false;
1157  }
1158  }
1159 
1160  refresh_bc();
1161 
1162  bool attacker_dies = false;
1163 
1164  if(drains_damage > 0) {
1165  attacker.get_unit().heal(drains_damage);
1166  } else if(drains_damage < 0) {
1167  attacker_dies = attacker.get_unit().take_hit(-drains_damage);
1168  }
1169 
1170  if(dies) {
1171  unit_killed(attacker, defender, attacker_stats, defender_stats, false);
1172  update_fog = true;
1173  }
1174 
1175  if(attacker_dies) {
1176  unit_killed(defender, attacker, defender_stats, attacker_stats, true);
1177  (attacker_turn ? update_att_fog_ : update_def_fog_) = true;
1178  }
1179 
1180  if(dies) {
1181  update_minimap_ = true;
1182  return false;
1183  }
1184 
1185  if(hits) {
1186  unit& defender_unit = defender.get_unit();
1187 
1188  if(attacker_stats->poisons && !defender_unit.get_state(unit::STATE_POISONED)) {
1189  defender_unit.set_state(unit::STATE_POISONED, true);
1190  LOG_NG << "defender poisoned\n";
1191  }
1192 
1193  if(attacker_stats->slows && !defender_unit.get_state(unit::STATE_SLOWED)) {
1194  defender_unit.set_state(unit::STATE_SLOWED, true);
1195  update_fog = true;
1196  defender.damage_ = defender_stats->slow_damage;
1197  LOG_NG << "defender slowed\n";
1198  }
1199 
1200  // If the defender is petrified, the fight stops immediately
1201  if(attacker_stats->petrifies) {
1202  defender_unit.set_state(unit::STATE_PETRIFIED, true);
1203  update_fog = true;
1204  attacker.n_attacks_ = 0;
1205  defender.n_attacks_ = -1; // Petrified.
1206  resources::game_events->pump().fire("petrified", defender.loc_, attacker.loc_);
1207  refresh_bc();
1208  }
1209  }
1210 
1211  // Delay until here so that poison and slow go through
1212  if(attacker_dies) {
1213  update_minimap_ = true;
1214  return false;
1215  }
1216 
1217  --attacker.n_attacks_;
1218  return true;
1219 }
1220 
1221 void attack::unit_killed(unit_info& attacker,
1222  unit_info& defender,
1223  const battle_context_unit_stats*& attacker_stats,
1224  const battle_context_unit_stats*& defender_stats,
1225  bool drain_killed)
1226 {
1227  attacker.xp_ = game_config::kill_xp(defender.get_unit().level());
1228  defender.xp_ = 0;
1229 
1230  resources::screen->invalidate(attacker.loc_);
1231 
1232  game_events::entity_location death_loc(defender.loc_, defender.id_);
1233  game_events::entity_location attacker_loc(attacker.loc_, attacker.id_);
1234 
1235  std::string undead_variation = defender.get_unit().undead_variation();
1236 
1237  fire_event("attack_end");
1238  refresh_bc();
1239 
1240  // Get weapon info for last_breath and die events.
1241  config dat;
1242  config a_weapon_cfg = attacker_stats->weapon && attacker.valid() ? attacker_stats->weapon->to_config() : config();
1243  config d_weapon_cfg = defender_stats->weapon && defender.valid() ? defender_stats->weapon->to_config() : config();
1244 
1245  if(a_weapon_cfg["name"].empty()) {
1246  a_weapon_cfg["name"] = "none";
1247  }
1248 
1249  if(d_weapon_cfg["name"].empty()) {
1250  d_weapon_cfg["name"] = "none";
1251  }
1252 
1253  dat.add_child("first", d_weapon_cfg);
1254  dat.add_child("second", a_weapon_cfg);
1255 
1256  resources::game_events->pump().fire("last_breath", death_loc, attacker_loc, dat);
1257  refresh_bc();
1258 
1259  // WML has invalidated the dying unit, abort.
1260  if(!defender.valid() || defender.get_unit().hitpoints() > 0) {
1261  return;
1262  }
1263 
1264  if(!attacker.valid()) {
1266  defender.loc_,
1267  defender.get_unit(),
1268  nullptr,
1269  defender_stats->weapon
1270  );
1271  } else {
1273  defender.loc_,
1274  defender.get_unit(),
1275  attacker_stats->weapon,
1276  defender_stats->weapon,
1277  attacker.loc_,
1278  &attacker.get_unit()
1279  );
1280  }
1281 
1282  resources::game_events->pump().fire("die", death_loc, attacker_loc, dat);
1283  refresh_bc();
1284 
1285  if(!defender.valid() || defender.get_unit().hitpoints() > 0) {
1286  // WML has invalidated the dying unit, abort
1287  return;
1288  }
1289 
1290  units_.erase(defender.loc_);
1291  resources::whiteboard->on_kill_unit();
1292 
1293  // Plague units make new units on the target hex.
1294  if(attacker.valid() && attacker_stats->plagues && !drain_killed) {
1295  LOG_NG << "trying to reanimate " << attacker_stats->plague_type << '\n';
1296 
1297  if(const unit_type* reanimator = unit_types.find(attacker_stats->plague_type)) {
1298  LOG_NG << "found unit type:" << reanimator->id() << '\n';
1299 
1300  unit_ptr newunit(new unit(*reanimator, attacker.get_unit().side(), true, unit_race::MALE));
1301  newunit->set_attacks(0);
1302  newunit->set_movement(0, true);
1303  newunit->set_facing(map_location::get_opposite_dir(attacker.get_unit().facing()));
1304 
1305  // Apply variation
1306  if(undead_variation != "null") {
1307  config mod;
1308  config& variation = mod.add_child("effect");
1309  variation["apply_to"] = "variation";
1310  variation["name"] = undead_variation;
1311  newunit->add_modification("variation", mod);
1312  newunit->heal_fully();
1313  }
1314 
1315  newunit->set_location(death_loc);
1316  units_.insert(newunit);
1317 
1318  game_events::entity_location reanim_loc(defender.loc_, newunit->underlying_id());
1319  resources::game_events->pump().fire("unit_placed", reanim_loc);
1320 
1321  preferences::encountered_units().insert(newunit->type_id());
1322 
1323  if(update_display_) {
1324  resources::screen->invalidate(death_loc);
1325  }
1326  }
1327  } else {
1328  LOG_NG << "unit not reanimated\n";
1329  }
1330 }
1331 
1332 void attack::perform()
1333 {
1334  // Stop the user from issuing any commands while the units are fighting.
1335  const events::command_disabler disable_commands;
1336 
1337  if(!a_.valid() || !d_.valid()) {
1338  return;
1339  }
1340 
1341  // no attack weapon => stop here and don't attack
1342  if(a_.weapon_ < 0) {
1343  a_.get_unit().set_attacks(a_.get_unit().attacks_left() - 1);
1344  a_.get_unit().set_movement(-1, true);
1345  return;
1346  }
1347 
1348  a_.get_unit().set_facing(a_.loc_.get_relative_dir(d_.loc_));
1349  d_.get_unit().set_facing(d_.loc_.get_relative_dir(a_.loc_));
1350 
1351  a_.get_unit().set_attacks(a_.get_unit().attacks_left() - 1);
1352 
1353  VALIDATE(a_.weapon_ < static_cast<int>(a_.get_unit().attacks().size()),
1354  _("An invalid attacker weapon got selected."));
1355 
1356  a_.get_unit().set_movement(a_.get_unit().movement_left() - a_.get_unit().attacks()[a_.weapon_].movement_used(), true);
1357  a_.get_unit().set_state(unit::STATE_NOT_MOVED, false);
1358  a_.get_unit().set_resting(false);
1359  d_.get_unit().set_resting(false);
1360 
1361  // If the attacker was invisible, she isn't anymore!
1362  a_.get_unit().set_state(unit::STATE_UNCOVERED, true);
1363 
1364  bc_.reset(new battle_context(units_, a_.loc_, d_.loc_, a_.weapon_, d_.weapon_));
1365 
1366  a_stats_ = &bc_->get_attacker_stats();
1367  d_stats_ = &bc_->get_defender_stats();
1368 
1369  if(a_stats_->weapon) {
1370  a_.weap_id_ = a_stats_->weapon->id();
1371  }
1372 
1373  if(d_stats_->weapon) {
1374  d_.weap_id_ = d_stats_->weapon->id();
1375  }
1376 
1377  try {
1378  fire_event("attack");
1379  } catch(attack_end_exception) {
1380  return;
1381  }
1382 
1383  refresh_bc();
1384 
1385  DBG_NG << "getting attack statistics\n";
1386  statistics::attack_context attack_stats(
1387  a_.get_unit(), d_.get_unit(), a_stats_->chance_to_hit, d_stats_->chance_to_hit);
1388 
1389  a_.orig_attacks_ = a_stats_->num_blows;
1390  d_.orig_attacks_ = d_stats_->num_blows;
1391  a_.n_attacks_ = a_.orig_attacks_;
1392  d_.n_attacks_ = d_.orig_attacks_;
1393  a_.xp_ = d_.get_unit().level();
1394  d_.xp_ = a_.get_unit().level();
1395 
1396  bool defender_strikes_first = (d_stats_->firststrike && !a_stats_->firststrike);
1397  unsigned int rounds = std::max<unsigned int>(a_stats_->rounds, d_stats_->rounds) - 1;
1398  const int defender_side = d_.get_unit().side();
1399 
1400  LOG_NG << "Fight: (" << a_.loc_ << ") vs (" << d_.loc_ << ") ATT: " << a_stats_->weapon->name() << " "
1401  << a_stats_->damage << "-" << a_stats_->num_blows << "(" << a_stats_->chance_to_hit
1402  << "%) vs DEF: " << (d_stats_->weapon ? d_stats_->weapon->name() : "none") << " " << d_stats_->damage << "-"
1403  << d_stats_->num_blows << "(" << d_stats_->chance_to_hit << "%)"
1404  << (defender_strikes_first ? " defender first-strike" : "") << "\n";
1405 
1406  // Play the pre-fight animation
1407  unit_display::unit_draw_weapon(a_.loc_, a_.get_unit(), a_stats_->weapon, d_stats_->weapon, d_.loc_, &d_.get_unit());
1408 
1409  for(;;) {
1410  DBG_NG << "start of attack loop...\n";
1411  ++abs_n_attack_;
1412 
1413  if(a_.n_attacks_ > 0 && !defender_strikes_first) {
1414  if(!perform_hit(true, attack_stats)) {
1415  DBG_NG << "broke from attack loop on attacker turn\n";
1416  break;
1417  }
1418  }
1419 
1420  // If the defender got to strike first, they use it up here.
1421  defender_strikes_first = false;
1422  ++abs_n_defend_;
1423 
1424  if(d_.n_attacks_ > 0) {
1425  if(!perform_hit(false, attack_stats)) {
1426  DBG_NG << "broke from attack loop on defender turn\n";
1427  break;
1428  }
1429  }
1430 
1431  // Continue the fight to death; if one of the units got petrified,
1432  // either n_attacks or n_defends is -1
1433  if(rounds > 0 && d_.n_attacks_ == 0 && a_.n_attacks_ == 0) {
1434  a_.n_attacks_ = a_.orig_attacks_;
1435  d_.n_attacks_ = d_.orig_attacks_;
1436  --rounds;
1437  defender_strikes_first = (d_stats_->firststrike && !a_stats_->firststrike);
1438  }
1439 
1440  if(a_.n_attacks_ <= 0 && d_.n_attacks_ <= 0) {
1441  fire_event("attack_end");
1442  refresh_bc();
1443  break;
1444  }
1445  }
1446 
1447  if(update_def_fog_) {
1448  actions::recalculate_fog(defender_side);
1449  }
1450 
1451  // TODO: if we knew the viewing team, we could skip this display update
1452  if(update_minimap_ && update_display_) {
1454  }
1455 
1456  if(a_.valid()) {
1457  unit& u = a_.get_unit();
1458  u.anim_comp().set_standing();
1459  u.set_experience(u.experience() + a_.xp_);
1460  }
1461 
1462  if(d_.valid()) {
1463  unit& u = d_.get_unit();
1464  u.anim_comp().set_standing();
1465  u.set_experience(u.experience() + d_.xp_);
1466  }
1467 
1468  unit_display::unit_sheath_weapon(a_.loc_, a_.valid() ? &a_.get_unit() : nullptr, a_stats_->weapon, d_stats_->weapon,
1469  d_.loc_, d_.valid() ? &d_.get_unit() : nullptr);
1470 
1471  if(update_display_) {
1474  resources::screen->invalidate(d_.loc_);
1475  }
1476 
1477  if(OOS_error_) {
1478  replay::process_error(errbuf_.str());
1479  }
1480 }
1481 
1482 void attack::check_replay_attack_result(
1483  bool& hits, int ran_num, int& damage, config replay_results, unit_info& attacker)
1484 {
1485  int results_chance = replay_results["chance"];
1486  bool results_hits = replay_results["hits"].to_bool();
1487  int results_damage = replay_results["damage"];
1488 
1489 #if 0
1490  errbuf_ << "SYNC: In attack " << a_.dump() << " vs " << d_.dump()
1491  << " replay data differs from local calculated data:"
1492  << " chance to hit in data source: " << results_chance
1493  << " chance to hit in calculated: " << attacker.cth_
1494  << " chance to hit in data source: " << results_chance
1495  << " chance to hit in calculated: " << attacker.cth_
1496  ;
1497 
1498  attacker.cth_ = results_chance;
1499  hits = results_hits;
1500  damage = results_damage;
1501 
1502  OOS_error_ = true;
1503 #endif
1504 
1505  if(results_chance != attacker.cth_) {
1506  errbuf_ << "SYNC: In attack " << a_.dump() << " vs " << d_.dump()
1507  << ": chance to hit is inconsistent. Data source: " << results_chance
1508  << "; Calculation: " << attacker.cth_ << " (over-riding game calculations with data source results)\n";
1509  attacker.cth_ = results_chance;
1510  OOS_error_ = true;
1511  }
1512 
1513  if(results_hits != hits) {
1514  errbuf_ << "SYNC: In attack " << a_.dump() << " vs " << d_.dump() << ": the data source says the hit was "
1515  << (results_hits ? "successful" : "unsuccessful") << ", while in-game calculations say the hit was "
1516  << (hits ? "successful" : "unsuccessful") << " random number: " << ran_num << " = " << (ran_num % 100)
1517  << "/" << results_chance << " (over-riding game calculations with data source results)\n";
1518  hits = results_hits;
1519  OOS_error_ = true;
1520  }
1521 
1522  if(results_damage != damage) {
1523  errbuf_ << "SYNC: In attack " << a_.dump() << " vs " << d_.dump() << ": the data source says the hit did "
1524  << results_damage << " damage, while in-game calculations show the hit doing " << damage
1525  << " damage (over-riding game calculations with data source results)\n";
1526  damage = results_damage;
1527  OOS_error_ = true;
1528  }
1529 }
1530 } // end anonymous namespace
1531 
1532 
1533 // ==================================================================================
1534 // FREE-STANDING FUNCTIONS
1535 // ==================================================================================
1536 
1537 void attack_unit(const map_location& attacker,
1538  const map_location& defender,
1539  int attack_with,
1540  int defend_with,
1541  bool update_display)
1542 {
1543  attack dummy(attacker, defender, attack_with, defend_with, update_display);
1544  dummy.perform();
1545 }
1546 
1548  const map_location& defender,
1549  int attack_with,
1550  int defend_with,
1551  bool update_display,
1552  const ai::unit_advancements_aspect& ai_advancement)
1553 {
1554  attack_unit(attacker, defender, attack_with, defend_with, update_display);
1555 
1557  if(atku != resources::gameboard->units().end()) {
1558  advance_unit_at(advance_unit_params(attacker).ai_advancements(ai_advancement));
1559  }
1560 
1562  if(defu != resources::gameboard->units().end()) {
1563  advance_unit_at(advance_unit_params(defender).ai_advancements(ai_advancement));
1564  }
1565 }
1566 
1567 std::pair<int, map_location> under_leadership(const unit_map& units, const map_location& loc)
1568 {
1569  const unit_map::const_iterator un = units.find(loc);
1570  if(un == units.end()) {
1571  return {0, map_location::null_location()};
1572  }
1573 
1574  unit_ability_list abil = un->get_abilities("leadership");
1575  return abil.highest("value");
1576 }
1577 
1578 int combat_modifier(const unit_map& units,
1579  const gamemap& map,
1580  const map_location& loc,
1581  unit_type::ALIGNMENT alignment,
1582  bool is_fearless)
1583 {
1584  const tod_manager& tod_m = *resources::tod_manager;
1585  int lawful_bonus = tod_m.get_illuminated_time_of_day(units, map, loc).lawful_bonus;
1586  return generic_combat_modifier(lawful_bonus, alignment, is_fearless);
1587 }
1588 
1589 int generic_combat_modifier(int lawful_bonus, unit_type::ALIGNMENT alignment, bool is_fearless)
1590 {
1591  int bonus;
1592 
1593  switch(alignment.v) {
1594  case unit_type::ALIGNMENT::LAWFUL:
1595  bonus = lawful_bonus;
1596  break;
1597  case unit_type::ALIGNMENT::NEUTRAL:
1598  bonus = 0;
1599  break;
1600  case unit_type::ALIGNMENT::CHAOTIC:
1601  bonus = -lawful_bonus;
1602  break;
1603  case unit_type::ALIGNMENT::LIMINAL:
1604  bonus = -std::abs(lawful_bonus);
1605  break;
1606  default:
1607  bonus = 0;
1608  }
1609 
1610  if(is_fearless) {
1611  bonus = std::max<int>(bonus, 0);
1612  }
1613 
1614  return bonus;
1615 }
1616 
1617 bool backstab_check(const map_location& attacker_loc,
1618  const map_location& defender_loc,
1619  const unit_map& units,
1620  const std::vector<team>& teams)
1621 {
1622  const unit_map::const_iterator defender = units.find(defender_loc);
1623  if(defender == units.end()) {
1624  return false; // No defender
1625  }
1626 
1627  map_location adj[6];
1628  get_adjacent_tiles(defender_loc, adj);
1629 
1630  int i;
1631 
1632  for(i = 0; i != 6; ++i) {
1633  if(adj[i] == attacker_loc) {
1634  break;
1635  }
1636  }
1637 
1638  if(i >= 6) {
1639  return false; // Attack not from adjacent location
1640  }
1641 
1642  const unit_map::const_iterator opp = units.find(adj[(i + 3) % 6]);
1643 
1644  // No opposite unit.
1645  if(opp == units.end()) {
1646  return false;
1647  }
1648 
1649  if(opp->incapacitated()) {
1650  return false;
1651  }
1652 
1653  // If sides aren't valid teams, then they are enemies.
1654  if(size_t(defender->side() - 1) >= teams.size() || size_t(opp->side() - 1) >= teams.size()) {
1655  return true;
1656  }
1657 
1658  // Defender and opposite are enemies.
1659  if(teams[defender->side() - 1].is_enemy(opp->side())) {
1660  return true;
1661  }
1662 
1663  // Defender and opposite are friends.
1664  return false;
1665 }
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:435
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:3023
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:495
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:100
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:723
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:721
unit_type_data unit_types
Definition: types.cpp:1455
The unit is petrified - it cannot move or be attacked.
Definition: unit.hpp:722
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:790
void defend_result(hit_result res, int damage, int drain)
Definition: statistics.cpp:482
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:82
void redraw_minimap()
Schedule the minimap to be redrawn.
Definition: display.hpp:616
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:1337
#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:720
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:393
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:543
unit_race::GENDER gender() const
The gender of this unit.
Definition: unit.hpp:360
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:459
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:399
int defense_modifier(const t_translation::terrain_code &terrain) const
The unit's defense on a given terrain.
Definition: unit.cpp:1538
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:567
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:724
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:411
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:1589
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:58
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:81
All combat-related info.
unit_animation_component & anim_comp() const
Definition: unit.hpp:1328
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:59
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:1617
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:256
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:513
#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:1289
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:785
bool disable
Attack has disable special.
Definition: attack.hpp:65
size_t i
Definition: function.cpp:933
bool firststrike
Attack has firststrike special.
Definition: attack.hpp:64
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:722
rng * generator
This generator is automatically synced during synced context.
Definition: random.cpp:57
config & add_child(config_key_type key)
Definition: config.cpp:456
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:491
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:1537
bool is_fearless() const
Gets whether this unit is fearless - ie, unaffected by time of day.
Definition: unit.hpp:1038
Standard logging facilities (interface).
unit_type::ALIGNMENT alignment() const
The alignment of this unit.
Definition: unit.hpp:370
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:1567
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:226
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:828
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:1547
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:1578