58 #define DBG_NG LOG_STREAM(debug, log_engine)
59 #define LOG_NG LOG_STREAM(info, log_engine)
60 #define WRN_NG LOG_STREAM(err, log_engine)
61 #define ERR_NG LOG_STREAM(err, log_engine)
64 #define DBG_AT LOG_STREAM(debug, log_attack)
65 #define LOG_AT LOG_STREAM(info, log_attack)
66 #define WRN_AT LOG_STREAM(err, log_attack)
67 #define ERR_AT LOG_STREAM(err, log_attack)
70 #define LOG_CF LOG_STREAM(info, log_config)
83 utils::optional<int> opp_terrain_defense,
84 utils::optional<int> lawful_bonus
87 , attack_num(u_attack_num)
88 , is_attacker(attacking)
89 , is_poisoned(up->get_state(
unit::STATE_POISONED))
90 , is_slowed(up->get_state(
unit::STATE_SLOWED))
100 , experience(up->experience())
101 , max_experience(up->max_experience())
105 , max_hp(up->max_hitpoints())
117 const unit& opp = *oppp;
124 LOG_CF <<
"Unit with " << u.
hitpoints() <<
" hitpoints found, set to 0 for damage calculations";
146 rounds =
weapon->get_specials_and_abilities(
"berserk").highest(
"value", 1).first;
152 const bool out_of_range = distance >
weapon->max_range() || distance <
weapon->min_range();
153 disable =
weapon->has_special_or_ability(
"disable") || out_of_range;
172 signed int cth =
weapon->modified_chance_to_hit(opp_base_cth);
181 double base_damage =
weapon->modified_damage();
184 int damage_multiplier = 100;
205 const auto [damage_type, resistance_modifier] =
weapon->effective_damage_type();
206 damage_multiplier *= resistance_modifier;
247 , attacker_combatant_()
248 , defender_combatant_()
250 std::size_t a_wep_uindex =
static_cast<std::size_t
>(a_wep_index);
251 std::size_t d_wep_uindex =
static_cast<std::size_t
>(d_wep_index);
282 : attacker_stats_(nullptr)
283 , defender_stats_(nullptr)
284 , attacker_combatant_(nullptr)
285 , defender_combatant_(nullptr)
297 const double harm_weight = 1.0 - aggression;
299 if(attacker_weapon == -1) {
301 n_attacker, n_defender, attacker_loc, defender_loc, harm_weight, prev_def
304 else if(defender_weapon == -1) {
306 n_attacker, n_defender, attacker_weapon, attacker_loc, defender_loc, prev_def
310 *
this =
battle_context(n_attacker, attacker_loc, attacker_weapon, n_defender, defender_loc, defender_weapon);
320 , attacker_combatant_(nullptr)
321 , defender_combatant_(nullptr)
393 double attack_weight_a = us_a.
u_.
weapon->attack_weight();
394 double attack_weight_b = us_b.
u_.
weapon->attack_weight();
395 double damage_a = (them_a.
u_.
hp - them_a.
average_hp()) * attack_weight_a;
396 double damage_b = (them_b.
u_.
hp - them_b.
average_hp()) * attack_weight_b;
399 a = (us_a.
average_hp() - poison_a_us) * harm_weight + damage_a + poison_a_them;
400 b = (us_b.
average_hp() - poison_b_us) * harm_weight + damage_b + poison_b_them;
411 return damage_a >= damage_b;
422 std::vector<battle_context> choices;
425 const auto attacks = attacker->
attacks();
426 for(std::size_t
i = 0;
i < attacks.size(); ++
i) {
437 choices.emplace_back(std::move(bc));
440 if(choices.empty()) {
441 return battle_context(attacker, attacker_loc, -1, defender, defender_loc, -1);
444 if(choices.size() == 1) {
445 return std::move(choices[0]);
450 for(
auto& choice : choices) {
454 if(!best_choice || choice.
better_attack(*best_choice, harm_weight)) {
455 best_choice = &choice;
460 return std::move(*best_choice);
463 return battle_context(attacker, attacker_loc, -1, defender, defender_loc, -1);
470 unsigned attacker_weapon,
476 const auto attackers_attacks = attacker->
attacks();
477 VALIDATE(attacker_weapon < attackers_attacks.size(),
_(
"An invalid attacker weapon got selected."));
479 const attack_type& att = attackers_attacks[attacker_weapon];
480 auto no_weapon = [&]() {
return battle_context(attacker, attacker_loc, attacker_weapon, defender, defender_loc, -1); };
481 std::vector<battle_context> choices;
484 const auto defenses = defender->
attacks();
485 for(std::size_t
i = 0;
i < defenses.size(); ++
i) {
491 battle_context bc(attacker, attacker_loc, attacker_weapon, defender, defender_loc,
i);
500 choices.emplace_back(std::move(bc));
503 if(choices.empty()) {
507 if(choices.size() == 1) {
509 return std::move(choices[0]);
519 double max_weight = 0.0;
521 for(
const auto& choice : choices) {
522 const double d_weight = choice.defender_stats_->weapon->defense_weight();
524 if(d_weight >= max_weight) {
527 max_weight = d_weight;
528 int rating =
static_cast<int>(
531 if(d_weight > max_weight || rating < min_rating) {
540 for(
auto& choice : choices) {
541 const double d_weight = choice.
defender_stats_->weapon->defense_weight();
543 choice.simulate(prev_def);
546 int simple_rating =
static_cast<int>(
547 choice.defender_stats_->num_blows * choice.defender_stats_->damage * choice.defender_stats_->chance_to_hit * d_weight);
550 if(simple_rating >= min_rating && (!best_choice || choice.
better_defense(*best_choice, 1.0))) {
551 best_choice = &choice;
555 return best_choice ? std::move(*best_choice) : no_weapon();
565 void refresh_weapon_index(
int& weap_index,
const std::string& weap_id,
const attack_itors& attacks)
568 if(attacks.empty()) {
574 if(weap_index >= 0 && weap_index <
static_cast<int>(attacks.size()) && attacks[weap_index].id() == weap_id) {
579 if(!weap_id.empty()) {
580 for(
int i = 0; i < static_cast<int>(attacks.size()); ++
i) {
581 if(attacks[
i].
id() == weap_id) {
601 bool update_display =
true);
606 class attack_end_exception
612 void fire_event_impl(
const std::string&
n,
bool reversed);
622 std::string weap_id_;
641 void check_replay_attack_result(
bool&,
int,
int&,
config, unit_info&);
646 std::unique_ptr<battle_context> bc_;
651 int abs_n_attack_, abs_n_defend_;
653 bool update_att_fog_, update_def_fog_, update_minimap_;
657 std::ostringstream errbuf_;
659 bool update_display_;
664 std::vector<bool> prng_attacker_;
665 std::vector<bool> prng_defender_;
685 id_ =
i->underlying_id();
688 unit& attack::unit_info::get_unit()
691 assert(
i.valid() &&
i->underlying_id() == id_);
695 unit_ptr attack::unit_info::get_unit_ptr()
698 if(
i.valid() &&
i->underlying_id() == id_) {
699 return i.get_shared_ptr();
704 bool attack::unit_info::valid()
707 return i.valid() &&
i->underlying_id() == id_;
710 std::string attack::unit_info::dump()
713 s << get_unit().type_id() <<
" (" << loc_.wml_x() <<
',' << loc_.wml_y() <<
')';
727 , update_att_fog_(false)
728 , update_def_fog_(false)
729 , update_minimap_(false)
734 , update_display_(update_display)
743 LOG_NG <<
"Using experimental PRNG for combat";
749 fire_event_impl(
n,
false);
752 void attack::fire_event_impl(
const std::string&
n,
bool reverse)
754 LOG_NG <<
"attack: firing '" <<
n <<
"' event";
766 if(a_stats_->weapon !=
nullptr &&
a_.valid()) {
767 a_stats_->weapon->write(a_weapon_cfg);
770 if(d_stats_->weapon !=
nullptr && d_.valid()) {
771 d_stats_->weapon->write(d_weapon_cfg);
774 if(a_weapon_cfg[
"name"].empty()) {
775 a_weapon_cfg[
"name"] =
"none";
778 if(d_weapon_cfg[
"name"].empty()) {
779 d_weapon_cfg[
"name"] =
"none";
782 if(
n ==
"attack_end") {
790 if(
n ==
"attacker_hits" ||
n ==
"defender_hits" ||
n ==
"unit_hits") {
794 const int defender_side = d_.get_unit().side();
805 if(wml_aborted || !
a_.valid() || !d_.valid()
810 if(update_display_) {
815 throw attack_end_exception();
819 void attack::refresh_bc()
823 refresh_weapon_index(
a_.weapon_,
a_.weap_id_,
a_.get_unit().attacks());
827 refresh_weapon_index(d_.weapon_, d_.weap_id_, d_.get_unit().attacks());
830 if(!
a_.valid() || !d_.valid()) {
833 =
a_.valid() &&
a_.weapon_ >= 0 ?
a_.get_unit().attacks()[
a_.weapon_].shared_from_this() :
nullptr;
836 = d_.valid() && d_.weapon_ >= 0 ? d_.get_unit().attacks()[d_.weapon_].shared_from_this() :
nullptr;
843 a_stats_ = &bc_->get_attacker_stats();
844 d_stats_ = &bc_->get_defender_stats();
846 a_.cth_ = a_stats_->chance_to_hit;
847 d_.cth_ = d_stats_->chance_to_hit;
848 a_.damage_ = a_stats_->damage;
849 d_.damage_ = d_stats_->damage;
854 unit_info& attacker = attacker_turn ?
a_ : d_;
855 unit_info& defender = attacker_turn ? d_ :
a_;
862 int& abs_n = attacker_turn ? abs_n_attack_ : abs_n_defend_;
863 bool& update_fog = attacker_turn ? update_def_fog_ : update_att_fog_;
869 std::vector<bool>& prng_seq = attacker_turn ? prng_attacker_ : prng_defender_;
871 if(prng_seq.empty()) {
872 const int ntotal = attacker.cth_*attacker.n_attacks_;
873 int num_hits = ntotal/100;
874 const int additional_hit_chance = ntotal%100;
875 if(additional_hit_chance > 0 &&
randomness::generator->get_random_int(0, 99) < additional_hit_chance) {
879 std::vector<int> indexes;
880 for(
int i = 0;
i != attacker.n_attacks_; ++
i) {
881 prng_seq.push_back(
false);
882 indexes.push_back(
i);
885 for(
int i = 0;
i != num_hits; ++
i) {
887 prng_seq[indexes[
n]] =
true;
888 indexes.erase(indexes.begin() +
n);
892 bool does_hit = prng_seq.back();
894 ran_num = does_hit ? 0 : 99;
898 bool hits = (ran_num < attacker.cth_);
902 damage = attacker.damage_;
908 const config local_results {
"chance", attacker.cth_,
"hits", hits,
"damage", damage};
914 check_replay_attack_result(hits, ran_num, damage, replay_results, attacker);
918 int damage_done = std::min<int>(defender.get_unit().hitpoints(), attacker.damage_);
921 double expected_damage = damage_done * attacker.cth_ * 0.01;
929 int drains_damage = 0;
930 if(hits && attacker_stats->
drains) {
935 std::min<int>(drains_damage, attacker.get_unit().max_hitpoints() - attacker.get_unit().hitpoints());
938 drains_damage = std::max<int>(drains_damage, 1 - attacker.get_unit().hitpoints());
941 if(update_display_) {
942 std::ostringstream float_text;
943 std::vector<std::string> extra_hit_sounds;
946 const unit& defender_unit = defender.get_unit();
967 if(
prefs::get().show_attack_miss_indicator()) {
968 float_text <<
_(
"attack^miss");
975 attacker.loc_, defender.loc_,
978 abs_n, float_text.str(), drains_damage,
"",
979 &extra_hit_sounds, attacker_turn
983 bool dies = defender.get_unit().take_hit(damage);
984 LOG_NG <<
"defender took " << damage << (dies ?
" and died\n" :
"\n");
992 attacker.cth_, damage_done, drains_damage
1000 attacker.cth_, damage_done, drains_damage
1004 replay_results.
clear();
1009 if(!equals_replay) {
1010 bool results_dies = replay_results[
"dies"].to_bool();
1012 errbuf_ <<
"SYNC: In attack " <<
a_.dump() <<
" vs " << d_.dump() <<
": the data source says the "
1013 << (attacker_turn ?
"defender" :
"attacker") <<
' ' << (results_dies ?
"perished" :
"survived")
1014 <<
" while in-game calculations show it " << (dies ?
"perished" :
"survived")
1015 <<
" (over-riding game calculations with data source results)\n";
1017 dies = results_dies;
1021 defender.get_unit().set_hitpoints(0);
1029 fire_event(attacker_turn ?
"attacker_hits" :
"defender_hits");
1030 fire_event_impl(
"unit_hits", !attacker_turn);
1031 }
catch(
const attack_end_exception&) {
1037 fire_event(attacker_turn ?
"attacker_misses" :
"defender_misses");
1038 fire_event_impl(
"unit_misses", !attacker_turn);
1039 }
catch(
const attack_end_exception&) {
1047 bool attacker_dies =
false;
1049 if(drains_damage > 0) {
1050 attacker.get_unit().heal(drains_damage);
1051 }
else if(drains_damage < 0) {
1052 attacker_dies = attacker.get_unit().take_hit(-drains_damage);
1056 unit_killed(attacker, defender, attacker_stats, defender_stats,
false);
1061 unit_killed(defender, attacker, defender_stats, attacker_stats,
true);
1062 (attacker_turn ? update_att_fog_ : update_def_fog_) =
true;
1066 update_minimap_ =
true;
1071 unit& defender_unit = defender.get_unit();
1075 LOG_NG <<
"defender poisoned";
1082 LOG_NG <<
"defender slowed";
1089 attacker.n_attacks_ = 0;
1090 defender.n_attacks_ = -1;
1098 update_minimap_ =
true;
1102 --attacker.n_attacks_;
1106 if (attacker_stats->
weapon ==
nullptr){
1107 attacker.n_attacks_ = 0;
1108 attacker.orig_attacks_ = 0;
1110 if (defender_stats->
weapon ==
nullptr){
1111 defender.n_attacks_ = 0;
1112 defender.orig_attacks_ = 0;
1118 void attack::unit_killed(unit_info& attacker,
1119 unit_info& defender,
1132 std::string undead_variation = defender.get_unit().undead_variation();
1142 if(a_weapon_cfg[
"name"].empty()) {
1143 a_weapon_cfg[
"name"] =
"none";
1146 if(d_weapon_cfg[
"name"].empty()) {
1147 d_weapon_cfg[
"name"] =
"none";
1157 if(!defender.valid() || defender.get_unit().hitpoints() > 0) {
1161 if(!attacker.valid()) {
1164 defender.get_unit(),
1171 defender.get_unit(),
1175 attacker.get_unit_ptr()
1182 if(!defender.valid() || defender.get_unit().hitpoints() > 0) {
1187 units_.erase(defender.loc_);
1191 if(attacker.valid() && attacker_stats->
plagues && !drain_killed) {
1195 LOG_NG <<
"found unit type:" << reanimator->id();
1198 newunit->set_attacks(0);
1199 newunit->set_movement(0,
true);
1203 if(undead_variation !=
"null") {
1206 variation[
"apply_to"] =
"variation";
1207 variation[
"name"] = undead_variation;
1208 newunit->add_modification(
"variation", mod);
1209 newunit->heal_fully();
1212 newunit->set_location(death_loc);
1220 if(update_display_) {
1225 LOG_NG <<
"unit not reanimated";
1229 void attack::perform()
1234 if(!
a_.valid() || !d_.valid()) {
1239 if(
a_.weapon_ < 0) {
1240 a_.get_unit().set_attacks(
a_.get_unit().attacks_left() - 1);
1241 a_.get_unit().set_movement(-1,
true);
1245 if(
a_.get_unit().attacks_left() <= 0) {
1246 LOG_NG <<
"attack::perform(): not enough ap.";
1252 a_stats_ = &bc_->get_attacker_stats();
1253 d_stats_ = &bc_->get_defender_stats();
1255 if(a_stats_->weapon) {
1256 a_.weap_id_ = a_stats_->weapon->id();
1259 if(d_stats_->weapon) {
1260 d_.weap_id_ = d_stats_->weapon->id();
1263 a_.get_unit().set_facing(
a_.loc_.get_relative_dir(d_.loc_));
1264 d_.get_unit().set_facing(d_.loc_.get_relative_dir(
a_.loc_));
1268 }
catch(
const attack_end_exception&) {
1272 VALIDATE(
a_.weapon_ <
static_cast<int>(
a_.get_unit().attacks().size()),
1273 _(
"An invalid attacker weapon got selected."));
1275 a_.get_unit().set_attacks(
a_.get_unit().attacks_left() -
a_.get_unit().attacks()[
a_.weapon_].attacks_used());
1276 a_.get_unit().set_movement(
a_.get_unit().movement_left() -
a_.get_unit().attacks()[
a_.weapon_].movement_used(),
true);
1278 a_.get_unit().set_resting(
false);
1279 d_.get_unit().set_resting(
false);
1284 if(a_stats_->disable) {
1285 LOG_NG <<
"attack::perform(): tried to attack with a disabled attack.";
1291 }
catch(
const attack_end_exception&) {
1295 DBG_NG <<
"getting attack statistics";
1297 a_.get_unit(), d_.get_unit(), a_stats_->chance_to_hit, d_stats_->chance_to_hit);
1299 a_.orig_attacks_ = a_stats_->num_blows;
1300 d_.orig_attacks_ = d_stats_->num_blows;
1301 a_.n_attacks_ =
a_.orig_attacks_;
1302 d_.n_attacks_ = d_.orig_attacks_;
1306 bool defender_strikes_first = (d_stats_->firststrike && !a_stats_->firststrike);
1307 unsigned int rounds = std::max<unsigned int>(a_stats_->rounds, d_stats_->rounds) - 1;
1308 const int defender_side = d_.get_unit().side();
1310 LOG_NG <<
"Fight: (" <<
a_.loc_ <<
") vs (" << d_.loc_ <<
") ATT: " << a_stats_->weapon->name() <<
" "
1311 << a_stats_->damage <<
"-" << a_stats_->num_blows <<
"(" << a_stats_->chance_to_hit
1312 <<
"%) vs DEF: " << (d_stats_->weapon ? d_stats_->weapon->name() :
"none") <<
" " << d_stats_->damage <<
"-"
1313 << d_stats_->num_blows <<
"(" << d_stats_->chance_to_hit <<
"%)"
1314 << (defender_strikes_first ?
" defender first-strike" :
"");
1320 DBG_NG <<
"start of attack loop...";
1323 if(
a_.n_attacks_ > 0 && !defender_strikes_first) {
1324 if(!perform_hit(
true, attack_stats)) {
1325 DBG_NG <<
"broke from attack loop on attacker turn";
1331 defender_strikes_first =
false;
1334 if(d_.n_attacks_ > 0) {
1335 if(!perform_hit(
false, attack_stats)) {
1336 DBG_NG <<
"broke from attack loop on defender turn";
1343 if(rounds > 0 && d_.n_attacks_ == 0 &&
a_.n_attacks_ == 0) {
1344 a_.n_attacks_ =
a_.orig_attacks_;
1345 d_.n_attacks_ = d_.orig_attacks_;
1347 defender_strikes_first = (d_stats_->firststrike && !a_stats_->firststrike);
1350 if(
a_.n_attacks_ <= 0 && d_.n_attacks_ <= 0) {
1360 if(update_def_fog_) {
1365 if(update_minimap_ && update_display_) {
1376 unit& u = d_.get_unit();
1382 d_.loc_, d_.get_unit_ptr());
1384 if(update_display_) {
1395 void attack::check_replay_attack_result(
1396 bool& hits,
int ran_num,
int& damage,
config replay_results, unit_info& attacker)
1398 int results_chance = replay_results[
"chance"].to_int();
1399 bool results_hits = replay_results[
"hits"].to_bool();
1400 int results_damage = replay_results[
"damage"].to_int();
1403 errbuf_ <<
"SYNC: In attack " <<
a_.dump() <<
" vs " << d_.dump()
1404 <<
" replay data differs from local calculated data:"
1405 <<
" chance to hit in data source: " << results_chance
1406 <<
" chance to hit in calculated: " << attacker.cth_
1407 <<
" chance to hit in data source: " << results_chance
1408 <<
" chance to hit in calculated: " << attacker.cth_
1411 attacker.cth_ = results_chance;
1412 hits = results_hits;
1413 damage = results_damage;
1418 if(results_chance != attacker.cth_) {
1419 errbuf_ <<
"SYNC: In attack " <<
a_.dump() <<
" vs " << d_.dump()
1420 <<
": chance to hit is inconsistent. Data source: " << results_chance
1421 <<
"; Calculation: " << attacker.cth_ <<
" (over-riding game calculations with data source results)\n";
1422 attacker.cth_ = results_chance;
1426 if(results_hits != hits) {
1427 errbuf_ <<
"SYNC: In attack " <<
a_.dump() <<
" vs " << d_.dump() <<
": the data source says the hit was "
1428 << (results_hits ?
"successful" :
"unsuccessful") <<
", while in-game calculations say the hit was "
1429 << (hits ?
"successful" :
"unsuccessful") <<
" random number: " << ran_num <<
" = " << (ran_num % 100)
1430 <<
"/" << results_chance <<
" (over-riding game calculations with data source results)\n";
1431 hits = results_hits;
1435 if(results_damage != damage) {
1436 errbuf_ <<
"SYNC: In attack " <<
a_.dump() <<
" vs " << d_.dump() <<
": the data source says the hit did "
1437 << results_damage <<
" damage, while in-game calculations show the hit doing " << damage
1438 <<
" damage (over-riding game calculations with data source results)\n";
1439 damage = results_damage;
1454 bool update_display)
1456 attack
dummy(attacker, defender, attack_with, defend_with, update_display);
1464 bool update_display)
1466 attack_unit(attacker, defender, attack_with, defend_with, update_display);
1518 case unit_alignments::type::lawful:
1519 bonus = lawful_bonus;
1521 case unit_alignments::type::neutral:
1524 case unit_alignments::type::chaotic:
1525 bonus = -lawful_bonus;
1527 case unit_alignments::type::liminal:
1528 bonus = max_liminal_bonus-std::abs(lawful_bonus);
1535 bonus = std::max<int>(bonus, 0);
1544 const std::vector<team>& teams)
1547 if(defender == units.
end()) {
1554 for(
i = 0;
i < adj.size(); ++
i) {
1555 if(adj[
i] == attacker_loc) {
1567 if(opp == units.
end()) {
1571 if(opp->incapacitated()) {
1576 if(std::size_t(defender->side() - 1) >= teams.size() || std::size_t(opp->side() - 1) >= teams.size()) {
1581 if(teams[defender->side() - 1].is_enemy(opp->side())) {
int generic_combat_modifier(int lawful_bonus, unit_alignments::type alignment, bool is_fearless, int max_liminal_bonus)
Returns the amount that a unit's damage should be multiplied by due to a given lawful_bonus.
static lg::log_domain log_engine("engine")
bool backstab_check(const map_location &attacker_loc, const map_location &defender_loc, const unit_map &units, const std::vector< team > &teams)
Function to check if an attack will satisfy the requirements for backstab.
int combat_modifier(const unit_map &units, const gamemap &map, const map_location &loc, unit_alignments::type alignment, bool is_fearless)
Returns the amount that a unit's damage should be multiplied by due to the current time of day.
int under_leadership(const unit &u, const map_location &loc)
Tests if the unit at loc is currently affected by leadership.
void attack_unit_and_advance(const map_location &attacker, const map_location &defender, int attack_with, int defend_with, bool update_display)
Performs an attack, and advanced the units afterwards.
void attack_unit(const map_location &attacker, const map_location &defender, int attack_with, int defend_with, bool update_display)
Performs an attack.
static lg::log_domain log_attack("engine/attack")
static lg::log_domain log_config("config")
Various functions that implement attacks and attack calculations.
void advance_unit_at(const advance_unit_params ¶ms)
Various functions that implement advancements of units.
boost::iterator_range< boost::indirect_iterator< attack_list::iterator > > attack_itors
double defense_weight() const
double attack_weight() const
const std::string & range() const
Computes the statistics of a battle between an attacker and a defender unit.
std::unique_ptr< battle_context_unit_stats > defender_stats_
std::unique_ptr< combatant > defender_combatant_
std::unique_ptr< battle_context_unit_stats > attacker_stats_
Statistics of the units.
static battle_context choose_attacker_weapon(nonempty_unit_const_ptr attacker, const nonempty_unit_const_ptr &defender, const map_location &attacker_loc, const map_location &defender_loc, double harm_weight, const combatant *prev_def)
std::unique_ptr< combatant > attacker_combatant_
Outcome of simulated fight.
void simulate(const combatant *prev_def)
const combatant & get_attacker_combatant(const combatant *prev_def=nullptr)
Get the simulation results.
const combatant & get_defender_combatant(const combatant *prev_def=nullptr)
static bool better_combat(const combatant &us_a, const combatant &them_a, const combatant &us_b, const combatant &them_b, double harm_weight)
static battle_context choose_defender_weapon(nonempty_unit_const_ptr attacker, nonempty_unit_const_ptr defender, unsigned attacker_weapon, const map_location &attacker_loc, const map_location &defender_loc, const combatant *prev_def)
battle_context(const unit_map &units, const map_location &attacker_loc, const map_location &defender_loc, int attacker_weapon=-1, int defender_weapon=-1, double aggression=0.0, const combatant *prev_def=nullptr, unit_const_ptr attacker_ptr=unit_const_ptr(), unit_const_ptr defender_ptr=unit_const_ptr())
If no attacker_weapon is given, we select the best one, based on harm_weight (1.0 means 1 hp lost cou...
bool better_defense(class battle_context &that, double harm_weight)
Given this harm_weight, is this attack better than that?
bool better_attack(class battle_context &that, double harm_weight)
Given this harm_weight, is this attack better than that?
virtual bool local_checkup(const config &expected_data, config &real_data)=0
Compares data to the results calculated during the original game.
A config object defines a single node in a WML file, with access to child nodes.
config & add_child(std::string_view key)
void insert(std::string_view key, T &&value)
Inserts an attribute into the config.
void redraw_minimap()
Schedule the minimap to be redrawn.
bool invalidate(const map_location &loc)
Function to invalidate a specific tile for redrawing.
static display * get_singleton()
Returns the display object if a display object exists.
virtual const unit_map & units() const override
virtual const gamemap & map() const override
void clear_variable(const std::string &varname)
Clears attributes config children does nothing if varname is no valid variable name.
config::attribute_value & get_variable(const std::string &varname)
throws invalid_variablename_exception if varname is no valid variable name.
void invalidate_unit()
Function to invalidate that unit status displayed on the sidebar.
static game_display * get_singleton()
game_events::wml_event_pump & pump()
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.
terrain_code get_terrain(const map_location &loc) const
Looks up terrain at a particular location.
Encapsulates the map of the game.
bool is_village(const map_location &loc) const
std::set< std::string > & encountered_units()
int get_random_int(int min, int max)
static void process_error(const std::string &msg)
active_ability_list get_abilities_weapons(const std::string &tag, const unit &un) const
static specials_context_t make(specials_combatant &&self, specials_combatant &&other, bool attacking)
bool is_enemy(int n) const
int get_max_liminal_bonus() const
time_of_day get_illuminated_time_of_day(const unit_map &units, const gamemap &map, const map_location &loc, int for_turn=0) const
Returns time of day object for the passed turn at a location.
int get_composite_value() const
void set_standing(bool with_bars=true)
Sets the animation state to standing.
Container associating units to locations.
unit_iterator find(std::size_t id)
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.
A single unit type that the player may recruit.
const std::string & parent_id() const
The id of the original type from which this (variation) descended.
This class represents a single unit of a specific type.
static unit_ptr create(const config &cfg, bool use_traits=false, const vconfig *vcfg=nullptr)
Initializes a unit from a config.
static std::string _(const char *str)
active_ability_list get_abilities(const std::string &tag_name, const map_location &loc) const
Gets the unit's active abilities of a particular type if it were on a specified location.
int max_hitpoints() const
The max number of hitpoints this unit can have.
unit_alignments::type alignment() const
The alignment of this unit.
void set_state(const std::string &state, bool value)
Set whether the unit is affected by a status effect.
int hitpoints() const
The current number of hitpoints this unit has.
bool get_state(const std::string &state) const
Check if the unit is affected by a status effect.
const std::string & undead_variation() const
const unit_type & type() const
This unit's type, accounting for gender and variation.
int experience() const
The current number of experience points this unit has.
void set_experience(int xp)
Sets the current experience point amount.
unit_race::GENDER gender() const
The gender of this unit.
@ STATE_NOT_MOVED
The unit is uncovered - it was hiding but has been spotted.
@ STATE_PETRIFIED
The unit is poisoned - it loses health each turn.
@ STATE_POISONED
The unit is slowed - it moves slower and does less damage.
@ STATE_UNCOVERED
The unit is petrified - it cannot move or be attacked.
attack_itors attacks()
Gets an iterator over this unit's attacks.
int defense_modifier(const t_translation::terrain_code &terrain, const map_location &loc) const
The unit's defense on a given terrain.
unit_animation_component & anim_comp() const
bool is_fearless() const
Gets whether this unit is fearless - ie, unaffected by time of day.
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.
void get_adjacent_tiles(const map_location &a, utils::span< map_location, 6 > res)
Function which, given a location, will place all adjacent locations in res.
Standard logging facilities (interface).
#define log_scope2(domain, description)
constexpr int round_damage(double base_damage, int bonus, int divisor)
round (base_damage * bonus / divisor) to the closest integer, but up or down towards base_damage
void recalculate_fog(int side)
Function that recalculates the fog of war.
bool fire_event(const ui_event event, const std::vector< std::pair< widget *, ui_event >> &event_chain, widget *dispatcher, widget *w, F &&... params)
Helper function for fire_event.
rng * generator
This generator is automatically synced during synced context.
::tod_manager * tod_manager
game_events::manager * game_events
game_classification * classification
play_controller * controller
std::shared_ptr< wb::manager > whiteboard
void unit_die(const map_location &loc, unit &loser, const const_attack_ptr &attack, const const_attack_ptr &secondary_attack, const map_location &winner_loc, const unit_ptr &winner)
Show a unit fading out.
void unit_draw_weapon(const map_location &loc, unit &attacker, const const_attack_ptr &attack, const const_attack_ptr &secondary_attack, const map_location &defender_loc, const unit_ptr &defender)
Play a pre-fight animation First unit is the attacker, second unit the defender.
void unit_attack(display *disp, game_board &board, const map_location &a, const map_location &b, int damage, const attack_type &attack, const const_attack_ptr &secondary_attack, int swing, const std::string &hit_text, int drain_amount, const std::string &att_text, const std::vector< std::string > *extra_hit_sounds, bool attacking)
Make the unit on tile 'a' attack the unit on tile 'b'.
void unit_sheath_weapon(const map_location &primary_loc, const unit_ptr &primary_unit, const const_attack_ptr &primary_attack, const const_attack_ptr &secondary_attack, const map_location &secondary_loc, const unit_ptr &secondary_unit)
Play a post-fight animation Both unit can be set to null, only valid units will play their animation.
std::size_t size(std::string_view str)
Length in characters of a UTF-8 string.
std::shared_ptr< const unit > unit_const_ptr
std::shared_ptr< const attack_type > const_attack_ptr
std::shared_ptr< unit > unit_ptr
Define the game's event mechanism.
const config & ability_cfg() const
advances the unit at loc if it has enough experience, maximum 20 times.
Structure describing the statistics of a unit involved in the battle.
bool slows
Attack slows opponent when it hits.
unsigned int num_blows
Effective number of blows, takes swarm into account.
std::string plague_type
The plague type used by the attack, if any.
bool petrifies
Attack petrifies opponent when it hits.
int drain_percent
Percentage of damage recovered as health.
unsigned int hp
Hitpoints of the unit at the beginning of the battle.
int slow_damage
Effective damage if unit becomes slowed (== damage, if already slowed)
bool drains
Attack drains opponent when it hits.
unsigned int swarm_min
Minimum number of blows with swarm (equal to num_blows if swarm isn't used).
bool swarm
Attack has swarm special.
const_attack_ptr weapon
The weapon used by the unit to attack the opponent, or nullptr if there is none.
bool is_slowed
True if the unit is slowed at the beginning of the battle.
unsigned int rounds
Berserk special can force us to fight more than one round.
unsigned int swarm_max
Maximum number of blows with swarm (equal to num_blows if swarm isn't used).
battle_context_unit_stats(nonempty_unit_const_ptr u, const map_location &u_loc, int u_attack_num, bool attacking, nonempty_unit_const_ptr opp, const map_location &opp_loc, const const_attack_ptr &opp_weapon, utils::optional< int > opp_terrain_defense={}, utils::optional< int > lawful_bonus={})
unsigned int calc_blows(unsigned new_hp) const
Calculates the number of blows we would have if we had new_hp instead of the recorded hp.
int damage
Effective damage of the weapon (all factors accounted for).
bool disable
Attack has disable special.
bool poisons
Attack poisons opponent when it hits.
unsigned int chance_to_hit
Effective chance to hit as a percentage (all factors accounted for).
int drain_constant
Base HP drained regardless of damage dealt.
bool firststrike
Attack has firststrike special.
int attack_num
Index into unit->attacks() or -1 for none.
bool plagues
Attack turns opponent into a zombie when fatal.
std::vector< double > hp_dist
Resulting probability distribution (might be not as large as max_hp)
double poisoned
Resulting chance we are poisoned.
const battle_context_unit_stats & u_
double average_hp(unsigned int healing=0) const
What's the average hp (weighted average of hp_dist).
Encapsulates the map of the game.
static constexpr direction get_opposite_direction(direction d)
void attack_result(hit_result res, int cth, int damage, int drain)
void attack_expected_damage(double attacker_inflict, double defender_inflict)
void defend_result(hit_result res, int cth, int damage, int drain)
Object which defines a time of day with associated bonuses, image, sounds etc.
int lawful_bonus
The % bonus lawful units receive.
pointer get_shared_ptr() const
This is exactly the same as operator-> but it's slightly more readable, and can replace &*iter syntax...
checkup * checkup_instance
static map_location::direction n
static map_location::direction s
unit_type_data unit_types
Display units performing various actions: moving, attacking, and dying.
Various functions implementing vision (through fog of war and shroud).
Add a special kind of assert to validate whether the input from WML doesn't contain any problems that...
#define VALIDATE(cond, message)
The macro to use for the validation of WML.