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