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