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