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