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)
90 if(chance_to_hit == 0) {
93 if(chance_to_hit == 100) {
97 const int expected_hits = (chance_to_hit * strikes_left + credit) / 100;
98 const bool does_hit = rng.
get_random_int(0, strikes_left - 1) < expected_hits;
99 credit += chance_to_hit - (does_hit ? 100 : 0);
111 utils::optional<int> opp_terrain_defense,
112 utils::optional<int> lawful_bonus
115 , attack_num(u_attack_num)
116 , is_attacker(attacking)
117 , is_poisoned(up->get_state(
unit::STATE_POISONED))
118 , is_slowed(up->get_state(
unit::STATE_SLOWED))
127 , leadership_bonus(0)
128 , experience(up->experience())
129 , max_experience(up->max_experience())
133 , max_hp(up->max_hitpoints())
145 const unit& opp = *oppp;
152 LOG_CF <<
"Unit with " << u.
hitpoints() <<
" hitpoints found, set to 0 for damage calculations";
174 rounds =
weapon->get_specials_and_abilities(
"berserk").highest(
"value", 1).first;
180 const bool out_of_range = distance >
weapon->max_range() || distance <
weapon->min_range();
181 disable =
weapon->has_special_or_ability(
"disable") || out_of_range;
200 signed int cth =
weapon->modified_chance_to_hit(opp_base_cth);
209 double base_damage =
weapon->modified_damage();
212 int damage_multiplier = 100;
233 const auto [damage_type, resistance_modifier] =
weapon->effective_damage_type();
234 damage_multiplier *= resistance_modifier;
277 , attacker_combatant_()
278 , defender_combatant_()
279 , use_biased_rng_(use_biased_rng)
281 std::size_t a_wep_uindex =
static_cast<std::size_t
>(a_wep_index);
282 std::size_t d_wep_uindex =
static_cast<std::size_t
>(d_wep_index);
314 : attacker_stats_(nullptr)
315 , defender_stats_(nullptr)
316 , attacker_combatant_(nullptr)
317 , defender_combatant_(nullptr)
329 const double harm_weight = 1.0 - aggression;
331 if(attacker_weapon == -1) {
333 n_attacker, n_defender, attacker_loc, defender_loc, harm_weight, prev_def, use_biased_rng
336 else if(defender_weapon == -1) {
338 n_attacker, n_defender, attacker_weapon, attacker_loc, defender_loc, prev_def, use_biased_rng
343 n_attacker, attacker_loc, attacker_weapon, n_defender, defender_loc, defender_weapon, use_biased_rng);
353 , attacker_combatant_(nullptr)
354 , defender_combatant_(nullptr)
426 double attack_weight_a = us_a.
u_.
weapon->attack_weight();
427 double attack_weight_b = us_b.
u_.
weapon->attack_weight();
428 double damage_a = (them_a.
u_.
hp - them_a.
average_hp()) * attack_weight_a;
429 double damage_b = (them_b.
u_.
hp - them_b.
average_hp()) * attack_weight_b;
432 a = (us_a.
average_hp() - poison_a_us) * harm_weight + damage_a + poison_a_them;
433 b = (us_b.
average_hp() - poison_b_us) * harm_weight + damage_b + poison_b_them;
444 return damage_a >= damage_b;
456 std::vector<battle_context> choices;
459 const auto attacks = attacker->
attacks();
460 for(std::size_t
i = 0;
i < attacks.size(); ++
i) {
471 choices.emplace_back(std::move(bc));
474 if(choices.empty()) {
475 return battle_context(attacker, attacker_loc, -1, defender, defender_loc, -1, use_biased_rng);
478 if(choices.size() == 1) {
479 return std::move(choices[0]);
484 for(
auto& choice : choices) {
488 if(!best_choice || choice.
better_attack(*best_choice, harm_weight)) {
489 best_choice = &choice;
494 return std::move(*best_choice);
497 return battle_context(attacker, attacker_loc, -1, defender, defender_loc, -1, use_biased_rng);
504 unsigned attacker_weapon,
511 const auto attackers_attacks = attacker->
attacks();
512 VALIDATE(attacker_weapon < attackers_attacks.size(),
_(
"An invalid attacker weapon got selected."));
514 const attack_type& att = attackers_attacks[attacker_weapon];
515 auto no_weapon = [&]() {
return battle_context(attacker, attacker_loc, attacker_weapon, defender, defender_loc, -1, use_biased_rng); };
516 std::vector<battle_context> choices;
519 const auto defenses = defender->
attacks();
520 for(std::size_t
i = 0;
i < defenses.size(); ++
i) {
526 battle_context bc(attacker, attacker_loc, attacker_weapon, defender, defender_loc,
i, use_biased_rng);
535 choices.emplace_back(std::move(bc));
538 if(choices.empty()) {
542 if(choices.size() == 1) {
544 return std::move(choices[0]);
554 double max_weight = 0.0;
556 for(
const auto& choice : choices) {
557 const double d_weight = choice.defender_stats_->weapon->defense_weight();
559 if(d_weight >= max_weight) {
562 max_weight = d_weight;
563 int rating =
static_cast<int>(
566 if(d_weight > max_weight || rating < min_rating) {
575 for(
auto& choice : choices) {
576 const double d_weight = choice.
defender_stats_->weapon->defense_weight();
578 choice.simulate(prev_def);
581 int simple_rating =
static_cast<int>(
582 choice.defender_stats_->num_blows * choice.defender_stats_->damage * choice.defender_stats_->chance_to_hit * d_weight);
585 if(simple_rating >= min_rating && (!best_choice || choice.
better_defense(*best_choice, 1.0))) {
586 best_choice = &choice;
590 return best_choice ? std::move(*best_choice) : no_weapon();
600 void refresh_weapon_index(
int& weap_index,
const std::string& weap_id,
const attack_itors& attacks)
603 if(attacks.empty()) {
609 if(weap_index >= 0 && weap_index <
static_cast<int>(attacks.size()) && attacks[weap_index].id() == weap_id) {
614 if(!weap_id.empty()) {
615 for(
int i = 0; i < static_cast<int>(attacks.size()); ++
i) {
616 if(attacks[
i].
id() == weap_id) {
636 bool update_display =
true);
641 class attack_end_exception
647 void fire_event_impl(
const std::string&
n,
bool reversed);
657 std::string weap_id_;
676 void check_replay_attack_result(
bool&,
int,
int&,
config, unit_info&);
681 std::unique_ptr<battle_context> bc_;
686 int abs_n_attack_, abs_n_defend_;
688 bool update_att_fog_, update_def_fog_, update_minimap_;
692 std::ostringstream errbuf_;
694 bool update_display_;
704 hit_credits bias_attacker_ = { -1 , 0};
705 hit_credits bias_defender_ = { -1 , 0};
725 id_ =
i->underlying_id();
728 unit& attack::unit_info::get_unit()
731 assert(
i.valid() &&
i->underlying_id() == id_);
735 unit_ptr attack::unit_info::get_unit_ptr()
738 if(
i.valid() &&
i->underlying_id() == id_) {
739 return i.get_shared_ptr();
744 bool attack::unit_info::valid()
747 return i.valid() &&
i->underlying_id() == id_;
750 std::string attack::unit_info::dump()
753 s << get_unit().type_id() <<
" (" << loc_.wml_x() <<
',' << loc_.wml_y() <<
')';
767 , update_att_fog_(false)
768 , update_def_fog_(false)
769 , update_minimap_(false)
774 , update_display_(update_display)
781 LOG_NG <<
"Using experimental PRNG for combat";
787 fire_event_impl(
n,
false);
790 void attack::fire_event_impl(
const std::string&
n,
bool reverse)
792 LOG_NG <<
"attack: firing '" <<
n <<
"' event";
804 if(a_stats_->weapon !=
nullptr &&
a_.valid()) {
805 a_stats_->weapon->write(a_weapon_cfg);
808 if(d_stats_->weapon !=
nullptr && d_.valid()) {
809 d_stats_->weapon->write(d_weapon_cfg);
812 if(a_weapon_cfg[
"name"].empty()) {
813 a_weapon_cfg[
"name"] =
"none";
816 if(d_weapon_cfg[
"name"].empty()) {
817 d_weapon_cfg[
"name"] =
"none";
820 if(
n ==
"attack_end") {
828 if(
n ==
"attacker_hits" ||
n ==
"defender_hits" ||
n ==
"unit_hits") {
832 const int defender_side = d_.get_unit().side();
843 if(wml_aborted || !
a_.valid() || !d_.valid()
848 if(update_display_) {
853 throw attack_end_exception();
857 void attack::refresh_bc()
861 refresh_weapon_index(
a_.weapon_,
a_.weap_id_,
a_.get_unit().attacks());
865 refresh_weapon_index(d_.weapon_, d_.weap_id_, d_.get_unit().attacks());
868 if(!
a_.valid() || !d_.valid()) {
871 =
a_.valid() &&
a_.weapon_ >= 0 ?
a_.get_unit().attacks()[
a_.weapon_].shared_from_this() :
nullptr;
874 = d_.valid() && d_.weapon_ >= 0 ? d_.get_unit().attacks()[d_.weapon_].shared_from_this() :
nullptr;
881 a_stats_ = &bc_->get_attacker_stats();
882 d_stats_ = &bc_->get_defender_stats();
884 a_.cth_ = a_stats_->chance_to_hit;
885 d_.cth_ = d_stats_->chance_to_hit;
886 a_.damage_ = a_stats_->damage;
887 d_.damage_ = d_stats_->damage;
892 unit_info& attacker = attacker_turn ?
a_ : d_;
893 unit_info& defender = attacker_turn ? d_ :
a_;
900 int& abs_n = attacker_turn ? abs_n_attack_ : abs_n_defend_;
901 bool& update_fog = attacker_turn ? update_def_fog_ : update_att_fog_;
906 if(attacker.cth_ == 0 || attacker.cth_ == 100) {
911 auto& bias = attacker_turn ? bias_attacker_ : bias_defender_;
912 if(bias.prev_cth != attacker.cth_) {
914 bias.prev_cth = attacker.cth_;
918 ran_num = does_hit ? 0 : 99;
923 bool hits = (ran_num < attacker.cth_);
927 damage = attacker.damage_;
933 const config local_results {
"chance", attacker.cth_,
"hits", hits,
"damage", damage};
939 check_replay_attack_result(hits, ran_num, damage, replay_results, attacker);
943 int damage_done = std::min<int>(defender.get_unit().hitpoints(), attacker.damage_);
946 double expected_damage = damage_done * attacker.cth_ * 0.01;
954 int drains_damage = 0;
955 if(hits && attacker_stats->
drains) {
960 std::min<int>(drains_damage, attacker.get_unit().max_hitpoints() - attacker.get_unit().hitpoints());
963 drains_damage = std::max<int>(drains_damage, 1 - attacker.get_unit().hitpoints());
966 if(update_display_) {
967 std::ostringstream float_text;
968 std::vector<std::string> extra_hit_sounds;
971 const unit& defender_unit = defender.get_unit();
992 if(
prefs::get().show_attack_miss_indicator()) {
993 float_text <<
_(
"attack^miss");
1000 attacker.loc_, defender.loc_,
1003 abs_n, float_text.str(), drains_damage,
"",
1004 &extra_hit_sounds, attacker_turn
1008 bool dies = defender.get_unit().take_hit(damage);
1009 LOG_NG <<
"defender took " << damage << (dies ?
" and died\n" :
"\n");
1017 attacker.cth_, damage_done, drains_damage
1025 attacker.cth_, damage_done, drains_damage
1029 replay_results.
clear();
1034 if(!equals_replay) {
1035 bool results_dies = replay_results[
"dies"].to_bool();
1037 errbuf_ <<
"SYNC: In attack " <<
a_.dump() <<
" vs " << d_.dump() <<
": the data source says the "
1038 << (attacker_turn ?
"defender" :
"attacker") <<
' ' << (results_dies ?
"perished" :
"survived")
1039 <<
" while in-game calculations show it " << (dies ?
"perished" :
"survived")
1040 <<
" (over-riding game calculations with data source results)\n";
1042 dies = results_dies;
1046 defender.get_unit().set_hitpoints(0);
1054 fire_event(attacker_turn ?
"attacker_hits" :
"defender_hits");
1055 fire_event_impl(
"unit_hits", !attacker_turn);
1056 }
catch(
const attack_end_exception&) {
1062 fire_event(attacker_turn ?
"attacker_misses" :
"defender_misses");
1063 fire_event_impl(
"unit_misses", !attacker_turn);
1064 }
catch(
const attack_end_exception&) {
1072 bool attacker_dies =
false;
1074 if(drains_damage > 0) {
1075 attacker.get_unit().heal(drains_damage);
1076 }
else if(drains_damage < 0) {
1077 attacker_dies = attacker.get_unit().take_hit(-drains_damage);
1081 unit_killed(attacker, defender, attacker_stats, defender_stats,
false);
1086 unit_killed(defender, attacker, defender_stats, attacker_stats,
true);
1087 (attacker_turn ? update_att_fog_ : update_def_fog_) =
true;
1091 update_minimap_ =
true;
1096 unit& defender_unit = defender.get_unit();
1100 LOG_NG <<
"defender poisoned";
1107 LOG_NG <<
"defender slowed";
1114 attacker.n_attacks_ = 0;
1115 defender.n_attacks_ = -1;
1123 update_minimap_ =
true;
1127 --attacker.n_attacks_;
1131 if (attacker_stats->
weapon ==
nullptr){
1132 attacker.n_attacks_ = 0;
1133 attacker.orig_attacks_ = 0;
1135 if (defender_stats->
weapon ==
nullptr){
1136 defender.n_attacks_ = 0;
1137 defender.orig_attacks_ = 0;
1143 void attack::unit_killed(unit_info& attacker,
1144 unit_info& defender,
1157 std::string undead_variation = defender.get_unit().undead_variation();
1167 if(a_weapon_cfg[
"name"].empty()) {
1168 a_weapon_cfg[
"name"] =
"none";
1171 if(d_weapon_cfg[
"name"].empty()) {
1172 d_weapon_cfg[
"name"] =
"none";
1182 if(!defender.valid() || defender.get_unit().hitpoints() > 0) {
1186 if(!attacker.valid()) {
1189 defender.get_unit(),
1196 defender.get_unit(),
1200 attacker.get_unit_ptr()
1207 if(!defender.valid() || defender.get_unit().hitpoints() > 0) {
1212 units_.erase(defender.loc_);
1216 if(attacker.valid() && attacker_stats->
plagues && !drain_killed) {
1220 LOG_NG <<
"found unit type:" << reanimator->id();
1223 newunit->set_attacks(0);
1224 newunit->set_movement(0,
true);
1228 if(undead_variation !=
"null") {
1231 variation[
"apply_to"] =
"variation";
1232 variation[
"name"] = undead_variation;
1233 newunit->add_modification(
"variation", mod);
1234 newunit->heal_fully();
1237 newunit->set_location(death_loc);
1245 if(update_display_) {
1250 LOG_NG <<
"unit not reanimated";
1254 void attack::perform()
1259 if(!
a_.valid() || !d_.valid()) {
1264 if(
a_.weapon_ < 0) {
1265 a_.get_unit().set_attacks(
a_.get_unit().attacks_left() - 1);
1266 a_.get_unit().set_movement(-1,
true);
1270 if(
a_.get_unit().attacks_left() <= 0) {
1271 LOG_NG <<
"attack::perform(): not enough ap.";
1277 a_stats_ = &bc_->get_attacker_stats();
1278 d_stats_ = &bc_->get_defender_stats();
1280 if(a_stats_->weapon) {
1281 a_.weap_id_ = a_stats_->weapon->id();
1284 if(d_stats_->weapon) {
1285 d_.weap_id_ = d_stats_->weapon->id();
1288 a_.get_unit().set_facing(
a_.loc_.get_relative_dir(d_.loc_));
1289 d_.get_unit().set_facing(d_.loc_.get_relative_dir(
a_.loc_));
1293 }
catch(
const attack_end_exception&) {
1297 VALIDATE(
a_.weapon_ <
static_cast<int>(
a_.get_unit().attacks().size()),
1298 _(
"An invalid attacker weapon got selected."));
1300 a_.get_unit().set_attacks(
a_.get_unit().attacks_left() -
a_.get_unit().attacks()[
a_.weapon_].attacks_used());
1301 a_.get_unit().set_movement(
a_.get_unit().movement_left() -
a_.get_unit().attacks()[
a_.weapon_].movement_used(),
true);
1303 a_.get_unit().set_resting(
false);
1304 d_.get_unit().set_resting(
false);
1309 if(a_stats_->disable) {
1310 LOG_NG <<
"attack::perform(): tried to attack with a disabled attack.";
1316 }
catch(
const attack_end_exception&) {
1320 DBG_NG <<
"getting attack statistics";
1322 a_.get_unit(), d_.get_unit(), a_stats_->chance_to_hit, d_stats_->chance_to_hit);
1324 a_.orig_attacks_ = a_stats_->num_blows;
1325 d_.orig_attacks_ = d_stats_->num_blows;
1326 a_.n_attacks_ =
a_.orig_attacks_;
1327 d_.n_attacks_ = d_.orig_attacks_;
1331 bool defender_strikes_first = (d_stats_->firststrike && !a_stats_->firststrike);
1332 unsigned int rounds = std::max<unsigned int>(a_stats_->rounds, d_stats_->rounds) - 1;
1333 const int defender_side = d_.get_unit().side();
1335 LOG_NG <<
"Fight: (" <<
a_.loc_ <<
") vs (" << d_.loc_ <<
") ATT: " << a_stats_->weapon->name() <<
" "
1336 << a_stats_->damage <<
"-" << a_stats_->num_blows <<
"(" << a_stats_->chance_to_hit
1337 <<
"%) vs DEF: " << (d_stats_->weapon ? d_stats_->weapon->name() :
"none") <<
" " << d_stats_->damage <<
"-"
1338 << d_stats_->num_blows <<
"(" << d_stats_->chance_to_hit <<
"%)"
1339 << (defender_strikes_first ?
" defender first-strike" :
"");
1345 DBG_NG <<
"start of attack loop...";
1348 if(
a_.n_attacks_ > 0 && !defender_strikes_first) {
1349 if(!perform_hit(
true, attack_stats)) {
1350 DBG_NG <<
"broke from attack loop on attacker turn";
1356 defender_strikes_first =
false;
1359 if(d_.n_attacks_ > 0) {
1360 if(!perform_hit(
false, attack_stats)) {
1361 DBG_NG <<
"broke from attack loop on defender turn";
1368 if(rounds > 0 && d_.n_attacks_ == 0 &&
a_.n_attacks_ == 0) {
1369 a_.n_attacks_ =
a_.orig_attacks_;
1370 d_.n_attacks_ = d_.orig_attacks_;
1372 defender_strikes_first = (d_stats_->firststrike && !a_stats_->firststrike);
1375 if(
a_.n_attacks_ <= 0 && d_.n_attacks_ <= 0) {
1385 if(update_def_fog_) {
1390 if(update_minimap_ && update_display_) {
1401 unit& u = d_.get_unit();
1407 d_.loc_, d_.get_unit_ptr());
1409 if(update_display_) {
1420 void attack::check_replay_attack_result(
1421 bool& hits,
int ran_num,
int& damage,
config replay_results, unit_info& attacker)
1423 int results_chance = replay_results[
"chance"].to_int();
1424 bool results_hits = replay_results[
"hits"].to_bool();
1425 int results_damage = replay_results[
"damage"].to_int();
1428 errbuf_ <<
"SYNC: In attack " <<
a_.dump() <<
" vs " << d_.dump()
1429 <<
" replay data differs from local calculated data:"
1430 <<
" chance to hit in data source: " << results_chance
1431 <<
" chance to hit in calculated: " << attacker.cth_
1432 <<
" chance to hit in data source: " << results_chance
1433 <<
" chance to hit in calculated: " << attacker.cth_
1436 attacker.cth_ = results_chance;
1437 hits = results_hits;
1438 damage = results_damage;
1443 if(results_chance != attacker.cth_) {
1444 errbuf_ <<
"SYNC: In attack " <<
a_.dump() <<
" vs " << d_.dump()
1445 <<
": chance to hit is inconsistent. Data source: " << results_chance
1446 <<
"; Calculation: " << attacker.cth_ <<
" (over-riding game calculations with data source results)\n";
1447 attacker.cth_ = results_chance;
1451 if(results_hits != hits) {
1452 errbuf_ <<
"SYNC: In attack " <<
a_.dump() <<
" vs " << d_.dump() <<
": the data source says the hit was "
1453 << (results_hits ?
"successful" :
"unsuccessful") <<
", while in-game calculations say the hit was "
1454 << (hits ?
"successful" :
"unsuccessful") <<
" random number: " << ran_num <<
" = " << (ran_num % 100)
1455 <<
"/" << results_chance <<
" (over-riding game calculations with data source results)\n";
1456 hits = results_hits;
1460 if(results_damage != damage) {
1461 errbuf_ <<
"SYNC: In attack " <<
a_.dump() <<
" vs " << d_.dump() <<
": the data source says the hit did "
1462 << results_damage <<
" damage, while in-game calculations show the hit doing " << damage
1463 <<
" damage (over-riding game calculations with data source results)\n";
1464 damage = results_damage;
1479 bool update_display)
1481 attack
dummy(attacker, defender, attack_with, defend_with, update_display);
1489 bool update_display)
1491 attack_unit(attacker, defender, attack_with, defend_with, update_display);
1543 case unit_alignments::type::lawful:
1544 bonus = lawful_bonus;
1546 case unit_alignments::type::neutral:
1549 case unit_alignments::type::chaotic:
1550 bonus = -lawful_bonus;
1552 case unit_alignments::type::liminal:
1553 bonus = max_liminal_bonus-std::abs(lawful_bonus);
1560 bonus = std::max<int>(bonus, 0);
1569 const std::vector<team>& teams)
1572 if(defender == units.
end()) {
1579 for(
i = 0;
i < adj.size(); ++
i) {
1580 if(adj[
i] == attacker_loc) {
1592 if(opp == units.
end()) {
1596 if(opp->incapacitated()) {
1601 if(std::size_t(defender->side() - 1) >= teams.size() || std::size_t(opp->side() - 1) >= teams.size()) {
1606 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.
std::unique_ptr< combatant > attacker_combatant_
Outcome of simulated fight.
void simulate(const combatant *prev_def)
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, bool use_biased_rng)
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, bool use_biased_rng)
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(), bool use_biased_rng=false)
If no attacker_weapon is given, we select the best one, based on harm_weight (1.0 means 1 hp lost cou...
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)
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()
this class does not give synced random results derived classes might do.
virtual bool is_networked() const
Is this random source networked? If it is it's very important we do actually use this random source t...
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.
bool advances() const
Checks whether this unit is eligible for level-up.
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.
int roll_credit(randomness::rng &rng)
Rolls the credit a combatant starts a fight with.
bool enabled()
Returns true if the current game uses the biased random mode with a local RNG.
bool roll_hit(randomness::rng &rng, int strikes_left, int chance_to_hit, int &credit)
Rolls one strike.
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 can_advance
The unit can advance.
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.