1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-01-02 00:10:22 +02:00

Cumulative spell effects

* Added experimental support for cumulative effects for ENCHANTED bonus
* Updated and fixed SPECIAL_PECULIAR_ENCHANT processing
* Initial implementation of cumulative spell effects.
* Scheme for new spell feature - cumulative bonus.
This commit is contained in:
AlexVinS 2016-11-02 20:11:01 +03:00 committed by Arseniy Shestakov
parent 0c7eeeaa5c
commit 0f5202689e
18 changed files with 292 additions and 212 deletions

View File

@ -259,11 +259,9 @@ void CBattleAI::attemptCastingSpell()
{ {
StackWithBonuses swb; StackWithBonuses swb;
swb.stack = sta; swb.stack = sta;
Bonus pseudoBonus; //todo: handle effect actualization in HypotheticChangesToBattleState
pseudoBonus.sid = ps.spell->id; ps.spell->getEffects(swb.bonusesToAdd, skillLevel, false, hero->getEnchantPower(ps.spell));
pseudoBonus.val = skillLevel; ps.spell->getEffects(swb.bonusesToAdd, skillLevel, true, hero->getEnchantPower(ps.spell));
pseudoBonus.turnsRemain = 1; //TODO
CStack::stackEffectToFeature(swb.bonusesToAdd, pseudoBonus);
HypotheticChangesToBattleState state; HypotheticChangesToBattleState state;
state.bonusesOfStacks[swb.stack] = &swb; state.bonusesOfStacks[swb.stack] = &swb;
PotentialTargets pt(swb.stack, state); PotentialTargets pt(swb.stack, state);

View File

@ -3,6 +3,9 @@
GENERAL: GENERAL:
* Spectator mode was implemented through command-line options * Spectator mode was implemented through command-line options
SPELLS:
* Implemented cumulative effects for spells
0.98 -> 0.99 0.98 -> 0.99
GENERAL: GENERAL:

View File

@ -1359,52 +1359,44 @@ void CBattleInterface::spellCast(const BattleSpellCast *sc)
void CBattleInterface::battleStacksEffectsSet(const SetStackEffect & sse) void CBattleInterface::battleStacksEffectsSet(const SetStackEffect & sse)
{ {
if (sse.effect.back().sid == -1 && sse.stacks.size() == 1 && sse.effect.size() == 2) if(sse.stacks.size() == 1 && sse.effect.size() == 2 && sse.effect.back().sid == -1)
{ {
const Bonus & bns = sse.effect.front(); const Bonus & bns = sse.effect.front();
if (bns.source == Bonus::OTHER && bns.type == Bonus::PRIMARY_SKILL) if(bns.source == Bonus::OTHER && bns.type == Bonus::PRIMARY_SKILL)
{ {
//defensive stance //defensive stance
const CStack *stack = LOCPLINT->cb->battleGetStackByID(*sse.stacks.begin()); const CStack *stack = LOCPLINT->cb->battleGetStackByID(*sse.stacks.begin());
int txtid = 120; int txtid = 120;
if (stack->count != 1) if(stack->count != 1)
txtid++; //move to plural text txtid++; //move to plural text
BonusList defenseBonuses = *(stack->getBonuses(Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE))); BonusList defenseBonuses = *(stack->getBonuses(Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE)));
defenseBonuses.remove_if (Bonus::UntilGetsTurn); //remove bonuses gained from defensive stance defenseBonuses.remove_if(Bonus::UntilGetsTurn); //remove bonuses gained from defensive stance
int val = stack->Defense() - defenseBonuses.totalValue(); int val = stack->Defense() - defenseBonuses.totalValue();
auto txt = boost::format (CGI->generaltexth->allTexts[txtid]) % ((stack->count != 1) ? stack->getCreature()->namePl : stack->getCreature()->nameSing) % val; auto txt = boost::format(CGI->generaltexth->allTexts[txtid]) % ((stack->count != 1) ? stack->getCreature()->namePl : stack->getCreature()->nameSing) % val;
console->addText(boost::to_string(txt)); console->addText(boost::to_string(txt));
} }
} }
if (activeStack != nullptr) //it can be -1 when a creature casts effect if(activeStack != nullptr)
{
redrawBackgroundWithHexes(activeStack); redrawBackgroundWithHexes(activeStack);
}
} }
CBattleInterface::PossibleActions CBattleInterface::getCasterAction(const CSpell *spell, const ISpellCaster *caster, ECastingMode::ECastingMode mode) const CBattleInterface::PossibleActions CBattleInterface::getCasterAction(const CSpell * spell, const ISpellCaster * caster, ECastingMode::ECastingMode mode) const
{ {
PossibleActions spellSelMode = ANY_LOCATION; PossibleActions spellSelMode = ANY_LOCATION;
const CSpell::TargetInfo ti(spell, caster->getSpellSchoolLevel(spell), mode); const CSpell::TargetInfo ti(spell, caster->getSpellSchoolLevel(spell), mode);
if (ti.massive || ti.type == CSpell::NO_TARGET) if(ti.massive || ti.type == CSpell::NO_TARGET)
spellSelMode = NO_LOCATION; spellSelMode = NO_LOCATION;
else if (ti.type == CSpell::LOCATION && ti.clearAffected) else if(ti.type == CSpell::LOCATION && ti.clearAffected)
{
spellSelMode = FREE_LOCATION; spellSelMode = FREE_LOCATION;
} else if(ti.type == CSpell::CREATURE)
else if (ti.type == CSpell::CREATURE)
{
spellSelMode = AIMED_SPELL_CREATURE; spellSelMode = AIMED_SPELL_CREATURE;
} else if(ti.type == CSpell::OBSTACLE)
else if (ti.type == CSpell::OBSTACLE)
{
spellSelMode = OBSTACLE; spellSelMode = OBSTACLE;
}
return spellSelMode; return spellSelMode;
} }

