From baba3a46e1c4c569ad824ff80529dd4b4991690f Mon Sep 17 00:00:00 2001 From: AlexVinS Date: Wed, 16 Sep 2015 15:43:39 +0300 Subject: [PATCH] Centralize creature spell bonuses handling * unified heal and damage calculation * apply SPELL_DAMAGE_REDUCTION, MORE_DAMAGE_FROM_SPELL for Fairy dragon --- lib/spells/BattleSpellMechanics.cpp | 42 ++++---------- lib/spells/BattleSpellMechanics.h | 4 +- lib/spells/CDefaultSpellMechanics.cpp | 47 ++++++++------- lib/spells/CDefaultSpellMechanics.h | 16 +++++- lib/spells/CSpellHandler.cpp | 82 ++++++++++++++------------- lib/spells/CSpellHandler.h | 15 +++-- lib/spells/CreatureSpellMechanics.cpp | 8 +-- 7 files changed, 112 insertions(+), 102 deletions(-) diff --git a/lib/spells/BattleSpellMechanics.cpp b/lib/spells/BattleSpellMechanics.cpp index 4a9b0cff6..294093525 100644 --- a/lib/spells/BattleSpellMechanics.cpp +++ b/lib/spells/BattleSpellMechanics.cpp @@ -28,16 +28,10 @@ void HealingSpellMechanics::applyBattleEffects(const SpellCastEnvironment* env, { StacksHealedOrResurrected::HealInfo hi; hi.stackID = (attackedCre)->ID; - if (parameters.casterStack) //casted by creature - { - hi.healedHP = attackedCre->calculateHealedHealthPoints(hpGained, resurrect); - } - else - { - int stackHPgained = parameters.casterHero->getSpellBonus(owner, hpGained, attackedCre); - hi.healedHP = attackedCre->calculateHealedHealthPoints(stackHPgained, resurrect); - } - + int stackHPgained = hpGained; + if(ctx.caster != nullptr) + stackHPgained = ctx.caster->getSpellBonus(owner, hpGained, attackedCre); + hi.healedHP = attackedCre->calculateHealedHealthPoints(stackHPgained, resurrect); hi.lowLevelResurrection = (healLevel == EHealLevel::RESURRECT); shr.healedStacks.push_back(hi); } @@ -45,23 +39,11 @@ void HealingSpellMechanics::applyBattleEffects(const SpellCastEnvironment* env, env->sendAndApply(&shr); } -int HealingSpellMechanics::calculateHealedHP(const SpellCastEnvironment* env, const BattleSpellCastParameters& parameters, const SpellCastContext& ctx) const +int HealingSpellMechanics::calculateHealedHP(const SpellCastEnvironment* env, const BattleSpellCastParameters& parameters, SpellCastContext& ctx) const { - if(parameters.casterStack) - { - int unitSpellPower = parameters.casterStack->valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, owner->id.toEnum()); - if(unitSpellPower) - return parameters.casterStack->count * unitSpellPower; //Archangel - else //Faerie Dragon-like effect - commanders(?) - { - int spellPower = parameters.casterStack->valOfBonuses(Bonus::CREATURE_SPELL_POWER) * parameters.casterStack->count / 100; - return spellPower * owner->power + owner->getPower(ctx.effectLevel); - } - } - else - { - return parameters.usedSpellPower * owner->power + owner->getPower(ctx.effectLevel); //??? - } + if(ctx.effectValue != 0) + return ctx.effectValue; //Archangel + return owner->calculateRawEffectValue(ctx.effectLevel, ctx.effectPower); //??? } ///AntimagicMechanics @@ -411,7 +393,7 @@ void ObstacleMechanics::applyBattleEffects(const SpellCastEnvironment * env, Bat obstacle->casterSide = parameters.casterSide; obstacle->ID = owner->id; obstacle->spellLevel = parameters.spellLvl; - obstacle->casterSpellPower = parameters.usedSpellPower; + obstacle->casterSpellPower = ctx.effectPower; obstacle->uniqueID = obstacleIdToGive++; BattleObstaclePlaced bop; @@ -583,13 +565,13 @@ void SacrificeMechanics::applyBattleEffects(const SpellCastEnvironment * env, Ba env->sendAndApply(&bsr); } -int SacrificeMechanics::calculateHealedHP(const SpellCastEnvironment* env, const BattleSpellCastParameters& parameters, const SpellCastContext& ctx) const +int SacrificeMechanics::calculateHealedHP(const SpellCastEnvironment* env, const BattleSpellCastParameters& parameters, SpellCastContext& ctx) const { int res = 0; if(nullptr == parameters.selectedStack) env->complain("No stack to sacrifice."); else - res = (parameters.usedSpellPower + parameters.selectedStack->MaxHealth() + owner->getPower(ctx.effectLevel)) * parameters.selectedStack->count; + res = (ctx.effectPower + parameters.selectedStack->MaxHealth() + owner->getPower(ctx.effectLevel)) * parameters.selectedStack->count; return res; } @@ -644,7 +626,7 @@ void SummonMechanics::applyBattleEffects(const SpellCastEnvironment * env, Battl //TODO stack casting -> probably power will be zero; set the proper number of creatures manually int percentBonus = parameters.casterHero ? parameters.casterHero->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, owner->id.toEnum()) : 0; - bsa.amount = parameters.usedSpellPower + bsa.amount = ctx.effectPower * owner->getPower(parameters.spellLvl) * (100 + percentBonus) / 100.0; //new feature - percentage bonus if(bsa.amount) diff --git a/lib/spells/BattleSpellMechanics.h b/lib/spells/BattleSpellMechanics.h index c970ea215..0c8d0614e 100644 --- a/lib/spells/BattleSpellMechanics.h +++ b/lib/spells/BattleSpellMechanics.h @@ -25,7 +25,7 @@ public: HealingSpellMechanics(CSpell * s): DefaultSpellMechanics(s){}; protected: void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override; - virtual int calculateHealedHP(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, const SpellCastContext & ctx) const; + virtual int calculateHealedHP(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const; virtual EHealLevel getHealLevel(int effectLevel) const = 0; }; @@ -130,7 +130,7 @@ public: ESpellCastProblem::ESpellCastProblem canBeCasted(const CBattleInfoCallback * cb, PlayerColor player) const override; protected: void applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override; - int calculateHealedHP(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, const SpellCastContext & ctx) const override; + int calculateHealedHP(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override; }; ///all rising spells but SACRIFICE diff --git a/lib/spells/CDefaultSpellMechanics.cpp b/lib/spells/CDefaultSpellMechanics.cpp index c82625dae..7d1311151 100644 --- a/lib/spells/CDefaultSpellMechanics.cpp +++ b/lib/spells/CDefaultSpellMechanics.cpp @@ -325,7 +325,28 @@ void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleS StacksInjured si; SpellCastContext ctx(attackedCres, sc, si); ctx.effectLevel = calculateEffectLevel(parameters); + ctx.effectPower = parameters.usedSpellPower; + ctx.enchantPower = parameters.usedSpellPower; + ctx.effectValue = 0; + if(parameters.casterStack) + ctx.caster = parameters.casterStack; + else if(parameters.mode == ECastingMode::HERO_CASTING) + ctx.caster = parameters.cb->battleGetFightingHero(parameters.casterSide); + if(parameters.casterStack && parameters.mode != ECastingMode::MAGIC_MIRROR) + { + auto enchantPower = parameters.casterStack->valOfBonuses(Bonus::CREATURE_ENCHANT_POWER); + if(ctx.enchantPower == 0) + ctx.enchantPower = enchantPower; + //Fairy Dragon, etc. + int effectPower = parameters.casterStack->valOfBonuses(Bonus::CREATURE_SPELL_POWER) * parameters.casterStack->count / 100; + if(ctx.effectPower == 0) + ctx.effectPower = effectPower; + //Archangel, etc + int unitSpellPower = parameters.casterStack->valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, owner->id.toEnum()); + if(unitSpellPower != 0) + ctx.effectValue = parameters.casterStack->count * unitSpellPower; + } applyBattleEffects(env, parameters, ctx); env->sendAndApply(&sc); @@ -526,25 +547,16 @@ void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env, //applying effects if(owner->isOffensiveSpell()) { - int spellDamage = 0; - if(parameters.casterStack && parameters.mode != ECastingMode::MAGIC_MIRROR) - { - int unitSpellPower = parameters.casterStack->valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, owner->id.toEnum()); - if(unitSpellPower) - spellDamage = parameters.casterStack->count * unitSpellPower; //TODO: handle immunities - else //Faerie Dragon - { - parameters.usedSpellPower = parameters.casterStack->valOfBonuses(Bonus::CREATURE_SPELL_POWER) * parameters.casterStack->count / 100; - } - } + int spellDamage = ctx.effectValue; + int chainLightningModifier = 0; for(auto & attackedCre : ctx.attackedCres) { BattleStackAttacked bsa; - if(spellDamage) - bsa.damageAmount = spellDamage >> chainLightningModifier; + if(spellDamage != 0) + bsa.damageAmount = owner->adjustRawDamage(parameters.casterHero, attackedCre, spellDamage) >> chainLightningModifier; else - bsa.damageAmount = owner->calculateDamage(parameters.casterHero, attackedCre, ctx.effectLevel, parameters.usedSpellPower) >> chainLightningModifier; + bsa.damageAmount = owner->calculateDamage(parameters.casterHero, attackedCre, ctx.effectLevel, ctx.effectPower) >> chainLightningModifier; ctx.sc.dmgToDisplay += bsa.damageAmount; @@ -563,14 +575,9 @@ void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env, if(owner->hasEffects()) { - int stackSpellPower = 0; - if(parameters.casterStack && parameters.mode != ECastingMode::MAGIC_MIRROR) - { - stackSpellPower = parameters.casterStack->valOfBonuses(Bonus::CREATURE_ENCHANT_POWER); - } SetStackEffect sse; //get default spell duration (spell power with bonuses for heroes) - int duration = calculateDuration(parameters.casterHero, stackSpellPower ? stackSpellPower : parameters.usedSpellPower); + int duration = calculateDuration(parameters.casterHero, ctx.enchantPower); //generate actual stack bonuses { int maxDuration = 0; diff --git a/lib/spells/CDefaultSpellMechanics.h b/lib/spells/CDefaultSpellMechanics.h index edf248ab0..310216b64 100644 --- a/lib/spells/CDefaultSpellMechanics.h +++ b/lib/spells/CDefaultSpellMechanics.h @@ -19,11 +19,23 @@ class StacksInjured; struct SpellCastContext { SpellCastContext(std::vector & attackedCres, BattleSpellCast & sc, StacksInjured & si): - attackedCres(attackedCres), sc(sc), si(si){}; + attackedCres(attackedCres), sc(sc), si(si), + effectLevel(0),effectPower(0),enchantPower(0),effectValue(0) + { + }; std::vector & attackedCres; BattleSpellCast & sc; - StacksInjured & si; + StacksInjured & si; + ///spell school level to use for effects int effectLevel; + ///actual spell-power affecting effect values + int effectPower; + ///actual spell-power affecting effect duration + int enchantPower; + ///for FairyDragon-like casting, if !=0 ignores effectPower + int effectValue; + ///stack or hero + const ISpellCaster * caster; }; enum class ESpellCastResult diff --git a/lib/spells/CSpellHandler.cpp b/lib/spells/CSpellHandler.cpp index d84b6f108..baab35045 100644 --- a/lib/spells/CSpellHandler.cpp +++ b/lib/spells/CSpellHandler.cpp @@ -133,47 +133,10 @@ const CSpell::LevelInfo & CSpell::getLevelInfo(const int level) const ui32 CSpell::calculateDamage(const ISpellCaster * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower) const { - ui32 ret = 0; //value to return - //check if spell really does damage - if not, return 0 if(!isDamageSpell()) return 0; - - ret = usedSpellPower * power; - ret += getPower(spellSchoolLevel); - - //affected creature-specific part - if(nullptr != affectedCreature) - { - //applying protections - when spell has more then one elements, only one protection should be applied (I think) - forEachSchool([&](const SpellSchoolInfo & cnf, bool & stop) - { - if(affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, (ui8)cnf.id)) - { - ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, (ui8)cnf.id); - ret /= 100; - stop = true;//only bonus from one school is used - } - }); - - //general spell dmg reduction - if(affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, -1)) - { - ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, -1); - ret /= 100; - } - - //dmg increasing - if(affectedCreature->hasBonusOfType(Bonus::MORE_DAMAGE_FROM_SPELL, id)) - { - ret *= 100 + affectedCreature->valOfBonuses(Bonus::MORE_DAMAGE_FROM_SPELL, id.toEnum()); - ret /= 100; - } - } - - if(nullptr != caster) //todo: make sure that caster always present - ret = caster->getSpellBonus(this, ret, affectedCreature); - return ret; + return adjustRawDamage(caster, affectedCreature, calculateRawEffectValue(spellSchoolLevel, usedSpellPower)); } ESpellCastProblem::ESpellCastProblem CSpell::canBeCasted(const CBattleInfoCallback * cb, PlayerColor player) const @@ -395,6 +358,49 @@ ESpellCastProblem::ESpellCastProblem CSpell::isImmuneAt(const CBattleInfoCallbac return ESpellCastProblem::OK; } +int CSpell::adjustRawDamage(const ISpellCaster * caster, const CStack * affectedCreature, int rawDamage) const +{ + int ret = rawDamage; + //affected creature-specific part + if(nullptr != affectedCreature) + { + //applying protections - when spell has more then one elements, only one protection should be applied (I think) + forEachSchool([&](const SpellSchoolInfo & cnf, bool & stop) + { + if(affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, (ui8)cnf.id)) + { + ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, (ui8)cnf.id); + ret /= 100; + stop = true;//only bonus from one school is used + } + }); + + //general spell dmg reduction + if(affectedCreature->hasBonusOfType(Bonus::SPELL_DAMAGE_REDUCTION, -1)) + { + ret *= affectedCreature->valOfBonuses(Bonus::SPELL_DAMAGE_REDUCTION, -1); + ret /= 100; + } + + //dmg increasing + if(affectedCreature->hasBonusOfType(Bonus::MORE_DAMAGE_FROM_SPELL, id)) + { + ret *= 100 + affectedCreature->valOfBonuses(Bonus::MORE_DAMAGE_FROM_SPELL, id.toEnum()); + ret /= 100; + } + } + + if(nullptr != caster) //todo: make sure that caster always present + ret = caster->getSpellBonus(this, ret, affectedCreature); + + return ret; +} + +int CSpell::calculateRawEffectValue(int effectLevel, int effectPower) const +{ + return effectPower * power + getPower(effectLevel); +} + ESpellCastProblem::ESpellCastProblem CSpell::isImmuneBy(const IBonusBearer* obj) const { //todo: use new bonus API diff --git a/lib/spells/CSpellHandler.h b/lib/spells/CSpellHandler.h index 920dccb4d..c4d082a16 100644 --- a/lib/spells/CSpellHandler.h +++ b/lib/spells/CSpellHandler.h @@ -218,9 +218,6 @@ public: ///checks for creature immunity / anything that prevent casting *at given hex* - doesn't take into account general problems such as not having spellbook or mana points etc. ESpellCastProblem::ESpellCastProblem isImmuneAt(const CBattleInfoCallback * cb, const CGHeroInstance * caster, ECastingMode::ECastingMode mode, BattleHex destination) const; - //internal, for use only by Mechanics classes - ESpellCastProblem::ESpellCastProblem isImmuneBy(const IBonusBearer *obj) const; - ///calculate spell damage on stack taking caster`s secondary skills and affectedCreature`s bonuses into account ui32 calculateDamage(const ISpellCaster * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower) const; @@ -292,11 +289,17 @@ public: ///implementation of BattleSpellCast applying void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const; -public: - ///Client logic. - +public://Client logic. void prepareBattleLog(const CBattleInfoCallback * cb, const BattleSpellCast * packet, std::vector & logLines) const; +public://internal, for use only by Mechanics classes + ///applies caster`s secondary skills and affectedCreature`s to raw damage + int adjustRawDamage(const ISpellCaster * caster, const CStack * affectedCreature, int rawDamage) const; + ///returns raw damage or healed HP + int calculateRawEffectValue(int effectLevel, int effectPower) const; + ///generic immunity calculation + ESpellCastProblem::ESpellCastProblem isImmuneBy(const IBonusBearer *obj) const; + private: void setIsOffensive(const bool val); void setIsRising(const bool val); diff --git a/lib/spells/CreatureSpellMechanics.cpp b/lib/spells/CreatureSpellMechanics.cpp index 5e47e616d..330ce98dc 100644 --- a/lib/spells/CreatureSpellMechanics.cpp +++ b/lib/spells/CreatureSpellMechanics.cpp @@ -19,14 +19,14 @@ void AcidBreathDamageMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const { //calculating dmg to display - ctx.sc.dmgToDisplay = parameters.usedSpellPower; + ctx.sc.dmgToDisplay = ctx.effectPower; for(auto & attackedCre : ctx.attackedCres) //no immunities { BattleStackAttacked bsa; bsa.flags |= BattleStackAttacked::SPELL_EFFECT; bsa.spellID = owner->id; - bsa.damageAmount = parameters.usedSpellPower; //damage times the number of attackers + bsa.damageAmount = ctx.effectPower; //damage times the number of attackers bsa.stackAttacked = (attackedCre)->ID; bsa.attackerID = -1; (attackedCre)->prepareAttacked(bsa, env->getRandomGenerator()); @@ -38,7 +38,7 @@ void AcidBreathDamageMechanics::applyBattleEffects(const SpellCastEnvironment * void DeathStareMechanics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const { //calculating dmg to display - ctx.sc.dmgToDisplay = parameters.usedSpellPower; + ctx.sc.dmgToDisplay = ctx.effectPower; if(!ctx.attackedCres.empty()) vstd::amin(ctx.sc.dmgToDisplay, (*ctx.attackedCres.begin())->count); //stack is already reduced after attack @@ -47,7 +47,7 @@ void DeathStareMechanics::applyBattleEffects(const SpellCastEnvironment * env, B BattleStackAttacked bsa; bsa.flags |= BattleStackAttacked::SPELL_EFFECT; bsa.spellID = owner->id; - bsa.damageAmount = parameters.usedSpellPower * (attackedCre)->valOfBonuses(Bonus::STACK_HEALTH); + bsa.damageAmount = ctx.effectPower * (attackedCre)->valOfBonuses(Bonus::STACK_HEALTH);//todo: move here all DeathStare calculation bsa.stackAttacked = (attackedCre)->ID; bsa.attackerID = -1; (attackedCre)->prepareAttacked(bsa, env->getRandomGenerator());