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