View File

@ -19,25 +19,25 @@
{ {
//assumed verticalPosition: top //assumed verticalPosition: top
"type": "string", "type": "string",
"format": "defFile" "format": "defFile"
}, },
{ {
"type": "object", "type": "object",
"properties":{ "properties":{
"verticalPosition": {"type":"string", "enum":["top","bottom"]}, "verticalPosition": {"type":"string", "enum":["top","bottom"]},
"defName": {"type":"string", "format": "defFile"} "defName": {"type":"string", "format": "defFile"}
}, },
"additionalProperties" : false "additionalProperties" : false
} }
] ]
} }
}, },
"animation":{ "animation":{
"type": "object", "type": "object",
"additionalProperties" : false, "additionalProperties" : false,
"properties":{ "properties":{
"affect":{"$ref" : "#/definitions/animationQueue"}, "affect":{"$ref" : "#/definitions/animationQueue"},
"hit":{"$ref" : "#/definitions/animationQueue"}, "hit":{"$ref" : "#/definitions/animationQueue"},
"cast":{"$ref" : "#/definitions/animationQueue"}, "cast":{"$ref" : "#/definitions/animationQueue"},
"projectile":{ "projectile":{
"type":"array", "type":"array",
@ -46,11 +46,11 @@
"properties":{ "properties":{
"minimumAngle": {"type":"number", "minimum" : 0}, "minimumAngle": {"type":"number", "minimum" : 0},
"defName": {"type":"string", "format": "defFile"} "defName": {"type":"string", "format": "defFile"}
}, },
"additionalProperties" : false "additionalProperties" : false
} }
} }
} }
}, },
"flags" :{ "flags" :{
"type" : "object", "type" : "object",
@ -85,7 +85,14 @@
}, },
"effects":{ "effects":{
"type": "object", "type": "object",
"description": "Timed effects", "description": "Timed effects (updated by prolongation)",
"additionalProperties" : {
"$ref" : "vcmi:bonus"
}
},
"cumulativeEffects":{
"type": "object",
"description": "Timed effects (updated by unique bonus)",
"additionalProperties" : { "additionalProperties" : {
"$ref" : "vcmi:bonus" "$ref" : "vcmi:bonus"
} }
@ -107,16 +114,16 @@
{ {
"type": "boolean", "type": "boolean",
"description": "LOCATION target only. All affected hexes/tile must be clear" "description": "LOCATION target only. All affected hexes/tile must be clear"
} }
} }
} }
} }
}, },
"texts":{ "texts":{
"type": "object", "type": "object",
"additionalProperties" : false "additionalProperties" : false
} }
}, },
@ -239,9 +246,9 @@
"$ref" : "#/definitions/flags", "$ref" : "#/definitions/flags",
"description": "flags structure of bonus names, presence of all bonuses required to be affected by, can't be negated." "description": "flags structure of bonus names, presence of all bonuses required to be affected by, can't be negated."
}, },
"animation":{"$ref": "#/definitions/animation"}, "animation":{"$ref": "#/definitions/animation"},
"graphics":{ "graphics":{
"type": "object", "type": "object",
"additionalProperties" : false, "additionalProperties" : false,
@ -291,7 +298,7 @@
"additionalProperties" : false, "additionalProperties" : false,
"required" : ["none", "basic", "advanced", "expert"], "required" : ["none", "basic", "advanced", "expert"],
"properties":{ "properties":{
"base":{ "base":{
"type": "object", "type": "object",
"description": "will be merged with all levels", "description": "will be merged with all levels",

View File

@ -101,6 +101,7 @@
"levels" : { "levels" : {
"base":{ "base":{
"range" : "0", "range" : "0",
//no cumulative effect even with mods here
"effects" : { "effects" : {
"bindEffect" : { "bindEffect" : {
"val" : 0, "val" : 0,
@ -335,7 +336,7 @@
"base":{ "base":{
"range" : "0", "range" : "0",
"targetModifier":{"smart":true}, "targetModifier":{"smart":true},
"effects" : { "cumulativeEffects" : {
"primarySkill" : { "primarySkill" : {
"val" : -3, "val" : -3,
"type" : "PRIMARY_SKILL", "type" : "PRIMARY_SKILL",

View File

@ -13,6 +13,7 @@
"base":{ "base":{
"range" : "0", "range" : "0",
"targetModifier":{"smart":true}, "targetModifier":{"smart":true},
//no cumulative effect even with mods here
"effects" : { "effects" : {
"generalDamageReduction" : { "generalDamageReduction" : {
"type" : "GENERAL_DAMAGE_REDUCTION", "type" : "GENERAL_DAMAGE_REDUCTION",
@ -43,6 +44,7 @@
"base":{ "base":{
"range" : "0", "range" : "0",
"targetModifier":{"smart":true}, "targetModifier":{"smart":true},
//no cumulative effect even with mods here
"effects" : { "effects" : {
"generalDamageReduction" : { "generalDamageReduction" : {
"type" : "GENERAL_DAMAGE_REDUCTION", "type" : "GENERAL_DAMAGE_REDUCTION",
@ -558,7 +560,7 @@
"base":{ "base":{
"range" : "0", "range" : "0",
"targetModifier":{"smart":true}, "targetModifier":{"smart":true},
"effects" : { "cumulativeEffects" : {
"primarySkill" : { "primarySkill" : {
"type" : "PRIMARY_SKILL", "type" : "PRIMARY_SKILL",
"subtype" : "primSkill.defence", "subtype" : "primSkill.defence",
@ -569,14 +571,14 @@
} }
}, },
"advanced":{ "advanced":{
"effects" : { "cumulativeEffects" : {
"primarySkill" : { "primarySkill" : {
"val" : -4 "val" : -4
} }
} }
}, },
"expert":{ "expert":{
"effects" : { "cumulativeEffects" : {
"primarySkill" : { "primarySkill" : {
"val" : -5 "val" : -5
} }

View File

@ -104,21 +104,6 @@ si32 CStack::magicResistance() const
return magicResistance; return magicResistance;
} }
void CStack::stackEffectToFeature(std::vector<Bonus> & sf, const Bonus & sse)
{
const CSpell * sp = SpellID(sse.sid).toSpell();
std::vector<Bonus> tmp;
sp->getEffects(tmp, sse.val);
for(Bonus& b : tmp)
{
if(b.turnsRemain == 0)
b.turnsRemain = sse.turnsRemain;
sf.push_back(b);
}
}
bool CStack::willMove(int turn /*= 0*/) const bool CStack::willMove(int turn /*= 0*/) const
{ {
return ( turn ? true : !vstd::contains(state, EBattleStackState::DEFENDING) ) return ( turn ? true : !vstd::contains(state, EBattleStackState::DEFENDING) )

View File

@ -62,7 +62,6 @@ public:
ui32 calculateHealedHealthPoints(ui32 toHeal, const bool resurrect) const; ui32 calculateHealedHealthPoints(ui32 toHeal, const bool resurrect) const;
ui32 level() const; ui32 level() const;
si32 magicResistance() const override; //include aura of resistance si32 magicResistance() const override; //include aura of resistance
static void stackEffectToFeature(std::vector<Bonus> & sf, const Bonus & sse);
std::vector<si32> activeSpells() const; //returns vector of active spell IDs sorted by time of cast std::vector<si32> activeSpells() const; //returns vector of active spell IDs sorted by time of cast
const CGHeroInstance *getMyHero() const; //if stack belongs to hero (directly or was by him summoned) returns hero, nullptr otherwise const CGHeroInstance *getMyHero() const; //if stack belongs to hero (directly or was by him summoned) returns hero, nullptr otherwise
ui32 totalHealth() const; // total health for all creatures in stack; ui32 totalHealth() const; // total health for all creatures in stack;

View File

@ -1519,11 +1519,19 @@ struct SetStackEffect : public CPackForClient
void applyCl(CClient *cl); void applyCl(CClient *cl);
std::vector<ui32> stacks; //affected stacks (IDs) std::vector<ui32> stacks; //affected stacks (IDs)
//regular effects
std::vector<Bonus> effect; //bonuses to apply std::vector<Bonus> effect; //bonuses to apply
std::vector<std::pair<ui32, Bonus> > uniqueBonuses; //bonuses per single stack std::vector<std::pair<ui32, Bonus> > uniqueBonuses; //bonuses per single stack
//cumulative effects
std::vector<Bonus> cumulativeEffects; //bonuses to apply
std::vector<std::pair<ui32, Bonus> > cumulativeUniqueBonuses; //bonuses per single stack
template <typename Handler> void serialize(Handler &h, const int version) template <typename Handler> void serialize(Handler &h, const int version)
{ {
h & stacks & effect & uniqueBonuses; h & stacks & effect & uniqueBonuses;
h & cumulativeEffects & cumulativeUniqueBonuses;
} }
}; };

View File

@ -1554,33 +1554,39 @@ void actualizeEffect(CStack * s, const std::vector<Bonus> & ef)
DLL_LINKAGE void SetStackEffect::applyGs(CGameState *gs) DLL_LINKAGE void SetStackEffect::applyGs(CGameState *gs)
{ {
if(effect.empty()) if(effect.empty() && cumulativeEffects.empty())
{ {
logGlobal->errorStream() << "Trying to apply SetStackEffect with no effects"; logGlobal->errorStream() << "Trying to apply SetStackEffect with no effects";
return; return;
} }
int spellid = effect.begin()->sid; //effects' source ID si32 spellid = effect.empty() ? cumulativeEffects.begin()->sid : effect.begin()->sid; //effects' source ID
auto processEffect = [spellid, this](CStack * sta, const Bonus & effect) auto processEffect = [spellid, this](CStack * sta, const Bonus & effect, bool cumulative)
{ {
if(!sta->hasBonus(Selector::source(Bonus::SPELL_EFFECT, spellid).And(Selector::typeSubtype(effect.type, effect.subtype))) if(cumulative || !sta->hasBonus(Selector::source(Bonus::SPELL_EFFECT, spellid).And(Selector::typeSubtype(effect.type, effect.subtype))))
|| spellid == SpellID::DISRUPTING_RAY || spellid == SpellID::ACID_BREATH_DEFENSE)
{ {
//no such effect or cumulative - add new //no such effect or cumulative - add new
logBonus->traceStream() << sta->nodeName() << " receives a new bonus: " << effect.Description(); logBonus->traceStream() << sta->nodeName() << " receives a new bonus: " << effect.Description();
sta->addNewBonus(std::make_shared<Bonus>(effect)); sta->addNewBonus(std::make_shared<Bonus>(effect));
} }
else else
{
logBonus->traceStream() << sta->nodeName() << " updated bonus: " << effect.Description();
actualizeEffect(sta, effect); actualizeEffect(sta, effect);
}
}; };
for(ui32 id : stacks) for(ui32 id : stacks)
{ {
CStack *s = gs->curB->getStack(id); CStack *s = gs->curB->getStack(id);
if(s) if(s)
{
for(const Bonus & fromEffect : effect) for(const Bonus & fromEffect : effect)
processEffect(s, fromEffect); processEffect(s, fromEffect, false);
for(const Bonus & fromEffect : cumulativeEffects)
processEffect(s, fromEffect, true);
}
else else
logNetwork->errorStream() << "Cannot find stack " << id; logNetwork->errorStream() << "Cannot find stack " << id;
} }
@ -1589,7 +1595,16 @@ DLL_LINKAGE void SetStackEffect::applyGs(CGameState *gs)
{ {
CStack *s = gs->curB->getStack(para.first); CStack *s = gs->curB->getStack(para.first);
if(s) if(s)
processEffect(s, para.second); processEffect(s, para.second, false);
else
logNetwork->errorStream() << "Cannot find stack " << para.first;
}
for(auto & para : cumulativeUniqueBonuses)
{
CStack *s = gs->curB->getStack(para.first);
if(s)
processEffect(s, para.second, true);
else else
logNetwork->errorStream() << "Cannot find stack " << para.first; logNetwork->errorStream() << "Cannot find stack " << para.first;
} }

View File

@ -14,7 +14,7 @@
#include "../ConstTransitivePtr.h" #include "../ConstTransitivePtr.h"
#include "../GameConstants.h" #include "../GameConstants.h"
const ui32 SERIALIZATION_VERSION = 771; const ui32 SERIALIZATION_VERSION = 773;
const ui32 MINIMAL_SERIALIZATION_VERSION = 753; const ui32 MINIMAL_SERIALIZATION_VERSION = 753;
const std::string SAVEGAME_MAGIC = "VCMISVG"; const std::string SAVEGAME_MAGIC = "VCMISVG";

View File

@ -79,11 +79,12 @@ ESpellCastResult AdventureSpellMechanics::applyAdventureEffects(const SpellCastE
{ {
if(owner->hasEffects()) if(owner->hasEffects())
{ {
//todo: cumulative effects support
const int schoolLevel = parameters.caster->getSpellSchoolLevel(owner); const int schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
std::vector<Bonus> bonuses; std::vector<Bonus> bonuses;
owner->getEffects(bonuses, schoolLevel); owner->getEffects(bonuses, schoolLevel, false, parameters.caster->getEnchantPower(owner));
for(Bonus b : bonuses) for(Bonus b : bonuses)
{ {

View File

@ -445,120 +445,133 @@ void DefaultSpellMechanics::battleLogDefault(std::vector<MetaString> & logLines,
void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
{ {
//applying effects
if(owner->isOffensiveSpell()) if(owner->isOffensiveSpell())
{ defaultDamageEffect(env, parameters, ctx);
const int rawDamage = parameters.getEffectValue();
int chainLightningModifier = 0;
for(auto & attackedCre : ctx.attackedCres)
{
BattleStackAttacked bsa;
bsa.damageAmount = owner->adjustRawDamage(parameters.caster, attackedCre, rawDamage) >> chainLightningModifier;
ctx.addDamageToDisplay(bsa.damageAmount);
bsa.stackAttacked = (attackedCre)->ID;
if(parameters.mode == ECastingMode::ENCHANTER_CASTING) //multiple damage spells cast
bsa.attackerID = parameters.casterStack->ID;
else
bsa.attackerID = -1;
(attackedCre)->prepareAttacked(bsa, env->getRandomGenerator());
ctx.si.stacks.push_back(bsa);
if(owner->id == SpellID::CHAIN_LIGHTNING)
++chainLightningModifier;
}
}
if(owner->hasEffects()) if(owner->hasEffects())
defaultTimedEffect(env, parameters, ctx);
}
void DefaultSpellMechanics::defaultDamageEffect(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
{
const int rawDamage = parameters.getEffectValue();
int chainLightningModifier = 0;
for(auto & attackedCre : ctx.attackedCres)
{ {
SetStackEffect sse; BattleStackAttacked bsa;
//get default spell duration (spell power with bonuses for heroes) bsa.damageAmount = owner->adjustRawDamage(parameters.caster, attackedCre, rawDamage) >> chainLightningModifier;
int duration = parameters.enchantPower; ctx.addDamageToDisplay(bsa.damageAmount);
//generate actual stack bonuses
{
int maxDuration = 0;
std::vector<Bonus> tmp;
owner->getEffects(tmp, parameters.effectLevel);
for(Bonus& b : tmp)
{
//use configured duration if present
if(b.turnsRemain == 0)
b.turnsRemain = duration;
vstd::amax(maxDuration, b.turnsRemain);
sse.effect.push_back(b);
}
//if all spell effects have special duration, use it
duration = maxDuration;
}
//fix to original config: shield should display damage reduction
if(owner->id == SpellID::SHIELD || owner->id == SpellID::AIR_SHIELD)
{
sse.effect.back().val = (100 - sse.effect.back().val);
}
//we need to know who cast Bind
if(owner->id == SpellID::BIND && parameters.casterStack)
{
sse.effect.back().additionalInfo = parameters.casterStack->ID;
}
std::shared_ptr<Bonus> bonus = nullptr;
if(parameters.casterHero)
bonus = parameters.casterHero->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPECIAL_PECULIAR_ENCHANT, owner->id));
//TODO does hero specialty should affects his stack casting spells?
si32 power = 0; bsa.stackAttacked = (attackedCre)->ID;
for(const CStack * affected : ctx.attackedCres) if(parameters.mode == ECastingMode::ENCHANTER_CASTING) //multiple damage spells cast
{ bsa.attackerID = parameters.casterStack->ID;
sse.stacks.push_back(affected->ID); else
bsa.attackerID = -1;
(attackedCre)->prepareAttacked(bsa, env->getRandomGenerator());
ctx.si.stacks.push_back(bsa);
//Apply hero specials - peculiar enchants if(owner->id == SpellID::CHAIN_LIGHTNING)
const ui8 tier = std::max((ui8)1, affected->getCreature()->level); //don't divide by 0 for certain creatures (commanders, war machines) ++chainLightningModifier;
if(bonus)
{
switch(bonus->additionalInfo)
{
case 0: //normal
{
switch(tier)
{
case 1: case 2:
power = 3;
break;
case 3: case 4:
power = 2;
break;
case 5: case 6:
power = 1;
break;
}
Bonus specialBonus(sse.effect.back());
specialBonus.val = power; //it doesn't necessarily make sense for some spells, use it wisely
sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus)); //additional premy to given effect
}
break;
case 1: //only Coronius as yet
{
power = std::max(5 - tier, 0);
Bonus specialBonus(Bonus::N_TURNS, Bonus::PRIMARY_SKILL, Bonus::SPELL_EFFECT, power, owner->id, PrimarySkill::ATTACK);
specialBonus.turnsRemain = duration;
sse.uniqueBonuses.push_back(std::pair<ui32,Bonus> (affected->ID, specialBonus)); //additional attack to Slayer effect
}
break;
}
}
if (parameters.casterHero && parameters.casterHero->hasBonusOfType(Bonus::SPECIAL_BLESS_DAMAGE, owner->id)) //TODO: better handling of bonus percentages
{
int damagePercent = parameters.casterHero->level * parameters.casterHero->valOfBonuses(Bonus::SPECIAL_BLESS_DAMAGE, owner->id.toEnum()) / tier;
Bonus specialBonus(Bonus::N_TURNS, Bonus::CREATURE_DAMAGE, Bonus::SPELL_EFFECT, damagePercent, owner->id, 0, Bonus::PERCENT_TO_ALL);
specialBonus.turnsRemain = duration;
sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus));
}
}
if(!sse.stacks.empty())
env->sendAndApply(&sse);
} }
} }
void DefaultSpellMechanics::defaultTimedEffect(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
{
SetStackEffect sse;
//get default spell duration (spell power with bonuses for heroes)
int duration = parameters.enchantPower;
//generate actual stack bonuses
{
si32 maxDuration = 0;
owner->getEffects(sse.effect, parameters.effectLevel, false, duration, &maxDuration);
owner->getEffects(sse.cumulativeEffects, parameters.effectLevel, true, duration, &maxDuration);
//if all spell effects have special duration, use it later for special bonuses
duration = maxDuration;
}
//fix to original config: shield should display damage reduction
if(owner->id == SpellID::SHIELD || owner->id == SpellID::AIR_SHIELD)
{
sse.effect.at(sse.effect.size() - 1).val = (100 - sse.effect.back().val);
}
//we need to know who cast Bind
else if(owner->id == SpellID::BIND && parameters.casterStack)
{
sse.effect.at(sse.effect.size() - 1).additionalInfo = parameters.casterStack->ID;
}
std::shared_ptr<Bonus> bonus = nullptr;
if(parameters.casterHero)
bonus = parameters.casterHero->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPECIAL_PECULIAR_ENCHANT, owner->id));
//TODO does hero specialty should affects his stack casting spells?
for(const CStack * affected : ctx.attackedCres)
{
si32 power = 0;
sse.stacks.push_back(affected->ID);
//Apply hero specials - peculiar enchants
const ui8 tier = std::max((ui8)1, affected->getCreature()->level); //don't divide by 0 for certain creatures (commanders, war machines)
if(bonus)
{
switch(bonus->additionalInfo)
{
case 0: //normal
{
switch(tier)
{
case 1:
case 2:
power = 3;
break;
case 3:
case 4:
power = 2;
break;
case 5:
case 6:
power = 1;
break;
}
for(const Bonus & b : sse.effect)
{
Bonus specialBonus(b);
specialBonus.val = power; //it doesn't necessarily make sense for some spells, use it wisely
specialBonus.turnsRemain = duration;
sse.uniqueBonuses.push_back(std::pair<ui32, Bonus>(affected->ID, specialBonus)); //additional premy to given effect
}
for(const Bonus & b : sse.cumulativeEffects)
{
Bonus specialBonus(b);
specialBonus.val = power; //it doesn't necessarily make sense for some spells, use it wisely
specialBonus.turnsRemain = duration;
sse.cumulativeUniqueBonuses.push_back(std::pair<ui32, Bonus>(affected->ID, specialBonus)); //additional premy to given effect
}
}
break;
case 1: //only Coronius as yet
{
power = std::max(5 - tier, 0);
Bonus specialBonus(Bonus::N_TURNS, Bonus::PRIMARY_SKILL, Bonus::SPELL_EFFECT, power, owner->id, PrimarySkill::ATTACK);
specialBonus.turnsRemain = duration;
sse.uniqueBonuses.push_back(std::pair<ui32,Bonus>(affected->ID, specialBonus)); //additional attack to Slayer effect
}
break;
}
}
if(parameters.casterHero && parameters.casterHero->hasBonusOfType(Bonus::SPECIAL_BLESS_DAMAGE, owner->id)) //TODO: better handling of bonus percentages
{
int damagePercent = parameters.casterHero->level * parameters.casterHero->valOfBonuses(Bonus::SPECIAL_BLESS_DAMAGE, owner->id.toEnum()) / tier;
Bonus specialBonus(Bonus::N_TURNS, Bonus::CREATURE_DAMAGE, Bonus::SPELL_EFFECT, damagePercent, owner->id, 0, Bonus::PERCENT_TO_ALL);
specialBonus.turnsRemain = duration;
sse.uniqueBonuses.push_back(std::pair<ui32,Bonus>(affected->ID, specialBonus));
}
}
if(!sse.stacks.empty())
env->sendAndApply(&sse);
}
std::vector<BattleHex> DefaultSpellMechanics::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes) const std::vector<BattleHex> DefaultSpellMechanics::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes) const
{ {
using namespace SRSLPraserHelpers; using namespace SRSLPraserHelpers;

View File

@ -75,6 +75,9 @@ protected:
protected: protected:
void doDispell(BattleInfo * battle, const BattleSpellCast * packet, const CSelector & selector) const; void doDispell(BattleInfo * battle, const BattleSpellCast * packet, const CSelector & selector) const;
bool canDispell(const IBonusBearer * obj, const CSelector & selector, const std::string &cachingStr = "") const; bool canDispell(const IBonusBearer * obj, const CSelector & selector, const std::string &cachingStr = "") const;
void defaultDamageEffect(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const;
void defaultTimedEffect(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const;
private: private:
void cast(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, std::vector <const CStack*> & reflected) const; void cast(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, std::vector <const CStack*> & reflected) const;

View File

@ -350,7 +350,7 @@ bool CSpell::isSpecialSpell() const
bool CSpell::hasEffects() const bool CSpell::hasEffects() const
{ {
return !levels[0].effects.empty(); return !levels[0].effects.empty() || !levels[0].cumulativeEffects.empty();
} }
const std::string & CSpell::getIconImmune() const const std::string & CSpell::getIconImmune() const
@ -382,7 +382,7 @@ si32 CSpell::getProbability(const TFaction factionId) const
return probabilities.at(factionId); return probabilities.at(factionId);
} }
void CSpell::getEffects(std::vector<Bonus> & lst, const int level) const void CSpell::getEffects(std::vector<Bonus> & lst, const int level, const bool cumulative, const si32 duration, boost::optional<si32 *> maxDuration/* = boost::none*/) const
{ {
if(level < 0 || level >= GameConstants::SPELL_SCHOOL_LEVELS) if(level < 0 || level >= GameConstants::SPELL_SCHOOL_LEVELS)
{ {
@ -390,19 +390,29 @@ void CSpell::getEffects(std::vector<Bonus> & lst, const int level) const
return; return;
} }
const std::vector<Bonus> & effects = levels[level].effects; const auto & levelObject = levels.at(level);
if(effects.empty()) if(levelObject.effects.empty() && levelObject.cumulativeEffects.empty())
{ {
logGlobal->errorStream() << __FUNCTION__ << " This spell (" + name + ") has no effects for level " << level; logGlobal->errorStream() << __FUNCTION__ << " This spell (" + name + ") has no effects for level " << level;
return; return;
} }
const auto & effects = cumulative ? levelObject.cumulativeEffects : levelObject.effects;
lst.reserve(lst.size() + effects.size()); lst.reserve(lst.size() + effects.size());
for(const Bonus & b : effects) for(const auto b : effects)
{ {
lst.push_back(Bonus(b)); Bonus nb(*b);
//use configured duration if present
if(nb.turnsRemain == 0)
nb.turnsRemain = duration;
if(maxDuration)
vstd::amax(*(maxDuration.get()), nb.turnsRemain);
lst.push_back(nb);
} }
} }
@ -1029,9 +1039,25 @@ CSpell * CSpellHandler::loadFromJson(const JsonNode & json, const std::string &
if(usePowerAsValue) if(usePowerAsValue)
b->val = levelPower; b->val = levelPower;
levelObject.effectsTmp.push_back(b); levelObject.effects.push_back(b);
} }
for(const auto & elem : levelNode["cumulativeEffects"].Struct())
{
const JsonNode & bonusNode = elem.second;
auto b = JsonUtils::parseBonus(bonusNode);
const bool usePowerAsValue = bonusNode["val"].isNull();
//TODO: make this work. see CSpellHandler::afterLoadFinalization()
//b->sid = spell->id; //for all
b->source = Bonus::SPELL_EFFECT;//for all
if(usePowerAsValue)
b->val = levelPower;
levelObject.cumulativeEffects.push_back(b);
}
} }
return spell; return spell;
@ -1044,14 +1070,10 @@ void CSpellHandler::afterLoadFinalization()
{ {
for(auto & level: spell->levels) for(auto & level: spell->levels)
{ {
for(auto bonus : level.effectsTmp)
{
level.effects.push_back(*bonus);
}
level.effectsTmp.clear();
for(auto & bonus: level.effects) for(auto & bonus: level.effects)
bonus.sid = spell->id; bonus->sid = spell->id;
for(auto & bonus: level.cumulativeEffects)
bonus->sid = spell->id;
} }
spell->setup(); spell->setup();
} }

View File

@ -130,16 +130,35 @@ public:
bool clearAffected; bool clearAffected;
std::string range; std::string range;
std::vector<Bonus> effects; std::vector<std::shared_ptr<Bonus>> effects;
std::vector<std::shared_ptr<Bonus>> cumulativeEffects;
std::vector<std::shared_ptr<Bonus>> effectsTmp; //TODO: this should replace effects
LevelInfo(); LevelInfo();
~LevelInfo(); ~LevelInfo();
template <typename Handler> void serialize(Handler &h, const int version) template <typename Handler> void serialize(Handler &h, const int version)
{ {
h & description & cost & power & AIValue & smartTarget & range & effects; h & description & cost & power & AIValue & smartTarget & range;
if(version >= 773)
{
h & effects & cumulativeEffects;
}
else
{
//all old effects treated as not cumulative, special cases handled by CSpell::serialize
std::vector<Bonus> old;
h & old;
if(!h.saving)
{
effects.clear();
cumulativeEffects.clear();
for(const Bonus & oldBonus : old)
effects.push_back(std::make_shared<Bonus>(oldBonus));
}
}
h & clearTarget & clearAffected; h & clearTarget & clearAffected;
} }
}; };
@ -180,7 +199,7 @@ public:
si32 level; si32 level;
std::map<ESpellSchool, bool> school; //todo: use this instead of separate boolean fields std::map<ESpellSchool, bool> school;
si32 power; //spell's power si32 power; //spell's power
@ -215,8 +234,7 @@ public:
bool isSpecialSpell() const; bool isSpecialSpell() const;
bool hasEffects() const; bool hasEffects() const;
void getEffects(std::vector<Bonus> &lst, const int level) const; void getEffects(std::vector<Bonus> & lst, const int level, const bool cumulative, const si32 duration, boost::optional<si32 *> maxDuration = boost::none) const;
///calculate spell damage on stack taking caster`s secondary skills and affectedCreature`s bonuses into account ///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; ui32 calculateDamage(const ISpellCaster * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower) const;
@ -264,6 +282,12 @@ public:
if(!h.saving) if(!h.saving)
setup(); setup();
//backward compatibility
//can not be added to level structure as level structure does not know spell id
if(!h.saving && version < 773)
if(id == SpellID::DISRUPTING_RAY || id == SpellID::ACID_BREATH_DEFENSE)
for(auto & level : levels)
std::swap(level.effects, level.cumulativeEffects);
} }
friend class CSpellHandler; friend class CSpellHandler;
friend class Graphics; friend class Graphics;

View File

@ -4489,30 +4489,32 @@ bool CGameHandler::makeCustomAction(BattleAction &ba)
} }
void CGameHandler::stackAppearTrigger(const CStack *st) void CGameHandler::stackEnchantedTrigger(const CStack * st)
{ {
auto bl = *(st->getBonuses(Selector::type(Bonus::ENCHANTED))); auto bl = *(st->getBonuses(Selector::type(Bonus::ENCHANTED)));
for (auto b : bl) for(auto b : bl)
{ {
SetStackEffect sse; SetStackEffect sse;
int val = bl.valOfBonuses(Selector::typeSubtype(b->type, b->subtype)); int val = bl.valOfBonuses(Selector::typeSubtype(b->type, b->subtype));
if (val > 3) if(val > 3)
{ {
for (auto s : gs->curB->battleGetAllStacks()) for(auto s : gs->curB->battleGetAllStacks())
{ {
if (battleMatchOwner(st, s, true) && s->isValidTarget()) //all allied if(battleMatchOwner(st, s, true) && s->isValidTarget()) //all allied
sse.stacks.push_back (s->ID); sse.stacks.push_back (s->ID);
} }
} }
else else
sse.stacks.push_back (st->ID); sse.stacks.push_back (st->ID);
Bonus pseudoBonus; const CSpell * sp = SpellID(b->subtype).toSpell();
pseudoBonus.sid = b->subtype; const int level = ((val > 3) ? (val - 3) : val);
pseudoBonus.val = ((val > 3) ? (val - 3) : val);
pseudoBonus.turnsRemain = 50; sp->getEffects(sse.effect, level, false, 50);
st->stackEffectToFeature(sse.effect, pseudoBonus); //this makes effect accumulate for at most 50 turns by default, but effect may be permanent and last till the end of battle
if (sse.effect.size()) sp->getEffects(sse.cumulativeEffects, level, true, 50);
if(!sse.effect.empty() || !sse.cumulativeEffects.empty())
sendAndApply(&sse); sendAndApply(&sse);
} }
} }
@ -4526,7 +4528,6 @@ void CGameHandler::stackTurnTrigger(const CStack *st)
bte.additionalInfo = 0; bte.additionalInfo = 0;
if (st->alive()) if (st->alive())
{ {
stackAppearTrigger(st);
//unbind //unbind
if (st->hasBonus(Selector::type(Bonus::BIND_EFFECT))) if (st->hasBonus(Selector::type(Bonus::BIND_EFFECT)))
{ {
@ -5724,7 +5725,7 @@ void CGameHandler::runBattle()
} }
} }
stackAppearTrigger(stack); stackEnchantedTrigger(stack);
} }
//spells opening battle //spells opening battle
@ -5766,6 +5767,12 @@ void CGameHandler::runBattle()
const BattleInfo & curB = *gs->curB; const BattleInfo & curB = *gs->curB;
for(auto stack : curB.stacks)
{
if(stack->alive() && curB.round > 1)
stackEnchantedTrigger(stack);
}
//stack loop //stack loop
const CStack *next; const CStack *next;

View File

@ -196,7 +196,7 @@ public:
bool makeBattleAction(BattleAction &ba); bool makeBattleAction(BattleAction &ba);
bool makeAutomaticAction(const CStack *stack, BattleAction &ba); //used when action is taken by stack without volition of player (eg. unguided catapult attack) bool makeAutomaticAction(const CStack *stack, BattleAction &ba); //used when action is taken by stack without volition of player (eg. unguided catapult attack)
bool makeCustomAction(BattleAction &ba); bool makeCustomAction(BattleAction &ba);
void stackAppearTrigger(const CStack *stack); void stackEnchantedTrigger(const CStack * stack);
void stackTurnTrigger(const CStack *stack); void stackTurnTrigger(const CStack *stack);
void handleDamageFromObstacle(const CObstacleInstance &obstacle, const CStack * curStack); //checks if obstacle is land mine and handles possible consequences void handleDamageFromObstacle(const CObstacleInstance &obstacle, const CStack * curStack); //checks if obstacle is land mine and handles possible consequences
void removeObstacle(const CObstacleInstance &obstacle); void removeObstacle(const CObstacleInstance &obstacle);