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