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