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