56 #define DBG_NG LOG_STREAM(debug, log_engine)
57 #define LOG_NG LOG_STREAM(info, log_engine)
58 #define WRN_NG LOG_STREAM(err, log_engine)
59 #define ERR_NG LOG_STREAM(err, log_engine)
62 #define DBG_AT LOG_STREAM(debug, log_attack)
63 #define LOG_AT LOG_STREAM(info, log_attack)
64 #define WRN_AT LOG_STREAM(err, log_attack)
65 #define ERR_AT LOG_STREAM(err, log_attack)
68 #define LOG_CF LOG_STREAM(info, log_config)
82 , attack_num(u_attack_num)
83 , is_attacker(attacking)
84 , is_poisoned(up->get_state(
unit::STATE_POISONED))
85 , is_slowed(up->get_state(
unit::STATE_SLOWED))
94 , experience(up->experience())
95 , max_experience(up->max_experience())
99 , max_hp(up->max_hitpoints())
111 const unit& opp = *oppp;
118 LOG_CF <<
"Unit with " << u.
hitpoints() <<
" hitpoints found, set to 0 for damage calculations";
134 auto ctx =
weapon->specials_context(up, oppp, u_loc, opp_loc, attacking, opp_weapon);
135 utils::optional<decltype(ctx)> opp_ctx;
138 opp_ctx.emplace(opp_weapon->specials_context(oppp, up, opp_loc, u_loc, !attacking,
weapon));
145 rounds =
weapon->get_specials_and_abilities(
"berserk").highest(
"value", 1).first;
151 const bool out_of_range = distance >
weapon->max_range() || distance <
weapon->min_range();
152 disable =
weapon->has_special_or_ability(
"disable") || out_of_range;
170 - (opp_weapon ? opp_weapon->parry() : 0);
172 cth = std::clamp(cth, 0, 100);
174 cth =
weapon->composite_value(
weapon->get_specials_and_abilities(
"chance_to_hit"), cth);
184 int base_damage =
weapon->modified_damage();
187 int damage_multiplier = 100;
196 if(leader_bonus != 0) {
197 damage_multiplier += leader_bonus;
233 unsigned int opp_terrain_defense,
237 , is_attacker(attacking)
264 if(!u_type || !opp_type) {
286 utils::optional<decltype(ctx)> opp_ctx;
296 rounds =
weapon->get_specials(
"berserk").highest(
"value", 1).first;
311 signed int cth = 100 - opp_terrain_defense +
weapon->accuracy() - (opp_weapon ? opp_weapon->parry() : 0);
312 cth = std::clamp(cth, 0, 100);
314 cth =
weapon->composite_value(
weapon->get_specials(
"chance_to_hit"), cth);
318 int base_damage =
weapon->modified_damage();
319 int damage_multiplier = 100;
358 , attacker_combatant_()
359 , defender_combatant_()
361 size_t a_wep_uindex =
static_cast<size_t>(a_wep_index);
362 size_t d_wep_uindex =
static_cast<size_t>(d_wep_index);
364 const_attack_ptr a_wep(a_wep_uindex < attacker->attacks().
size() ? attacker->attacks()[a_wep_index].shared_from_this() :
nullptr);
365 const_attack_ptr d_wep(d_wep_uindex < defender->attacks().
size() ? defender->attacks()[d_wep_index].shared_from_this() :
nullptr);
393 : attacker_stats_(nullptr)
394 , defender_stats_(nullptr)
395 , attacker_combatant_(nullptr)
396 , defender_combatant_(nullptr)
408 const double harm_weight = 1.0 - aggression;
410 if(attacker_weapon == -1) {
412 n_attacker, n_defender, attacker_loc, defender_loc, harm_weight, prev_def
415 else if(defender_weapon == -1) {
417 n_attacker, n_defender, attacker_weapon, attacker_loc, defender_loc, prev_def
421 *
this =
battle_context(n_attacker, attacker_loc, attacker_weapon, n_defender, defender_loc, defender_weapon);
431 , attacker_combatant_(nullptr)
432 , defender_combatant_(nullptr)
504 double attack_weight_a = us_a.
u_.
weapon->attack_weight();
505 double attack_weight_b = us_b.
u_.
weapon->attack_weight();
506 double damage_a = (them_a.
u_.
hp - them_a.
average_hp()) * attack_weight_a;
507 double damage_b = (them_b.
u_.
hp - them_b.
average_hp()) * attack_weight_b;
510 a = (us_a.
average_hp() - poison_a_us) * harm_weight + damage_a + poison_a_them;
511 b = (us_b.
average_hp() - poison_b_us) * harm_weight + damage_b + poison_b_them;
522 return damage_a >= damage_b;
533 std::vector<battle_context> choices;
536 for(
size_t i = 0;
i < attacker->attacks().
size(); ++
i) {
547 choices.emplace_back(std::move(bc));
550 if(choices.empty()) {
551 return battle_context(attacker, attacker_loc, -1, defender, defender_loc, -1);
554 if(choices.size() == 1) {
555 return std::move(choices[0]);
560 for(
auto& choice : choices) {
564 if(!best_choice || choice.
better_attack(*best_choice, harm_weight)) {
565 best_choice = &choice;
570 return std::move(*best_choice);
573 return battle_context(attacker, attacker_loc, -1, defender, defender_loc, -1);
580 unsigned attacker_weapon,
586 VALIDATE(attacker_weapon < attacker->attacks().
size(),
_(
"An invalid attacker weapon got selected."));
588 const attack_type& att = attacker->attacks()[attacker_weapon];
589 auto no_weapon = [&]() {
return battle_context(attacker, attacker_loc, attacker_weapon, defender, defender_loc, -1); };
590 std::vector<battle_context> choices;
593 for(
size_t i = 0;
i < defender->attacks().
size(); ++
i) {
599 battle_context bc(attacker, attacker_loc, attacker_weapon, defender, defender_loc,
i);
608 choices.emplace_back(std::move(bc));
611 if(choices.empty()) {
615 if(choices.size() == 1) {
617 return std::move(choices[0]);
627 double max_weight = 0.0;
629 for(
const auto& choice : choices) {
630 const attack_type& def = defender->attacks()[choice.defender_stats_->attack_num];
636 int rating =
static_cast<int>(
648 for(
auto& choice : choices) {
649 const attack_type& def = defender->attacks()[choice.defender_stats_->attack_num];
651 choice.simulate(prev_def);
654 int simple_rating =
static_cast<int>(
655 choice.defender_stats_->num_blows * choice.defender_stats_->damage * choice.defender_stats_->chance_to_hit * def.
defense_weight());
658 if(simple_rating >= min_rating && (!best_choice || choice.
better_defense(*best_choice, 1.0))) {
659 best_choice = &choice;
663 return best_choice ? std::move(*best_choice) : no_weapon();
673 void refresh_weapon_index(
int& weap_index,
const std::string& weap_id,
attack_itors attacks)
676 if(attacks.empty()) {
682 if(weap_index >= 0 && weap_index <
static_cast<int>(attacks.size()) && attacks[weap_index].id() == weap_id) {
687 if(!weap_id.empty()) {
688 for(
int i = 0; i < static_cast<int>(attacks.size()); ++
i) {
689 if(attacks[
i].
id() == weap_id) {
709 bool update_display =
true);
714 class attack_end_exception
720 void fire_event_impl(
const std::string&
n,
bool reversed);
730 std::string weap_id_;
749 void check_replay_attack_result(
bool&,
int,
int&,
config, unit_info&);
754 std::unique_ptr<battle_context> bc_;
759 int abs_n_attack_, abs_n_defend_;
761 bool update_att_fog_, update_def_fog_, update_minimap_;
765 std::ostringstream errbuf_;
767 bool update_display_;
772 std::vector<bool> prng_attacker_;
773 std::vector<bool> prng_defender_;
793 id_ =
i->underlying_id();
796 unit& attack::unit_info::get_unit()
799 assert(
i.valid() &&
i->underlying_id() == id_);
803 unit_ptr attack::unit_info::get_unit_ptr()
806 if(
i.valid() &&
i->underlying_id() == id_) {
807 return i.get_shared_ptr();
812 bool attack::unit_info::valid()
815 return i.valid() &&
i->underlying_id() == id_;
818 std::string attack::unit_info::dump()
835 , update_att_fog_(false)
836 , update_def_fog_(false)
837 , update_minimap_(false)
842 , update_display_(update_display)
851 LOG_NG <<
"Using experimental PRNG for combat";
857 fire_event_impl(
n,
false);
860 void attack::fire_event_impl(
const std::string&
n,
bool reverse)
862 LOG_NG <<
"attack: firing '" <<
n <<
"' event";
866 config& a_weapon_cfg = ev_data.
add_child(reverse ?
"second" :
"first");
867 config& d_weapon_cfg = ev_data.
add_child(reverse ?
"first" :
"second");
870 utils::optional<attack_type::specials_context_t> a_ctx, d_ctx;
872 if(a_stats_->weapon !=
nullptr &&
a_.valid()) {
873 if(d_stats_->weapon !=
nullptr && d_.valid()) {
874 a_ctx.emplace(a_stats_->weapon->specials_context(
nullptr,
nullptr,
a_.loc_, d_.loc_,
true, d_stats_->weapon));
876 a_ctx.emplace(a_stats_->weapon->specials_context(
nullptr,
a_.loc_,
true));
878 a_stats_->weapon->write(a_weapon_cfg);
881 if(d_stats_->weapon !=
nullptr && d_.valid()) {
882 if(a_stats_->weapon !=
nullptr &&
a_.valid()) {
883 d_ctx.emplace(d_stats_->weapon->specials_context(
nullptr,
nullptr, d_.loc_,
a_.loc_,
false, a_stats_->weapon));
885 d_ctx.emplace(d_stats_->weapon->specials_context(
nullptr, d_.loc_,
false));
887 d_stats_->weapon->write(d_weapon_cfg);
890 if(a_weapon_cfg[
"name"].empty()) {
891 a_weapon_cfg[
"name"] =
"none";
894 if(d_weapon_cfg[
"name"].empty()) {
895 d_weapon_cfg[
"name"] =
"none";
898 if(
n ==
"attack_end") {
906 if(
n ==
"attacker_hits" ||
n ==
"defender_hits" ||
n ==
"unit_hits") {
910 const int defender_side = d_.get_unit().side();
921 if(wml_aborted || !
a_.valid() || !d_.valid()
926 if(update_display_) {
931 throw attack_end_exception();
935 void attack::refresh_bc()
939 refresh_weapon_index(
a_.weapon_,
a_.weap_id_,
a_.get_unit().attacks());
943 refresh_weapon_index(d_.weapon_, d_.weap_id_, d_.get_unit().attacks());
946 if(!
a_.valid() || !d_.valid()) {
949 =
a_.valid() &&
a_.weapon_ >= 0 ?
a_.get_unit().attacks()[
a_.weapon_].shared_from_this() :
nullptr;
952 = d_.valid() && d_.weapon_ >= 0 ? d_.get_unit().attacks()[d_.weapon_].shared_from_this() :
nullptr;
959 a_stats_ = &bc_->get_attacker_stats();
960 d_stats_ = &bc_->get_defender_stats();
962 a_.cth_ = a_stats_->chance_to_hit;
963 d_.cth_ = d_stats_->chance_to_hit;
964 a_.damage_ = a_stats_->damage;
965 d_.damage_ = d_stats_->damage;
970 unit_info& attacker = attacker_turn ?
a_ : d_;
971 unit_info& defender = attacker_turn ? d_ :
a_;
978 int& abs_n = attacker_turn ? abs_n_attack_ : abs_n_defend_;
979 bool& update_fog = attacker_turn ? update_def_fog_ : update_att_fog_;
985 std::vector<bool>& prng_seq = attacker_turn ? prng_attacker_ : prng_defender_;
987 if(prng_seq.empty()) {
988 const int ntotal = attacker.cth_*attacker.n_attacks_;
989 int num_hits = ntotal/100;
990 const int additional_hit_chance = ntotal%100;
991 if(additional_hit_chance > 0 &&
randomness::generator->get_random_int(0, 99) < additional_hit_chance) {
995 std::vector<int> indexes;
996 for(
int i = 0;
i != attacker.n_attacks_; ++
i) {
997 prng_seq.push_back(
false);
998 indexes.push_back(
i);
1001 for(
int i = 0;
i != num_hits; ++
i) {
1003 prng_seq[indexes[
n]] =
true;
1004 indexes.erase(indexes.begin() +
n);
1008 bool does_hit = prng_seq.back();
1009 prng_seq.pop_back();
1010 ran_num = does_hit ? 0 : 99;
1014 bool hits = (ran_num < attacker.cth_);
1018 damage = attacker.damage_;
1024 const config local_results {
"chance", attacker.cth_,
"hits", hits,
"damage", damage};
1029 if(!equals_replay) {
1030 check_replay_attack_result(hits, ran_num, damage, replay_results, attacker);
1034 int damage_done = std::min<int>(defender.get_unit().hitpoints(), attacker.damage_);
1037 double expected_damage = damage_done * attacker.cth_ * 0.01;
1045 int drains_damage = 0;
1046 if(hits && attacker_stats->
drains) {
1051 std::min<int>(drains_damage, attacker.get_unit().max_hitpoints() - attacker.get_unit().hitpoints());
1054 drains_damage = std::max<int>(drains_damage, 1 - attacker.get_unit().hitpoints());
1057 if(update_display_) {
1058 std::ostringstream float_text;
1059 std::vector<std::string> extra_hit_sounds;
1062 const unit& defender_unit = defender.get_unit();
1087 attacker.loc_, defender.loc_,
1090 abs_n, float_text.str(), drains_damage,
"",
1091 &extra_hit_sounds, attacker_turn
1095 bool dies = defender.get_unit().take_hit(damage);
1096 LOG_NG <<
"defender took " << damage << (dies ?
" and died\n" :
"\n");
1104 attacker.cth_, damage_done, drains_damage
1112 attacker.cth_, damage_done, drains_damage
1116 replay_results.
clear();
1121 if(!equals_replay) {
1122 bool results_dies = replay_results[
"dies"].to_bool();
1124 errbuf_ <<
"SYNC: In attack " <<
a_.dump() <<
" vs " << d_.dump() <<
": the data source says the "
1125 << (attacker_turn ?
"defender" :
"attacker") <<
' ' << (results_dies ?
"perished" :
"survived")
1126 <<
" while in-game calculations show it " << (dies ?
"perished" :
"survived")
1127 <<
" (over-riding game calculations with data source results)\n";
1129 dies = results_dies;
1133 defender.get_unit().set_hitpoints(0);
1141 fire_event(attacker_turn ?
"attacker_hits" :
"defender_hits");
1142 fire_event_impl(
"unit_hits", !attacker_turn);
1143 }
catch(
const attack_end_exception&) {
1149 fire_event(attacker_turn ?
"attacker_misses" :
"defender_misses");
1150 fire_event_impl(
"unit_misses", !attacker_turn);
1151 }
catch(
const attack_end_exception&) {
1159 bool attacker_dies =
false;
1161 if(drains_damage > 0) {
1162 attacker.get_unit().heal(drains_damage);
1163 }
else if(drains_damage < 0) {
1164 attacker_dies = attacker.get_unit().take_hit(-drains_damage);
1168 unit_killed(attacker, defender, attacker_stats, defender_stats,
false);
1173 unit_killed(defender, attacker, defender_stats, attacker_stats,
true);
1174 (attacker_turn ? update_att_fog_ : update_def_fog_) =
true;
1178 update_minimap_ =
true;
1183 unit& defender_unit = defender.get_unit();
1187 LOG_NG <<
"defender poisoned";
1194 LOG_NG <<
"defender slowed";
1201 attacker.n_attacks_ = 0;
1202 defender.n_attacks_ = -1;
1210 update_minimap_ =
true;
1214 --attacker.n_attacks_;
1218 if (attacker_stats->
weapon ==
nullptr){
1219 attacker.n_attacks_ = 0;
1220 attacker.orig_attacks_ = 0;
1222 if (defender_stats->
weapon ==
nullptr){
1223 defender.n_attacks_ = 0;
1224 defender.orig_attacks_ = 0;
1230 void attack::unit_killed(unit_info& attacker,
1231 unit_info& defender,
1244 std::string undead_variation = defender.get_unit().undead_variation();
1254 if(a_weapon_cfg[
"name"].empty()) {
1255 a_weapon_cfg[
"name"] =
"none";
1258 if(d_weapon_cfg[
"name"].empty()) {
1259 d_weapon_cfg[
"name"] =
"none";
1269 if(!defender.valid() || defender.get_unit().hitpoints() > 0) {
1273 if(!attacker.valid()) {
1276 defender.get_unit(),
1283 defender.get_unit(),
1287 attacker.get_unit_ptr()
1294 if(!defender.valid() || defender.get_unit().hitpoints() > 0) {
1299 units_.erase(defender.loc_);
1303 if(attacker.valid() && attacker_stats->
plagues && !drain_killed) {
1307 LOG_NG <<
"found unit type:" << reanimator->id();
1310 newunit->set_attacks(0);
1311 newunit->set_movement(0,
true);
1315 if(undead_variation !=
"null") {
1318 variation[
"apply_to"] =
"variation";
1319 variation[
"name"] = undead_variation;
1320 newunit->add_modification(
"variation", mod);
1321 newunit->heal_fully();
1324 newunit->set_location(death_loc);
1332 if(update_display_) {
1337 LOG_NG <<
"unit not reanimated";
1341 void attack::perform()
1346 if(!
a_.valid() || !d_.valid()) {
1351 if(
a_.weapon_ < 0) {
1352 a_.get_unit().set_attacks(
a_.get_unit().attacks_left() - 1);
1353 a_.get_unit().set_movement(-1,
true);
1357 if(
a_.get_unit().attacks_left() <= 0) {
1358 LOG_NG <<
"attack::perform(): not enough ap.";
1364 a_stats_ = &bc_->get_attacker_stats();
1365 d_stats_ = &bc_->get_defender_stats();
1367 if(a_stats_->weapon) {
1368 a_.weap_id_ = a_stats_->weapon->id();
1371 if(d_stats_->weapon) {
1372 d_.weap_id_ = d_stats_->weapon->id();
1375 a_.get_unit().set_facing(
a_.loc_.get_relative_dir(d_.loc_));
1376 d_.get_unit().set_facing(d_.loc_.get_relative_dir(
a_.loc_));
1380 }
catch(
const attack_end_exception&) {
1384 VALIDATE(
a_.weapon_ <
static_cast<int>(
a_.get_unit().attacks().size()),
1385 _(
"An invalid attacker weapon got selected."));
1387 a_.get_unit().set_attacks(
a_.get_unit().attacks_left() -
a_.get_unit().attacks()[
a_.weapon_].attacks_used());
1388 a_.get_unit().set_movement(
a_.get_unit().movement_left() -
a_.get_unit().attacks()[
a_.weapon_].movement_used(),
true);
1390 a_.get_unit().set_resting(
false);
1391 d_.get_unit().set_resting(
false);
1396 if(a_stats_->disable) {
1397 LOG_NG <<
"attack::perform(): tried to attack with a disabled attack.";
1403 }
catch(
const attack_end_exception&) {
1407 DBG_NG <<
"getting attack statistics";
1409 a_.get_unit(), d_.get_unit(), a_stats_->chance_to_hit, d_stats_->chance_to_hit);
1411 a_.orig_attacks_ = a_stats_->num_blows;
1412 d_.orig_attacks_ = d_stats_->num_blows;
1413 a_.n_attacks_ =
a_.orig_attacks_;
1414 d_.n_attacks_ = d_.orig_attacks_;
1418 bool defender_strikes_first = (d_stats_->firststrike && !a_stats_->firststrike);
1419 unsigned int rounds = std::max<unsigned int>(a_stats_->rounds, d_stats_->rounds) - 1;
1420 const int defender_side = d_.get_unit().side();
1422 LOG_NG <<
"Fight: (" <<
a_.loc_ <<
") vs (" << d_.loc_ <<
") ATT: " << a_stats_->weapon->name() <<
" "
1423 << a_stats_->damage <<
"-" << a_stats_->num_blows <<
"(" << a_stats_->chance_to_hit
1424 <<
"%) vs DEF: " << (d_stats_->weapon ? d_stats_->weapon->name() :
"none") <<
" " << d_stats_->damage <<
"-"
1425 << d_stats_->num_blows <<
"(" << d_stats_->chance_to_hit <<
"%)"
1426 << (defender_strikes_first ?
" defender first-strike" :
"");
1432 DBG_NG <<
"start of attack loop...";
1435 if(
a_.n_attacks_ > 0 && !defender_strikes_first) {
1436 if(!perform_hit(
true, attack_stats)) {
1437 DBG_NG <<
"broke from attack loop on attacker turn";
1443 defender_strikes_first =
false;
1446 if(d_.n_attacks_ > 0) {
1447 if(!perform_hit(
false, attack_stats)) {
1448 DBG_NG <<
"broke from attack loop on defender turn";
1455 if(rounds > 0 && d_.n_attacks_ == 0 &&
a_.n_attacks_ == 0) {
1456 a_.n_attacks_ =
a_.orig_attacks_;
1457 d_.n_attacks_ = d_.orig_attacks_;
1459 defender_strikes_first = (d_stats_->firststrike && !a_stats_->firststrike);
1462 if(
a_.n_attacks_ <= 0 && d_.n_attacks_ <= 0) {
1472 if(update_def_fog_) {
1477 if(update_minimap_ && update_display_) {
1488 unit& u = d_.get_unit();
1494 d_.loc_, d_.get_unit_ptr());
1496 if(update_display_) {
1507 void attack::check_replay_attack_result(
1508 bool& hits,
int ran_num,
int& damage,
config replay_results, unit_info& attacker)
1510 int results_chance = replay_results[
"chance"].to_int();
1511 bool results_hits = replay_results[
"hits"].to_bool();
1512 int results_damage = replay_results[
"damage"].to_int();
1515 errbuf_ <<
"SYNC: In attack " <<
a_.dump() <<
" vs " << d_.dump()
1516 <<
" replay data differs from local calculated data:"
1517 <<
" chance to hit in data source: " << results_chance
1518 <<
" chance to hit in calculated: " << attacker.cth_
1519 <<
" chance to hit in data source: " << results_chance
1520 <<
" chance to hit in calculated: " << attacker.cth_
1523 attacker.cth_ = results_chance;
1524 hits = results_hits;
1525 damage = results_damage;
1530 if(results_chance != attacker.cth_) {
1531 errbuf_ <<
"SYNC: In attack " <<
a_.dump() <<
" vs " << d_.dump()
1532 <<
": chance to hit is inconsistent. Data source: " << results_chance
1533 <<
"; Calculation: " << attacker.cth_ <<
" (over-riding game calculations with data source results)\n";
1534 attacker.cth_ = results_chance;
1538 if(results_hits != hits) {
1539 errbuf_ <<
"SYNC: In attack " <<
a_.dump() <<
" vs " << d_.dump() <<
": the data source says the hit was "
1540 << (results_hits ?
"successful" :
"unsuccessful") <<
", while in-game calculations say the hit was "
1541 << (hits ?
"successful" :
"unsuccessful") <<
" random number: " << ran_num <<
" = " << (ran_num % 100)
1542 <<
"/" << results_chance <<
" (over-riding game calculations with data source results)\n";
1543 hits = results_hits;
1547 if(results_damage != damage) {
1548 errbuf_ <<
"SYNC: In attack " <<
a_.dump() <<
" vs " << d_.dump() <<
": the data source says the hit did "
1549 << results_damage <<
" damage, while in-game calculations show the hit doing " << damage
1550 <<
" damage (over-riding game calculations with data source results)\n";
1551 damage = results_damage;
1566 bool update_display)
1568 attack
dummy(attacker, defender, attack_with, defend_with, update_display);
1576 bool update_display)
1578 attack_unit(attacker, defender, attack_with, defend_with, update_display);
1623 case unit_alignments::type::lawful:
1624 bonus = lawful_bonus;
1626 case unit_alignments::type::neutral:
1629 case unit_alignments::type::chaotic:
1630 bonus = -lawful_bonus;
1632 case unit_alignments::type::liminal:
1633 bonus = max_liminal_bonus-std::abs(lawful_bonus);
1640 bonus = std::max<int>(bonus, 0);
1649 const std::vector<team>& teams)
1652 if(defender == units.
end()) {
1659 for(
i = 0;
i < adj.size(); ++
i) {
1660 if(adj[
i] == attacker_loc) {
1672 if(opp == units.
end()) {
1676 if(opp->incapacitated()) {
1681 if(std::size_t(defender->side() - 1) >= teams.size() || std::size_t(opp->side() - 1) >= teams.size()) {
1686 if(teams[defender->side() - 1].is_enemy(opp->side())) {
int under_leadership(const unit &u, const map_location &loc, const_attack_ptr weapon, const_attack_ptr opp_weapon)
Tests if the unit at loc is currently affected by leadership.
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.
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_attacker_weapon(nonempty_unit_const_ptr attacker, nonempty_unit_const_ptr defender, const map_location &attacker_loc, const map_location &defender_loc, double harm_weight, 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.
void insert(config_key_type key, T &&value)
Inserts an attribute into the config.
config & add_child(config_key_type key)
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.
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)
bool is_enemy(int n) const
int get_max_liminal_bonus() const
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.
int resistance_against(const std::string &damage_name, bool attacker) const
Gets resistance while considering custom WML abilities.
int experience_needed(bool with_acceleration=true) const
bool musthave_status(const std::string &status) const
const std::string & undead_variation() const
Info on the type of unit that the unit reanimates as.
unit_alignments::type alignment() const
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)
unit_ability_list get_abilities_weapons(const std::string &tag_name, const map_location &loc, const_attack_ptr weapon=nullptr, const_attack_ptr opp_weapon=nullptr) const
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.
int damage_from(const attack_type &attack, bool attacker, const map_location &loc, const_attack_ptr weapon=nullptr) const
Calculates the damage this unit would take from a certain attack.
int defense_modifier(const t_translation::terrain_code &terrain) const
The unit's defense on a given terrain.
attack_itors attacks()
Gets an iterator over this unit's attacks.
unit_animation_component & anim_comp() const
bool is_fearless() const
Gets whether this unit is fearless - ie, unaffected by time of day.
void get_adjacent_tiles(const map_location &a, map_location *res)
Function which, given a location, will place all adjacent locations in res.
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.
Standard logging facilities (interface).
#define log_scope2(domain, description)
constexpr int round_damage(int base_damage, int bonus, int divisor)
round (base_damage * bonus / divisor) to the closest integer, but up or down towards base_damage
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_draw_weapon(const map_location &loc, unit &attacker, const_attack_ptr attack, const_attack_ptr secondary_attack, const map_location &defender_loc, unit_ptr defender)
Play a pre-fight animation First unit is the attacker, second unit the defender.
void unit_die(const map_location &loc, unit &loser, const_attack_ptr attack, const_attack_ptr secondary_attack, const map_location &winner_loc, unit_ptr winner)
Show a unit fading out.
void unit_sheath_weapon(const map_location &primary_loc, unit_ptr primary_unit, const_attack_ptr primary_attack, const_attack_ptr secondary_attack, const map_location &secondary_loc, unit_ptr secondary_unit)
Play a post-fight animation Both unit can be set to null, only valid units will play their animation.
void unit_attack(display *disp, game_board &board, const map_location &a, const map_location &b, int damage, const attack_type &attack, const_attack_ptr secondary_attack, int swing, const std::string &hit_text, int drain_amount, const std::string &att_text, const std::vector< std::string > *extra_hit_sounds, bool attacking)
Make the unit on tile 'a' attack the unit on tile 'b'.
std::size_t size(const std::string &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.
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.
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_attack_ptr opp_weapon)
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)
unsigned int max_experience
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).
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.
unsigned int max_hp
Maximum hitpoints of the unit.
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)
static const map_location & null_location()
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.
const config * ability_cfg
The contents of the ability tag, never nullptr.
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.