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