The Battle for Wesnoth  1.19.1+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"
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  double attack_weight_a = us_a.u_.weapon->attack_weight();
502  double attack_weight_b = us_b.u_.weapon->attack_weight();
503  double damage_a = (them_a.u_.hp - them_a.average_hp()) * attack_weight_a;
504  double damage_b = (them_b.u_.hp - them_b.average_hp()) * attack_weight_b;
505 
506  // Compare: damage to them - damage to us (average_hp replaces -damage)
507  a = (us_a.average_hp() - poison_a_us) * harm_weight + damage_a + poison_a_them;
508  b = (us_b.average_hp() - poison_b_us) * harm_weight + damage_b + poison_b_them;
509 
510  if(a - b < -0.01) {
511  return false;
512  }
513 
514  if(a - b > 0.01) {
515  return true;
516  }
517 
518  // All else equal: go for most damage.
519  return damage_a >= damage_b;
520 }
521 
523  nonempty_unit_const_ptr defender,
524  const map_location& attacker_loc,
525  const map_location& defender_loc,
526  double harm_weight,
527  const combatant* prev_def)
528 {
529  log_scope2(log_attack, "choose_attacker_weapon");
530  std::vector<battle_context> choices;
531 
532  // What options does attacker have?
533  for(size_t i = 0; i < attacker->attacks().size(); ++i) {
534  const attack_type& att = attacker->attacks()[i];
535 
536  if(att.attack_weight() <= 0) {
537  continue;
538  }
539  battle_context bc = choose_defender_weapon(attacker, defender, i, attacker_loc, defender_loc, prev_def);
540  //choose_defender_weapon will always choose the weapon that disabels the attackers weapon if possible.
541  if(bc.attacker_stats_->disable) {
542  continue;
543  }
544  choices.emplace_back(std::move(bc));
545  }
546 
547  if(choices.empty()) {
548  return battle_context(attacker, attacker_loc, -1, defender, defender_loc, -1);
549  }
550 
551  if(choices.size() == 1) {
552  return std::move(choices[0]);
553  }
554 
555  // Multiple options: simulate them, save best.
556  battle_context* best_choice = nullptr;
557  for(auto& choice : choices) {
558  // If choose_defender_weapon didn't simulate, do so now.
559  choice.simulate(prev_def);
560 
561  if(!best_choice || choice.better_attack(*best_choice, harm_weight)) {
562  best_choice = &choice;
563  }
564  }
565 
566  if(best_choice) {
567  return std::move(*best_choice);
568  }
569  else {
570  return battle_context(attacker, attacker_loc, -1, defender, defender_loc, -1);
571  }
572 }
573 
574 /** @todo FIXME: Hand previous defender unit in here. */
576  nonempty_unit_const_ptr defender,
577  unsigned attacker_weapon,
578  const map_location& attacker_loc,
579  const map_location& defender_loc,
580  const combatant* prev_def)
581 {
582  log_scope2(log_attack, "choose_defender_weapon");
583  VALIDATE(attacker_weapon < attacker->attacks().size(), _("An invalid attacker weapon got selected."));
584 
585  const attack_type& att = attacker->attacks()[attacker_weapon];
586  auto no_weapon = [&]() { return battle_context(attacker, attacker_loc, attacker_weapon, defender, defender_loc, -1); };
587  std::vector<battle_context> choices;
588 
589  // What options does defender have?
590  for(size_t i = 0; i < defender->attacks().size(); ++i) {
591  const attack_type& def = defender->attacks()[i];
592  if(def.range() != att.range() || def.defense_weight() <= 0) {
593  //no need to calculate the battle_context here.
594  continue;
595  }
596  battle_context bc(attacker, attacker_loc, attacker_weapon, defender, defender_loc, i);
597 
598  if(bc.defender_stats_->disable) {
599  continue;
600  }
601  if(bc.attacker_stats_->disable) {
602  //the defenders attack disables the attakers attack: always choose this one.
603  return bc;
604  }
605  choices.emplace_back(std::move(bc));
606  }
607 
608  if(choices.empty()) {
609  return no_weapon();
610  }
611 
612  if(choices.size() == 1) {
613  //only one usable weapon, don't simulate
614  return std::move(choices[0]);
615  }
616 
617  // Multiple options:
618  // First pass : get the best weight and the minimum simple rating for this weight.
619  // simple rating = number of blows * damage per blows (resistance taken in account) * cth * weight
620  // Eligible attacks for defense should have a simple rating greater or equal to this weight.
621 
622  int min_rating = 0;
623  {
624  double max_weight = 0.0;
625 
626  for(const auto& choice : choices) {
627  const attack_type& def = defender->attacks()[choice.defender_stats_->attack_num];
628 
629  if(def.defense_weight() >= max_weight) {
630  const battle_context_unit_stats& def_stats = *choice.defender_stats_;
631 
632  max_weight = def.defense_weight();
633  int rating = static_cast<int>(
634  def_stats.num_blows * def_stats.damage * def_stats.chance_to_hit * def.defense_weight());
635 
636  if(def.defense_weight() > max_weight || rating < min_rating) {
637  min_rating = rating;
638  }
639  }
640  }
641  }
642 
643  battle_context* best_choice = nullptr;
644  // Multiple options: simulate them, save best.
645  for(auto& choice : choices) {
646  const attack_type& def = defender->attacks()[choice.defender_stats_->attack_num];
647 
648  choice.simulate(prev_def);
649 
650 
651  int simple_rating = static_cast<int>(
652  choice.defender_stats_->num_blows * choice.defender_stats_->damage * choice.defender_stats_->chance_to_hit * def.defense_weight());
653 
654  //FIXME: make sure there is no mostake in the better_combat call-
655  if(simple_rating >= min_rating && (!best_choice || choice.better_defense(*best_choice, 1.0))) {
656  best_choice = &choice;
657  }
658  }
659 
660  return best_choice ? std::move(*best_choice) : no_weapon();
661 }
662 
663 
664 // ==================================================================================
665 // HELPERS
666 // ==================================================================================
667 
668 namespace
669 {
670 void refresh_weapon_index(int& weap_index, const std::string& weap_id, attack_itors attacks)
671 {
672  // No attacks to choose from.
673  if(attacks.empty()) {
674  weap_index = -1;
675  return;
676  }
677 
678  // The currently selected attack fits.
679  if(weap_index >= 0 && weap_index < static_cast<int>(attacks.size()) && attacks[weap_index].id() == weap_id) {
680  return;
681  }
682 
683  // Look up the weapon by id.
684  if(!weap_id.empty()) {
685  for(int i = 0; i < static_cast<int>(attacks.size()); ++i) {
686  if(attacks[i].id() == weap_id) {
687  weap_index = i;
688  return;
689  }
690  }
691  }
692 
693  // Lookup has failed.
694  weap_index = -1;
695  return;
696 }
697 
698 /** Helper class for performing an attack. */
699 class attack
700 {
701 public:
702  attack(const map_location& attacker,
703  const map_location& defender,
704  int attack_with,
705  int defend_with,
706  bool update_display = true);
707 
708  void perform();
709 
710 private:
711  class attack_end_exception
712  {
713  };
714 
715  bool perform_hit(bool, statistics_attack_context&);
716  void fire_event(const std::string& n);
717  void fire_event_impl(const std::string& n, bool reversed);
718  void refresh_bc();
719 
720  /** Structure holding unit info used in the attack action. */
721  struct unit_info
722  {
723  const map_location loc_;
724  int weapon_;
725  unit_map& units_;
726  std::size_t id_; /**< unit.underlying_id() */
727  std::string weap_id_;
728  int orig_attacks_;
729  int n_attacks_; /**< Number of attacks left. */
730  int cth_;
731  int damage_;
732  int xp_;
733 
734  unit_info(const map_location& loc, int weapon, unit_map& units);
735  unit& get_unit();
736  unit_ptr get_unit_ptr();
737  bool valid();
738 
739  std::string dump();
740  };
741 
742  /**
743  * Used in perform_hit to confirm a replay is in sync.
744  * Check OOS_error_ after this method, true if error detected.
745  */
746  void check_replay_attack_result(bool&, int, int&, config, unit_info&);
747 
748  void unit_killed(
749  unit_info&, unit_info&, const battle_context_unit_stats*&, const battle_context_unit_stats*&, bool);
750 
751  std::unique_ptr<battle_context> bc_;
752 
753  const battle_context_unit_stats* a_stats_;
754  const battle_context_unit_stats* d_stats_;
755 
756  int abs_n_attack_, abs_n_defend_;
757  // update_att_fog_ is not used, other than making some code simpler.
758  bool update_att_fog_, update_def_fog_, update_minimap_;
759 
760  unit_info a_, d_;
761  unit_map& units_;
762  std::ostringstream errbuf_;
763 
764  bool update_display_;
765  bool OOS_error_;
766 
767  bool use_prng_;
768 
769  std::vector<bool> prng_attacker_;
770  std::vector<bool> prng_defender_;
771 };
772 
773 attack::unit_info::unit_info(const map_location& loc, int weapon, unit_map& units)
774  : loc_(loc)
775  , weapon_(weapon)
776  , units_(units)
777  , id_()
778  , weap_id_()
779  , orig_attacks_(0)
780  , n_attacks_(0)
781  , cth_(0)
782  , damage_(0)
783  , xp_(0)
784 {
785  unit_map::iterator i = units_.find(loc_);
786  if(!i.valid()) {
787  return;
788  }
789 
790  id_ = i->underlying_id();
791 }
792 
793 unit& attack::unit_info::get_unit()
794 {
795  unit_map::iterator i = units_.find(loc_);
796  assert(i.valid() && i->underlying_id() == id_);
797  return *i;
798 }
799 
800 unit_ptr attack::unit_info::get_unit_ptr()
801 {
802  unit_map::iterator i = units_.find(loc_);
803  if(i.valid() && i->underlying_id() == id_) {
804  return i.get_shared_ptr();
805  }
806  return unit_ptr();
807 }
808 
809 bool attack::unit_info::valid()
810 {
811  unit_map::iterator i = units_.find(loc_);
812  return i.valid() && i->underlying_id() == id_;
813 }
814 
815 std::string attack::unit_info::dump()
816 {
817  std::stringstream s;
818  s << get_unit().type_id() << " (" << loc_.wml_x() << ',' << loc_.wml_y() << ')';
819  return s.str();
820 }
821 
822 attack::attack(const map_location& attacker,
823  const map_location& defender,
824  int attack_with,
825  int defend_with,
826  bool update_display)
827  : bc_(nullptr)
828  , a_stats_(nullptr)
829  , d_stats_(nullptr)
830  , abs_n_attack_(0)
831  , abs_n_defend_(0)
832  , update_att_fog_(false)
833  , update_def_fog_(false)
834  , update_minimap_(false)
835  , a_(attacker, attack_with, resources::gameboard->units())
836  , d_(defender, defend_with, resources::gameboard->units())
837  , units_(resources::gameboard->units())
838  , errbuf_()
839  , update_display_(update_display)
840  , OOS_error_(false)
841 
842  //new experimental prng mode.
843  , use_prng_(resources::classification->random_mode == "biased" && randomness::generator->is_networked() == false)
844  , prng_attacker_()
845  , prng_defender_()
846 {
847  if(use_prng_) {
848  LOG_NG << "Using experimental PRNG for combat";
849  }
850 }
851 
852 void attack::fire_event(const std::string& n)
853 {
854  fire_event_impl(n, false);
855 }
856 
857 void attack::fire_event_impl(const std::string& n, bool reverse)
858 {
859  LOG_NG << "attack: firing '" << n << "' event";
860 
861  // prepare the event data for weapon filtering
862  config ev_data;
863  config& a_weapon_cfg = ev_data.add_child(reverse ? "second" : "first");
864  config& d_weapon_cfg = ev_data.add_child(reverse ? "first" : "second");
865 
866  // Need these to ensure weapon filters work correctly
867  std::optional<attack_type::specials_context_t> a_ctx, d_ctx;
868 
869  if(a_stats_->weapon != nullptr && a_.valid()) {
870  if(d_stats_->weapon != nullptr && d_.valid()) {
871  a_ctx.emplace(a_stats_->weapon->specials_context(nullptr, nullptr, a_.loc_, d_.loc_, true, d_stats_->weapon));
872  } else {
873  a_ctx.emplace(a_stats_->weapon->specials_context(nullptr, a_.loc_, true));
874  }
875  a_stats_->weapon->write(a_weapon_cfg);
876  }
877 
878  if(d_stats_->weapon != nullptr && d_.valid()) {
879  if(a_stats_->weapon != nullptr && a_.valid()) {
880  d_ctx.emplace(d_stats_->weapon->specials_context(nullptr, nullptr, d_.loc_, a_.loc_, false, a_stats_->weapon));
881  } else {
882  d_ctx.emplace(d_stats_->weapon->specials_context(nullptr, d_.loc_, false));
883  }
884  d_stats_->weapon->write(d_weapon_cfg);
885  }
886 
887  if(a_weapon_cfg["name"].empty()) {
888  a_weapon_cfg["name"] = "none";
889  }
890 
891  if(d_weapon_cfg["name"].empty()) {
892  d_weapon_cfg["name"] = "none";
893  }
894 
895  if(n == "attack_end") {
896  // We want to fire attack_end event in any case! Even if one of units was removed by WML.
897  resources::game_events->pump().fire(n, a_.loc_, d_.loc_, ev_data);
898  return;
899  }
900 
901  // damage_inflicted is set in these events.
902  // TODO: should we set this value from unit_info::damage, or continue using the WML variable?
903  if(n == "attacker_hits" || n == "defender_hits" || n == "unit_hits") {
904  ev_data["damage_inflicted"] = resources::gamedata->get_variable("damage_inflicted");
905  }
906 
907  const int defender_side = d_.get_unit().side();
908 
909  bool wml_aborted;
910  std::tie(std::ignore, wml_aborted) = resources::game_events->pump().fire(n,
911  game_events::entity_location(reverse ? d_.loc_ : a_.loc_, reverse ? d_.id_ : a_.id_),
912  game_events::entity_location(reverse ? a_.loc_ : d_.loc_, reverse ? a_.id_ : d_.id_), ev_data);
913 
914  // The event could have killed either the attacker or
915  // defender, so we have to make sure they still exist.
916  refresh_bc();
917 
918  if(wml_aborted || !a_.valid() || !d_.valid()
919  || !resources::gameboard->get_team(a_.get_unit().side()).is_enemy(d_.get_unit().side())
920  ) {
921  actions::recalculate_fog(defender_side);
922 
923  if(update_display_) {
925  }
926 
927  fire_event("attack_end");
928  throw attack_end_exception();
929  }
930 }
931 
932 void attack::refresh_bc()
933 {
934  // Fix index of weapons.
935  if(a_.valid()) {
936  refresh_weapon_index(a_.weapon_, a_.weap_id_, a_.get_unit().attacks());
937  }
938 
939  if(d_.valid()) {
940  refresh_weapon_index(d_.weapon_, d_.weap_id_, d_.get_unit().attacks());
941  }
942 
943  if(!a_.valid() || !d_.valid()) {
944  // Fix pointer to weapons.
945  const_cast<battle_context_unit_stats*>(a_stats_)->weapon
946  = a_.valid() && a_.weapon_ >= 0 ? a_.get_unit().attacks()[a_.weapon_].shared_from_this() : nullptr;
947 
948  const_cast<battle_context_unit_stats*>(d_stats_)->weapon
949  = d_.valid() && d_.weapon_ >= 0 ? d_.get_unit().attacks()[d_.weapon_].shared_from_this() : nullptr;
950 
951  return;
952  }
953 
954  bc_.reset(new battle_context(units_, a_.loc_, d_.loc_, a_.weapon_, d_.weapon_));
955 
956  a_stats_ = &bc_->get_attacker_stats();
957  d_stats_ = &bc_->get_defender_stats();
958 
959  a_.cth_ = a_stats_->chance_to_hit;
960  d_.cth_ = d_stats_->chance_to_hit;
961  a_.damage_ = a_stats_->damage;
962  d_.damage_ = d_stats_->damage;
963 }
964 
965 bool attack::perform_hit(bool attacker_turn, statistics_attack_context& stats)
966 {
967  unit_info& attacker = attacker_turn ? a_ : d_;
968  unit_info& defender = attacker_turn ? d_ : a_;
969 
970  // NOTE: we need to use a reference-to-pointer here so a_stats_ and d_stats_ can be
971  // modified without. Using a pointer directly would render them invalid when that happened.
972  const battle_context_unit_stats*& attacker_stats = attacker_turn ? a_stats_ : d_stats_;
973  const battle_context_unit_stats*& defender_stats = attacker_turn ? d_stats_ : a_stats_;
974 
975  int& abs_n = attacker_turn ? abs_n_attack_ : abs_n_defend_;
976  bool& update_fog = attacker_turn ? update_def_fog_ : update_att_fog_;
977 
978  int ran_num;
979 
980  if(use_prng_) {
981 
982  std::vector<bool>& prng_seq = attacker_turn ? prng_attacker_ : prng_defender_;
983 
984  if(prng_seq.empty()) {
985  const int ntotal = attacker.cth_*attacker.n_attacks_;
986  int num_hits = ntotal/100;
987  const int additional_hit_chance = ntotal%100;
988  if(additional_hit_chance > 0 && randomness::generator->get_random_int(0, 99) < additional_hit_chance) {
989  ++num_hits;
990  }
991 
992  std::vector<int> indexes;
993  for(int i = 0; i != attacker.n_attacks_; ++i) {
994  prng_seq.push_back(false);
995  indexes.push_back(i);
996  }
997 
998  for(int i = 0; i != num_hits; ++i) {
999  int n = randomness::generator->get_random_int(0, static_cast<int>(indexes.size())-1);
1000  prng_seq[indexes[n]] = true;
1001  indexes.erase(indexes.begin() + n);
1002  }
1003  }
1004 
1005  bool does_hit = prng_seq.back();
1006  prng_seq.pop_back();
1007  ran_num = does_hit ? 0 : 99;
1008  } else {
1009  ran_num = randomness::generator->get_random_int(0, 99);
1010  }
1011  bool hits = (ran_num < attacker.cth_);
1012 
1013  int damage = 0;
1014  if(hits) {
1015  damage = attacker.damage_;
1016  resources::gamedata->get_variable("damage_inflicted") = damage;
1017  }
1018 
1019  // Make sure that if we're serializing a game here,
1020  // we got the same results as the game did originally.
1021  const config local_results {"chance", attacker.cth_, "hits", hits, "damage", damage};
1022 
1023  config replay_results;
1024  bool equals_replay = checkup_instance->local_checkup(local_results, replay_results);
1025 
1026  if(!equals_replay) {
1027  check_replay_attack_result(hits, ran_num, damage, replay_results, attacker);
1028  }
1029 
1030  // can do no more damage than the defender has hitpoints
1031  int damage_done = std::min<int>(defender.get_unit().hitpoints(), attacker.damage_);
1032 
1033  // expected damage = damage potential * chance to hit (as a percentage)
1034  double expected_damage = damage_done * attacker.cth_ * 0.01;
1035 
1036  if(attacker_turn) {
1037  stats.attack_expected_damage(expected_damage, 0);
1038  } else {
1039  stats.attack_expected_damage(0, expected_damage);
1040  }
1041 
1042  int drains_damage = 0;
1043  if(hits && attacker_stats->drains) {
1044  drains_damage = damage_done * attacker_stats->drain_percent / 100 + attacker_stats->drain_constant;
1045 
1046  // don't drain so much that the attacker gets more than his maximum hitpoints
1047  drains_damage =
1048  std::min<int>(drains_damage, attacker.get_unit().max_hitpoints() - attacker.get_unit().hitpoints());
1049 
1050  // if drain is negative, don't allow drain to kill the attacker
1051  drains_damage = std::max<int>(drains_damage, 1 - attacker.get_unit().hitpoints());
1052  }
1053 
1054  if(update_display_) {
1055  std::ostringstream float_text;
1056  std::vector<std::string> extra_hit_sounds;
1057 
1058  if(hits) {
1059  const unit& defender_unit = defender.get_unit();
1060  if(attacker_stats->poisons && !defender_unit.get_state(unit::STATE_POISONED)) {
1061  float_text << (defender_unit.gender() == unit_race::FEMALE ? _("female^poisoned") : _("poisoned"))
1062  << '\n';
1063 
1064  extra_hit_sounds.push_back(game_config::sounds::status::poisoned);
1065  }
1066 
1067  if(attacker_stats->slows && !defender_unit.get_state(unit::STATE_SLOWED)) {
1068  float_text << (defender_unit.gender() == unit_race::FEMALE ? _("female^slowed") : _("slowed")) << '\n';
1069 
1070  extra_hit_sounds.push_back(game_config::sounds::status::slowed);
1071  }
1072 
1073  if(attacker_stats->petrifies) {
1074  float_text << (defender_unit.gender() == unit_race::FEMALE ? _("female^petrified") : _("petrified"))
1075  << '\n';
1076 
1077  extra_hit_sounds.push_back(game_config::sounds::status::petrified);
1078  }
1079  }
1080 
1084  attacker.loc_, defender.loc_,
1085  damage,
1086  *attacker_stats->weapon, defender_stats->weapon,
1087  abs_n, float_text.str(), drains_damage, "",
1088  &extra_hit_sounds, attacker_turn
1089  );
1090  }
1091 
1092  bool dies = defender.get_unit().take_hit(damage);
1093  LOG_NG << "defender took " << damage << (dies ? " and died\n" : "\n");
1094 
1095  if(attacker_turn) {
1096  stats.attack_result(hits
1097  ? (dies
1101  attacker.cth_, damage_done, drains_damage
1102  );
1103  } else {
1104  stats.defend_result(hits
1105  ? (dies
1109  attacker.cth_, damage_done, drains_damage
1110  );
1111  }
1112 
1113  replay_results.clear();
1114 
1115  // There was also a attribute cfg["unit_hit"] which was never used so i deleted.
1116  equals_replay = checkup_instance->local_checkup(config{"dies", dies}, replay_results);
1117 
1118  if(!equals_replay) {
1119  bool results_dies = replay_results["dies"].to_bool();
1120 
1121  errbuf_ << "SYNC: In attack " << a_.dump() << " vs " << d_.dump() << ": the data source says the "
1122  << (attacker_turn ? "defender" : "attacker") << ' ' << (results_dies ? "perished" : "survived")
1123  << " while in-game calculations show it " << (dies ? "perished" : "survived")
1124  << " (over-riding game calculations with data source results)\n";
1125 
1126  dies = results_dies;
1127 
1128  // Set hitpoints to 0 so later checks don't invalidate the death.
1129  if(results_dies) {
1130  defender.get_unit().set_hitpoints(0);
1131  }
1132 
1133  OOS_error_ = true;
1134  }
1135 
1136  if(hits) {
1137  try {
1138  fire_event(attacker_turn ? "attacker_hits" : "defender_hits");
1139  fire_event_impl("unit_hits", !attacker_turn);
1140  } catch(const attack_end_exception&) {
1141  refresh_bc();
1142  return false;
1143  }
1144  } else {
1145  try {
1146  fire_event(attacker_turn ? "attacker_misses" : "defender_misses");
1147  fire_event_impl("unit_misses", !attacker_turn);
1148  } catch(const attack_end_exception&) {
1149  refresh_bc();
1150  return false;
1151  }
1152  }
1153 
1154  refresh_bc();
1155 
1156  bool attacker_dies = false;
1157 
1158  if(drains_damage > 0) {
1159  attacker.get_unit().heal(drains_damage);
1160  } else if(drains_damage < 0) {
1161  attacker_dies = attacker.get_unit().take_hit(-drains_damage);
1162  }
1163 
1164  if(dies) {
1165  unit_killed(attacker, defender, attacker_stats, defender_stats, false);
1166  update_fog = true;
1167  }
1168 
1169  if(attacker_dies) {
1170  unit_killed(defender, attacker, defender_stats, attacker_stats, true);
1171  (attacker_turn ? update_att_fog_ : update_def_fog_) = true;
1172  }
1173 
1174  if(dies) {
1175  update_minimap_ = true;
1176  return false;
1177  }
1178 
1179  if(hits) {
1180  unit& defender_unit = defender.get_unit();
1181 
1182  if(attacker_stats->poisons && !defender_unit.get_state(unit::STATE_POISONED)) {
1183  defender_unit.set_state(unit::STATE_POISONED, true);
1184  LOG_NG << "defender poisoned";
1185  }
1186 
1187  if(attacker_stats->slows && !defender_unit.get_state(unit::STATE_SLOWED)) {
1188  defender_unit.set_state(unit::STATE_SLOWED, true);
1189  update_fog = true;
1190  defender.damage_ = defender_stats->slow_damage;
1191  LOG_NG << "defender slowed";
1192  }
1193 
1194  // If the defender is petrified, the fight stops immediately
1195  if(attacker_stats->petrifies) {
1196  defender_unit.set_state(unit::STATE_PETRIFIED, true);
1197  update_fog = true;
1198  attacker.n_attacks_ = 0;
1199  defender.n_attacks_ = -1; // Petrified.
1200  resources::game_events->pump().fire("petrified", defender.loc_, attacker.loc_);
1201  refresh_bc();
1202  }
1203  }
1204 
1205  // Delay until here so that poison and slow go through
1206  if(attacker_dies) {
1207  update_minimap_ = true;
1208  return false;
1209  }
1210 
1211  --attacker.n_attacks_;
1212 
1213  // If an event removed a unit's weapon, set number of remaining attacks to zero
1214  // for that unit, but let the other unit continue
1215  if (attacker_stats->weapon == nullptr){
1216  attacker.n_attacks_ = 0;
1217  attacker.orig_attacks_ = 0;
1218  }
1219  if (defender_stats->weapon == nullptr){
1220  defender.n_attacks_ = 0;
1221  defender.orig_attacks_ = 0;
1222  }
1223 
1224  return true;
1225 }
1226 
1227 void attack::unit_killed(unit_info& attacker,
1228  unit_info& defender,
1229  const battle_context_unit_stats*& attacker_stats,
1230  const battle_context_unit_stats*& defender_stats,
1231  bool drain_killed)
1232 {
1233  attacker.xp_ = game_config::kill_xp(defender.get_unit().level());
1234  defender.xp_ = 0;
1235 
1236  display::get_singleton()->invalidate(attacker.loc_);
1237 
1238  game_events::entity_location death_loc(defender.loc_, defender.id_);
1239  game_events::entity_location attacker_loc(attacker.loc_, attacker.id_);
1240 
1241  std::string undead_variation = defender.get_unit().undead_variation();
1242 
1243  fire_event("attack_end");
1244  refresh_bc();
1245 
1246  // Get weapon info for last_breath and die events.
1247  config dat;
1248  config a_weapon_cfg = attacker_stats->weapon && attacker.valid() ? attacker_stats->weapon->to_config() : config();
1249  config d_weapon_cfg = defender_stats->weapon && defender.valid() ? defender_stats->weapon->to_config() : config();
1250 
1251  if(a_weapon_cfg["name"].empty()) {
1252  a_weapon_cfg["name"] = "none";
1253  }
1254 
1255  if(d_weapon_cfg["name"].empty()) {
1256  d_weapon_cfg["name"] = "none";
1257  }
1258 
1259  dat.add_child("first", d_weapon_cfg);
1260  dat.add_child("second", a_weapon_cfg);
1261 
1262  resources::game_events->pump().fire("last_breath", death_loc, attacker_loc, dat);
1263  refresh_bc();
1264 
1265  // WML has invalidated the dying unit, abort.
1266  if(!defender.valid() || defender.get_unit().hitpoints() > 0) {
1267  return;
1268  }
1269 
1270  if(!attacker.valid()) {
1272  defender.loc_,
1273  defender.get_unit(),
1274  nullptr,
1275  defender_stats->weapon
1276  );
1277  } else {
1279  defender.loc_,
1280  defender.get_unit(),
1281  attacker_stats->weapon,
1282  defender_stats->weapon,
1283  attacker.loc_,
1284  attacker.get_unit_ptr()
1285  );
1286  }
1287 
1288  resources::game_events->pump().fire("die", death_loc, attacker_loc, dat);
1289  refresh_bc();
1290 
1291  if(!defender.valid() || defender.get_unit().hitpoints() > 0) {
1292  // WML has invalidated the dying unit, abort
1293  return;
1294  }
1295 
1296  units_.erase(defender.loc_);
1297  resources::whiteboard->on_kill_unit();
1298 
1299  // Plague units make new units on the target hex.
1300  if(attacker.valid() && attacker_stats->plagues && !drain_killed) {
1301  LOG_NG << "trying to reanimate " << attacker_stats->plague_type;
1302 
1303  if(const unit_type* reanimator = unit_types.find(attacker_stats->plague_type)) {
1304  LOG_NG << "found unit type:" << reanimator->id();
1305 
1306  unit_ptr newunit = unit::create(*reanimator, attacker.get_unit().side(), true, unit_race::MALE);
1307  newunit->set_attacks(0);
1308  newunit->set_movement(0, true);
1309  newunit->set_facing(map_location::get_opposite_dir(attacker.get_unit().facing()));
1310 
1311  // Apply variation
1312  if(undead_variation != "null") {
1313  config mod;
1314  config& variation = mod.add_child("effect");
1315  variation["apply_to"] = "variation";
1316  variation["name"] = undead_variation;
1317  newunit->add_modification("variation", mod);
1318  newunit->heal_fully();
1319  }
1320 
1321  newunit->set_location(death_loc);
1322  units_.insert(newunit);
1323 
1324  game_events::entity_location reanim_loc(defender.loc_, newunit->underlying_id());
1325  resources::game_events->pump().fire("unit_placed", reanim_loc);
1326 
1327  prefs::get().encountered_units().insert(newunit->type_id());
1328 
1329  if(update_display_) {
1330  display::get_singleton()->invalidate(death_loc);
1331  }
1332  }
1333  } else {
1334  LOG_NG << "unit not reanimated";
1335  }
1336 }
1337 
1338 void attack::perform()
1339 {
1340  // Stop the user from issuing any commands while the units are fighting.
1341  const events::command_disabler disable_commands;
1342 
1343  if(!a_.valid() || !d_.valid()) {
1344  return;
1345  }
1346 
1347  // no attack weapon => stop here and don't attack
1348  if(a_.weapon_ < 0) {
1349  a_.get_unit().set_attacks(a_.get_unit().attacks_left() - 1);
1350  a_.get_unit().set_movement(-1, true);
1351  return;
1352  }
1353 
1354  if(a_.get_unit().attacks_left() <= 0) {
1355  LOG_NG << "attack::perform(): not enough ap.";
1356  return;
1357  }
1358 
1359  bc_.reset(new battle_context(units_, a_.loc_, d_.loc_, a_.weapon_, d_.weapon_));
1360 
1361  a_stats_ = &bc_->get_attacker_stats();
1362  d_stats_ = &bc_->get_defender_stats();
1363 
1364  if(a_stats_->weapon) {
1365  a_.weap_id_ = a_stats_->weapon->id();
1366  }
1367 
1368  if(d_stats_->weapon) {
1369  d_.weap_id_ = d_stats_->weapon->id();
1370  }
1371 
1372  a_.get_unit().set_facing(a_.loc_.get_relative_dir(d_.loc_));
1373  d_.get_unit().set_facing(d_.loc_.get_relative_dir(a_.loc_));
1374 
1375  try {
1376  fire_event("pre_attack");
1377  } catch(const attack_end_exception&) {
1378  return;
1379  }
1380 
1381  VALIDATE(a_.weapon_ < static_cast<int>(a_.get_unit().attacks().size()),
1382  _("An invalid attacker weapon got selected."));
1383 
1384  a_.get_unit().set_attacks(a_.get_unit().attacks_left() - a_.get_unit().attacks()[a_.weapon_].attacks_used());
1385  a_.get_unit().set_movement(a_.get_unit().movement_left() - a_.get_unit().attacks()[a_.weapon_].movement_used(), true);
1386  a_.get_unit().set_state(unit::STATE_NOT_MOVED, false);
1387  a_.get_unit().set_resting(false);
1388  d_.get_unit().set_resting(false);
1389 
1390  // If the attacker was invisible, she isn't anymore!
1391  a_.get_unit().set_state(unit::STATE_UNCOVERED, true);
1392 
1393  if(a_stats_->disable) {
1394  LOG_NG << "attack::perform(): tried to attack with a disabled attack.";
1395  return;
1396  }
1397 
1398  try {
1399  fire_event("attack");
1400  } catch(const attack_end_exception&) {
1401  return;
1402  }
1403 
1404  DBG_NG << "getting attack statistics";
1405  statistics_attack_context attack_stats(resources::controller->statistics(),
1406  a_.get_unit(), d_.get_unit(), a_stats_->chance_to_hit, d_stats_->chance_to_hit);
1407 
1408  a_.orig_attacks_ = a_stats_->num_blows;
1409  d_.orig_attacks_ = d_stats_->num_blows;
1410  a_.n_attacks_ = a_.orig_attacks_;
1411  d_.n_attacks_ = d_.orig_attacks_;
1412  a_.xp_ = game_config::combat_xp(d_.get_unit().level());
1413  d_.xp_ = game_config::combat_xp(a_.get_unit().level());
1414 
1415  bool defender_strikes_first = (d_stats_->firststrike && !a_stats_->firststrike);
1416  unsigned int rounds = std::max<unsigned int>(a_stats_->rounds, d_stats_->rounds) - 1;
1417  const int defender_side = d_.get_unit().side();
1418 
1419  LOG_NG << "Fight: (" << a_.loc_ << ") vs (" << d_.loc_ << ") ATT: " << a_stats_->weapon->name() << " "
1420  << a_stats_->damage << "-" << a_stats_->num_blows << "(" << a_stats_->chance_to_hit
1421  << "%) vs DEF: " << (d_stats_->weapon ? d_stats_->weapon->name() : "none") << " " << d_stats_->damage << "-"
1422  << d_stats_->num_blows << "(" << d_stats_->chance_to_hit << "%)"
1423  << (defender_strikes_first ? " defender first-strike" : "");
1424 
1425  // Play the pre-fight animation
1426  unit_display::unit_draw_weapon(a_.loc_, a_.get_unit(), a_stats_->weapon, d_stats_->weapon, d_.loc_, d_.get_unit_ptr());
1427 
1428  while(true) {
1429  DBG_NG << "start of attack loop...";
1430  ++abs_n_attack_;
1431 
1432  if(a_.n_attacks_ > 0 && !defender_strikes_first) {
1433  if(!perform_hit(true, attack_stats)) {
1434  DBG_NG << "broke from attack loop on attacker turn";
1435  break;
1436  }
1437  }
1438 
1439  // If the defender got to strike first, they use it up here.
1440  defender_strikes_first = false;
1441  ++abs_n_defend_;
1442 
1443  if(d_.n_attacks_ > 0) {
1444  if(!perform_hit(false, attack_stats)) {
1445  DBG_NG << "broke from attack loop on defender turn";
1446  break;
1447  }
1448  }
1449 
1450  // Continue the fight to death; if one of the units got petrified,
1451  // either n_attacks or n_defends is -1
1452  if(rounds > 0 && d_.n_attacks_ == 0 && a_.n_attacks_ == 0) {
1453  a_.n_attacks_ = a_.orig_attacks_;
1454  d_.n_attacks_ = d_.orig_attacks_;
1455  --rounds;
1456  defender_strikes_first = (d_stats_->firststrike && !a_stats_->firststrike);
1457  }
1458 
1459  if(a_.n_attacks_ <= 0 && d_.n_attacks_ <= 0) {
1460  fire_event("attack_end");
1461  refresh_bc();
1462  break;
1463  }
1464  }
1465 
1466  // Set by attacker_hits and defender_hits and unit_hits events.
1467  resources::gamedata->clear_variable("damage_inflicted");
1468 
1469  if(update_def_fog_) {
1470  actions::recalculate_fog(defender_side);
1471  }
1472 
1473  // TODO: if we knew the viewing team, we could skip this display update
1474  if(update_minimap_ && update_display_) {
1476  }
1477 
1478  if(a_.valid()) {
1479  unit& u = a_.get_unit();
1480  u.anim_comp().set_standing();
1481  u.set_experience(u.experience() + a_.xp_);
1482  }
1483 
1484  if(d_.valid()) {
1485  unit& u = d_.get_unit();
1486  u.anim_comp().set_standing();
1487  u.set_experience(u.experience() + d_.xp_);
1488  }
1489 
1490  unit_display::unit_sheath_weapon(a_.loc_, a_.get_unit_ptr(), a_stats_->weapon, d_stats_->weapon,
1491  d_.loc_, d_.get_unit_ptr());
1492 
1493  if(update_display_) {
1496  display::get_singleton()->invalidate(d_.loc_);
1497  }
1498 
1499  if(OOS_error_) {
1500  replay::process_error(errbuf_.str());
1501  }
1502 }
1503 
1504 void attack::check_replay_attack_result(
1505  bool& hits, int ran_num, int& damage, config replay_results, unit_info& attacker)
1506 {
1507  int results_chance = replay_results["chance"];
1508  bool results_hits = replay_results["hits"].to_bool();
1509  int results_damage = replay_results["damage"];
1510 
1511 #if 0
1512  errbuf_ << "SYNC: In attack " << a_.dump() << " vs " << d_.dump()
1513  << " replay data differs from local calculated data:"
1514  << " chance to hit in data source: " << results_chance
1515  << " chance to hit in calculated: " << attacker.cth_
1516  << " chance to hit in data source: " << results_chance
1517  << " chance to hit in calculated: " << attacker.cth_
1518  ;
1519 
1520  attacker.cth_ = results_chance;
1521  hits = results_hits;
1522  damage = results_damage;
1523 
1524  OOS_error_ = true;
1525 #endif
1526 
1527  if(results_chance != attacker.cth_) {
1528  errbuf_ << "SYNC: In attack " << a_.dump() << " vs " << d_.dump()
1529  << ": chance to hit is inconsistent. Data source: " << results_chance
1530  << "; Calculation: " << attacker.cth_ << " (over-riding game calculations with data source results)\n";
1531  attacker.cth_ = results_chance;
1532  OOS_error_ = true;
1533  }
1534 
1535  if(results_hits != hits) {
1536  errbuf_ << "SYNC: In attack " << a_.dump() << " vs " << d_.dump() << ": the data source says the hit was "
1537  << (results_hits ? "successful" : "unsuccessful") << ", while in-game calculations say the hit was "
1538  << (hits ? "successful" : "unsuccessful") << " random number: " << ran_num << " = " << (ran_num % 100)
1539  << "/" << results_chance << " (over-riding game calculations with data source results)\n";
1540  hits = results_hits;
1541  OOS_error_ = true;
1542  }
1543 
1544  if(results_damage != damage) {
1545  errbuf_ << "SYNC: In attack " << a_.dump() << " vs " << d_.dump() << ": the data source says the hit did "
1546  << results_damage << " damage, while in-game calculations show the hit doing " << damage
1547  << " damage (over-riding game calculations with data source results)\n";
1548  damage = results_damage;
1549  OOS_error_ = true;
1550  }
1551 }
1552 } // end anonymous namespace
1553 
1554 
1555 // ==================================================================================
1556 // FREE-STANDING FUNCTIONS
1557 // ==================================================================================
1558 
1559 void attack_unit(const map_location& attacker,
1560  const map_location& defender,
1561  int attack_with,
1562  int defend_with,
1563  bool update_display)
1564 {
1565  attack dummy(attacker, defender, attack_with, defend_with, update_display);
1566  dummy.perform();
1567 }
1568 
1570  const map_location& defender,
1571  int attack_with,
1572  int defend_with,
1573  bool update_display)
1574 {
1575  attack_unit(attacker, defender, attack_with, defend_with, update_display);
1576 
1578  if(atku != resources::gameboard->units().end()) {
1580  }
1581 
1583  if(defu != resources::gameboard->units().end()) {
1585  }
1586 }
1587 
1588 int under_leadership(const unit &u, const map_location& loc, const_attack_ptr weapon, const_attack_ptr opp_weapon)
1589 {
1590  unit_ability_list abil = u.get_abilities_weapons("leadership", loc, weapon, opp_weapon);
1591  unit_abilities::effect leader_effect(abil, 0, nullptr, unit_abilities::EFFECT_CUMULABLE);
1592  return leader_effect.get_composite_value();
1593 }
1594 
1595 int combat_modifier(const unit_map& units,
1596  const gamemap& map,
1597  const map_location& loc,
1598  unit_alignments::type alignment,
1599  bool is_fearless)
1600 {
1601  const tod_manager& tod_m = *resources::tod_manager;
1602  const time_of_day& effective_tod = tod_m.get_illuminated_time_of_day(units, map, loc);
1603  return combat_modifier(effective_tod, alignment, is_fearless);
1604 }
1605 
1606 int combat_modifier(const time_of_day& effective_tod,
1607  unit_alignments::type alignment,
1608  bool is_fearless)
1609 {
1610  const tod_manager& tod_m = *resources::tod_manager;
1611  const int lawful_bonus = effective_tod.lawful_bonus;
1612  return generic_combat_modifier(lawful_bonus, alignment, is_fearless, tod_m.get_max_liminal_bonus());
1613 }
1614 
1615 int generic_combat_modifier(int lawful_bonus, unit_alignments::type alignment, bool is_fearless, int max_liminal_bonus)
1616 {
1617  int bonus;
1618 
1619  switch(alignment) {
1620  case unit_alignments::type::lawful:
1621  bonus = lawful_bonus;
1622  break;
1623  case unit_alignments::type::neutral:
1624  bonus = 0;
1625  break;
1626  case unit_alignments::type::chaotic:
1627  bonus = -lawful_bonus;
1628  break;
1629  case unit_alignments::type::liminal:
1630  bonus = max_liminal_bonus-std::abs(lawful_bonus);
1631  break;
1632  default:
1633  bonus = 0;
1634  }
1635 
1636  if(is_fearless) {
1637  bonus = std::max<int>(bonus, 0);
1638  }
1639 
1640  return bonus;
1641 }
1642 
1643 bool backstab_check(const map_location& attacker_loc,
1644  const map_location& defender_loc,
1645  const unit_map& units,
1646  const std::vector<team>& teams)
1647 {
1648  const unit_map::const_iterator defender = units.find(defender_loc);
1649  if(defender == units.end()) {
1650  return false; // No defender
1651  }
1652 
1653  const auto adj = get_adjacent_tiles(defender_loc);
1654  unsigned i;
1655 
1656  for(i = 0; i < adj.size(); ++i) {
1657  if(adj[i] == attacker_loc) {
1658  break;
1659  }
1660  }
1661 
1662  if(i >= 6) {
1663  return false; // Attack not from adjacent location
1664  }
1665 
1666  const unit_map::const_iterator opp = units.find(adj[(i + 3) % 6]);
1667 
1668  // No opposite unit.
1669  if(opp == units.end()) {
1670  return false;
1671  }
1672 
1673  if(opp->incapacitated()) {
1674  return false;
1675  }
1676 
1677  // If sides aren't valid teams, then they are enemies.
1678  if(std::size_t(defender->side() - 1) >= teams.size() || std::size_t(opp->side() - 1) >= teams.size()) {
1679  return true;
1680  }
1681 
1682  // Defender and opposite are enemies.
1683  if(teams[defender->side() - 1].is_enemy(opp->side())) {
1684  return true;
1685  }
1686 
1687  // Defender and opposite are friends.
1688  return false;
1689 }
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:1588
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:1615
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:1643
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:1595
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:1569
#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:1559
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:522
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:575
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
std::set< std::string > & encountered_units()
static prefs & get()
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: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: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:1398
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:1349
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:1796
attack_itors attacks()
Gets an iterator over this unit's attacks.
Definition: unit.hpp:927
unit_animation_component & anim_comp() const
Definition: unit.hpp:1599
bool is_fearless() const
Gets whether this unit is fearless - ie, unaffected by time of day.
Definition: unit.hpp:1279
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:278
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.
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.
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: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