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