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