2023-01-14 19:01:53 +02:00
/*
* DamageCalculator . cpp , part of VCMI engine
*
* Authors : listed in file AUTHORS in main folder
*
* License : GNU General Public License v2 .0 or later
* Full text of license available in license . txt file , in main folder
*
*/
# include "StdInc.h"
2024-01-08 21:50:37 +02:00
2023-01-14 19:01:53 +02:00
# include "DamageCalculator.h"
# include "CBattleInfoCallback.h"
# include "Unit.h"
2023-05-01 19:29:53 +02:00
# include "../bonuses/Bonus.h"
2023-01-14 19:01:53 +02:00
# include "../mapObjects/CGTownInstance.h"
# include "../spells/CSpellHandler.h"
2023-03-15 21:34:29 +02:00
# include "../GameSettings.h"
2024-04-17 17:24:13 +02:00
# include "../VCMI_Lib.h"
2023-01-14 19:01:53 +02:00
2023-01-14 19:40:34 +02:00
VCMI_LIB_NAMESPACE_BEGIN
2023-03-23 17:49:33 +02:00
DamageRange DamageCalculator : : getBaseDamageSingle ( ) const
2023-01-14 19:01:53 +02:00
{
2023-03-23 17:49:33 +02:00
int64_t minDmg = 0.0 ;
int64_t maxDmg = 0.0 ;
2023-01-14 19:01:53 +02:00
minDmg = info . attacker - > getMinDamage ( info . shooting ) ;
maxDmg = info . attacker - > getMaxDamage ( info . shooting ) ;
2024-04-17 17:24:13 +02:00
if ( minDmg > maxDmg )
{
2024-04-18 02:40:25 +02:00
const auto & creatureName = info . attacker - > creatureId ( ) . toEntity ( VLC ) - > getNamePluralTranslated ( ) ;
logGlobal - > error ( " Creature %s: min damage %lld exceeds max damage %lld. " , creatureName , minDmg , maxDmg ) ;
logGlobal - > error ( " This may lead to unexpected results, please report it to the mod's creator. " ) ;
// to avoid an RNG crash and make bless and curse spells work as expected
2024-04-17 17:24:13 +02:00
std : : swap ( minDmg , maxDmg ) ;
}
2023-01-14 19:01:53 +02:00
if ( info . attacker - > creatureIndex ( ) = = CreatureID : : ARROW_TOWERS )
2023-01-16 18:28:05 +02:00
{
2023-02-15 00:44:59 +02:00
const auto * town = callback . battleGetDefendedTown ( ) ;
2023-01-16 18:28:05 +02:00
assert ( town ) ;
switch ( info . attacker - > getPosition ( ) )
{
case BattleHex : : CASTLE_CENTRAL_TOWER :
return town - > getKeepDamageRange ( ) ;
case BattleHex : : CASTLE_BOTTOM_TOWER :
case BattleHex : : CASTLE_UPPER_TOWER :
return town - > getTowerDamageRange ( ) ;
default :
assert ( 0 ) ;
}
}
2023-01-14 19:01:53 +02:00
const std : : string cachingStrSiedgeWeapon = " type_SIEGE_WEAPON " ;
2023-05-01 00:20:01 +02:00
static const auto selectorSiedgeWeapon = Selector : : type ( ) ( BonusType : : SIEGE_WEAPON ) ;
2023-01-14 19:01:53 +02:00
2023-01-16 18:28:05 +02:00
if ( info . attacker - > hasBonus ( selectorSiedgeWeapon , cachingStrSiedgeWeapon ) & & info . attacker - > creatureIndex ( ) ! = CreatureID : : ARROW_TOWERS )
{
2023-08-19 20:43:50 +02:00
auto retrieveHeroPrimSkill = [ & ] ( PrimarySkill skill ) - > int
2023-01-14 19:01:53 +02:00
{
2023-10-21 13:50:42 +02:00
std : : shared_ptr < const Bonus > b = info . attacker - > getBonus ( Selector : : sourceTypeSel ( BonusSource : : HERO_BASE_SKILL ) . And ( Selector : : typeSubtype ( BonusType : : PRIMARY_SKILL , BonusSubtypeID ( skill ) ) ) ) ;
2023-01-16 18:28:05 +02:00
return b ? b - > val : 0 ;
2023-01-14 19:01:53 +02:00
} ;
2023-01-16 18:28:05 +02:00
//minDmg and maxDmg are multiplied by hero attack + 1
2023-01-14 19:01:53 +02:00
minDmg * = retrieveHeroPrimSkill ( PrimarySkill : : ATTACK ) + 1 ;
maxDmg * = retrieveHeroPrimSkill ( PrimarySkill : : ATTACK ) + 1 ;
}
return { minDmg , maxDmg } ;
}
2023-03-23 17:49:33 +02:00
DamageRange DamageCalculator : : getBaseDamageBlessCurse ( ) const
2023-01-14 19:01:53 +02:00
{
const std : : string cachingStrForcedMinDamage = " type_ALWAYS_MINIMUM_DAMAGE " ;
2023-05-01 00:20:01 +02:00
static const auto selectorForcedMinDamage = Selector : : type ( ) ( BonusType : : ALWAYS_MINIMUM_DAMAGE ) ;
2023-01-14 19:01:53 +02:00
const std : : string cachingStrForcedMaxDamage = " type_ALWAYS_MAXIMUM_DAMAGE " ;
2023-05-01 00:20:01 +02:00
static const auto selectorForcedMaxDamage = Selector : : type ( ) ( BonusType : : ALWAYS_MAXIMUM_DAMAGE ) ;
2023-01-14 19:01:53 +02:00
TConstBonusListPtr curseEffects = info . attacker - > getBonuses ( selectorForcedMinDamage , cachingStrForcedMinDamage ) ;
TConstBonusListPtr blessEffects = info . attacker - > getBonuses ( selectorForcedMaxDamage , cachingStrForcedMaxDamage ) ;
int curseBlessAdditiveModifier = blessEffects - > totalValue ( ) - curseEffects - > totalValue ( ) ;
2023-03-23 17:49:33 +02:00
DamageRange baseDamage = getBaseDamageSingle ( ) ;
DamageRange modifiedDamage = {
std : : max ( static_cast < int64_t > ( 1 ) , baseDamage . min + curseBlessAdditiveModifier ) ,
std : : max ( static_cast < int64_t > ( 1 ) , baseDamage . max + curseBlessAdditiveModifier )
2023-01-14 19:01:53 +02:00
} ;
2023-01-14 19:22:32 +02:00
if ( curseEffects - > size ( ) & & blessEffects - > size ( ) )
2023-01-14 19:01:53 +02:00
{
logGlobal - > warn ( " Stack has both curse and bless! Effects will negate each other! " ) ;
return modifiedDamage ;
}
if ( curseEffects - > size ( ) )
{
return {
2023-03-23 17:49:33 +02:00
modifiedDamage . min ,
modifiedDamage . min
2023-01-14 19:01:53 +02:00
} ;
}
if ( blessEffects - > size ( ) )
{
return {
2023-03-23 17:49:33 +02:00
modifiedDamage . max ,
modifiedDamage . max
2023-01-14 19:01:53 +02:00
} ;
}
return modifiedDamage ;
}
2023-03-23 17:49:33 +02:00
DamageRange DamageCalculator : : getBaseDamageStack ( ) const
2023-01-14 19:01:53 +02:00
{
auto stackSize = info . attacker - > getCount ( ) ;
auto baseDamage = getBaseDamageBlessCurse ( ) ;
return {
2023-03-23 17:49:33 +02:00
baseDamage . min * stackSize ,
baseDamage . max * stackSize
2023-01-14 19:01:53 +02:00
} ;
}
int DamageCalculator : : getActorAttackBase ( ) const
{
return info . attacker - > getAttack ( info . shooting ) ;
}
int DamageCalculator : : getActorAttackEffective ( ) const
{
2024-01-08 20:37:04 +02:00
return getActorAttackBase ( ) + getActorAttackSlayer ( ) + getActorAttackIgnored ( ) ;
}
int DamageCalculator : : getActorAttackIgnored ( ) const
{
2024-01-10 23:18:53 +02:00
int multAttackReductionPercent = battleBonusValue ( info . defender , Selector : : type ( ) ( BonusType : : ENEMY_ATTACK_REDUCTION ) ) ;
2024-01-08 20:37:04 +02:00
2024-01-10 23:18:53 +02:00
if ( multAttackReductionPercent > 0 )
2024-01-08 20:37:04 +02:00
{
2024-01-10 23:18:53 +02:00
int reduction = ( getActorAttackBase ( ) * multAttackReductionPercent + 49 ) / 100 ; //using ints so 1.5 for 5 attack is rounded down as in HotA / h3assist etc. (keep in mind h3assist 1.2 shows wrong value for 15 attack points and unupg. nix)
return - std : : min ( reduction , getActorAttackBase ( ) ) ;
2024-01-08 20:37:04 +02:00
}
return 0 ;
2023-01-14 19:01:53 +02:00
}
int DamageCalculator : : getActorAttackSlayer ( ) const
{
const std : : string cachingStrSlayer = " type_SLAYER " ;
2023-05-01 00:20:01 +02:00
static const auto selectorSlayer = Selector : : type ( ) ( BonusType : : SLAYER ) ;
2023-01-14 19:01:53 +02:00
2023-12-10 19:17:09 +02:00
if ( ! info . defender - > hasBonusOfType ( BonusType : : KING ) )
return 0 ;
2023-01-14 19:01:53 +02:00
auto slayerEffects = info . attacker - > getBonuses ( selectorSlayer , cachingStrSlayer ) ;
2023-05-01 00:20:01 +02:00
auto slayerAffected = info . defender - > unitType ( ) - > valOfBonuses ( Selector : : type ( ) ( BonusType : : KING ) ) ;
2023-01-14 19:01:53 +02:00
if ( std : : shared_ptr < const Bonus > slayerEffect = slayerEffects - > getFirst ( Selector : : all ) )
{
const auto spLevel = slayerEffect - > val ;
2023-03-04 22:43:10 +02:00
bool isAffected = spLevel > = slayerAffected ;
2023-01-14 19:01:53 +02:00
if ( isAffected )
{
2023-10-05 15:13:52 +02:00
SpellID spell ( SpellID : : SLAYER ) ;
int attackBonus = spell . toSpell ( ) - > getLevelPower ( spLevel ) ;
2023-10-21 13:50:42 +02:00
if ( info . attacker - > hasBonusOfType ( BonusType : : SPECIAL_PECULIAR_ENCHANT , BonusSubtypeID ( spell ) ) )
2023-01-14 19:01:53 +02:00
{
2023-04-05 02:26:29 +02:00
ui8 attackerTier = info . attacker - > unitType ( ) - > getLevel ( ) ;
2023-01-14 19:01:53 +02:00
ui8 specialtyBonus = std : : max ( 5 - attackerTier , 0 ) ;
attackBonus + = specialtyBonus ;
}
return attackBonus ;
}
}
return 0 ;
}
int DamageCalculator : : getTargetDefenseBase ( ) const
{
return info . defender - > getDefense ( info . shooting ) ;
}
int DamageCalculator : : getTargetDefenseEffective ( ) const
{
return getTargetDefenseBase ( ) + getTargetDefenseIgnored ( ) ;
}
int DamageCalculator : : getTargetDefenseIgnored ( ) const
{
2023-05-01 00:20:01 +02:00
double multDefenceReduction = battleBonusValue ( info . attacker , Selector : : type ( ) ( BonusType : : ENEMY_DEFENCE_REDUCTION ) ) / 100.0 ;
2023-01-14 19:01:53 +02:00
if ( multDefenceReduction > 0 )
{
int reduction = std : : floor ( multDefenceReduction * getTargetDefenseBase ( ) ) + 1 ;
return - std : : min ( reduction , getTargetDefenseBase ( ) ) ;
}
return 0 ;
}
double DamageCalculator : : getAttackSkillFactor ( ) const
{
int attackAdvantage = getActorAttackEffective ( ) - getTargetDefenseEffective ( ) ;
2023-01-14 19:22:32 +02:00
if ( attackAdvantage > 0 )
2023-01-14 19:01:53 +02:00
{
2023-03-15 23:47:26 +02:00
const double attackMultiplier = VLC - > settings ( ) - > getDouble ( EGameSettings : : COMBAT_ATTACK_POINT_DAMAGE_FACTOR ) ;
const double attackMultiplierCap = VLC - > settings ( ) - > getDouble ( EGameSettings : : COMBAT_ATTACK_POINT_DAMAGE_FACTOR_CAP ) ;
2023-01-14 19:01:53 +02:00
const double attackFactor = std : : min ( attackMultiplier * attackAdvantage , attackMultiplierCap ) ;
return attackFactor ;
}
return 0.f ;
}
double DamageCalculator : : getAttackBlessFactor ( ) const
{
const std : : string cachingStrDamage = " type_GENERAL_DAMAGE_PREMY " ;
2023-05-01 00:20:01 +02:00
static const auto selectorDamage = Selector : : type ( ) ( BonusType : : GENERAL_DAMAGE_PREMY ) ;
2023-01-14 19:01:53 +02:00
return info . attacker - > valOfBonuses ( selectorDamage , cachingStrDamage ) / 100.0 ;
}
double DamageCalculator : : getAttackOffenseArcheryFactor ( ) const
{
2023-02-13 01:16:42 +02:00
2023-01-14 19:01:53 +02:00
if ( info . shooting )
{
2023-02-13 01:16:42 +02:00
const std : : string cachingStrArchery = " type_PERCENTAGE_DAMAGE_BOOSTs_1 " ;
2023-10-21 13:50:42 +02:00
static const auto selectorArchery = Selector : : typeSubtype ( BonusType : : PERCENTAGE_DAMAGE_BOOST , BonusCustomSubtype : : damageTypeRanged ) ;
2023-01-14 19:01:53 +02:00
return info . attacker - > valOfBonuses ( selectorArchery , cachingStrArchery ) / 100.0 ;
}
2023-02-13 01:16:42 +02:00
const std : : string cachingStrOffence = " type_PERCENTAGE_DAMAGE_BOOSTs_0 " ;
2023-10-21 13:50:42 +02:00
static const auto selectorOffence = Selector : : typeSubtype ( BonusType : : PERCENTAGE_DAMAGE_BOOST , BonusCustomSubtype : : damageTypeMelee ) ;
2023-02-13 01:16:42 +02:00
return info . attacker - > valOfBonuses ( selectorOffence , cachingStrOffence ) / 100.0 ;
2023-01-14 19:01:53 +02:00
}
double DamageCalculator : : getAttackLuckFactor ( ) const
{
2023-01-14 19:22:32 +02:00
if ( info . luckyStrike )
2023-01-14 19:01:53 +02:00
return 1.0 ;
return 0.0 ;
}
double DamageCalculator : : getAttackDeathBlowFactor ( ) const
{
2023-01-14 19:22:32 +02:00
if ( info . deathBlow )
2023-01-14 19:01:53 +02:00
return 1.0 ;
return 0.0 ;
}
double DamageCalculator : : getAttackDoubleDamageFactor ( ) const
{
2023-02-26 00:19:39 +02:00
if ( info . doubleDamage ) {
const auto cachingStr = " type_BONUS_DAMAGE_PERCENTAGEs_ " + std : : to_string ( info . attacker - > creatureIndex ( ) ) ;
2023-10-21 13:50:42 +02:00
const auto selector = Selector : : typeSubtype ( BonusType : : BONUS_DAMAGE_PERCENTAGE , BonusSubtypeID ( info . attacker - > creatureId ( ) ) ) ;
2023-02-26 00:19:39 +02:00
return info . attacker - > valOfBonuses ( selector , cachingStr ) / 100.0 ;
}
2023-01-14 19:01:53 +02:00
return 0.0 ;
}
double DamageCalculator : : getAttackJoustingFactor ( ) const
{
const std : : string cachingStrJousting = " type_JOUSTING " ;
2023-05-01 00:20:01 +02:00
static const auto selectorJousting = Selector : : type ( ) ( BonusType : : JOUSTING ) ;
2023-01-14 19:01:53 +02:00
const std : : string cachingStrChargeImmunity = " type_CHARGE_IMMUNITY " ;
2023-05-01 00:20:01 +02:00
static const auto selectorChargeImmunity = Selector : : type ( ) ( BonusType : : CHARGE_IMMUNITY ) ;
2023-01-14 19:01:53 +02:00
//applying jousting bonus
if ( info . chargeDistance > 0 & & info . attacker - > hasBonus ( selectorJousting , cachingStrJousting ) & & ! info . defender - > hasBonus ( selectorChargeImmunity , cachingStrChargeImmunity ) )
2023-03-04 22:09:04 +02:00
return info . chargeDistance * ( info . attacker - > valOfBonuses ( selectorJousting ) ) / 100.0 ;
2023-01-14 19:01:53 +02:00
return 0.0 ;
}
double DamageCalculator : : getAttackHateFactor ( ) const
{
//assume that unit have only few HATE features and cache them all
const std : : string cachingStrHate = " type_HATE " ;
2023-05-01 00:20:01 +02:00
static const auto selectorHate = Selector : : type ( ) ( BonusType : : HATE ) ;
2023-01-14 19:01:53 +02:00
auto allHateEffects = info . attacker - > getBonuses ( selectorHate , cachingStrHate ) ;
2023-10-21 13:50:42 +02:00
return allHateEffects - > valOfBonuses ( Selector : : subtype ( ) ( BonusSubtypeID ( info . defender - > creatureId ( ) ) ) ) / 100.0 ;
2023-01-14 19:01:53 +02:00
}
2024-01-10 23:56:26 +02:00
double DamageCalculator : : getAttackRevengeFactor ( ) const
{
if ( info . attacker - > hasBonusOfType ( BonusType : : REVENGE ) ) //HotA Haspid ability
{
int totalStackCount = info . attacker - > unitBaseAmount ( ) ;
int currentStackHealth = info . attacker - > getAvailableHealth ( ) ;
int creatureHealth = info . attacker - > getMaxHealth ( ) ;
return sqrt ( static_cast < double > ( ( totalStackCount + 1 ) * creatureHealth ) / ( currentStackHealth + creatureHealth ) - 1 ) ;
}
return 0.0 ;
}
2023-01-14 19:01:53 +02:00
double DamageCalculator : : getDefenseSkillFactor ( ) const
{
int defenseAdvantage = getTargetDefenseEffective ( ) - getActorAttackEffective ( ) ;
//bonus from attack/defense skills
if ( defenseAdvantage > 0 ) //decreasing dmg
{
2023-03-15 23:47:26 +02:00
const double defenseMultiplier = VLC - > settings ( ) - > getDouble ( EGameSettings : : COMBAT_DEFENSE_POINT_DAMAGE_FACTOR ) ;
const double defenseMultiplierCap = VLC - > settings ( ) - > getDouble ( EGameSettings : : COMBAT_DEFENSE_POINT_DAMAGE_FACTOR_CAP ) ;
2023-01-14 19:01:53 +02:00
const double dec = std : : min ( defenseMultiplier * defenseAdvantage , defenseMultiplierCap ) ;
return dec ;
}
return 0.0 ;
}
double DamageCalculator : : getDefenseArmorerFactor ( ) const
{
2023-02-15 01:05:36 +02:00
const std : : string cachingStrArmorer = " type_GENERAL_DAMAGE_REDUCTIONs_N1_NsrcSPELL_EFFECT " ;
2023-10-21 13:50:42 +02:00
static const auto selectorArmorer = Selector : : typeSubtype ( BonusType : : GENERAL_DAMAGE_REDUCTION , BonusCustomSubtype : : damageTypeAll ) . And ( Selector : : sourceTypeSel ( BonusSource : : SPELL_EFFECT ) . Not ( ) ) ;
2023-01-14 19:01:53 +02:00
return info . defender - > valOfBonuses ( selectorArmorer , cachingStrArmorer ) / 100.0 ;
}
double DamageCalculator : : getDefenseMagicShieldFactor ( ) const
{
const std : : string cachingStrMeleeReduction = " type_GENERAL_DAMAGE_REDUCTIONs_0 " ;
2023-10-21 13:50:42 +02:00
static const auto selectorMeleeReduction = Selector : : typeSubtype ( BonusType : : GENERAL_DAMAGE_REDUCTION , BonusCustomSubtype : : damageTypeMelee ) ;
2023-01-14 19:01:53 +02:00
const std : : string cachingStrRangedReduction = " type_GENERAL_DAMAGE_REDUCTIONs_1 " ;
2023-10-21 13:50:42 +02:00
static const auto selectorRangedReduction = Selector : : typeSubtype ( BonusType : : GENERAL_DAMAGE_REDUCTION , BonusCustomSubtype : : damageTypeRanged ) ;
2023-01-14 19:01:53 +02:00
//handling spell effects - shield and air shield
if ( info . shooting )
return info . defender - > valOfBonuses ( selectorRangedReduction , cachingStrRangedReduction ) / 100.0 ;
else
return info . defender - > valOfBonuses ( selectorMeleeReduction , cachingStrMeleeReduction ) / 100.0 ;
}
double DamageCalculator : : getDefenseRangePenaltiesFactor ( ) const
{
if ( info . shooting )
{
BattleHex attackerPos = info . attackerPos . isValid ( ) ? info . attackerPos : info . attacker - > getPosition ( ) ;
BattleHex defenderPos = info . defenderPos . isValid ( ) ? info . defenderPos : info . defender - > getPosition ( ) ;
const std : : string cachingStrAdvAirShield = " isAdvancedAirShield " ;
auto isAdvancedAirShield = [ ] ( const Bonus * bonus )
{
2023-05-01 00:20:01 +02:00
return bonus - > source = = BonusSource : : SPELL_EFFECT
2023-10-21 13:50:42 +02:00
& & bonus - > sid = = BonusSourceID ( SpellID ( SpellID : : AIR_SHIELD ) )
2023-10-05 15:13:52 +02:00
& & bonus - > val > = MasteryLevel : : ADVANCED ;
2023-01-14 19:01:53 +02:00
} ;
const bool distPenalty = callback . battleHasDistancePenalty ( info . attacker , attackerPos , defenderPos ) ;
if ( distPenalty | | info . defender - > hasBonus ( isAdvancedAirShield , cachingStrAdvAirShield ) )
return 0.5 ;
}
else
{
const std : : string cachingStrNoMeleePenalty = " type_NO_MELEE_PENALTY " ;
2023-05-01 00:20:01 +02:00
static const auto selectorNoMeleePenalty = Selector : : type ( ) ( BonusType : : NO_MELEE_PENALTY ) ;
2023-01-14 19:01:53 +02:00
if ( info . attacker - > isShooter ( ) & & ! info . attacker - > hasBonus ( selectorNoMeleePenalty , cachingStrNoMeleePenalty ) )
return 0.5 ;
}
return 0.0 ;
}
double DamageCalculator : : getDefenseObstacleFactor ( ) const
{
if ( info . shooting )
{
BattleHex attackerPos = info . attackerPos . isValid ( ) ? info . attackerPos : info . attacker - > getPosition ( ) ;
BattleHex defenderPos = info . defenderPos . isValid ( ) ? info . defenderPos : info . defender - > getPosition ( ) ;
const bool obstaclePenalty = callback . battleHasWallPenalty ( info . attacker , attackerPos , defenderPos ) ;
if ( obstaclePenalty )
return 0.5 ;
}
return 0.0 ;
}
double DamageCalculator : : getDefenseUnluckyFactor ( ) const
{
2023-01-14 19:22:32 +02:00
if ( info . unluckyStrike )
2023-01-14 19:01:53 +02:00
return 0.5 ;
return 0.0 ;
}
double DamageCalculator : : getDefenseBlindParalysisFactor ( ) const
{
2023-05-01 00:20:01 +02:00
double multAttackReduction = battleBonusValue ( info . attacker , Selector : : type ( ) ( BonusType : : GENERAL_ATTACK_REDUCTION ) ) / 100.0 ;
2023-01-14 19:01:53 +02:00
return multAttackReduction ;
}
double DamageCalculator : : getDefenseForgetfulnessFactor ( ) const
{
if ( info . shooting )
{
//todo: set actual percentage in spell bonus configuration instead of just level; requires non trivial backward compatibility handling
//get list first, total value of 0 also counts
2023-05-01 00:20:01 +02:00
TConstBonusListPtr forgetfulList = info . attacker - > getBonuses ( Selector : : type ( ) ( BonusType : : FORGETFULL ) , " type_FORGETFULL " ) ;
2023-01-14 19:01:53 +02:00
if ( ! forgetfulList - > empty ( ) )
{
int forgetful = forgetfulList - > valOfBonuses ( Selector : : all ) ;
//none of basic level
if ( forgetful = = 0 | | forgetful = = 1 )
return 0.5 ;
else
logGlobal - > warn ( " Attempt to calculate shooting damage with adv+ FORGETFULL effect " ) ;
}
}
return 0.0 ;
}
double DamageCalculator : : getDefensePetrificationFactor ( ) const
{
// Creatures that are petrified by a Basilisk's Petrifying attack or a Medusa's Stone gaze take 50% damage (R8 = 0.50) from ranged and melee attacks. Taking damage also deactivates the effect.
2023-02-15 01:05:36 +02:00
const std : : string cachingStrAllReduction = " type_GENERAL_DAMAGE_REDUCTIONs_N1_srcSPELL_EFFECT " ;
2023-10-21 13:50:42 +02:00
static const auto selectorAllReduction = Selector : : typeSubtype ( BonusType : : GENERAL_DAMAGE_REDUCTION , BonusCustomSubtype : : damageTypeAll ) . And ( Selector : : sourceTypeSel ( BonusSource : : SPELL_EFFECT ) ) ;
2023-01-14 19:01:53 +02:00
return info . defender - > valOfBonuses ( selectorAllReduction , cachingStrAllReduction ) / 100.0 ;
}
double DamageCalculator : : getDefenseMagicFactor ( ) const
{
// Magic Elementals deal half damage (R8 = 0.50) against Magic Elementals and Black Dragons. This is not affected by the Orb of Vulnerability, Anti-Magic, or Magic Resistance.
if ( info . attacker - > creatureIndex ( ) = = CreatureID : : MAGIC_ELEMENTAL )
{
const std : : string cachingStrMagicImmunity = " type_LEVEL_SPELL_IMMUNITY " ;
2023-05-01 00:20:01 +02:00
static const auto selectorMagicImmunity = Selector : : type ( ) ( BonusType : : LEVEL_SPELL_IMMUNITY ) ;
2023-01-14 19:01:53 +02:00
if ( info . defender - > valOfBonuses ( selectorMagicImmunity , cachingStrMagicImmunity ) > = 5 )
return 0.5 ;
}
return 0.0 ;
}
double DamageCalculator : : getDefenseMindFactor ( ) const
{
// Psychic Elementals deal half damage (R8 = 0.50) against creatures that are immune to Mind spells, such as Giants and Undead. This is not affected by the Orb of Vulnerability.
if ( info . attacker - > creatureIndex ( ) = = CreatureID : : PSYCHIC_ELEMENTAL )
{
const std : : string cachingStrMindImmunity = " type_MIND_IMMUNITY " ;
2023-05-01 00:20:01 +02:00
static const auto selectorMindImmunity = Selector : : type ( ) ( BonusType : : MIND_IMMUNITY ) ;
2023-01-14 19:01:53 +02:00
if ( info . defender - > hasBonus ( selectorMindImmunity , cachingStrMindImmunity ) )
return 0.5 ;
}
return 0.0 ;
}
std : : vector < double > DamageCalculator : : getAttackFactors ( ) const
{
return {
getAttackSkillFactor ( ) ,
getAttackOffenseArcheryFactor ( ) ,
getAttackBlessFactor ( ) ,
getAttackLuckFactor ( ) ,
getAttackJoustingFactor ( ) ,
getAttackDeathBlowFactor ( ) ,
getAttackDoubleDamageFactor ( ) ,
2024-01-11 22:10:22 +02:00
getAttackHateFactor ( ) ,
getAttackRevengeFactor ( )
2023-01-14 19:01:53 +02:00
} ;
}
std : : vector < double > DamageCalculator : : getDefenseFactors ( ) const
{
return {
getDefenseSkillFactor ( ) ,
getDefenseArmorerFactor ( ) ,
getDefenseMagicShieldFactor ( ) ,
getDefenseRangePenaltiesFactor ( ) ,
getDefenseObstacleFactor ( ) ,
getDefenseBlindParalysisFactor ( ) ,
getDefenseUnluckyFactor ( ) ,
getDefenseForgetfulnessFactor ( ) ,
getDefensePetrificationFactor ( ) ,
getDefenseMagicFactor ( ) ,
getDefenseMindFactor ( )
} ;
}
2023-03-24 17:17:17 +02:00
DamageRange DamageCalculator : : getCasualties ( const DamageRange & damageDealt ) const
{
return {
getCasualties ( damageDealt . min ) ,
getCasualties ( damageDealt . max ) ,
} ;
}
int64_t DamageCalculator : : getCasualties ( int64_t damageDealt ) const
{
if ( damageDealt < info . defender - > getFirstHPleft ( ) )
return 0 ;
int64_t damageLeft = damageDealt - info . defender - > getFirstHPleft ( ) ;
2023-05-01 19:29:53 +02:00
int64_t killsLeft = damageLeft / info . defender - > getMaxHealth ( ) ;
2023-03-24 17:17:17 +02:00
return 1 + killsLeft ;
}
2023-01-14 19:01:53 +02:00
int DamageCalculator : : battleBonusValue ( const IBonusBearer * bearer , const CSelector & selector ) const
{
2023-05-01 00:20:01 +02:00
auto noLimit = Selector : : effectRange ( ) ( BonusLimitEffect : : NO_LIMIT ) ;
2023-01-14 19:01:53 +02:00
auto limitMatches = info . shooting
2023-05-01 00:20:01 +02:00
? Selector : : effectRange ( ) ( BonusLimitEffect : : ONLY_DISTANCE_FIGHT )
: Selector : : effectRange ( ) ( BonusLimitEffect : : ONLY_MELEE_FIGHT ) ;
2023-01-14 19:01:53 +02:00
//any regular bonuses or just ones for melee/ranged
return bearer - > getBonuses ( selector , noLimit . Or ( limitMatches ) ) - > totalValue ( ) ;
} ;
2023-03-24 17:17:17 +02:00
DamageEstimation DamageCalculator : : calculateDmgRange ( ) const
2023-01-14 19:01:53 +02:00
{
2023-03-24 17:17:17 +02:00
DamageRange damageBase = getBaseDamageStack ( ) ;
2023-01-14 19:01:53 +02:00
auto attackFactors = getAttackFactors ( ) ;
auto defenseFactors = getDefenseFactors ( ) ;
double attackFactorTotal = 1.0 ;
double defenseFactorTotal = 1.0 ;
for ( auto & factor : attackFactors )
{
assert ( factor > = 0.0 ) ;
attackFactorTotal + = factor ;
}
for ( auto & factor : defenseFactors )
{
assert ( factor > = 0.0 ) ;
2024-01-11 22:10:22 +02:00
defenseFactorTotal * = ( 1 - std : : min ( 1.0 , factor ) ) ;
2023-01-14 19:01:53 +02:00
}
2024-01-11 22:10:22 +02:00
double resultingFactor = attackFactorTotal * defenseFactorTotal ;
2023-03-24 17:17:17 +02:00
DamageRange damageDealt {
2024-01-11 22:10:22 +02:00
std : : max < int64_t > ( 1.0 , std : : floor ( damageBase . min * resultingFactor ) ) ,
std : : max < int64_t > ( 1.0 , std : : floor ( damageBase . max * resultingFactor ) )
2023-01-14 19:01:53 +02:00
} ;
2023-03-24 17:17:17 +02:00
DamageRange killsDealt = getCasualties ( damageDealt ) ;
return DamageEstimation { damageDealt , killsDealt } ;
2023-01-14 19:01:53 +02:00
}
2023-01-14 19:40:34 +02:00
VCMI_LIB_NAMESPACE_END