1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

Centralize creature spell bonuses handling

* unified heal and damage calculation
* apply SPELL_DAMAGE_REDUCTION, MORE_DAMAGE_FROM_SPELL for Fairy dragon
This commit is contained in:
AlexVinS 2015-09-16 15:43:39 +03:00
parent 67bd698019
commit baba3a46e1
7 changed files with 112 additions and 102 deletions

View File

@ -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)

View File

@ -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

View File

@ -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;

View File

@ -19,11 +19,23 @@ class StacksInjured;
struct SpellCastContext
{
SpellCastContext(std::vector<const CStack *> & 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<const CStack *> & 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

View File

@ -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

View File

@ -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<std::string> & 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);

View File

@ -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());