The Battle for Wesnoth  1.19.14+dev
attack.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2025
3  by David White <dave@whitevine.net>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 /**
17  * @file
18  * Fighting.
19  */
20 
21 #include "actions/attack.hpp"
22 
23 #include <utility>
24 
25 #include "actions/advancement.hpp"
26 #include "actions/vision.hpp"
27 
28 #include "game_classification.hpp"
29 #include "game_config.hpp"
30 #include "game_data.hpp"
31 #include "game_events/pump.hpp"
32 #include "gettext.hpp"
33 #include "log.hpp"
34 #include "map/map.hpp"
35 #include "mouse_handler_base.hpp"
36 #include "play_controller.hpp"
38 #include "random.hpp"
39 #include "replay.hpp"
40 #include "resources.hpp"
41 #include "statistics.hpp"
42 #include "synced_checkup.hpp"
43 #include "team.hpp"
44 #include "tod_manager.hpp"
45 #include "units/abilities.hpp"
47 #include "units/map.hpp"
48 #include "units/udisplay.hpp"
49 #include "units/unit.hpp"
50 #include "units/types.hpp"
51 #include "utils/optional_fwd.hpp"
52 #include "whiteboard/manager.hpp"
53 #include "wml_exception.hpp"
54 
55 #include "utils/optional_fwd.hpp"
56 
57 static lg::log_domain log_engine("engine");
58 #define DBG_NG LOG_STREAM(debug, log_engine)
59 #define LOG_NG LOG_STREAM(info, log_engine)
60 #define WRN_NG LOG_STREAM(err, log_engine)
61 #define ERR_NG LOG_STREAM(err, log_engine)
62 
63 static lg::log_domain log_attack("engine/attack");
64 #define DBG_AT LOG_STREAM(debug, log_attack)
65 #define LOG_AT LOG_STREAM(info, log_attack)
66 #define WRN_AT LOG_STREAM(err, log_attack)
67 #define ERR_AT LOG_STREAM(err, log_attack)
68 
69 static lg::log_domain log_config("config");
70 #define LOG_CF LOG_STREAM(info, log_config)
71 
72 // ==================================================================================
73 // BATTLE CONTEXT UNIT STATS
74 // ==================================================================================
75 
77  const map_location& u_loc,
78  int u_attack_num,
79  bool attacking,
81  const map_location& opp_loc,
82  const const_attack_ptr& opp_weapon)
83  : weapon(nullptr)
84  , attack_num(u_attack_num)
85  , is_attacker(attacking)
86  , is_poisoned(up->get_state(unit::STATE_POISONED))
87  , is_slowed(up->get_state(unit::STATE_SLOWED))
88  , slows(false)
89  , drains(false)
90  , petrifies(false)
91  , plagues(false)
92  , poisons(false)
93  , swarm(false)
94  , firststrike(false)
95  , disable(false)
96  , experience(up->experience())
97  , max_experience(up->max_experience())
98  , level(up->level())
99  , rounds(1)
100  , hp(0)
101  , max_hp(up->max_hitpoints())
102  , chance_to_hit(0)
103  , damage(0)
104  , slow_damage(0)
105  , drain_percent(0)
106  , drain_constant(0)
107  , num_blows(0)
108  , swarm_min(0)
109  , swarm_max(0)
110  , plague_type()
111 {
112  const unit& u = *up;
113  const unit& opp = *oppp;
114  // Get the current state of the unit.
115  if(attack_num >= 0) {
116  weapon = u.attacks()[attack_num].shared_from_this();
117  }
118 
119  if(u.hitpoints() < 0) {
120  LOG_CF << "Unit with " << u.hitpoints() << " hitpoints found, set to 0 for damage calculations";
121  hp = 0;
122  } else if(u.hitpoints() > u.max_hitpoints()) {
123  // If a unit has more hp than its maximum, the engine will fail with an
124  // assertion failure due to accessing the prob_matrix out of bounds.
125  hp = u.max_hitpoints();
126  } else {
127  hp = u.hitpoints();
128  }
129 
130  // Exit if no weapon.
131  if(!weapon) {
132  return;
133  }
134 
135  // Get the weapon characteristics as appropriate.
136  auto ctx = weapon->specials_context(up, oppp, u_loc, opp_loc, attacking, opp_weapon);
137  utils::optional<decltype(ctx)> opp_ctx;
138 
139  if(opp_weapon) {
140  opp_ctx.emplace(opp_weapon->specials_context(oppp, up, opp_loc, u_loc, !attacking, weapon));
141  }
142 
143  slows = weapon->has_special_or_ability("slow") && !opp.get_state("unslowable") ;
144  drains = !opp.get_state("undrainable") && weapon->has_special_or_ability("drains");
145  petrifies = !opp.get_state("unpetrifiable") && weapon->has_special_or_ability("petrifies");
146  poisons = !opp.get_state("unpoisonable") && weapon->has_special_or_ability("poison") && !opp.get_state(unit::STATE_POISONED);
147  rounds = weapon->get_specials_and_abilities("berserk").highest("value", 1).first;
148 
149  firststrike = weapon->has_special_or_ability("firststrike");
150 
151  {
152  const int distance = distance_between(u_loc, opp_loc);
153  const bool out_of_range = distance > weapon->max_range() || distance < weapon->min_range();
154  disable = weapon->has_special_or_ability("disable") || out_of_range;
155  }
156 
157  // Handle plague.
158  unit_ability_list plague_specials = weapon->get_specials_and_abilities("plague");
159  plagues = !opp.get_state("unplagueable") && !plague_specials.empty() &&
160  opp.undead_variation() != "null" && !resources::gameboard->map().is_village(opp_loc);
161 
162  if(plagues) {
163  plague_type = (*plague_specials.front().ability_cfg)["type"].str();
164 
165  if(plague_type.empty()) {
166  plague_type = u.type().parent_id();
167  }
168  }
169 
170  // Compute chance to hit.
171  signed int cth = weapon->modified_chance_to_hit(opp.defense_modifier(resources::gameboard->map().get_terrain(opp_loc)));
172 
173  if(opp.get_state("invulnerable")) {
174  cth = 0;
175  }
176 
177  chance_to_hit = std::clamp(cth, 0, 100);
178 
179  // Compute base damage done with the weapon.
180  double base_damage = weapon->modified_damage();
181 
182  // Get the damage multiplier applied to the base damage of the weapon.
183  int damage_multiplier = 100;
184 
185  // Time of day bonus.
186  unit_alignments::type alignment = weapon->alignment().value_or(u.alignment());
187  damage_multiplier += combat_modifier(
188  resources::gameboard->units(), resources::gameboard->map(), u_loc, alignment, u.is_fearless());
189 
190  // Leadership bonus.
191  int leader_bonus = under_leadership(u, u_loc, weapon, opp_weapon);
192  if(leader_bonus != 0) {
193  damage_multiplier += leader_bonus;
194  }
195 
196  // Resistance modifier.
197  damage_multiplier *= opp.damage_from(*weapon, !attacking, opp_loc, opp_weapon);
198 
199  // Compute both the normal and slowed damage.
200  damage = round_damage(base_damage, damage_multiplier, 10000);
201  slow_damage = round_damage(base_damage, damage_multiplier, 20000);
202 
203  if(is_slowed) {
205  }
206 
207  // Compute drain amounts only if draining is possible.
208  if(drains) {
209  // Compute the drain percent (with 50% as the base for backward compatibility)
210  drain_percent = weapon->composite_value(weapon->get_specials_and_abilities("drains"), 50);
211  }
212 
213  // Add heal_on_hit (the drain constant)
214  drain_constant += weapon->composite_value(weapon->get_specials_and_abilities("heal_on_hit"), 0);
215 
217 
218  // Compute the number of blows and handle swarm.
219  weapon->modified_attacks(swarm_min, swarm_max);
222 }
223 
225  const_attack_ptr att_weapon,
226  bool attacking,
227  const unit_type* opp_type,
228  const const_attack_ptr& opp_weapon,
229  unsigned int opp_terrain_defense,
230  int lawful_bonus)
231  : weapon(std::move(att_weapon))
232  , attack_num(-2) // This is and stays invalid. Always use weapon when using this constructor.
233  , is_attacker(attacking)
234  , is_poisoned(false)
235  , is_slowed(false)
236  , slows(false)
237  , drains(false)
238  , petrifies(false)
239  , plagues(false)
240  , poisons(false)
241  , swarm(false)
242  , firststrike(false)
243  , disable(false)
244  , experience(0)
245  , max_experience(0)
246  , level(0)
247  , rounds(1)
248  , hp(0)
249  , max_hp(0)
250  , chance_to_hit(0)
251  , damage(0)
252  , slow_damage(0)
253  , drain_percent(0)
254  , drain_constant(0)
255  , num_blows(0)
256  , swarm_min(0)
257  , swarm_max(0)
258  , plague_type()
259 {
260  if(!u_type || !opp_type) {
261  return;
262  }
263 
264  // Get the current state of the unit.
265  if(u_type->hitpoints() < 0) {
266  hp = 0;
267  } else {
268  hp = u_type->hitpoints();
269  }
270 
271  max_experience = u_type->experience_needed();
272  level = (u_type->level());
273  max_hp = (u_type->hitpoints());
274 
275  // Exit if no weapon.
276  if(!weapon) {
277  return;
278  }
279 
280  // Get the weapon characteristics as appropriate.
281  auto ctx = weapon->specials_context(*u_type, map_location::null_location(), attacking);
282  utils::optional<decltype(ctx)> opp_ctx;
283 
284  if(opp_weapon) {
285  opp_ctx.emplace(opp_weapon->specials_context(*opp_type, map_location::null_location(), !attacking));
286  }
287 
288  slows = weapon->has_special("slow");
289  drains = !opp_type->musthave_status("undrainable") && weapon->has_special("drains");
290  petrifies = weapon->has_special("petrifies");
291  poisons = !opp_type->musthave_status("unpoisonable") && weapon->has_special("poison");
292  rounds = weapon->get_specials("berserk").highest("value", 1).first;
293  firststrike = weapon->has_special("firststrike");
294  disable = weapon->has_special("disable");
295 
296  unit_ability_list plague_specials = weapon->get_specials("plague");
297  plagues = !opp_type->musthave_status("unplagueable") && !plague_specials.empty() &&
298  opp_type->undead_variation() != "null";
299 
300  if(plagues) {
301  plague_type = (*plague_specials.front().ability_cfg)["type"].str();
302  if(plague_type.empty()) {
303  plague_type = u_type->parent_id();
304  }
305  }
306 
307  signed int cth = weapon->modified_chance_to_hit(100 - opp_terrain_defense, true);
308 
309  chance_to_hit = std::clamp(cth, 0, 100);
310 
311  double base_damage = weapon->modified_damage();
312  int damage_multiplier = 100;
313  unit_alignments::type alignment = weapon->alignment().value_or(u_type->alignment());
314  damage_multiplier
315  += generic_combat_modifier(lawful_bonus, alignment, u_type->musthave_status("fearless"), 0);
316  damage_multiplier *= opp_type->resistance_against(weapon->type(), !attacking);
317 
318  damage = round_damage(base_damage, damage_multiplier, 10000);
319  slow_damage = round_damage(base_damage, damage_multiplier, 20000);
320 
321  if(drains) {
322  // Compute the drain percent (with 50% as the base for backward compatibility)
323  drain_percent = weapon->composite_value(weapon->get_specials("drains"), 50);
324  }
325 
326  // Add heal_on_hit (the drain constant)
327  drain_constant += weapon->composite_value(weapon->get_specials("heal_on_hit"), 0);
328 
330 
331  // Compute the number of blows and handle swarm.
332  weapon->modified_attacks(swarm_min, swarm_max);
335 }
336 
337 
338 // ==================================================================================
339 // BATTLE CONTEXT
340 // ==================================================================================
341 
343  nonempty_unit_const_ptr attacker,
344  const map_location& a_loc,
345  int a_wep_index,
346  nonempty_unit_const_ptr defender,
347  const map_location& d_loc,
348  int d_wep_index)
349  : attacker_stats_()
350  , defender_stats_()
351  , attacker_combatant_()
352  , defender_combatant_()
353 {
354  std::size_t a_wep_uindex = static_cast<std::size_t>(a_wep_index);
355  std::size_t d_wep_uindex = static_cast<std::size_t>(d_wep_index);
356 
357  const_attack_ptr a_wep(a_wep_uindex < attacker->attacks().size() ? attacker->attacks()[a_wep_index].shared_from_this() : nullptr);
358  const_attack_ptr d_wep(d_wep_uindex < defender->attacks().size() ? defender->attacks()[d_wep_index].shared_from_this() : nullptr);
359 
360  attacker_stats_.reset(new battle_context_unit_stats(attacker, a_loc, a_wep_index, true , defender, d_loc, d_wep));
361  defender_stats_.reset(new battle_context_unit_stats(defender, d_loc, d_wep_index, false, attacker, a_loc, a_wep));
362 }
363 
364 void battle_context::simulate(const combatant* prev_def)
365 {
366  assert((attacker_combatant_.get() != nullptr) == (defender_combatant_.get() != nullptr));
367  assert(attacker_stats_);
368  assert(defender_stats_);
369  if(!attacker_combatant_) {
371  defender_combatant_.reset(new combatant(*defender_stats_, prev_def));
373  }
374 }
375 
376 // more like a factory method than a constructor, always calls one of the other constructors.
378  const map_location& attacker_loc,
379  const map_location& defender_loc,
380  int attacker_weapon,
381  int defender_weapon,
382  double aggression,
383  const combatant* prev_def,
384  unit_const_ptr attacker,
385  unit_const_ptr defender)
386  : attacker_stats_(nullptr)
387  , defender_stats_(nullptr)
388  , attacker_combatant_(nullptr)
389  , defender_combatant_(nullptr)
390 {
391  //TODO: maybe check before dereferencing units.find(attacker_loc),units.find(defender_loc) ?
392  if(!attacker) {
393  attacker = units.find(attacker_loc).get_shared_ptr();
394  }
395  if(!defender) {
396  defender = units.find(defender_loc).get_shared_ptr();
397  }
398  nonempty_unit_const_ptr n_attacker { attacker };
399  nonempty_unit_const_ptr n_defender { defender };
400 
401  const double harm_weight = 1.0 - aggression;
402 
403  if(attacker_weapon == -1) {
404  *this = choose_attacker_weapon(
405  n_attacker, n_defender, attacker_loc, defender_loc, harm_weight, prev_def
406  );
407  }
408  else if(defender_weapon == -1) {
409  *this = choose_defender_weapon(
410  n_attacker, n_defender, attacker_weapon, attacker_loc, defender_loc, prev_def
411  );
412  }
413  else {
414  *this = battle_context(n_attacker, attacker_loc, attacker_weapon, n_defender, defender_loc, defender_weapon);
415  }
416 
417  assert(attacker_stats_);
418  assert(defender_stats_);
419 }
420 
422  : attacker_stats_(new battle_context_unit_stats(att))
423  , defender_stats_(new battle_context_unit_stats(def))
424  , attacker_combatant_(nullptr)
425  , defender_combatant_(nullptr)
426 {
427 }
428 
429 
430 /** @todo FIXME: better to initialize combatant initially (move into
431  battle_context_unit_stats?), just do fight() when required. */
433 {
434  // We calculate this lazily, since AI doesn't always need it.
435  simulate(prev_def);
436  return *attacker_combatant_;
437 }
438 
440 {
441  // We calculate this lazily, since AI doesn't always need it.
442  simulate(prev_def);
443  return *defender_combatant_;
444 }
445 
446 // Given this harm_weight, are we better than that other context?
447 bool battle_context::better_attack(class battle_context& that, double harm_weight)
448 {
449  return better_combat(
452  that.get_attacker_combatant(),
453  that.get_defender_combatant(),
454  harm_weight
455  );
456 }
457 
458 // Given this harm_weight, are we better than that other context?
459 bool battle_context::better_defense(class battle_context& that, double harm_weight)
460 {
461  return better_combat(
464  that.get_defender_combatant(),
465  that.get_attacker_combatant(),
466  harm_weight
467  );
468 }
469 
470 // Does combat A give us a better result than combat B?
472  const combatant& them_a,
473  const combatant& us_b,
474  const combatant& them_b,
475  double harm_weight)
476 {
477  double a, b;
478 
479  // Compare: P(we kill them) - P(they kill us).
480  a = them_a.hp_dist[0] - us_a.hp_dist[0] * harm_weight;
481  b = them_b.hp_dist[0] - us_b.hp_dist[0] * harm_weight;
482 
483  if(a - b < -0.01) {
484  return false;
485  }
486 
487  if(a - b > 0.01) {
488  return true;
489  }
490 
491  // Add poison to calculations, but poison bonus should only be applied if the unit survives
492  double poison_a_us = us_a.poisoned > 0 ? (us_a.poisoned - us_a.hp_dist[0]) * game_config::poison_amount : 0;
493  double poison_a_them = them_a.poisoned > 0 ? (them_a.poisoned - them_a.hp_dist[0]) * game_config::poison_amount : 0;
494  double poison_b_us = us_b.poisoned > 0 ? (us_b.poisoned - us_b.hp_dist[0]) * game_config::poison_amount : 0;
495  double poison_b_them = them_b.poisoned > 0 ? (them_b.poisoned - them_b.hp_dist[0]) * game_config::poison_amount : 0;
496 
497  double attack_weight_a = us_a.u_.weapon->attack_weight();
498  double attack_weight_b = us_b.u_.weapon->attack_weight();
499  double damage_a = (them_a.u_.hp - them_a.average_hp()) * attack_weight_a;
500  double damage_b = (them_b.u_.hp - them_b.average_hp()) * attack_weight_b;
501 
502  // Compare: damage to them - damage to us (average_hp replaces -damage)
503  a = (us_a.average_hp() - poison_a_us) * harm_weight + damage_a + poison_a_them;
504  b = (us_b.average_hp() - poison_b_us) * harm_weight + damage_b + poison_b_them;
505 
506  if(a - b < -0.01) {
507  return false;
508  }
509 
510  if(a - b > 0.01) {
511  return true;
512  }
513 
514  // All else equal: go for most damage.
515  return damage_a >= damage_b;
516 }
517 
519  const nonempty_unit_const_ptr& defender,
520  const map_location& attacker_loc,
521  const map_location& defender_loc,
522  double harm_weight,
523  const combatant* prev_def)
524 {
525  log_scope2(log_attack, "choose_attacker_weapon");
526  std::vector<battle_context> choices;
527 
528  // What options does attacker have?
529  for(std::size_t i = 0; i < attacker->attacks().size(); ++i) {
530  const attack_type& att = attacker->attacks()[i];
531 
532  if(att.attack_weight() <= 0) {
533  continue;
534  }
535  battle_context bc = choose_defender_weapon(attacker, defender, i, attacker_loc, defender_loc, prev_def);
536  //choose_defender_weapon will always choose the weapon that disabels the attackers weapon if possible.
537  if(bc.attacker_stats_->disable) {
538  continue;
539  }
540  choices.emplace_back(std::move(bc));
541  }
542 
543  if(choices.empty()) {
544  return battle_context(attacker, attacker_loc, -1, defender, defender_loc, -1);
545  }
546 
547  if(choices.size() == 1) {
548  return std::move(choices[0]);
549  }
550 
551  // Multiple options: simulate them, save best.
552  battle_context* best_choice = nullptr;
553  for(auto& choice : choices) {
554  // If choose_defender_weapon didn't simulate, do so now.
555  choice.simulate(prev_def);
556 
557  if(!best_choice || choice.better_attack(*best_choice, harm_weight)) {
558  best_choice = &choice;
559  }
560  }
561 
562  if(best_choice) {
563  return std::move(*best_choice);
564  }
565  else {
566  return battle_context(attacker, attacker_loc, -1, defender, defender_loc, -1);
567  }
568 }
569 
570 /** @todo FIXME: Hand previous defender unit in here. */
572  nonempty_unit_const_ptr defender,
573  unsigned attacker_weapon,
574  const map_location& attacker_loc,
575  const map_location& defender_loc,
576  const combatant* prev_def)
577 {
578  log_scope2(log_attack, "choose_defender_weapon");
579  VALIDATE(attacker_weapon < attacker->attacks().size(), _("An invalid attacker weapon got selected."));
580 
581  const attack_type& att = attacker->attacks()[attacker_weapon];
582  auto no_weapon = [&]() { return battle_context(attacker, attacker_loc, attacker_weapon, defender, defender_loc, -1); };
583  std::vector<battle_context> choices;
584 
585  // What options does defender have?
586  for(std::size_t i = 0; i < defender->attacks().size(); ++i) {
587  const attack_type& def = defender->attacks()[i];
588  if(def.range() != att.range() || def.defense_weight() <= 0) {
589  //no need to calculate the battle_context here.
590  continue;
591  }
592  battle_context bc(attacker, attacker_loc, attacker_weapon, defender, defender_loc, i);
593 
594  if(bc.defender_stats_->disable) {
595  continue;
596  }
597  if(bc.attacker_stats_->disable) {
598  //the defenders attack disables the attakers attack: always choose this one.
599  return bc;
600  }
601  choices.emplace_back(std::move(bc));
602  }
603 
604  if(choices.empty()) {
605  return no_weapon();
606  }
607 
608  if(choices.size() == 1) {
609  //only one usable weapon, don't simulate
610  return std::move(choices[0]);
611  }
612 
613  // Multiple options:
614  // First pass : get the best weight and the minimum simple rating for this weight.
615  // simple rating = number of blows * damage per blows (resistance taken in account) * cth * weight
616  // Eligible attacks for defense should have a simple rating greater or equal to this weight.
617 
618  int min_rating = 0;
619  {
620  double max_weight = 0.0;
621 
622  for(const auto& choice : choices) {
623  const attack_type& def = defender->attacks()[choice.defender_stats_->attack_num];
624 
625  if(def.defense_weight() >= max_weight) {
626  const battle_context_unit_stats& def_stats = *choice.defender_stats_;
627 
628  max_weight = def.defense_weight();
629  int rating = static_cast<int>(
630  def_stats.num_blows * def_stats.damage * def_stats.chance_to_hit * def.defense_weight());
631 
632  if(def.defense_weight() > max_weight || rating < min_rating) {
633  min_rating = rating;
634  }
635  }
636  }
637  }
638 
639  battle_context* best_choice = nullptr;
640  // Multiple options: simulate them, save best.
641  for(auto& choice : choices) {
642  const attack_type& def = defender->attacks()[choice.defender_stats_->attack_num];
643 
644  choice.simulate(prev_def);
645 
646 
647  int simple_rating = static_cast<int>(
648  choice.defender_stats_->num_blows * choice.defender_stats_->damage * choice.defender_stats_->chance_to_hit * def.defense_weight());
649 
650  //FIXME: make sure there is no mostake in the better_combat call-
651  if(simple_rating >= min_rating && (!best_choice || choice.better_defense(*best_choice, 1.0))) {
652  best_choice = &choice;
653  }
654  }
655 
656  return best_choice ? std::move(*best_choice) : no_weapon();
657 }
658 
659 
660 // ==================================================================================
661 // HELPERS
662 // ==================================================================================
663 
664 namespace
665 {
666 void refresh_weapon_index(int& weap_index, const std::string& weap_id, const attack_itors& attacks)
667 {
668  // No attacks to choose from.
669  if(attacks.empty()) {
670  weap_index = -1;
671  return;
672  }
673 
674  // The currently selected attack fits.
675  if(weap_index >= 0 && weap_index < static_cast<int>(attacks.size()) && attacks[weap_index].id() == weap_id) {
676  return;
677  }
678 
679  // Look up the weapon by id.
680  if(!weap_id.empty()) {
681  for(int i = 0; i < static_cast<int>(attacks.size()); ++i) {
682  if(attacks[i].id() == weap_id) {
683  weap_index = i;
684  return;
685  }
686  }
687  }
688 
689  // Lookup has failed.
690  weap_index = -1;
691  return;
692 }
693 
694 /** Helper class for performing an attack. */
695 class attack
696 {
697 public:
698  attack(const map_location& attacker,
699  const map_location& defender,
700  int attack_with,
701  int defend_with,
702  bool update_display = true);
703 
704  void perform();
705 
706 private:
707  class attack_end_exception
708  {
709  };
710 
711  bool perform_hit(bool, statistics_attack_context&);
712  void fire_event(const std::string& n);
713  void fire_event_impl(const std::string& n, bool reversed);
714  void refresh_bc();
715 
716  /** Structure holding unit info used in the attack action. */
717  struct unit_info
718  {
719  const map_location loc_;
720  int weapon_;
721  unit_map& units_;
722  std::size_t id_; /**< unit.underlying_id() */
723  std::string weap_id_;
724  int orig_attacks_;
725  int n_attacks_; /**< Number of attacks left. */
726  int cth_;
727  int damage_;
728  int xp_;
729 
730  unit_info(const map_location& loc, int weapon, unit_map& units);
731  unit& get_unit();
732  unit_ptr get_unit_ptr();
733  bool valid();
734 
735  std::string dump();
736  };
737 
738  /**
739  * Used in perform_hit to confirm a replay is in sync.
740  * Check OOS_error_ after this method, true if error detected.
741  */
742  void check_replay_attack_result(bool&, int, int&, config, unit_info&);
743 
744  void unit_killed(
745  unit_info&, unit_info&, const battle_context_unit_stats*&, const battle_context_unit_stats*&, bool);
746 
747  std::unique_ptr<battle_context> bc_;
748 
749  const battle_context_unit_stats* a_stats_;
750  const battle_context_unit_stats* d_stats_;
751 
752  int abs_n_attack_, abs_n_defend_;
753  // update_att_fog_ is not used, other than making some code simpler.
754  bool update_att_fog_, update_def_fog_, update_minimap_;
755 
756  unit_info a_, d_;
757  unit_map& units_;
758  std::ostringstream errbuf_;
759 
760  bool update_display_;
761  bool OOS_error_;
762 
763  bool use_prng_;
764 
765  std::vector<bool> prng_attacker_;
766  std::vector<bool> prng_defender_;
767 };
768 
769 attack::unit_info::unit_info(const map_location& loc, int weapon, unit_map& units)
770  : loc_(loc)
771  , weapon_(weapon)
772  , units_(units)
773  , id_()
774  , weap_id_()
775  , orig_attacks_(0)
776  , n_attacks_(0)
777  , cth_(0)
778  , damage_(0)
779  , xp_(0)
780 {
781  unit_map::iterator i = units_.find(loc_);
782  if(!i.valid()) {
783  return;
784  }
785 
786  id_ = i->underlying_id();
787 }
788 
789 unit& attack::unit_info::get_unit()
790 {
791  unit_map::iterator i = units_.find(loc_);
792  assert(i.valid() && i->underlying_id() == id_);
793  return *i;
794 }
795 
796 unit_ptr attack::unit_info::get_unit_ptr()
797 {
798  unit_map::iterator i = units_.find(loc_);
799  if(i.valid() && i->underlying_id() == id_) {
800  return i.get_shared_ptr();
801  }
802  return unit_ptr();
803 }
804 
805 bool attack::unit_info::valid()
806 {
807  unit_map::iterator i = units_.find(loc_);
808  return i.valid() && i->underlying_id() == id_;
809 }
810 
811 std::string attack::unit_info::dump()
812 {
813  std::stringstream s;
814  s << get_unit().type_id() << " (" << loc_.wml_x() << ',' << loc_.wml_y() << ')';
815  return s.str();
816 }
817 
818 attack::attack(const map_location& attacker,
819  const map_location& defender,
820  int attack_with,
821  int defend_with,
822  bool update_display)
823  : bc_(nullptr)
824  , a_stats_(nullptr)
825  , d_stats_(nullptr)
826  , abs_n_attack_(0)
827  , abs_n_defend_(0)
828  , update_att_fog_(false)
829  , update_def_fog_(false)
830  , update_minimap_(false)
831  , a_(attacker, attack_with, resources::gameboard->units())
832  , d_(defender, defend_with, resources::gameboard->units())
833  , units_(resources::gameboard->units())
834  , errbuf_()
835  , update_display_(update_display)
836  , OOS_error_(false)
837 
838  //new experimental prng mode.
839  , use_prng_(resources::classification->random_mode == "biased" && randomness::generator->is_networked() == false)
840  , prng_attacker_()
841  , prng_defender_()
842 {
843  if(use_prng_) {
844  LOG_NG << "Using experimental PRNG for combat";
845  }
846 }
847 
848 void attack::fire_event(const std::string& n)
849 {
850  fire_event_impl(n, false);
851 }
852 
853 void attack::fire_event_impl(const std::string& n, bool reverse)
854 {
855  LOG_NG << "attack: firing '" << n << "' event";
856 
857  // prepare the event data for weapon filtering
858  config ev_data;
859  config& a_weapon_cfg = ev_data.add_child(reverse ? "second" : "first");
860  config& d_weapon_cfg = ev_data.add_child(reverse ? "first" : "second");
861 
862  // Need these to ensure weapon filters work correctly
863  utils::optional<attack_type::specials_context_t> a_ctx, d_ctx;
864 
865  if(a_stats_->weapon != nullptr && a_.valid()) {
866  if(d_stats_->weapon != nullptr && d_.valid()) {
867  a_ctx.emplace(a_stats_->weapon->specials_context(nullptr, nullptr, a_.loc_, d_.loc_, true, d_stats_->weapon));
868  } else {
869  a_ctx.emplace(a_stats_->weapon->specials_context(nullptr, a_.loc_, true));
870  }
871  a_stats_->weapon->write(a_weapon_cfg);
872  }
873 
874  if(d_stats_->weapon != nullptr && d_.valid()) {
875  if(a_stats_->weapon != nullptr && a_.valid()) {
876  d_ctx.emplace(d_stats_->weapon->specials_context(nullptr, nullptr, d_.loc_, a_.loc_, false, a_stats_->weapon));
877  } else {
878  d_ctx.emplace(d_stats_->weapon->specials_context(nullptr, d_.loc_, false));
879  }
880  d_stats_->weapon->write(d_weapon_cfg);
881  }
882 
883  if(a_weapon_cfg["name"].empty()) {
884  a_weapon_cfg["name"] = "none";
885  }
886 
887  if(d_weapon_cfg["name"].empty()) {
888  d_weapon_cfg["name"] = "none";
889  }
890 
891  if(n == "attack_end") {
892  // We want to fire attack_end event in any case! Even if one of units was removed by WML.
893  resources::game_events->pump().fire(n, a_.loc_, d_.loc_, ev_data);
894  return;
895  }
896 
897  // damage_inflicted is set in these events.
898  // TODO: should we set this value from unit_info::damage, or continue using the WML variable?
899  if(n == "attacker_hits" || n == "defender_hits" || n == "unit_hits") {
900  ev_data["damage_inflicted"] = resources::gamedata->get_variable("damage_inflicted");
901  }
902 
903  const int defender_side = d_.get_unit().side();
904 
905  bool wml_aborted;
906  std::tie(std::ignore, wml_aborted) = resources::game_events->pump().fire(n,
907  game_events::entity_location(reverse ? d_.loc_ : a_.loc_, reverse ? d_.id_ : a_.id_),
908  game_events::entity_location(reverse ? a_.loc_ : d_.loc_, reverse ? a_.id_ : d_.id_), ev_data);
909 
910  // The event could have killed either the attacker or
911  // defender, so we have to make sure they still exist.
912  refresh_bc();
913 
914  if(wml_aborted || !a_.valid() || !d_.valid()
915  || !resources::gameboard->get_team(a_.get_unit().side()).is_enemy(d_.get_unit().side())
916  ) {
917  actions::recalculate_fog(defender_side);
918 
919  if(update_display_) {
921  }
922 
923  fire_event("attack_end");
924  throw attack_end_exception();
925  }
926 }
927 
928 void attack::refresh_bc()
929 {
930  // Fix index of weapons.
931  if(a_.valid()) {
932  refresh_weapon_index(a_.weapon_, a_.weap_id_, a_.get_unit().attacks());
933  }
934 
935  if(d_.valid()) {
936  refresh_weapon_index(d_.weapon_, d_.weap_id_, d_.get_unit().attacks());
937  }
938 
939  if(!a_.valid() || !d_.valid()) {
940  // Fix pointer to weapons.
941  const_cast<battle_context_unit_stats*>(a_stats_)->weapon
942  = a_.valid() && a_.weapon_ >= 0 ? a_.get_unit().attacks()[a_.weapon_].shared_from_this() : nullptr;
943 
944  const_cast<battle_context_unit_stats*>(d_stats_)->weapon
945  = d_.valid() && d_.weapon_ >= 0 ? d_.get_unit().attacks()[d_.weapon_].shared_from_this() : nullptr;
946 
947  return;
948  }
949 
950  bc_.reset(new battle_context(units_, a_.loc_, d_.loc_, a_.weapon_, d_.weapon_));
951 
952  a_stats_ = &bc_->get_attacker_stats();
953  d_stats_ = &bc_->get_defender_stats();
954 
955  a_.cth_ = a_stats_->chance_to_hit;
956  d_.cth_ = d_stats_->chance_to_hit;
957  a_.damage_ = a_stats_->damage;
958  d_.damage_ = d_stats_->damage;
959 }
960 
961 bool attack::perform_hit(bool attacker_turn, statistics_attack_context& stats)
962 {
963  unit_info& attacker = attacker_turn ? a_ : d_;
964  unit_info& defender = attacker_turn ? d_ : a_;
965 
966  // NOTE: we need to use a reference-to-pointer here so a_stats_ and d_stats_ can be
967  // modified without. Using a pointer directly would render them invalid when that happened.
968  const battle_context_unit_stats*& attacker_stats = attacker_turn ? a_stats_ : d_stats_;
969  const battle_context_unit_stats*& defender_stats = attacker_turn ? d_stats_ : a_stats_;
970 
971  int& abs_n = attacker_turn ? abs_n_attack_ : abs_n_defend_;
972  bool& update_fog = attacker_turn ? update_def_fog_ : update_att_fog_;
973 
974  int ran_num;
975 
976  if(use_prng_) {
977 
978  std::vector<bool>& prng_seq = attacker_turn ? prng_attacker_ : prng_defender_;
979 
980  if(prng_seq.empty()) {
981  const int ntotal = attacker.cth_*attacker.n_attacks_;
982  int num_hits = ntotal/100;
983  const int additional_hit_chance = ntotal%100;
984  if(additional_hit_chance > 0 && randomness::generator->get_random_int(0, 99) < additional_hit_chance) {
985  ++num_hits;
986  }
987 
988  std::vector<int> indexes;
989  for(int i = 0; i != attacker.n_attacks_; ++i) {
990  prng_seq.push_back(false);
991  indexes.push_back(i);
992  }
993 
994  for(int i = 0; i != num_hits; ++i) {
995  int n = randomness::generator->get_random_int(0, static_cast<int>(indexes.size())-1);
996  prng_seq[indexes[n]] = true;
997  indexes.erase(indexes.begin() + n);
998  }
999  }
1000 
1001  bool does_hit = prng_seq.back();
1002  prng_seq.pop_back();
1003  ran_num = does_hit ? 0 : 99;
1004  } else {
1005  ran_num = randomness::generator->get_random_int(0, 99);
1006  }
1007  bool hits = (ran_num < attacker.cth_);
1008 
1009  int damage = 0;
1010  if(hits) {
1011  damage = attacker.damage_;
1012  resources::gamedata->get_variable("damage_inflicted") = damage;
1013  }
1014 
1015  // Make sure that if we're serializing a game here,
1016  // we got the same results as the game did originally.
1017  const config local_results {"chance", attacker.cth_, "hits", hits, "damage", damage};
1018 
1019  config replay_results;
1020  bool equals_replay = checkup_instance->local_checkup(local_results, replay_results);
1021 
1022  if(!equals_replay) {
1023  check_replay_attack_result(hits, ran_num, damage, replay_results, attacker);
1024  }
1025 
1026  // can do no more damage than the defender has hitpoints
1027  int damage_done = std::min<int>(defender.get_unit().hitpoints(), attacker.damage_);
1028 
1029  // expected damage = damage potential * chance to hit (as a percentage)
1030  double expected_damage = damage_done * attacker.cth_ * 0.01;
1031 
1032  if(attacker_turn) {
1033  stats.attack_expected_damage(expected_damage, 0);
1034  } else {
1035  stats.attack_expected_damage(0, expected_damage);
1036  }
1037 
1038  int drains_damage = 0;
1039  if(hits && attacker_stats->drains) {
1040  drains_damage = damage_done * attacker_stats->drain_percent / 100 + attacker_stats->drain_constant;
1041 
1042  // don't drain so much that the attacker gets more than his maximum hitpoints
1043  drains_damage =
1044  std::min<int>(drains_damage, attacker.get_unit().max_hitpoints() - attacker.get_unit().hitpoints());
1045 
1046  // if drain is negative, don't allow drain to kill the attacker
1047  drains_damage = std::max<int>(drains_damage, 1 - attacker.get_unit().hitpoints());
1048  }
1049 
1050  if(update_display_) {
1051  std::ostringstream float_text;
1052  std::vector<std::string> extra_hit_sounds;
1053 
1054  if(hits) {
1055  const unit& defender_unit = defender.get_unit();
1056  if(attacker_stats->poisons && !defender_unit.get_state(unit::STATE_POISONED)) {
1057  float_text << (defender_unit.gender() == unit_race::FEMALE ? _("female^poisoned") : _("poisoned"))
1058  << '\n';
1059 
1060  extra_hit_sounds.push_back(game_config::sounds::status::poisoned);
1061  }
1062 
1063  if(attacker_stats->slows && !defender_unit.get_state(unit::STATE_SLOWED)) {
1064  float_text << (defender_unit.gender() == unit_race::FEMALE ? _("female^slowed") : _("slowed")) << '\n';
1065 
1066  extra_hit_sounds.push_back(game_config::sounds::status::slowed);
1067  }
1068 
1069  if(attacker_stats->petrifies) {
1070  float_text << (defender_unit.gender() == unit_race::FEMALE ? _("female^petrified") : _("petrified"))
1071  << '\n';
1072 
1073  extra_hit_sounds.push_back(game_config::sounds::status::petrified);
1074  }
1075  } else {
1076  if(prefs::get().show_attack_miss_indicator()) {
1077  float_text << _("attack^miss");
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_direction(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"].to_int();
1508  bool results_hits = replay_results["hits"].to_bool();
1509  int results_damage = replay_results["damage"].to_int();
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, std::move(weapon), std::move(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:58
#define LOG_CF
Definition: attack.cpp:70
#define LOG_NG
Definition: attack.cpp:59
void attack_unit(const map_location &attacker, const map_location &defender, int attack_with, int defend_with, bool update_display)
Performs an attack.
Definition: attack.cpp:1559
static lg::log_domain log_attack("engine/attack")
static lg::log_domain log_config("config")
Various functions that implement attacks and attack calculations.
map_location loc
Definition: move.cpp:172
void advance_unit_at(const advance_unit_params &params)
Various functions that implement advancements of units.
static auto & dummy
boost::iterator_range< boost::indirect_iterator< attack_list::iterator > > attack_itors
double defense_weight() const
Definition: attack_type.hpp:56
double attack_weight() const
Definition: attack_type.hpp:55
const std::string & range() const
Definition: attack_type.hpp:46
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
static battle_context choose_attacker_weapon(nonempty_unit_const_ptr attacker, const nonempty_unit_const_ptr &defender, const map_location &attacker_loc, const map_location &defender_loc, double harm_weight, const combatant *prev_def)
Definition: attack.cpp:518
std::unique_ptr< combatant > attacker_combatant_
Outcome of simulated fight.
Definition: attack.hpp:248
void simulate(const combatant *prev_def)
Definition: attack.cpp:364
const combatant & get_attacker_combatant(const combatant *prev_def=nullptr)
Get the simulation results.
Definition: attack.cpp:432
const combatant & get_defender_combatant(const combatant *prev_def=nullptr)
Definition: attack.cpp:439
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:471
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:571
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:377
bool better_defense(class battle_context &that, double harm_weight)
Given this harm_weight, is this attack better than that?
Definition: attack.cpp:459
bool better_attack(class battle_context &that, double harm_weight)
Given this harm_weight, is this attack better than that?
Definition: attack.cpp:447
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:158
void insert(config_key_type key, T &&value)
Inserts an attribute into the config.
Definition: config.hpp:520
void clear()
Definition: config.cpp:818
config & add_child(config_key_type key)
Definition: config.cpp:436
void redraw_minimap()
Schedule the minimap to be redrawn.
Definition: display.cpp:1476
bool invalidate(const map_location &loc)
Function to invalidate a specific tile for redrawing.
Definition: display.cpp:2960
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:92
virtual const unit_map & units() const override
Definition: game_board.hpp:107
virtual const gamemap & map() const override
Definition: game_board.hpp:97
void clear_variable(const std::string &varname)
Clears attributes config children does nothing if varname is no valid variable name.
Definition: game_data.cpp:118
config::attribute_value & get_variable(const std::string &varname)
throws invalid_variablename_exception if varname is no valid variable name.
Definition: game_data.cpp:66
void invalidate_unit()
Function to invalidate that unit status displayed on the sidebar.
static game_display * get_singleton()
game_events::wml_event_pump & pump()
Definition: manager.cpp:257
pump_result_t fire(const std::string &event, const entity_location &loc1=entity_location::null_entity, const entity_location &loc2=entity_location::null_entity, const config &data=config())
Function to fire an event.
Definition: pump.cpp:399
terrain_code get_terrain(const map_location &loc) const
Looks up terrain at a particular location.
Definition: map.cpp:301
Encapsulates the map of the game.
Definition: map.hpp:172
bool is_village(const map_location &loc) const
Definition: map.cpp:66
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:185
bool is_enemy(int n) const
Definition: team.hpp:267
int get_max_liminal_bonus() const
time_of_day get_illuminated_time_of_day(const unit_map &units, const gamemap &map, const map_location &loc, int for_turn=0) const
Returns time of day object for the passed turn at a location.
int get_composite_value() const
Definition: abilities.hpp:59
unit_ability & front()
Definition: unit.hpp:90
bool empty() const
Definition: unit.hpp:89
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:1224
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:148
int hitpoints() const
Definition: types.hpp:164
int resistance_against(const std::string &damage_name, bool attacker) const
Gets resistance while considering custom WML abilities.
Definition: types.cpp:732
int experience_needed(bool with_acceleration=true) const
Definition: types.cpp:533
bool musthave_status(const std::string &status) const
Definition: types.cpp:628
const std::string & undead_variation() const
Info on the type of unit that the unit reanimates as.
Definition: types.hpp:136
int level() const
Definition: types.hpp:167
unit_alignments::type alignment() const
Definition: types.hpp:196
This class represents a single unit of a specific type.
Definition: unit.hpp:132
static unit_ptr create(const config &cfg, bool use_traits=false, const vconfig *vcfg=nullptr)
Initializes a unit from a config.
Definition: unit.hpp:200
variant a_
Definition: function.cpp:821
std::size_t i
Definition: function.cpp:1032
static std::string _(const char *str)
Definition: gettext.hpp:97
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:293
int max_hitpoints() const
The max number of hitpoints this unit can have.
Definition: unit.hpp:520
unit_alignments::type alignment() const
The alignment of this unit.
Definition: unit.hpp:490
void set_state(const std::string &state, bool value)
Set whether the unit is affected by a status effect.
Definition: unit.cpp:1482
int hitpoints() const
The current number of hitpoints this unit has.
Definition: unit.hpp:514
bool get_state(const std::string &state) const
Check if the unit is affected by a status effect.
Definition: unit.cpp:1423
const std::string & undead_variation() const
Definition: unit.hpp:598
const unit_type & type() const
This unit's type, accounting for gender and variation.
Definition: unit.hpp:354
int experience() const
The current number of experience points this unit has.
Definition: unit.hpp:538
void set_experience(int xp)
Sets the current experience point amount.
Definition: unit.hpp:568
unit_race::GENDER gender() const
The gender of this unit.
Definition: unit.hpp:480
@ STATE_SLOWED
Definition: unit.hpp:868
@ STATE_NOT_MOVED
The unit is uncovered - it was hiding but has been spotted.
Definition: unit.hpp:872
@ STATE_PETRIFIED
The unit is poisoned - it loses health each turn.
Definition: unit.hpp:870
@ STATE_POISONED
The unit is slowed - it moves slower and does less damage.
Definition: unit.hpp:869
@ STATE_UNCOVERED
The unit is petrified - it cannot move or be attacked.
Definition: unit.hpp:871
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:986
int defense_modifier(const t_translation::terrain_code &terrain) const
The unit's defense on a given terrain.
Definition: unit.cpp:1754
attack_itors attacks()
Gets an iterator over this unit's attacks.
Definition: unit.hpp:941
unit_animation_component & anim_comp() const
Definition: unit.hpp:1641
bool is_fearless() const
Gets whether this unit is fearless - ie, unaffected by time of day.
Definition: unit.hpp:1302
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:583
void get_adjacent_tiles(const map_location &a, utils::span< map_location, 6 > res)
Function which, given a location, will place all adjacent locations in res.
Definition: location.cpp:512
Standard logging facilities (interface).
#define log_scope2(domain, description)
Definition: log.hpp:276
constexpr int round_damage(double base_damage, int bonus, int divisor)
round (base_damage * bonus / divisor) to the closest integer, but up or down towards base_damage
Definition: math.hpp:80
void recalculate_fog(int side)
Function that recalculates the fog of war.
Definition: vision.cpp:697
int kill_xp(int level)
Definition: game_config.hpp:48
int combat_xp(int level)
Definition: game_config.hpp:53
bool fire_event(const ui_event event, const std::vector< std::pair< widget *, ui_event >> &event_chain, widget *dispatcher, widget *w, F &&... params)
Helper function for fire_event.
rng * generator
This generator is automatically synced during synced context.
Definition: random.cpp:60
::tod_manager * tod_manager
Definition: resources.cpp:29
game_board * gameboard
Definition: resources.cpp:20
game_data * gamedata
Definition: resources.cpp:22
game_events::manager * game_events
Definition: resources.cpp:24
game_classification * classification
Definition: resources.cpp:34
play_controller * controller
Definition: resources.cpp:21
std::shared_ptr< wb::manager > whiteboard
Definition: resources.cpp:33
void unit_die(const map_location &loc, unit &loser, const const_attack_ptr &attack, const const_attack_ptr &secondary_attack, const map_location &winner_loc, const unit_ptr &winner)
Show a unit fading out.
Definition: udisplay.cpp:574
void unit_draw_weapon(const map_location &loc, unit &attacker, const const_attack_ptr &attack, const const_attack_ptr &secondary_attack, const map_location &defender_loc, const unit_ptr &defender)
Play a pre-fight animation First unit is the attacker, second unit the defender.
Definition: udisplay.cpp:524
void unit_attack(display *disp, game_board &board, const map_location &a, const map_location &b, int damage, const attack_type &attack, const const_attack_ptr &secondary_attack, int swing, const std::string &hit_text, int drain_amount, const std::string &att_text, const std::vector< std::string > *extra_hit_sounds, bool attacking)
Make the unit on tile 'a' attack the unit on tile 'b'.
Definition: udisplay.cpp:601
void unit_sheath_weapon(const map_location &primary_loc, const unit_ptr &primary_unit, const const_attack_ptr &primary_attack, const const_attack_ptr &secondary_attack, const map_location &secondary_loc, const unit_ptr &secondary_unit)
Play a post-fight animation Both unit can be set to null, only valid units will play their animation.
Definition: udisplay.cpp:544
std::size_t size(std::string_view str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:85
constexpr auto reverse
Definition: ranges.hpp:40
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
battle_context_unit_stats(nonempty_unit_const_ptr u, const map_location &u_loc, int u_attack_num, bool attacking, nonempty_unit_const_ptr opp, const map_location &opp_loc, const const_attack_ptr &opp_weapon)
Definition: attack.cpp:76
bool petrifies
Attack petrifies opponent when it hits.
Definition: attack.hpp:59
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:46
static constexpr direction get_opposite_direction(direction d)
Definition: location.hpp:76
static const map_location & null_location()
Definition: location.hpp:103
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:58
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:1463
Display units performing various actions: moving, attacking, and dying.
Various functions implementing vision (through fog of war and shroud).
Add a special kind of assert to validate whether the input from WML doesn't contain any problems that...
#define VALIDATE(cond, message)
The macro to use for the validation of WML.
#define b