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