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