27 #include "formula/callable_objects.hpp" 61 #define DBG_NG LOG_STREAM(debug, log_engine) 62 #define LOG_NG LOG_STREAM(info, log_engine) 63 #define WRN_NG LOG_STREAM(err, log_engine) 64 #define ERR_NG LOG_STREAM(err, log_engine) 67 #define DBG_AT LOG_STREAM(debug, log_attack) 68 #define LOG_AT LOG_STREAM(info, log_attack) 69 #define WRN_AT LOG_STREAM(err, log_attack) 70 #define ERR_AT LOG_STREAM(err, log_attack) 73 #define LOG_CF LOG_STREAM(info, log_config) 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))
99 , experience(up->experience())
100 , max_experience(up->max_experience())
104 , max_hp(up->max_hitpoints())
116 const unit& opp = *oppp;
123 LOG_CF <<
"Unit with " << u.
hitpoints() <<
" hitpoints found, set to 0 for damage calculations";
139 auto ctx =
weapon->specials_context(up, oppp, u_loc, opp_loc, attacking, opp_weapon);
140 std::optional<decltype(ctx)> opp_ctx;
143 opp_ctx.emplace(opp_weapon->specials_context(oppp, up, opp_loc, u_loc, !attacking,
weapon));
150 rounds =
weapon->get_specials_and_abilities(
"berserk").highest(
"value", 1).first;
156 const bool out_of_range = distance >
weapon->max_range() || distance <
weapon->min_range();
175 - (opp_weapon ? opp_weapon->parry() : 0);
177 cth = std::clamp(cth, 0, 100);
191 int base_damage =
weapon->modified_damage();
194 int damage_multiplier = 100;
202 if(leader_bonus != 0) {
203 damage_multiplier += leader_bonus;
243 unsigned int opp_terrain_defense,
274 if(!u_type || !opp_type) {
296 std::optional<decltype(ctx)> opp_ctx;
306 rounds =
weapon->get_specials(
"berserk").highest(
"value", 1).first;
321 signed int cth = 100 - opp_terrain_defense +
weapon->accuracy() - (opp_weapon ? opp_weapon->parry() : 0);
322 cth = std::clamp(cth, 0, 100);
330 int base_damage =
weapon->modified_damage();
331 int damage_multiplier = 100;
374 , attacker_combatant_()
375 , defender_combatant_()
377 size_t a_wep_uindex =
static_cast<size_t>(a_wep_index);
378 size_t d_wep_uindex =
static_cast<size_t>(d_wep_index);
380 const_attack_ptr a_wep(a_wep_uindex < attacker->attacks().
size() ? attacker->attacks()[a_wep_index].shared_from_this() :
nullptr);
381 const_attack_ptr d_wep(d_wep_uindex < defender->attacks().
size() ? defender->attacks()[d_wep_index].shared_from_this() :
nullptr);
424 const double harm_weight = 1.0 - aggression;
426 if(attacker_weapon == -1) {
428 n_attacker, n_defender, attacker_loc, defender_loc, harm_weight, prev_def
431 else if(defender_weapon == -1) {
433 n_attacker, n_defender, attacker_weapon, attacker_loc, defender_loc, prev_def
437 *
this =
battle_context(n_attacker, attacker_loc, attacker_weapon, n_defender, defender_loc, defender_weapon);
544 std::vector<battle_context> choices;
547 for(
size_t i = 0;
i < attacker->attacks().size(); ++
i) {
558 choices.emplace_back(std::move(bc));
561 if(choices.empty()) {
562 return battle_context(attacker, attacker_loc, -1, defender, defender_loc, -1);
565 if(choices.size() == 1) {
566 return std::move(choices[0]);
571 for(
auto& choice : choices) {
575 if(!best_choice || choice.
better_attack(*best_choice, harm_weight)) {
576 best_choice = &choice;
581 return std::move(*best_choice);
584 return battle_context(attacker, attacker_loc, -1, defender, defender_loc, -1);
591 unsigned attacker_weapon,
597 VALIDATE(attacker_weapon < attacker->attacks().
size(),
_(
"An invalid attacker weapon got selected."));
599 const attack_type& att = attacker->attacks()[attacker_weapon];
600 auto no_weapon = [&]() {
return battle_context(attacker, attacker_loc, attacker_weapon, defender, defender_loc, -1); };
601 std::vector<battle_context> choices;
604 for(
size_t i = 0;
i < defender->attacks().size(); ++
i) {
610 battle_context bc(attacker, attacker_loc, attacker_weapon, defender, defender_loc,
i);
619 choices.emplace_back(std::move(bc));
622 if(choices.empty()) {
626 if(choices.size() == 1) {
628 return std::move(choices[0]);
638 double max_weight = 0.0;
640 for(
const auto& choice : choices) {
641 const attack_type& def = defender->attacks()[choice.defender_stats_->attack_num];
647 int rating =
static_cast<int>(
659 for(
auto& choice : choices) {
660 const attack_type& def = defender->attacks()[choice.defender_stats_->attack_num];
662 choice.simulate(prev_def);
665 int simple_rating =
static_cast<int>(
666 choice.defender_stats_->num_blows * choice.defender_stats_->damage * choice.defender_stats_->chance_to_hit * def.
defense_weight());
669 if(simple_rating >= min_rating && (!best_choice || choice.
better_defense(*best_choice, 1.0))) {
670 best_choice = &choice;
674 return best_choice ? std::move(*best_choice) : no_weapon();
684 void refresh_weapon_index(
int& weap_index,
const std::string& weap_id,
attack_itors attacks)
687 if(attacks.empty()) {
693 if(weap_index >= 0 && weap_index < static_cast<int>(attacks.size()) && attacks[weap_index].
id() == weap_id) {
698 if(!weap_id.empty()) {
699 for(
int i = 0; i < static_cast<int>(attacks.size()); ++
i) {
700 if(attacks[
i].
id() == weap_id) {
720 bool update_display =
true);
725 class attack_end_exception
740 std::string weap_id_;
759 void check_replay_attack_result(
bool&,
int,
int&,
config, unit_info&);
764 std::unique_ptr<battle_context> bc_;
769 int abs_n_attack_, abs_n_defend_;
771 bool update_att_fog_, update_def_fog_, update_minimap_;
775 std::ostringstream errbuf_;
777 bool update_display_;
782 std::vector<bool> prng_attacker_;
783 std::vector<bool> prng_defender_;
803 id_ = i->underlying_id();
806 unit& attack::unit_info::get_unit()
809 assert(i.
valid() && i->underlying_id() == id_);
813 unit_ptr attack::unit_info::get_unit_ptr()
816 if(i.
valid() && i->underlying_id() == id_) {
822 bool attack::unit_info::valid()
825 return i.
valid() && i->underlying_id() == id_;
828 std::string attack::unit_info::dump()
845 , update_att_fog_(
false)
846 , update_def_fog_(
false)
847 , update_minimap_(
false)
852 , update_display_(update_display)
861 LOG_NG <<
"Using experimental PRNG for combat";
867 LOG_NG <<
"attack: firing '" << n <<
"' event";
875 std::optional<attack_type::specials_context_t> a_ctx, d_ctx;
877 if(a_stats_->weapon !=
nullptr &&
a_.valid()) {
878 if(d_stats_->weapon !=
nullptr && d_.valid()) {
879 a_ctx.emplace(a_stats_->weapon->specials_context(
nullptr,
nullptr,
a_.loc_, d_.loc_,
true, d_stats_->weapon));
881 a_ctx.emplace(a_stats_->weapon->specials_context(
nullptr,
a_.loc_,
true));
883 a_stats_->weapon->write(a_weapon_cfg);
886 if(d_stats_->weapon !=
nullptr && d_.valid()) {
887 if(a_stats_->weapon !=
nullptr &&
a_.valid()) {
888 d_ctx.emplace(d_stats_->weapon->specials_context(
nullptr,
nullptr, d_.loc_,
a_.loc_,
false, a_stats_->weapon));
890 d_ctx.emplace(d_stats_->weapon->specials_context(
nullptr, d_.loc_,
false));
892 d_stats_->weapon->write(d_weapon_cfg);
895 if(a_weapon_cfg[
"name"].empty()) {
896 a_weapon_cfg[
"name"] =
"none";
899 if(d_weapon_cfg[
"name"].empty()) {
900 d_weapon_cfg[
"name"] =
"none";
903 if(n ==
"attack_end") {
911 if(n ==
"attacker_hits" || n ==
"defender_hits") {
915 const int defender_side = d_.get_unit().side();
926 if(wml_aborted || !
a_.valid() || !d_.valid()
931 if(update_display_) {
936 throw attack_end_exception();
940 void attack::refresh_bc()
944 refresh_weapon_index(
a_.weapon_,
a_.weap_id_,
a_.get_unit().attacks());
948 refresh_weapon_index(d_.weapon_, d_.weap_id_, d_.get_unit().attacks());
951 if(!
a_.valid() || !d_.valid()) {
954 =
a_.valid() &&
a_.weapon_ >= 0 ?
a_.get_unit().attacks()[
a_.weapon_].shared_from_this() :
nullptr;
957 = d_.valid() && d_.weapon_ >= 0 ? d_.get_unit().attacks()[d_.weapon_].shared_from_this() :
nullptr;
964 a_stats_ = &bc_->get_attacker_stats();
965 d_stats_ = &bc_->get_defender_stats();
967 a_.cth_ = a_stats_->chance_to_hit;
968 d_.cth_ = d_stats_->chance_to_hit;
969 a_.damage_ = a_stats_->damage;
970 d_.damage_ = d_stats_->damage;
975 unit_info& attacker = attacker_turn ?
a_ : d_;
976 unit_info& defender = attacker_turn ? d_ :
a_;
983 int& abs_n = attacker_turn ? abs_n_attack_ : abs_n_defend_;
984 bool& update_fog = attacker_turn ? update_def_fog_ : update_att_fog_;
990 std::vector<bool>& prng_seq = attacker_turn ? prng_attacker_ : prng_defender_;
992 if(prng_seq.empty()) {
993 const int ntotal = attacker.cth_*attacker.n_attacks_;
994 int num_hits = ntotal/100;
995 const int additional_hit_chance = ntotal%100;
996 if(additional_hit_chance > 0 &&
randomness::generator->get_random_int(0, 99) < additional_hit_chance) {
1000 std::vector<int> indexes;
1001 for(
int i = 0;
i != attacker.n_attacks_; ++
i) {
1002 prng_seq.push_back(
false);
1003 indexes.push_back(
i);
1006 for(
int i = 0;
i != num_hits; ++
i) {
1008 prng_seq[indexes[
n]] =
true;
1009 indexes.erase(indexes.begin() +
n);
1013 bool does_hit = prng_seq.back();
1014 prng_seq.pop_back();
1015 ran_num = does_hit ? 0 : 99;
1019 bool hits = (ran_num < attacker.cth_);
1023 damage = attacker.damage_;
1029 const config local_results {
"chance", attacker.cth_,
"hits", hits,
"damage", damage};
1034 if(!equals_replay) {
1035 check_replay_attack_result(hits, ran_num, damage, replay_results, attacker);
1039 int damage_done = std::min<int>(defender.get_unit().hitpoints(), attacker.damage_);
1042 double expected_damage = damage_done * attacker.cth_ * 0.01;
1050 int drains_damage = 0;
1051 if(hits && attacker_stats->
drains) {
1056 std::min<int>(drains_damage, attacker.get_unit().max_hitpoints() - attacker.get_unit().hitpoints());
1059 drains_damage = std::max<int>(drains_damage, 1 - attacker.get_unit().hitpoints());
1062 if(update_display_) {
1063 std::ostringstream float_text;
1064 std::vector<std::string> extra_hit_sounds;
1067 const unit& defender_unit = defender.get_unit();
1092 attacker.loc_, defender.loc_,
1095 abs_n, float_text.str(), drains_damage,
"",
1096 &extra_hit_sounds, attacker_turn
1100 bool dies = defender.get_unit().take_hit(damage);
1101 LOG_NG <<
"defender took " << damage << (dies ?
" and died\n" :
"\n");
1109 attacker.cth_, damage_done, drains_damage
1117 attacker.cth_, damage_done, drains_damage
1121 replay_results.
clear();
1126 if(!equals_replay) {
1127 bool results_dies = replay_results[
"dies"].to_bool();
1129 errbuf_ <<
"SYNC: In attack " <<
a_.dump() <<
" vs " << d_.dump() <<
": the data source says the " 1130 << (attacker_turn ?
"defender" :
"attacker") <<
' ' << (results_dies ?
"perished" :
"survived")
1131 <<
" while in-game calculations show it " << (dies ?
"perished" :
"survived")
1132 <<
" (over-riding game calculations with data source results)\n";
1134 dies = results_dies;
1138 defender.get_unit().set_hitpoints(0);
1146 fire_event(attacker_turn ?
"attacker_hits" :
"defender_hits");
1147 }
catch(
const attack_end_exception&) {
1153 fire_event(attacker_turn ?
"attacker_misses" :
"defender_misses");
1154 }
catch(
const attack_end_exception&) {
1162 bool attacker_dies =
false;
1164 if(drains_damage > 0) {
1165 attacker.get_unit().heal(drains_damage);
1166 }
else if(drains_damage < 0) {
1167 attacker_dies = attacker.get_unit().take_hit(-drains_damage);
1171 unit_killed(attacker, defender, attacker_stats, defender_stats,
false);
1176 unit_killed(defender, attacker, defender_stats, attacker_stats,
true);
1177 (attacker_turn ? update_att_fog_ : update_def_fog_) =
true;
1181 update_minimap_ =
true;
1186 unit& defender_unit = defender.get_unit();
1190 LOG_NG <<
"defender poisoned";
1197 LOG_NG <<
"defender slowed";
1204 attacker.n_attacks_ = 0;
1205 defender.n_attacks_ = -1;
1213 update_minimap_ =
true;
1217 --attacker.n_attacks_;
1221 if (attacker_stats->
weapon ==
nullptr){
1222 attacker.n_attacks_ = 0;
1224 if (defender_stats->
weapon ==
nullptr){
1225 defender.n_attacks_ = 0;
1231 void attack::unit_killed(unit_info& attacker,
1232 unit_info& defender,
1245 std::string undead_variation = defender.get_unit().undead_variation();
1255 if(a_weapon_cfg[
"name"].empty()) {
1256 a_weapon_cfg[
"name"] =
"none";
1259 if(d_weapon_cfg[
"name"].empty()) {
1260 d_weapon_cfg[
"name"] =
"none";
1270 if(!defender.valid() || defender.get_unit().hitpoints() > 0) {
1274 if(!attacker.valid()) {
1277 defender.get_unit(),
1284 defender.get_unit(),
1288 attacker.get_unit_ptr()
1295 if(!defender.valid() || defender.get_unit().hitpoints() > 0) {
1300 units_.erase(defender.loc_);
1304 if(attacker.valid() && attacker_stats->
plagues && !drain_killed) {
1308 LOG_NG <<
"found unit type:" << reanimator->id();
1311 newunit->set_attacks(0);
1312 newunit->set_movement(0,
true);
1316 if(undead_variation !=
"null") {
1319 variation[
"apply_to"] =
"variation";
1320 variation[
"name"] = undead_variation;
1321 newunit->add_modification(
"variation", mod);
1322 newunit->heal_fully();
1325 newunit->set_location(death_loc);
1333 if(update_display_) {
1338 LOG_NG <<
"unit not reanimated";
1342 void attack::perform()
1347 if(!
a_.valid() || !d_.valid()) {
1352 if(
a_.weapon_ < 0) {
1353 a_.get_unit().set_attacks(
a_.get_unit().attacks_left() - 1);
1354 a_.get_unit().set_movement(-1,
true);
1358 if(
a_.get_unit().attacks_left() <= 0) {
1359 LOG_NG <<
"attack::perform(): not enough ap.";
1365 a_stats_ = &bc_->get_attacker_stats();
1366 d_stats_ = &bc_->get_defender_stats();
1368 if(a_stats_->weapon) {
1369 a_.weap_id_ = a_stats_->weapon->id();
1372 if(d_stats_->weapon) {
1373 d_.weap_id_ = d_stats_->weapon->id();
1376 a_.get_unit().set_facing(
a_.loc_.get_relative_dir(d_.loc_));
1377 d_.get_unit().set_facing(d_.loc_.get_relative_dir(
a_.loc_));
1381 }
catch(
const attack_end_exception&) {
1385 a_.get_unit().set_attacks(
a_.get_unit().attacks_left() - 1);
1387 VALIDATE(
a_.weapon_ < static_cast<int>(
a_.get_unit().attacks().size()),
1388 _(
"An invalid attacker weapon got selected."));
1390 a_.get_unit().set_movement(
a_.get_unit().movement_left() -
a_.get_unit().attacks()[
a_.weapon_].movement_used(),
true);
1392 a_.get_unit().set_resting(
false);
1393 d_.get_unit().set_resting(
false);
1398 if(a_stats_->disable) {
1399 LOG_NG <<
"attack::perform(): tried to attack with a disabled attack.";
1405 }
catch(
const attack_end_exception&) {
1409 DBG_NG <<
"getting attack statistics";
1411 a_.get_unit(), d_.get_unit(), a_stats_->chance_to_hit, d_stats_->chance_to_hit);
1413 a_.orig_attacks_ = a_stats_->num_blows;
1414 d_.orig_attacks_ = d_stats_->num_blows;
1415 a_.n_attacks_ =
a_.orig_attacks_;
1416 d_.n_attacks_ = d_.orig_attacks_;
1420 bool defender_strikes_first = (d_stats_->firststrike && !a_stats_->firststrike);
1421 unsigned int rounds = std::max<unsigned int>(a_stats_->rounds, d_stats_->rounds) - 1;
1422 const int defender_side = d_.get_unit().side();
1424 LOG_NG <<
"Fight: (" <<
a_.loc_ <<
") vs (" << d_.loc_ <<
") ATT: " << a_stats_->weapon->name() <<
" " 1425 << a_stats_->damage <<
"-" << a_stats_->num_blows <<
"(" << a_stats_->chance_to_hit
1426 <<
"%) vs DEF: " << (d_stats_->weapon ? d_stats_->weapon->name() :
"none") <<
" " << d_stats_->damage <<
"-" 1427 << d_stats_->num_blows <<
"(" << d_stats_->chance_to_hit <<
"%)" 1428 << (defender_strikes_first ?
" defender first-strike" :
"");
1434 DBG_NG <<
"start of attack loop...";
1437 if(
a_.n_attacks_ > 0 && !defender_strikes_first) {
1438 if(!perform_hit(
true, attack_stats)) {
1439 DBG_NG <<
"broke from attack loop on attacker turn";
1445 defender_strikes_first =
false;
1448 if(d_.n_attacks_ > 0) {
1449 if(!perform_hit(
false, attack_stats)) {
1450 DBG_NG <<
"broke from attack loop on defender turn";
1457 if(rounds > 0 && d_.n_attacks_ == 0 &&
a_.n_attacks_ == 0) {
1458 a_.n_attacks_ =
a_.orig_attacks_;
1459 d_.n_attacks_ = d_.orig_attacks_;
1461 defender_strikes_first = (d_stats_->firststrike && !a_stats_->firststrike);
1464 if(
a_.n_attacks_ <= 0 && d_.n_attacks_ <= 0) {
1474 if(update_def_fog_) {
1479 if(update_minimap_ && update_display_) {
1490 unit& u = d_.get_unit();
1496 d_.loc_, d_.get_unit_ptr());
1498 if(update_display_) {
1509 void attack::check_replay_attack_result(
1510 bool& hits,
int ran_num,
int& damage,
config replay_results, unit_info& attacker)
1512 int results_chance = replay_results[
"chance"];
1513 bool results_hits = replay_results[
"hits"].to_bool();
1514 int results_damage = replay_results[
"damage"];
1517 errbuf_ <<
"SYNC: In attack " <<
a_.dump() <<
" vs " << d_.dump()
1518 <<
" replay data differs from local calculated data:" 1519 <<
" chance to hit in data source: " << results_chance
1520 <<
" chance to hit in calculated: " << attacker.cth_
1521 <<
" chance to hit in data source: " << results_chance
1522 <<
" chance to hit in calculated: " << attacker.cth_
1525 attacker.cth_ = results_chance;
1526 hits = results_hits;
1527 damage = results_damage;
1532 if(results_chance != attacker.cth_) {
1533 errbuf_ <<
"SYNC: In attack " <<
a_.dump() <<
" vs " << d_.dump()
1534 <<
": chance to hit is inconsistent. Data source: " << results_chance
1535 <<
"; Calculation: " << attacker.cth_ <<
" (over-riding game calculations with data source results)\n";
1536 attacker.cth_ = results_chance;
1540 if(results_hits != hits) {
1541 errbuf_ <<
"SYNC: In attack " <<
a_.dump() <<
" vs " << d_.dump() <<
": the data source says the hit was " 1542 << (results_hits ?
"successful" :
"unsuccessful") <<
", while in-game calculations say the hit was " 1543 << (hits ?
"successful" :
"unsuccessful") <<
" random number: " << ran_num <<
" = " << (ran_num % 100)
1544 <<
"/" << results_chance <<
" (over-riding game calculations with data source results)\n";
1545 hits = results_hits;
1549 if(results_damage != damage) {
1550 errbuf_ <<
"SYNC: In attack " <<
a_.dump() <<
" vs " << d_.dump() <<
": the data source says the hit did " 1551 << results_damage <<
" damage, while in-game calculations show the hit doing " << damage
1552 <<
" damage (over-riding game calculations with data source results)\n";
1553 damage = results_damage;
1568 bool update_display)
1570 attack dummy(attacker, defender, attack_with, defend_with, update_display);
1578 bool update_display)
1580 attack_unit(attacker, defender, attack_with, defend_with, update_display);
1625 case unit_alignments::type::lawful:
1626 bonus = lawful_bonus;
1628 case unit_alignments::type::neutral:
1631 case unit_alignments::type::chaotic:
1632 bonus = -lawful_bonus;
1634 case unit_alignments::type::liminal:
1635 bonus = max_liminal_bonus-std::abs(lawful_bonus);
1642 bonus = std::max<int>(bonus, 0);
1651 const std::vector<team>& teams)
1654 if(defender == units.
end()) {
1661 for(i = 0; i < adj.size(); ++
i) {
1662 if(adj[i] == attacker_loc) {
1674 if(opp == units.
end()) {
1678 if(opp->incapacitated()) {
1683 if(std::size_t(defender->side() - 1) >= teams.size() || std::size_t(opp->side() - 1) >= teams.size()) {
1688 if(teams[defender->side() - 1].is_enemy(opp->side())) {
const_attack_ptr weapon
The weapon used by the unit to attack the opponent, or nullptr if there is none.
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)
void set_experience(int xp)
Sets the current experience point amount.
std::unique_ptr< combatant > attacker_combatant_
Outcome of simulated fight.
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...
const std::string & parent_id() const
The id of the original type from which this (variation) descended.
static lg::log_domain log_attack("engine/attack")
::tod_manager * tod_manager
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.
int get_max_liminal_bonus() const
std::vector< double > hp_dist
Resulting probability distribution (might be not as large as max_hp)
static display * get_singleton()
Returns the display object if a display object exists.
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.
const config * ability_cfg
The contents of the ability tag, never nullptr.
void get_adjacent_tiles(const map_location &a, map_location *res)
Function which, given a location, will place all adjacent locations in res.
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...
virtual const unit_map & units() const override
bool invalidate(const map_location &loc)
Function to invalidate a specific tile for redrawing.
std::string plague_type
The plague type used by the attack, if any.
static lg::log_domain log_config("config")
This class represents a single unit of a specific type.
game_classification * classification
boost::iterator_range< boost::indirect_iterator< attack_list::iterator > > attack_itors
unsigned int hp
Hitpoints of the unit at the beginning of the battle.
Various functions implementing vision (through fog of war and shroud).
The unit is petrified - it cannot move or be attacked.
Various functions that implement attacks and attack calculations.
bool get_state(const std::string &state) const
Check if the unit is affected by a status effect.
Add a special kind of assert to validate whether the input from WML doesn't contain any problems that...
static bool better_combat(const combatant &us_a, const combatant &them_a, const combatant &us_b, const combatant &them_b, double harm_weight)
unit_race::GENDER gender() const
The gender of this unit.
const combatant & get_attacker_combatant(const combatant *prev_def=nullptr)
Get the simulation results.
int hitpoints() const
The current number of hitpoints this unit has.
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.
double average_hp(unsigned int healing=0) const
What's the average hp (weighted average of hp_dist).
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.
void invalidate_unit()
Function to invalidate that unit status displayed on the sidebar.
virtual const gamemap & map() const override
int lawful_bonus
The % bonus lawful units receive.
The unit is slowed - it moves slower and does less damage.
int experience_needed(bool with_acceleration=true) const
unit_type_data unit_types
The unit is poisoned - it loses health each turn.
int resistance_against(const std::string &damage_name, bool attacker) const
Gets resistance while considering custom WML abilities.
bool is_slowed
True if the unit is slowed at the beginning of the battle.
void insert(config_key_type key, T &&value)
Inserts an attribute into the config.
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 ...
bool slows
Attack slows opponent when it hits.
int drain_constant
Base HP drained regardless of damage dealt.
static std::string _(const char *str)
void redraw_minimap()
Schedule the minimap to be redrawn.
std::shared_ptr< unit > unit_ptr
unsigned int chance_to_hit
Effective chance to hit as a percentage (all factors accounted for).
static unit_ptr create(const config &cfg, bool use_traits=false, const vconfig *vcfg=nullptr)
Initializes a unit from a config.
A single unit type that the player may recruit.
config::attribute_value & get_variable(const std::string &varname)
throws invalid_variablename_exception if varname is no valid variable name.
const unit_type & type() const
This unit's type, accounting for gender and variation.
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.
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)
bool poisons
Attack poisons opponent when it hits.
const combatant & get_defender_combatant(const combatant *prev_def=nullptr)
std::shared_ptr< const unit > unit_const_ptr
Object which defines a time of day with associated bonuses, image, sounds etc.
int defense_modifier(const t_translation::terrain_code &terrain) const
The unit's defense on a given terrain.
bool musthave_status(const std::string &status) const
int damage
Effective damage of the weapon (all factors accounted for).
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Various functions that implement advancements of units.
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.
void set_state(const std::string &state, bool value)
Set whether the unit is affected by a status effect.
#define VALIDATE(cond, message)
The macro to use for the validation of WML.
bool better_defense(class battle_context &that, double harm_weight)
Given this harm_weight, is this attack better than that?
unit_alignments::type alignment() const
void attack_result(hit_result res, int cth, int damage, int drain)
unsigned int rounds
Berserk special can force us to fight more than one round.
unsigned int swarm_min
Minimum number of blows with swarm (equal to num_blows if swarm isn't used).
std::unique_ptr< battle_context_unit_stats > defender_stats_
bool plagues
Attack turns opponent into a zombie when fatal.
bool better_attack(class battle_context &that, double harm_weight)
Given this harm_weight, is this attack better than that?
Encapsulates the map of the game.
void recalculate_fog(int side)
Function that recalculates the fog of war.
Computes the statistics of a battle between an attacker and a defender unit.
const std::string & range() const
checkup * checkup_instance
#define log_scope2(domain, description)
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)
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'.
int attack_num
Index into unit->attacks() or -1 for none.
Structure describing the statistics of a unit involved in the battle.
The unit is uncovered - it was hiding but has been spotted.
std::unique_ptr< battle_context_unit_stats > attacker_stats_
Statistics of the units.
game_events::manager * game_events
advances the unit at loc if it has enough experience, maximum 20 times.
unit_alignments::type alignment() const
The alignment of this unit.
Encapsulates the map of the game.
unit_iterator find(std::size_t id)
std::shared_ptr< wb::manager > whiteboard
static void process_error(const std::string &msg)
const std::string & undead_variation() const
Info on the type of unit that the unit reanimates as.
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.
pointer get_shared_ptr() const
This is exactly the same as operator-> but it's slightly more readable, and can replace &*iter syntax...
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.
bool swarm
Attack has swarm special.
int slow_damage
Effective damage if unit becomes slowed (== damage, if already slowed)
unit_animation_component & anim_comp() const
std::unique_ptr< combatant > defender_combatant_
int max_hitpoints() const
The max number of hitpoints this unit can have.
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.
static map_location::DIRECTION s
double attack_weight() const
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.
Define the game's event mechanism.
attack_itors attacks()
Gets an iterator over this unit's attacks.
bool disable
Attack has disable special.
bool firststrike
Attack has firststrike special.
int get_composite_value() const
double defense_weight() const
bool is_poisoned
True if the unit is poisoned at the beginning of the battle.
std::set< std::string > & encountered_units()
int get_random_int(int min, int max)
This helper method provides a random int from the underlying generator, using results of next_random...
void advance_unit_at(const advance_unit_params ¶ms)
rng * generator
This generator is automatically synced during synced context.
config & add_child(config_key_type key)
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.
int drain_percent
Percentage of damage recovered as health.
const std::string & undead_variation() const
static DIRECTION get_opposite_dir(DIRECTION d)
bool is_village(const map_location &loc) const
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.
bool is_fearless() const
Gets whether this unit is fearless - ie, unaffected by time of day.
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 experience() const
The current number of experience points this unit has.
void attack_expected_damage(double attacker_inflict, double defender_inflict)
void attack_unit(const map_location &attacker, const map_location &defender, int attack_with, int defend_with, bool update_display)
Performs an attack.
void defend_result(hit_result res, int cth, int damage, int drain)
Standard logging facilities (interface).
static const map_location & null_location()
Container associating units to locations.
unsigned int num_blows
Effective number of blows, takes swarm into account.
unsigned int max_hp
Maximum hitpoints of the unit.
bool is_attacker
True if the unit is the attacker.
game_events::wml_event_pump & pump()
void set_standing(bool with_bars=true)
Sets the animation state to standing.
unsigned int max_experience
void clear_variable(const std::string &varname)
Clears attributes config children does nothing if varname is no valid variable name.
virtual bool local_checkup(const config &expected_data, config &real_data)=0
Compares data to the results calculated during the original game.
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
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...
void simulate(const combatant *prev_def)
bool petrifies
Attack petrifies opponent when it hits.
static lg::log_domain log_engine("engine")
A config object defines a single node in a WML file, with access to child nodes.
std::shared_ptr< const attack_type > const_attack_ptr
static map_location::DIRECTION n
bool drains
Attack drains opponent when it hits.
Display units performing various actions: moving, attacking, and dying.
double poisoned
Resulting chance we are poisoned.
unsigned int swarm_max
Maximum number of blows with swarm (equal to num_blows if swarm isn't used).
static game_display * get_singleton()