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;
248 , attacker_combatant_()
249 , defender_combatant_()
251 std::size_t a_wep_uindex =
static_cast<std::size_t
>(a_wep_index);
252 std::size_t d_wep_uindex =
static_cast<std::size_t
>(d_wep_index);
283 : attacker_stats_(nullptr)
284 , defender_stats_(nullptr)
285 , attacker_combatant_(nullptr)
286 , defender_combatant_(nullptr)
298 const double harm_weight = 1.0 - aggression;
300 if(attacker_weapon == -1) {
302 n_attacker, n_defender, attacker_loc, defender_loc, harm_weight, prev_def
305 else if(defender_weapon == -1) {
307 n_attacker, n_defender, attacker_weapon, attacker_loc, defender_loc, prev_def
311 *
this =
battle_context(n_attacker, attacker_loc, attacker_weapon, n_defender, defender_loc, defender_weapon);
321 , attacker_combatant_(nullptr)
322 , defender_combatant_(nullptr)
394 double attack_weight_a = us_a.
u_.
weapon->attack_weight();
395 double attack_weight_b = us_b.
u_.
weapon->attack_weight();
396 double damage_a = (them_a.
u_.
hp - them_a.
average_hp()) * attack_weight_a;
397 double damage_b = (them_b.
u_.
hp - them_b.
average_hp()) * attack_weight_b;
400 a = (us_a.
average_hp() - poison_a_us) * harm_weight + damage_a + poison_a_them;
401 b = (us_b.
average_hp() - poison_b_us) * harm_weight + damage_b + poison_b_them;
412 return damage_a >= damage_b;
423 std::vector<battle_context> choices;
426 const auto attacks = attacker->
attacks();
427 for(std::size_t
i = 0;
i < attacks.size(); ++
i) {
438 choices.emplace_back(std::move(bc));
441 if(choices.empty()) {
442 return battle_context(attacker, attacker_loc, -1, defender, defender_loc, -1);
445 if(choices.size() == 1) {
446 return std::move(choices[0]);
451 for(
auto& choice : choices) {
455 if(!best_choice || choice.
better_attack(*best_choice, harm_weight)) {
456 best_choice = &choice;
461 return std::move(*best_choice);
464 return battle_context(attacker, attacker_loc, -1, defender, defender_loc, -1);
471 unsigned attacker_weapon,
477 const auto attackers_attacks = attacker->
attacks();
478 VALIDATE(attacker_weapon < attackers_attacks.size(),
_(
"An invalid attacker weapon got selected."));
480 const attack_type& att = attackers_attacks[attacker_weapon];
481 auto no_weapon = [&]() {
return battle_context(attacker, attacker_loc, attacker_weapon, defender, defender_loc, -1); };
482 std::vector<battle_context> choices;
485 const auto defenses = defender->
attacks();
486 for(std::size_t
i = 0;
i < defenses.size(); ++
i) {
492 battle_context bc(attacker, attacker_loc, attacker_weapon, defender, defender_loc,
i);
501 choices.emplace_back(std::move(bc));
504 if(choices.empty()) {
508 if(choices.size() == 1) {
510 return std::move(choices[0]);
520 double max_weight = 0.0;
522 for(
const auto& choice : choices) {
523 const double d_weight = choice.defender_stats_->weapon->defense_weight();
525 if(d_weight >= max_weight) {
528 max_weight = d_weight;
529 int rating =
static_cast<int>(
532 if(d_weight > max_weight || rating < min_rating) {
541 for(
auto& choice : choices) {
542 const double d_weight = choice.
defender_stats_->weapon->defense_weight();
544 choice.simulate(prev_def);
547 int simple_rating =
static_cast<int>(
548 choice.defender_stats_->num_blows * choice.defender_stats_->damage * choice.defender_stats_->chance_to_hit * d_weight);
551 if(simple_rating >= min_rating && (!best_choice || choice.
better_defense(*best_choice, 1.0))) {
552 best_choice = &choice;
556 return best_choice ? std::move(*best_choice) : no_weapon();
566 void refresh_weapon_index(
int& weap_index,
const std::string& weap_id,
const attack_itors& attacks)
569 if(attacks.empty()) {
575 if(weap_index >= 0 && weap_index <
static_cast<int>(attacks.size()) && attacks[weap_index].id() == weap_id) {
580 if(!weap_id.empty()) {
581 for(
int i = 0; i < static_cast<int>(attacks.size()); ++
i) {
582 if(attacks[
i].
id() == weap_id) {
602 bool update_display =
true);
607 class attack_end_exception
613 void fire_event_impl(
const std::string&
n,
bool reversed);
623 std::string weap_id_;
642 void check_replay_attack_result(
bool&,
int,
int&,
config, unit_info&);
647 std::unique_ptr<battle_context> bc_;
652 int abs_n_attack_, abs_n_defend_;
654 bool update_att_fog_, update_def_fog_, update_minimap_;
658 std::ostringstream errbuf_;
660 bool update_display_;
665 std::vector<bool> prng_attacker_;
666 std::vector<bool> prng_defender_;
686 id_ =
i->underlying_id();
689 unit& attack::unit_info::get_unit()
692 assert(
i.valid() &&
i->underlying_id() == id_);
696 unit_ptr attack::unit_info::get_unit_ptr()
699 if(
i.valid() &&
i->underlying_id() == id_) {
700 return i.get_shared_ptr();
705 bool attack::unit_info::valid()
708 return i.valid() &&
i->underlying_id() == id_;
711 std::string attack::unit_info::dump()
714 s << get_unit().type_id() <<
" (" << loc_.wml_x() <<
',' << loc_.wml_y() <<
')';
728 , update_att_fog_(false)
729 , update_def_fog_(false)
730 , update_minimap_(false)
735 , update_display_(update_display)
744 LOG_NG <<
"Using experimental PRNG for combat";
750 fire_event_impl(
n,
false);
753 void attack::fire_event_impl(
const std::string&
n,
bool reverse)
755 LOG_NG <<
"attack: firing '" <<
n <<
"' event";
767 if(a_stats_->weapon !=
nullptr &&
a_.valid()) {
768 a_stats_->weapon->write(a_weapon_cfg);
771 if(d_stats_->weapon !=
nullptr && d_.valid()) {
772 d_stats_->weapon->write(d_weapon_cfg);
775 if(a_weapon_cfg[
"name"].empty()) {
776 a_weapon_cfg[
"name"] =
"none";
779 if(d_weapon_cfg[
"name"].empty()) {
780 d_weapon_cfg[
"name"] =
"none";
783 if(
n ==
"attack_end") {
791 if(
n ==
"attacker_hits" ||
n ==
"defender_hits" ||
n ==
"unit_hits") {
795 const int defender_side = d_.get_unit().side();
806 if(wml_aborted || !
a_.valid() || !d_.valid()
811 if(update_display_) {
816 throw attack_end_exception();
820 void attack::refresh_bc()
824 refresh_weapon_index(
a_.weapon_,
a_.weap_id_,
a_.get_unit().attacks());
828 refresh_weapon_index(d_.weapon_, d_.weap_id_, d_.get_unit().attacks());
831 if(!
a_.valid() || !d_.valid()) {
834 =
a_.valid() &&
a_.weapon_ >= 0 ?
a_.get_unit().attacks()[
a_.weapon_].shared_from_this() :
nullptr;
837 = d_.valid() && d_.weapon_ >= 0 ? d_.get_unit().attacks()[d_.weapon_].shared_from_this() :
nullptr;
844 a_stats_ = &bc_->get_attacker_stats();
845 d_stats_ = &bc_->get_defender_stats();
847 a_.cth_ = a_stats_->chance_to_hit;
848 d_.cth_ = d_stats_->chance_to_hit;
849 a_.damage_ = a_stats_->damage;
850 d_.damage_ = d_stats_->damage;
855 unit_info& attacker = attacker_turn ?
a_ : d_;
856 unit_info& defender = attacker_turn ? d_ :
a_;
863 int& abs_n = attacker_turn ? abs_n_attack_ : abs_n_defend_;
864 bool& update_fog = attacker_turn ? update_def_fog_ : update_att_fog_;
870 std::vector<bool>& prng_seq = attacker_turn ? prng_attacker_ : prng_defender_;
872 if(prng_seq.empty()) {
873 const int ntotal = attacker.cth_*attacker.n_attacks_;
874 int num_hits = ntotal/100;
875 const int additional_hit_chance = ntotal%100;
876 if(additional_hit_chance > 0 &&
randomness::generator->get_random_int(0, 99) < additional_hit_chance) {
880 std::vector<int> indexes;
881 for(
int i = 0;
i != attacker.n_attacks_; ++
i) {
882 prng_seq.push_back(
false);
883 indexes.push_back(
i);
886 for(
int i = 0;
i != num_hits; ++
i) {
888 prng_seq[indexes[
n]] =
true;
889 indexes.erase(indexes.begin() +
n);
893 bool does_hit = prng_seq.back();
895 ran_num = does_hit ? 0 : 99;
899 bool hits = (ran_num < attacker.cth_);
903 damage = attacker.damage_;
909 const config local_results {
"chance", attacker.cth_,
"hits", hits,
"damage", damage};
915 check_replay_attack_result(hits, ran_num, damage, replay_results, attacker);
919 int damage_done = std::min<int>(defender.get_unit().hitpoints(), attacker.damage_);
922 double expected_damage = damage_done * attacker.cth_ * 0.01;
930 int drains_damage = 0;
931 if(hits && attacker_stats->
drains) {
936 std::min<int>(drains_damage, attacker.get_unit().max_hitpoints() - attacker.get_unit().hitpoints());
939 drains_damage = std::max<int>(drains_damage, 1 - attacker.get_unit().hitpoints());
942 if(update_display_) {
943 std::ostringstream float_text;
944 std::vector<std::string> extra_hit_sounds;
947 const unit& defender_unit = defender.get_unit();
968 if(
prefs::get().show_attack_miss_indicator()) {
969 float_text <<
_(
"attack^miss");
976 attacker.loc_, defender.loc_,
979 abs_n, float_text.str(), drains_damage,
"",
980 &extra_hit_sounds, attacker_turn
984 bool dies = defender.get_unit().take_hit(damage);
985 LOG_NG <<
"defender took " << damage << (dies ?
" and died\n" :
"\n");
993 attacker.cth_, damage_done, drains_damage
1001 attacker.cth_, damage_done, drains_damage
1005 replay_results.
clear();
1010 if(!equals_replay) {
1011 bool results_dies = replay_results[
"dies"].to_bool();
1013 errbuf_ <<
"SYNC: In attack " <<
a_.dump() <<
" vs " << d_.dump() <<
": the data source says the "
1014 << (attacker_turn ?
"defender" :
"attacker") <<
' ' << (results_dies ?
"perished" :
"survived")
1015 <<
" while in-game calculations show it " << (dies ?
"perished" :
"survived")
1016 <<
" (over-riding game calculations with data source results)\n";
1018 dies = results_dies;
1022 defender.get_unit().set_hitpoints(0);
1030 fire_event(attacker_turn ?
"attacker_hits" :
"defender_hits");
1031 fire_event_impl(
"unit_hits", !attacker_turn);
1032 }
catch(
const attack_end_exception&) {
1038 fire_event(attacker_turn ?
"attacker_misses" :
"defender_misses");
1039 fire_event_impl(
"unit_misses", !attacker_turn);
1040 }
catch(
const attack_end_exception&) {
1048 bool attacker_dies =
false;
1050 if(drains_damage > 0) {
1051 attacker.get_unit().heal(drains_damage);
1052 }
else if(drains_damage < 0) {
1053 attacker_dies = attacker.get_unit().take_hit(-drains_damage);
1057 unit_killed(attacker, defender, attacker_stats, defender_stats,
false);
1062 unit_killed(defender, attacker, defender_stats, attacker_stats,
true);
1063 (attacker_turn ? update_att_fog_ : update_def_fog_) =
true;
1067 update_minimap_ =
true;
1072 unit& defender_unit = defender.get_unit();
1076 LOG_NG <<
"defender poisoned";
1083 LOG_NG <<
"defender slowed";
1090 attacker.n_attacks_ = 0;
1091 defender.n_attacks_ = -1;
1099 update_minimap_ =
true;
1103 --attacker.n_attacks_;
1107 if (attacker_stats->
weapon ==
nullptr){
1108 attacker.n_attacks_ = 0;
1109 attacker.orig_attacks_ = 0;
1111 if (defender_stats->
weapon ==
nullptr){
1112 defender.n_attacks_ = 0;
1113 defender.orig_attacks_ = 0;
1119 void attack::unit_killed(unit_info& attacker,
1120 unit_info& defender,
1133 std::string undead_variation = defender.get_unit().undead_variation();
1143 if(a_weapon_cfg[
"name"].empty()) {
1144 a_weapon_cfg[
"name"] =
"none";
1147 if(d_weapon_cfg[
"name"].empty()) {
1148 d_weapon_cfg[
"name"] =
"none";
1158 if(!defender.valid() || defender.get_unit().hitpoints() > 0) {
1162 if(!attacker.valid()) {
1165 defender.get_unit(),
1172 defender.get_unit(),
1176 attacker.get_unit_ptr()
1183 if(!defender.valid() || defender.get_unit().hitpoints() > 0) {
1188 units_.erase(defender.loc_);
1192 if(attacker.valid() && attacker_stats->
plagues && !drain_killed) {
1196 LOG_NG <<
"found unit type:" << reanimator->id();
1199 newunit->set_attacks(0);
1200 newunit->set_movement(0,
true);
1204 if(undead_variation !=
"null") {
1207 variation[
"apply_to"] =
"variation";
1208 variation[
"name"] = undead_variation;
1209 newunit->add_modification(
"variation", mod);
1210 newunit->heal_fully();
1213 newunit->set_location(death_loc);
1221 if(update_display_) {
1226 LOG_NG <<
"unit not reanimated";
1230 void attack::perform()
1235 if(!
a_.valid() || !d_.valid()) {
1240 if(
a_.weapon_ < 0) {
1241 a_.get_unit().set_attacks(
a_.get_unit().attacks_left() - 1);
1242 a_.get_unit().set_movement(-1,
true);
1246 if(
a_.get_unit().attacks_left() <= 0) {
1247 LOG_NG <<
"attack::perform(): not enough ap.";
1253 a_stats_ = &bc_->get_attacker_stats();
1254 d_stats_ = &bc_->get_defender_stats();
1256 if(a_stats_->weapon) {
1257 a_.weap_id_ = a_stats_->weapon->id();
1260 if(d_stats_->weapon) {
1261 d_.weap_id_ = d_stats_->weapon->id();
1264 a_.get_unit().set_facing(
a_.loc_.get_relative_dir(d_.loc_));
1265 d_.get_unit().set_facing(d_.loc_.get_relative_dir(
a_.loc_));
1269 }
catch(
const attack_end_exception&) {
1273 VALIDATE(
a_.weapon_ <
static_cast<int>(
a_.get_unit().attacks().size()),
1274 _(
"An invalid attacker weapon got selected."));
1276 a_.get_unit().set_attacks(
a_.get_unit().attacks_left() -
a_.get_unit().attacks()[
a_.weapon_].attacks_used());
1277 a_.get_unit().set_movement(
a_.get_unit().movement_left() -
a_.get_unit().attacks()[
a_.weapon_].movement_used(),
true);
1279 a_.get_unit().set_resting(
false);
1280 d_.get_unit().set_resting(
false);
1285 if(a_stats_->disable) {
1286 LOG_NG <<
"attack::perform(): tried to attack with a disabled attack.";
1292 }
catch(
const attack_end_exception&) {
1296 DBG_NG <<
"getting attack statistics";
1298 a_.get_unit(), d_.get_unit(), a_stats_->chance_to_hit, d_stats_->chance_to_hit);
1300 a_.orig_attacks_ = a_stats_->num_blows;
1301 d_.orig_attacks_ = d_stats_->num_blows;
1302 a_.n_attacks_ =
a_.orig_attacks_;
1303 d_.n_attacks_ = d_.orig_attacks_;
1307 bool defender_strikes_first = (d_stats_->firststrike && !a_stats_->firststrike);
1308 unsigned int rounds = std::max<unsigned int>(a_stats_->rounds, d_stats_->rounds) - 1;
1309 const int defender_side = d_.get_unit().side();
1311 LOG_NG <<
"Fight: (" <<
a_.loc_ <<
") vs (" << d_.loc_ <<
") ATT: " << a_stats_->weapon->name() <<
" "
1312 << a_stats_->damage <<
"-" << a_stats_->num_blows <<
"(" << a_stats_->chance_to_hit
1313 <<
"%) vs DEF: " << (d_stats_->weapon ? d_stats_->weapon->name() :
"none") <<
" " << d_stats_->damage <<
"-"
1314 << d_stats_->num_blows <<
"(" << d_stats_->chance_to_hit <<
"%)"
1315 << (defender_strikes_first ?
" defender first-strike" :
"");
1321 DBG_NG <<
"start of attack loop...";
1324 if(
a_.n_attacks_ > 0 && !defender_strikes_first) {
1325 if(!perform_hit(
true, attack_stats)) {
1326 DBG_NG <<
"broke from attack loop on attacker turn";
1332 defender_strikes_first =
false;
1335 if(d_.n_attacks_ > 0) {
1336 if(!perform_hit(
false, attack_stats)) {
1337 DBG_NG <<
"broke from attack loop on defender turn";
1344 if(rounds > 0 && d_.n_attacks_ == 0 &&
a_.n_attacks_ == 0) {
1345 a_.n_attacks_ =
a_.orig_attacks_;
1346 d_.n_attacks_ = d_.orig_attacks_;
1348 defender_strikes_first = (d_stats_->firststrike && !a_stats_->firststrike);
1351 if(
a_.n_attacks_ <= 0 && d_.n_attacks_ <= 0) {
1361 if(update_def_fog_) {
1366 if(update_minimap_ && update_display_) {
1377 unit& u = d_.get_unit();
1383 d_.loc_, d_.get_unit_ptr());
1385 if(update_display_) {
1396 void attack::check_replay_attack_result(
1397 bool& hits,
int ran_num,
int& damage,
config replay_results, unit_info& attacker)
1399 int results_chance = replay_results[
"chance"].to_int();
1400 bool results_hits = replay_results[
"hits"].to_bool();
1401 int results_damage = replay_results[
"damage"].to_int();
1404 errbuf_ <<
"SYNC: In attack " <<
a_.dump() <<
" vs " << d_.dump()
1405 <<
" replay data differs from local calculated data:"
1406 <<
" chance to hit in data source: " << results_chance
1407 <<
" chance to hit in calculated: " << attacker.cth_
1408 <<
" chance to hit in data source: " << results_chance
1409 <<
" chance to hit in calculated: " << attacker.cth_
1412 attacker.cth_ = results_chance;
1413 hits = results_hits;
1414 damage = results_damage;
1419 if(results_chance != attacker.cth_) {
1420 errbuf_ <<
"SYNC: In attack " <<
a_.dump() <<
" vs " << d_.dump()
1421 <<
": chance to hit is inconsistent. Data source: " << results_chance
1422 <<
"; Calculation: " << attacker.cth_ <<
" (over-riding game calculations with data source results)\n";
1423 attacker.cth_ = results_chance;
1427 if(results_hits != hits) {
1428 errbuf_ <<
"SYNC: In attack " <<
a_.dump() <<
" vs " << d_.dump() <<
": the data source says the hit was "
1429 << (results_hits ?
"successful" :
"unsuccessful") <<
", while in-game calculations say the hit was "
1430 << (hits ?
"successful" :
"unsuccessful") <<
" random number: " << ran_num <<
" = " << (ran_num % 100)
1431 <<
"/" << results_chance <<
" (over-riding game calculations with data source results)\n";
1432 hits = results_hits;
1436 if(results_damage != damage) {
1437 errbuf_ <<
"SYNC: In attack " <<
a_.dump() <<
" vs " << d_.dump() <<
": the data source says the hit did "
1438 << results_damage <<
" damage, while in-game calculations show the hit doing " << damage
1439 <<
" damage (over-riding game calculations with data source results)\n";
1440 damage = results_damage;
1455 bool update_display)
1457 attack
dummy(attacker, defender, attack_with, defend_with, update_display);
1465 bool update_display)
1467 attack_unit(attacker, defender, attack_with, defend_with, update_display);
1519 case unit_alignments::type::lawful:
1520 bonus = lawful_bonus;
1522 case unit_alignments::type::neutral:
1525 case unit_alignments::type::chaotic:
1526 bonus = -lawful_bonus;
1528 case unit_alignments::type::liminal:
1529 bonus = max_liminal_bonus-std::abs(lawful_bonus);
1536 bonus = std::max<int>(bonus, 0);
1545 const std::vector<team>& teams)
1548 if(defender == units.
end()) {
1555 for(
i = 0;
i < adj.size(); ++
i) {
1556 if(adj[
i] == attacker_loc) {
1568 if(opp == units.
end()) {
1572 if(opp->incapacitated()) {
1577 if(std::size_t(defender->side() - 1) >= teams.size() || std::size_t(opp->side() - 1) >= teams.size()) {
1582 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.
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.
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.