26 #include "formula/callable_objects.hpp" 59 #define DBG_NG LOG_STREAM(debug, log_engine) 60 #define LOG_NG LOG_STREAM(info, log_engine) 61 #define WRN_NG LOG_STREAM(err, log_engine) 62 #define ERR_NG LOG_STREAM(err, log_engine) 65 #define DBG_AT LOG_STREAM(debug, log_attack) 66 #define LOG_AT LOG_STREAM(info, log_attack) 67 #define WRN_AT LOG_STREAM(err, log_attack) 68 #define ERR_AT LOG_STREAM(err, log_attack) 71 #define LOG_CF LOG_STREAM(info, log_config) 86 , attack_num(u_attack_num)
87 , is_attacker(attacking)
88 , is_poisoned(up->get_state(
unit::STATE_POISONED))
89 , 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\n";
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));
151 rounds =
weapon->get_special_ability(
"berserk").highest(
"value", 1).first;
157 const bool out_of_range = distance >
weapon->max_range() || distance <
weapon->min_range();
158 disable =
weapon->get_special_bool(
"disable") || out_of_range;
164 if(!alt_plague_specials.
empty() && plague_specials.
empty()){
165 plague_specials = alt_plague_specials;
180 - (opp_weapon ? opp_weapon->parry() : 0);
182 cth = std::clamp(cth, 0, 100);
199 int damage_multiplier = 100;
207 if(leader_bonus != 0) {
208 damage_multiplier += leader_bonus;
248 unsigned int opp_terrain_defense,
280 if(!u_type || !opp_type) {
302 std::optional<decltype(ctx)> opp_ctx;
312 rounds =
weapon->get_specials(
"berserk").highest(
"value", 1).first;
327 signed int cth = 100 - opp_terrain_defense +
weapon->accuracy() - (opp_weapon ? opp_weapon->parry() : 0);
328 cth = std::clamp(cth, 0, 100);
337 int damage_multiplier = 100;
381 , attacker_combatant_()
382 , defender_combatant_()
384 size_t a_wep_uindex =
static_cast<size_t>(a_wep_index);
385 size_t d_wep_uindex =
static_cast<size_t>(d_wep_index);
387 const_attack_ptr a_wep(a_wep_uindex < attacker->attacks().
size() ? attacker->attacks()[a_wep_index].shared_from_this() :
nullptr);
388 const_attack_ptr d_wep(d_wep_uindex < defender->attacks().
size() ? defender->attacks()[d_wep_index].shared_from_this() :
nullptr);
431 const double harm_weight = 1.0 - aggression;
433 if(attacker_weapon == -1) {
435 n_attacker, n_defender, units, attacker_loc, defender_loc, harm_weight, prev_def
438 else if(defender_weapon == -1) {
440 n_attacker, n_defender, attacker_weapon, units, attacker_loc, defender_loc, prev_def
444 *
this =
battle_context(n_attacker, attacker_loc, attacker_weapon, n_defender, defender_loc, defender_weapon, units);
552 std::vector<battle_context> choices;
555 for(
size_t i = 0;
i < attacker->attacks().size(); ++
i) {
566 choices.emplace_back(std::move(bc));
569 if(choices.empty()) {
570 return battle_context(attacker, attacker_loc, -1, defender, defender_loc, -1, units);
573 if(choices.size() == 1) {
574 return std::move(choices[0]);
579 for(
auto& choice : choices) {
583 if(!best_choice || choice.
better_attack(*best_choice, harm_weight)) {
584 best_choice = &choice;
589 return std::move(*best_choice);
592 return battle_context(attacker, attacker_loc, -1, defender, defender_loc, -1, units);
599 unsigned attacker_weapon,
606 VALIDATE(attacker_weapon < attacker->attacks().
size(),
_(
"An invalid attacker weapon got selected."));
608 const attack_type& att = attacker->attacks()[attacker_weapon];
609 auto no_weapon = [&]() {
return battle_context(attacker, attacker_loc, attacker_weapon, defender, defender_loc, -1, units); };
610 std::vector<battle_context> choices;
613 for(
size_t i = 0;
i < defender->attacks().size(); ++
i) {
619 battle_context bc(attacker, attacker_loc, attacker_weapon, defender, defender_loc,
i, units);
628 choices.emplace_back(std::move(bc));
631 if(choices.empty()) {
635 if(choices.size() == 1) {
637 return std::move(choices[0]);
647 double max_weight = 0.0;
649 for(
const auto& choice : choices) {
650 const attack_type& def = defender->attacks()[choice.defender_stats_->attack_num];
656 int rating =
static_cast<int>(
668 for(
auto& choice : choices) {
669 const attack_type& def = defender->attacks()[choice.defender_stats_->attack_num];
671 choice.simulate(prev_def);
674 int simple_rating =
static_cast<int>(
675 choice.defender_stats_->num_blows * choice.defender_stats_->damage * choice.defender_stats_->chance_to_hit * def.
defense_weight());
678 if(simple_rating >= min_rating && (!best_choice || choice.
better_defense(*best_choice, 1.0))) {
679 best_choice = &choice;
683 return best_choice ? std::move(*best_choice) : no_weapon();
693 void refresh_weapon_index(
int& weap_index,
const std::string& weap_id,
attack_itors attacks)
696 if(attacks.empty()) {
702 if(weap_index >= 0 && weap_index < static_cast<int>(attacks.size()) && attacks[weap_index].
id() == weap_id) {
707 if(!weap_id.empty()) {
708 for(
int i = 0; i < static_cast<int>(attacks.size()); ++
i) {
709 if(attacks[
i].
id() == weap_id) {
729 bool update_display =
true);
734 class attack_end_exception
749 std::string weap_id_;
768 void check_replay_attack_result(
bool&,
int,
int&,
config, unit_info&);
773 std::unique_ptr<battle_context> bc_;
778 int abs_n_attack_, abs_n_defend_;
780 bool update_att_fog_, update_def_fog_, update_minimap_;
784 std::ostringstream errbuf_;
786 bool update_display_;
791 std::vector<bool> prng_attacker_;
792 std::vector<bool> prng_defender_;
812 id_ = i->underlying_id();
815 unit& attack::unit_info::get_unit()
818 assert(i.
valid() && i->underlying_id() == id_);
822 unit_ptr attack::unit_info::get_unit_ptr()
825 if(i.
valid() && i->underlying_id() == id_) {
831 bool attack::unit_info::valid()
834 return i.
valid() && i->underlying_id() == id_;
837 std::string attack::unit_info::dump()
854 , update_att_fog_(
false)
855 , update_def_fog_(
false)
856 , update_minimap_(
false)
861 , update_display_(update_display)
870 LOG_NG <<
"Using experimental PRNG for combat\n";
876 LOG_NG <<
"attack: firing '" << n <<
"' event\n";
884 std::optional<attack_type::specials_context_t> a_ctx, d_ctx;
886 if(a_stats_->weapon !=
nullptr &&
a_.valid()) {
887 if(d_stats_->weapon !=
nullptr && d_.valid()) {
888 a_ctx.emplace(a_stats_->weapon->specials_context(
nullptr,
nullptr,
a_.loc_, d_.loc_,
true, d_stats_->weapon));
890 a_ctx.emplace(a_stats_->weapon->specials_context(
nullptr,
a_.loc_,
true));
892 a_stats_->weapon->write(a_weapon_cfg);
895 if(d_stats_->weapon !=
nullptr && d_.valid()) {
896 if(a_stats_->weapon !=
nullptr &&
a_.valid()) {
897 d_ctx.emplace(d_stats_->weapon->specials_context(
nullptr,
nullptr, d_.loc_,
a_.loc_,
false, a_stats_->weapon));
899 d_ctx.emplace(d_stats_->weapon->specials_context(
nullptr, d_.loc_,
false));
901 d_stats_->weapon->write(d_weapon_cfg);
904 if(a_weapon_cfg[
"name"].empty()) {
905 a_weapon_cfg[
"name"] =
"none";
908 if(d_weapon_cfg[
"name"].empty()) {
909 d_weapon_cfg[
"name"] =
"none";
912 if(n ==
"attack_end") {
920 if(n ==
"attacker_hits" || n ==
"defender_hits") {
924 const int defender_side = d_.get_unit().side();
935 if(wml_aborted || !
a_.valid() || !d_.valid()
940 if(update_display_) {
945 throw attack_end_exception();
949 void attack::refresh_bc()
953 refresh_weapon_index(
a_.weapon_,
a_.weap_id_,
a_.get_unit().attacks());
957 refresh_weapon_index(d_.weapon_, d_.weap_id_, d_.get_unit().attacks());
960 if(!
a_.valid() || !d_.valid()) {
963 =
a_.valid() &&
a_.weapon_ >= 0 ?
a_.get_unit().attacks()[
a_.weapon_].shared_from_this() :
nullptr;
966 = d_.valid() && d_.weapon_ >= 0 ? d_.get_unit().attacks()[d_.weapon_].shared_from_this() :
nullptr;
973 a_stats_ = &bc_->get_attacker_stats();
974 d_stats_ = &bc_->get_defender_stats();
976 a_.cth_ = a_stats_->chance_to_hit;
977 d_.cth_ = d_stats_->chance_to_hit;
978 a_.damage_ = a_stats_->damage;
979 d_.damage_ = d_stats_->damage;
984 unit_info& attacker = attacker_turn ?
a_ : d_;
985 unit_info& defender = attacker_turn ? d_ :
a_;
992 int& abs_n = attacker_turn ? abs_n_attack_ : abs_n_defend_;
993 bool& update_fog = attacker_turn ? update_def_fog_ : update_att_fog_;
999 std::vector<bool>& prng_seq = attacker_turn ? prng_attacker_ : prng_defender_;
1001 if(prng_seq.empty()) {
1002 const int ntotal = attacker.cth_*attacker.n_attacks_;
1003 int num_hits = ntotal/100;
1004 const int additional_hit_chance = ntotal%100;
1005 if(additional_hit_chance > 0 &&
randomness::generator->get_random_int(0, 99) < additional_hit_chance) {
1009 std::vector<int> indexes;
1010 for(
int i = 0;
i != attacker.n_attacks_; ++
i) {
1011 prng_seq.push_back(
false);
1012 indexes.push_back(
i);
1015 for(
int i = 0;
i != num_hits; ++
i) {
1017 prng_seq[indexes[
n]] =
true;
1018 indexes.erase(indexes.begin() +
n);
1022 bool does_hit = prng_seq.back();
1023 prng_seq.pop_back();
1024 ran_num = does_hit ? 0 : 99;
1028 bool hits = (ran_num < attacker.cth_);
1032 damage = attacker.damage_;
1038 const config local_results {
"chance", attacker.cth_,
"hits", hits,
"damage", damage};
1043 if(!equals_replay) {
1044 check_replay_attack_result(hits, ran_num, damage, replay_results, attacker);
1048 int damage_done = std::min<int>(defender.get_unit().hitpoints(), attacker.damage_);
1051 double expected_damage = damage_done * attacker.cth_ * 0.01;
1059 int drains_damage = 0;
1060 if(hits && attacker_stats->
drains) {
1065 std::min<int>(drains_damage, attacker.get_unit().max_hitpoints() - attacker.get_unit().hitpoints());
1068 drains_damage = std::max<int>(drains_damage, 1 - attacker.get_unit().hitpoints());
1071 if(update_display_) {
1072 std::ostringstream float_text;
1073 std::vector<std::string> extra_hit_sounds;
1076 const unit& defender_unit = defender.get_unit();
1101 attacker.loc_, defender.loc_,
1104 abs_n, float_text.str(), drains_damage,
"",
1105 &extra_hit_sounds, attacker_turn
1109 bool dies = defender.get_unit().take_hit(damage);
1110 LOG_NG <<
"defender took " << damage << (dies ?
" and died\n" :
"\n");
1118 attacker.cth_, damage_done, drains_damage
1126 attacker.cth_, damage_done, drains_damage
1130 replay_results.
clear();
1135 if(!equals_replay) {
1136 bool results_dies = replay_results[
"dies"].to_bool();
1138 errbuf_ <<
"SYNC: In attack " <<
a_.dump() <<
" vs " << d_.dump() <<
": the data source says the " 1139 << (attacker_turn ?
"defender" :
"attacker") <<
' ' << (results_dies ?
"perished" :
"survived")
1140 <<
" while in-game calculations show it " << (dies ?
"perished" :
"survived")
1141 <<
" (over-riding game calculations with data source results)\n";
1143 dies = results_dies;
1147 defender.get_unit().set_hitpoints(0);
1155 fire_event(attacker_turn ?
"attacker_hits" :
"defender_hits");
1156 }
catch(
const attack_end_exception&) {
1162 fire_event(attacker_turn ?
"attacker_misses" :
"defender_misses");
1163 }
catch(
const attack_end_exception&) {
1171 bool attacker_dies =
false;
1173 if(drains_damage > 0) {
1174 attacker.get_unit().heal(drains_damage);
1175 }
else if(drains_damage < 0) {
1176 attacker_dies = attacker.get_unit().take_hit(-drains_damage);
1180 unit_killed(attacker, defender, attacker_stats, defender_stats,
false);
1185 unit_killed(defender, attacker, defender_stats, attacker_stats,
true);
1186 (attacker_turn ? update_att_fog_ : update_def_fog_) =
true;
1190 update_minimap_ =
true;
1195 unit& defender_unit = defender.get_unit();
1199 LOG_NG <<
"defender poisoned\n";
1206 LOG_NG <<
"defender slowed\n";
1213 attacker.n_attacks_ = 0;
1214 defender.n_attacks_ = -1;
1222 update_minimap_ =
true;
1226 --attacker.n_attacks_;
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() <<
'\n';
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\n";
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.\n";
1362 a_.get_unit().set_facing(
a_.loc_.get_relative_dir(d_.loc_));
1363 d_.get_unit().set_facing(d_.loc_.get_relative_dir(
a_.loc_));
1365 a_.get_unit().set_attacks(
a_.get_unit().attacks_left() - 1);
1367 VALIDATE(
a_.weapon_ < static_cast<int>(
a_.get_unit().attacks().size()),
1368 _(
"An invalid attacker weapon got selected."));
1370 a_.get_unit().set_movement(
a_.get_unit().movement_left() -
a_.get_unit().attacks()[
a_.weapon_].movement_used(),
true);
1372 a_.get_unit().set_resting(
false);
1373 d_.get_unit().set_resting(
false);
1380 a_stats_ = &bc_->get_attacker_stats();
1381 d_stats_ = &bc_->get_defender_stats();
1383 if(a_stats_->disable) {
1384 LOG_NG <<
"attack::perform(): tried to attack with a disabled attack.\n";
1388 if(a_stats_->weapon) {
1389 a_.weap_id_ = a_stats_->weapon->id();
1392 if(d_stats_->weapon) {
1393 d_.weap_id_ = d_stats_->weapon->id();
1398 }
catch(
const attack_end_exception&) {
1404 DBG_NG <<
"getting attack statistics\n";
1406 a_.get_unit(), d_.get_unit(), a_stats_->chance_to_hit, d_stats_->chance_to_hit);
1408 a_.orig_attacks_ = a_stats_->num_blows;
1409 d_.orig_attacks_ = d_stats_->num_blows;
1410 a_.n_attacks_ =
a_.orig_attacks_;
1411 d_.n_attacks_ = d_.orig_attacks_;
1415 bool defender_strikes_first = (d_stats_->firststrike && !a_stats_->firststrike);
1416 unsigned int rounds = std::max<unsigned int>(a_stats_->rounds, d_stats_->rounds) - 1;
1417 const int defender_side = d_.get_unit().side();
1419 LOG_NG <<
"Fight: (" <<
a_.loc_ <<
") vs (" << d_.loc_ <<
") ATT: " << a_stats_->weapon->name() <<
" " 1420 << a_stats_->damage <<
"-" << a_stats_->num_blows <<
"(" << a_stats_->chance_to_hit
1421 <<
"%) vs DEF: " << (d_stats_->weapon ? d_stats_->weapon->name() :
"none") <<
" " << d_stats_->damage <<
"-" 1422 << d_stats_->num_blows <<
"(" << d_stats_->chance_to_hit <<
"%)" 1423 << (defender_strikes_first ?
" defender first-strike" :
"") <<
"\n";
1429 DBG_NG <<
"start of attack loop...\n";
1432 if(
a_.n_attacks_ > 0 && !defender_strikes_first) {
1433 if(!perform_hit(
true, attack_stats)) {
1434 DBG_NG <<
"broke from attack loop on attacker turn\n";
1440 defender_strikes_first =
false;
1443 if(d_.n_attacks_ > 0) {
1444 if(!perform_hit(
false, attack_stats)) {
1445 DBG_NG <<
"broke from attack loop on defender turn\n";
1452 if(rounds > 0 && d_.n_attacks_ == 0 &&
a_.n_attacks_ == 0) {
1453 a_.n_attacks_ =
a_.orig_attacks_;
1454 d_.n_attacks_ = d_.orig_attacks_;
1456 defender_strikes_first = (d_stats_->firststrike && !a_stats_->firststrike);
1459 if(
a_.n_attacks_ <= 0 && d_.n_attacks_ <= 0) {
1469 if(update_def_fog_) {
1474 if(update_minimap_ && update_display_) {
1485 unit& u = d_.get_unit();
1491 d_.loc_, d_.get_unit_ptr());
1493 if(update_display_) {
1504 void attack::check_replay_attack_result(
1505 bool& hits,
int ran_num,
int& damage,
config replay_results, unit_info& attacker)
1507 int results_chance = replay_results[
"chance"];
1508 bool results_hits = replay_results[
"hits"].to_bool();
1509 int results_damage = replay_results[
"damage"];
1512 errbuf_ <<
"SYNC: In attack " <<
a_.dump() <<
" vs " << d_.dump()
1513 <<
" replay data differs from local calculated data:" 1514 <<
" chance to hit in data source: " << results_chance
1515 <<
" chance to hit in calculated: " << attacker.cth_
1516 <<
" chance to hit in data source: " << results_chance
1517 <<
" chance to hit in calculated: " << attacker.cth_
1520 attacker.cth_ = results_chance;
1521 hits = results_hits;
1522 damage = results_damage;
1527 if(results_chance != attacker.cth_) {
1528 errbuf_ <<
"SYNC: In attack " <<
a_.dump() <<
" vs " << d_.dump()
1529 <<
": chance to hit is inconsistent. Data source: " << results_chance
1530 <<
"; Calculation: " << attacker.cth_ <<
" (over-riding game calculations with data source results)\n";
1531 attacker.cth_ = results_chance;
1535 if(results_hits != hits) {
1536 errbuf_ <<
"SYNC: In attack " <<
a_.dump() <<
" vs " << d_.dump() <<
": the data source says the hit was " 1537 << (results_hits ?
"successful" :
"unsuccessful") <<
", while in-game calculations say the hit was " 1538 << (hits ?
"successful" :
"unsuccessful") <<
" random number: " << ran_num <<
" = " << (ran_num % 100)
1539 <<
"/" << results_chance <<
" (over-riding game calculations with data source results)\n";
1540 hits = results_hits;
1544 if(results_damage != damage) {
1545 errbuf_ <<
"SYNC: In attack " <<
a_.dump() <<
" vs " << d_.dump() <<
": the data source says the hit did " 1546 << results_damage <<
" damage, while in-game calculations show the hit doing " << damage
1547 <<
" damage (over-riding game calculations with data source results)\n";
1548 damage = results_damage;
1563 bool update_display)
1565 attack
dummy(attacker, defender, attack_with, defend_with, update_display);
1573 bool update_display)
1575 attack_unit(attacker, defender, attack_with, defend_with, update_display);
1619 switch(alignment.v) {
1620 case unit_type::ALIGNMENT::LAWFUL:
1621 bonus = lawful_bonus;
1623 case unit_type::ALIGNMENT::NEUTRAL:
1626 case unit_type::ALIGNMENT::CHAOTIC:
1627 bonus = -lawful_bonus;
1629 case unit_type::ALIGNMENT::LIMINAL:
1630 bonus = std::max(0, max_liminal_bonus-std::abs(lawful_bonus));
1637 bonus = std::max<int>(bonus, 0);
1646 const std::vector<team>& teams)
1649 if(defender == units.
end()) {
1656 for(i = 0; i < adj.size(); ++
i) {
1657 if(adj[i] == attacker_loc) {
1669 if(opp == units.
end()) {
1673 if(opp->incapacitated()) {
1678 if(std::size_t(defender->side() - 1) >= teams.size() || std::size_t(opp->side() - 1) >= teams.size()) {
1683 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.
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.
static battle_context choose_defender_weapon(nonempty_unit_const_ptr attacker, nonempty_unit_const_ptr defender, unsigned attacker_weapon, const unit_map &units, const map_location &attacker_loc, const map_location &defender_loc, const combatant *prev_def)
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.
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)
Returns a reference to the first child with the given key.
bool slows
Attack slows opponent when it hits.
int generic_combat_modifier(int lawful_bonus, unit_type::ALIGNMENT 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.
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 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.
bool backstab_pos
True if the attacker is in position to backstab the defender (this is used to determine whether to ap...
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?
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)
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.
Encapsulates the map of the game.
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 ...
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 fire_event(const ui_event event, std::vector< std::pair< widget *, ui_event >> &event_chain, widget *dispatcher, widget *w, F &&... params)
Helper function for fire_event.
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 experience() const
The current number of experience points this unit has.
UNIT_ALIGNMENT alignment() const
The alignment of this unit.
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.
static battle_context choose_attacker_weapon(nonempty_unit_const_ptr attacker, nonempty_unit_const_ptr defender, const unit_map &units, const map_location &attacker_loc, const map_location &defender_loc, double harm_weight, const combatant *prev_def)
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
ALIGNMENT alignment() const
static map_location::DIRECTION n
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, const unit_map &units)
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()
int combat_modifier(const unit_map &units, const gamemap &map, const map_location &loc, unit_type::ALIGNMENT alignment, bool is_fearless)
Returns the amount that a unit's damage should be multiplied by due to the current time of day...