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:
parent
0c7eeeaa5c
commit
0f5202689e
@ -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);
|
||||||
|
@ -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:
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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",
|
||||||
|
@ -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",
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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) )
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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";
|
||||||
|
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user