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