battle_context_unit_stats::battle_context_unit_stats(const unit &u, const map_location& u_loc, int u_attack_num, bool attacking, const unit &opp, const map_location& opp_loc, const attack_type *opp_weapon, const unit_map& units) : weapon(NULL), attack_num(u_attack_num), is_attacker(attacking), is_poisoned(u.get_state(unit::STATE_POISONED)), is_slowed(u.get_state(unit::STATE_SLOWED)), slows(false), drains(false), petrifies(false), plagues(false), poisons(false), backstab_pos(false), swarm(false), firststrike(false), experience(u.experience()), max_experience(u.max_experience()), level(u.level()), rounds(1), hp(0), max_hp(u.max_hitpoints()), chance_to_hit(0), damage(0), slow_damage(0), drain_percent(0), drain_constant(0), num_blows(0), swarm_min(0), swarm_max(0), plague_type() { // Get the current state of the unit. if (attack_num >= 0) { weapon = &u.attacks()[attack_num]; } if(u.hitpoints() < 0) { LOG_CF << "Unit with " << u.hitpoints() << " hitpoints found, set to 0 for damage calculations\n"; hp = 0; } else if(u.hitpoints() > u.max_hitpoints()) { // If a unit has more hp than its maximum, the engine will fail // with an assertion failure due to accessing the prob_matrix // out of bounds. hp = u.max_hitpoints(); } else { hp = u.hitpoints(); } // Get the weapon characteristics, if any. if (weapon) { weapon->set_specials_context(u_loc, opp_loc, attacking, opp_weapon); if (opp_weapon) opp_weapon->set_specials_context(opp_loc, u_loc, !attacking, weapon); slows = weapon->get_special_bool("slow"); drains = !opp.get_state("undrainable") && weapon->get_special_bool("drains"); petrifies = weapon->get_special_bool("petrifies"); poisons = !opp.get_state("unpoisonable") && weapon->get_special_bool("poison") && !opp.get_state(unit::STATE_POISONED); backstab_pos = is_attacker && backstab_check(u_loc, opp_loc, units, *resources::teams); rounds = weapon->get_specials("berserk").highest("value", 1).first; firststrike = weapon->get_special_bool("firststrike"); // Handle plague. unit_ability_list plague_specials = weapon->get_specials("plague"); plagues = !opp.get_state("unplagueable") && !plague_specials.empty() && strcmp(opp.undead_variation().c_str(), "null") && !resources::game_map->is_village(opp_loc); if (plagues) { plague_type = (*plague_specials.front().first)["type"].str(); if (plague_type.empty()) plague_type = u.type().base_id(); } // Compute chance to hit. chance_to_hit = opp.defense_modifier( resources::game_map->get_terrain(opp_loc)) + weapon->accuracy() - (opp_weapon ? opp_weapon->parry() : 0); if(chance_to_hit > 100) { chance_to_hit = 100; } unit_ability_list cth_specials = weapon->get_specials("chance_to_hit"); unit_abilities::effect cth_effects(cth_specials, chance_to_hit, backstab_pos); chance_to_hit = cth_effects.get_composite_value(); // Compute base damage done with the weapon. int base_damage = weapon->modified_damage(backstab_pos); // Get the damage multiplier applied to the base damage of the weapon. int damage_multiplier = 100; // Time of day bonus. damage_multiplier += combat_modifier(u_loc, u.alignment(), u.is_fearless()); // Leadership bonus. int leader_bonus = 0; if (under_leadership(units, u_loc, &leader_bonus).valid()) damage_multiplier += leader_bonus; // Resistance modifier. damage_multiplier *= opp.damage_from(*weapon, !attacking, opp_loc); // Compute both the normal and slowed damage. damage = round_damage(base_damage, damage_multiplier, 10000); slow_damage = round_damage(base_damage, damage_multiplier, 20000); if (is_slowed) damage = slow_damage; // Compute drain amounts only if draining is possible. if(drains) { unit_ability_list drain_specials = weapon->get_specials("drains"); // Compute the drain percent (with 50% as the base for backward compatibility) unit_abilities::effect drain_percent_effects(drain_specials, 50, backstab_pos); drain_percent = drain_percent_effects.get_composite_value(); } // Add heal_on_hit (the drain constant) unit_ability_list heal_on_hit_specials = weapon->get_specials("heal_on_hit"); unit_abilities::effect heal_on_hit_effects(heal_on_hit_specials, 0, backstab_pos); drain_constant += heal_on_hit_effects.get_composite_value(); drains = drain_constant || drain_percent; // Compute the number of blows and handle swarm. weapon->modified_attacks(backstab_pos, swarm_min, swarm_max); swarm = swarm_min != swarm_max; num_blows = calc_blows(hp); } }
battle_context_unit_stats::battle_context_unit_stats(const unit_type* u_type, const attack_type* att_weapon, bool attacking, const unit_type* opp_type, const attack_type* opp_weapon, unsigned int opp_terrain_defense, int lawful_bonus) : weapon(att_weapon), attack_num(-2), // This is and stays invalid. Always use weapon, when using this constructor. is_attacker(attacking), is_poisoned(false), is_slowed(false), slows(false), drains(false), petrifies(false), plagues(false), poisons(false), backstab_pos(false), swarm(false), firststrike(false), disable(false), experience(0), max_experience(0), level(0), rounds(1), hp(0), max_hp(0), chance_to_hit(0), damage(0), slow_damage(0), drain_percent(0), drain_constant(0), num_blows(0), swarm_min(0), swarm_max(0), plague_type() { if (!u_type || !opp_type) { return; } // Get the current state of the unit. if (u_type->hitpoints() < 0) { hp = 0; } else { hp = u_type->hitpoints(); } max_experience = u_type->experience_needed(); level = (u_type->level()); max_hp = (u_type->hitpoints()); // Get the weapon characteristics, if any. if (weapon) { weapon->set_specials_context(map_location::null_location(), attacking); if (opp_weapon) { opp_weapon->set_specials_context(map_location::null_location(), !attacking); } slows = weapon->get_special_bool("slow"); drains = !opp_type->musthave_status("undrainable") && weapon->get_special_bool("drains"); petrifies = weapon->get_special_bool("petrifies"); poisons = !opp_type->musthave_status("unpoisonable") && weapon->get_special_bool("poison"); rounds = weapon->get_specials("berserk").highest("value", 1).first; firststrike = weapon->get_special_bool("firststrike"); disable = weapon->get_special_bool("disable"); unit_ability_list plague_specials = weapon->get_specials("plague"); plagues = !opp_type->musthave_status("unplagueable") && !plague_specials.empty() && strcmp(opp_type->undead_variation().c_str(), "null"); if (plagues) { plague_type = (*plague_specials.front().first)["type"].str(); if (plague_type.empty()) { plague_type = u_type->base_id(); } } signed int cth = 100 - opp_terrain_defense + weapon->accuracy() - (opp_weapon ? opp_weapon->parry() : 0); cth = std::min(100, cth); cth = std::max(0, cth); chance_to_hit = cth; unit_ability_list cth_specials = weapon->get_specials("chance_to_hit"); unit_abilities::effect cth_effects(cth_specials, chance_to_hit, backstab_pos); chance_to_hit = cth_effects.get_composite_value(); int base_damage = weapon->modified_damage(backstab_pos); int damage_multiplier = 100; damage_multiplier += generic_combat_modifier(lawful_bonus, u_type->alignment(), u_type->musthave_status("fearless")); damage_multiplier *= opp_type->resistance_against(weapon->type(), !attacking); damage = round_damage(base_damage, damage_multiplier, 10000); slow_damage = round_damage(base_damage, damage_multiplier, 20000); if (drains) { unit_ability_list drain_specials = weapon->get_specials("drains"); // Compute the drain percent (with 50% as the base for backward compatibility) unit_abilities::effect drain_percent_effects(drain_specials, 50, backstab_pos); drain_percent = drain_percent_effects.get_composite_value(); } // Add heal_on_hit (the drain constant) unit_ability_list heal_on_hit_specials = weapon->get_specials("heal_on_hit"); unit_abilities::effect heal_on_hit_effects(heal_on_hit_specials, 0, backstab_pos); drain_constant += heal_on_hit_effects.get_composite_value(); drains = drain_constant || drain_percent; // Compute the number of blows and handle swarm. weapon->modified_attacks(backstab_pos, swarm_min, swarm_max); swarm = swarm_min != swarm_max; num_blows = calc_blows(hp); } }