From a06dae1f96a822768a37f932fea9ddad54f1d265 Mon Sep 17 00:00:00 2001 From: AlexVinS Date: Thu, 13 Nov 2014 04:53:25 +0300 Subject: [PATCH] Move getAffectedCreatures to CSpell. + more drafts --- AI/BattleAI/BattleAI.cpp | 8 +-- lib/CBattleCallback.cpp | 121 ++------------------------------ lib/CBattleCallback.h | 1 - lib/CSpellHandler.cpp | 147 +++++++++++++++++++++++++++++++++++++-- lib/CSpellHandler.h | 82 +++++++++++++++------- lib/GameConstants.h | 8 ++- lib/SpellMechanics.cpp | 46 +++++++++--- lib/SpellMechanics.h | 30 +++++--- server/CGameHandler.cpp | 33 ++------- 9 files changed, 275 insertions(+), 201 deletions(-) diff --git a/AI/BattleAI/BattleAI.cpp b/AI/BattleAI/BattleAI.cpp index 6ead457d5..51d1fdeb2 100644 --- a/AI/BattleAI/BattleAI.cpp +++ b/AI/BattleAI/BattleAI.cpp @@ -457,12 +457,8 @@ void CBattleAI::attemptCastingSpell() case OFFENSIVE_SPELL: { int damageDealt = 0, damageReceived = 0; - - auto stacksSuffering = cb->getAffectedCreatures(ps.spell, skillLevel, playerID, ps.dest); - vstd::erase_if(stacksSuffering, [&](const CStack * s) -> bool - { - return ESpellCastProblem::OK != ps.spell->isImmuneByStack(hero, ECastingMode::HERO_CASTING, s); - }); + + auto stacksSuffering = ps.spell->getAffectedStacks(cb.get(), ECastingMode::HERO_CASTING, playerID, skillLevel, ps.dest, hero); if(stacksSuffering.empty()) return -1; diff --git a/lib/CBattleCallback.cpp b/lib/CBattleCallback.cpp index d2ea2aafa..ce086bedc 100644 --- a/lib/CBattleCallback.cpp +++ b/lib/CBattleCallback.cpp @@ -1582,7 +1582,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleIsImmune(const C for(auto s : stacks) { - ESpellCastProblem::ESpellCastProblem res = spell->isImmuneByStack(caster,mode,s); + ESpellCastProblem::ESpellCastProblem res = spell->isImmuneByStack(caster,s); if(res == ESpellCastProblem::OK) { @@ -1661,7 +1661,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell auto stacks = spell->isNegative() ? battleAliveStacks(!side) : battleAliveStacks(); for(auto stack : stacks) { - if(ESpellCastProblem::OK == spell->isImmuneByStack(castingHero, mode, stack)) + if(ESpellCastProblem::OK == spell->isImmuneByStack(castingHero, stack)) { allStacksImmune = false; break; @@ -1705,7 +1705,7 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell for(const CStack * stack : battleGetAllStacks()) //dead stacks will be immune anyway { - bool immune = ESpellCastProblem::OK != spell->isImmuneByStack(caster, mode, stack); + bool immune = ESpellCastProblem::OK != spell->isImmuneByStack(caster, stack); bool casterStack = stack->owner == caster->getOwner(); if(spell->id == SpellID::SACRIFICE) @@ -1763,8 +1763,6 @@ std::vector CBattleInfoCallback::battleGetPossibleTargets(PlayerColor std::vector ret; RETURN_IF_NOT_BATTLE(ret); - auto mode = ECastingMode::HERO_CASTING; //TODO get rid of this! - switch(spell->getTargetType()) { case CSpell::CREATURE: @@ -1774,7 +1772,7 @@ std::vector CBattleInfoCallback::battleGetPossibleTargets(PlayerColor for(const CStack * stack : battleAliveStacks()) { - bool immune = ESpellCastProblem::OK != spell->isImmuneByStack(caster, mode, stack); + bool immune = ESpellCastProblem::OK != spell->isImmuneByStack(caster, stack); bool casterStack = stack->owner == caster->getOwner(); if(!immune) @@ -1908,117 +1906,6 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell return battleIsImmune(nullptr, spell, mode, dest); } -std::set CBattleInfoCallback::getAffectedCreatures(const CSpell * spell, int skillLevel, PlayerColor attackerOwner, BattleHex destinationTile) -{ - std::set attackedCres; //std::set to exclude multiple occurrences of two hex creatures - - const ui8 attackerSide = playerToSide(attackerOwner) == 1; - const auto attackedHexes = spell->rangeInHexes(destinationTile, skillLevel, attackerSide); - - const CSpell::TargetInfo ti = spell->getTargetInfo(skillLevel); - //TODO: more generic solution for mass spells - if (spell->id == SpellID::CHAIN_LIGHTNING) - { - std::set possibleHexes; - for (auto stack : battleGetAllStacks()) - { - if (stack->isValidTarget()) - { - for (auto hex : stack->getHexes()) - { - possibleHexes.insert (hex); - } - } - } - int targetsOnLevel[4] = {4, 4, 5, 5}; - - BattleHex lightningHex = destinationTile; - for (int i = 0; i < targetsOnLevel[skillLevel]; ++i) - { - auto stack = battleGetStackByPos (lightningHex, true); - if (!stack) - break; - attackedCres.insert (stack); - for (auto hex : stack->getHexes()) - { - possibleHexes.erase (hex); //can't hit same place twice - } - if (possibleHexes.empty()) //not enough targets - break; - lightningHex = BattleHex::getClosestTile (stack->attackerOwned, destinationTile, possibleHexes); - } - } - else if (spell->getLevelInfo(skillLevel).range.size() > 1) //custom many-hex range - { - for(BattleHex hex : attackedHexes) - { - if(const CStack * st = battleGetStackByPos(hex, ti.onlyAlive)) - { - if (spell->id == SpellID::DEATH_CLOUD) //Death Cloud //TODO: fireball and fire immunity - { - if (st->isLiving() || st->coversPos(destinationTile)) //directly hit or alive - { - attackedCres.insert(st); - } - } - else - attackedCres.insert(st); - } - } - } - else if(spell->getTargetType() == CSpell::CREATURE) - { - auto predicate = [=](const CStack * s){ - const bool positiveToAlly = spell->isPositive() && s->owner == attackerOwner; - const bool negativeToEnemy = spell->isNegative() && s->owner != attackerOwner; - const bool validTarget = s->isValidTarget(!ti.onlyAlive); //todo: this should be handled by spell class - - //for single target spells select stacks covering destination tile - const bool rangeCovers = ti.massive || s->coversPos(destinationTile); - //handle smart targeting - const bool positivenessFlag = !ti.smart || spell->isNeutral() || positiveToAlly || negativeToEnemy; - - return rangeCovers && positivenessFlag && validTarget; - }; - - TStacks stacks = battleGetStacksIf(predicate); - - if (ti.massive) - { - //for massive spells add all targets - for (auto stack : stacks) - attackedCres.insert(stack); - - } - else - { - //for single target spells we must select one target. Alive stack is preferred (issue #1763) - for(auto stack : stacks) - { - if(stack->alive()) - { - attackedCres.insert(stack); - break; - } - } - - if(attackedCres.empty() && !stacks.empty()) - { - attackedCres.insert(stacks.front()); - } - } - } - else //custom range from attackedHexes - { - for(BattleHex hex : attackedHexes) - { - if(const CStack * st = battleGetStackByPos(hex, ti.onlyAlive)) - attackedCres.insert(st); - } - } - return attackedCres; -} - const CStack * CBattleInfoCallback::getStackIf(std::function pred) const { RETURN_IF_NOT_BATTLE(nullptr); diff --git a/lib/CBattleCallback.h b/lib/CBattleCallback.h index a1fae5373..a5ea58451 100644 --- a/lib/CBattleCallback.h +++ b/lib/CBattleCallback.h @@ -283,7 +283,6 @@ public: std::vector battleGetPossibleTargets(PlayerColor player, const CSpell *spell) const; ui32 calculateHealedHP(int healedHealth, const CSpell * spell, const CStack * stack) const; //for Archangel ui32 calculateHealedHP(const CSpell * spell, int usedSpellPower, int spellSchoolLevel, const CStack * stack) const; //healing spells casted by stacks - std::set getAffectedCreatures(const CSpell * s, int skillLevel, PlayerColor attackerOwner, BattleHex destinationTile); //calculates stack affected by given spell SpellID battleGetRandomStackSpell(const CStack * stack, ERandomSpell mode) const; SpellID getRandomBeneficialSpell(const CStack * subject) const; diff --git a/lib/CSpellHandler.cpp b/lib/CSpellHandler.cpp index 7bf617114..2f45f909d 100644 --- a/lib/CSpellHandler.cpp +++ b/lib/CSpellHandler.cpp @@ -12,6 +12,7 @@ #include "mapObjects/CGHeroInstance.h" #include "BattleState.h" +#include "CBattleCallback.h" #include "SpellMechanics.h" @@ -380,12 +381,130 @@ std::vector CSpell::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, return ret; } +std::set CSpell::getAffectedStacks(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, PlayerColor casterColor, int spellLvl, BattleHex destination, const CGHeroInstance * caster) const +{ + std::set attackedCres;//std::set to exclude multiple occurrences of two hex creatures + + const ui8 attackerSide = cb->playerToSide(casterColor) == 1; + const auto attackedHexes = rangeInHexes(destination, spellLvl, attackerSide); + + const CSpell::TargetInfo ti = getTargetInfoEx(spellLvl, mode); + + + //TODO: more generic solution for mass spells + if (id == SpellID::CHAIN_LIGHTNING) + { + std::set possibleHexes; + for (auto stack : cb->battleGetAllStacks()) + { + if (stack->isValidTarget()) + { + for (auto hex : stack->getHexes()) + { + possibleHexes.insert (hex); + } + } + } + int targetsOnLevel[4] = {4, 4, 5, 5}; + + BattleHex lightningHex = destination; + for (int i = 0; i < targetsOnLevel[spellLvl]; ++i) + { + auto stack = cb->battleGetStackByPos (lightningHex, true); + if (!stack) + break; + attackedCres.insert (stack); + for (auto hex : stack->getHexes()) + { + possibleHexes.erase (hex); //can't hit same place twice + } + if (possibleHexes.empty()) //not enough targets + break; + lightningHex = BattleHex::getClosestTile (stack->attackerOwned, destination, possibleHexes); + } + } + else if (getLevelInfo(spellLvl).range.size() > 1) //custom many-hex range + { + for(BattleHex hex : attackedHexes) + { + if(const CStack * st = cb->battleGetStackByPos(hex, ti.onlyAlive)) + { + attackedCres.insert(st); + } + } + } + else if(getTargetType() == CSpell::CREATURE) + { + auto predicate = [=](const CStack * s){ + const bool positiveToAlly = isPositive() && s->owner == casterColor; + const bool negativeToEnemy = isNegative() && s->owner != casterColor; + const bool validTarget = s->isValidTarget(!ti.onlyAlive); //todo: this should be handled by spell class + + //for single target spells select stacks covering destination tile + const bool rangeCovers = ti.massive || s->coversPos(destination); + //handle smart targeting + const bool positivenessFlag = !ti.smart || isNeutral() || positiveToAlly || negativeToEnemy; + + return rangeCovers && positivenessFlag && validTarget; + }; + + TStacks stacks = cb->battleGetStacksIf(predicate); + + if (ti.massive) + { + //for massive spells add all targets + for (auto stack : stacks) + attackedCres.insert(stack); + + } + else + { + //for single target spells we must select one target. Alive stack is preferred (issue #1763) + for(auto stack : stacks) + { + if(stack->alive()) + { + attackedCres.insert(stack); + break; + } + } + + if(attackedCres.empty() && !stacks.empty()) + { + attackedCres.insert(stacks.front()); + } + } + } + else //custom range from attackedHexes + { + for(BattleHex hex : attackedHexes) + { + if(const CStack * st = cb->battleGetStackByPos(hex, ti.onlyAlive)) + attackedCres.insert(st); + } + } + + //now handle immunities + auto predicate = [&, this](const CStack * s)->bool + { + bool hitDirectly = ti.alwaysHitDirectly && s->coversPos(destination); + bool notImmune = (ESpellCastProblem::OK == isImmuneByStack(caster, s)); + + return !(hitDirectly || notImmune); + }; + + vstd::erase_if(attackedCres, predicate); + + return attackedCres; +} + + CSpell::ETargetType CSpell::getTargetType() const { return targetType; } -const CSpell::TargetInfo CSpell::getTargetInfo(const int level) const +CSpell::TargetInfo CSpell::getTargetInfo(const int level) const { TargetInfo info; @@ -395,10 +514,28 @@ const CSpell::TargetInfo CSpell::getTargetInfo(const int level) const info.smart = levelInfo.smartTarget; info.massive = levelInfo.range == "X"; info.onlyAlive = !isRisingSpell(); + info.alwaysHitDirectly = false; return info; } +CSpell::TargetInfo CSpell::getTargetInfoEx(const int level, ECastingMode::ECastingMode mode) const +{ + TargetInfo info = getTargetInfo(level); + + if(mode == ECastingMode::ENCHANTER_CASTING) + { + info.smart = true; //FIXME: not sure about that, this makes all spells smart in this mode + info.massive = true; + } + else if(mode == ECastingMode::SPELL_LIKE_ATTACK) + { + info.alwaysHitDirectly = true; + } + + return info; +} + bool CSpell::isCombatSpell() const { @@ -595,9 +732,9 @@ ESpellCastProblem::ESpellCastProblem CSpell::isImmuneBy(const IBonusBearer* obj) return ESpellCastProblem::NOT_DECIDED; } -ESpellCastProblem::ESpellCastProblem CSpell::isImmuneByStack(const CGHeroInstance* caster, ECastingMode::ECastingMode mode, const CStack* obj) const +ESpellCastProblem::ESpellCastProblem CSpell::isImmuneByStack(const CGHeroInstance* caster, const CStack* obj) const { - const auto immuneResult = mechanics->isImmuneByStack(caster,mode,obj); + const auto immuneResult = mechanics->isImmuneByStack(caster,obj); if (ESpellCastProblem::NOT_DECIDED != immuneResult) return immuneResult; @@ -649,7 +786,7 @@ void CSpell::setupMechanics() switch (id) { case SpellID::CLONE: - mechanics = new CloneMechnics(this); + mechanics = new CloneMechanics(this); break; case SpellID::DISPEL_HELPFUL_SPELLS: mechanics = new DispellHelpfulMechanics(this); @@ -660,6 +797,8 @@ void CSpell::setupMechanics() default: if(isRisingSpell()) mechanics = new SpecialRisingSpellMechanics(this); + else if(isOffensiveSpell()) + mechanics = new OffenciveSpellMechnics(this); else mechanics = new DefaultSpellMechanics(this); break; diff --git a/lib/CSpellHandler.h b/lib/CSpellHandler.h index eb5d3e930..55b5d9f70 100644 --- a/lib/CSpellHandler.h +++ b/lib/CSpellHandler.h @@ -4,6 +4,7 @@ #include "../lib/ConstTransitivePtr.h" #include "int3.h" #include "GameConstants.h" +#include "BattleHex.h" #include "HeroBonus.h" @@ -17,12 +18,16 @@ * */ -class CLegacyConfigParser; -struct BattleHex; class CSpell; +class ISpellMechanics; + +class CLegacyConfigParser; + class CGHeroInstance; class CStack; +class CBattleInfoCallback; + struct CPackForClient; struct SpellSchoolInfo @@ -73,28 +78,19 @@ struct DLL_LINKAGE SpellCastContext { public: SpellCastEnvironment * env; + + int spellLvl; +// BattleHex destination; + ui8 casterSide; + PlayerColor casterColor; + CGHeroInstance * caster; + CGHeroInstance * secHero; + int usedSpellPower; + ECastingMode::ECastingMode mode; + CStack * targetStack; + CStack * selectedStack; }; -class DLL_LINKAGE ISpellMechanics -{ -public: - ISpellMechanics(CSpell * s); - virtual ~ISpellMechanics(){}; - - virtual ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) = 0; - - /** \brief - * - * \param - * \return true if no error - * - */ - virtual bool adventureCast(SpellCastContext & context) = 0; - virtual bool battleCast(SpellCastContext & context) = 0; - -protected: - CSpell * owner; -}; class DLL_LINKAGE CSpell { @@ -137,6 +133,8 @@ public: bool smart; bool massive; bool onlyAlive; + ///no immunity on primary target (mostly spell-like attack) + bool alwaysHitDirectly; }; SpellID id; @@ -168,7 +166,8 @@ public: si16 mainEffectAnim; //main spell effect animation, in AC format (or -1 when none) ETargetType getTargetType() const; //deprecated - const CSpell::TargetInfo getTargetInfo(const int level) const; + CSpell::TargetInfo getTargetInfo(const int level) const; + CSpell::TargetInfo getTargetInfoEx(const int level, ECastingMode::ECastingMode mode) const; bool isCombatSpell() const; bool isAdventureSpell() const; @@ -192,7 +191,7 @@ public: ESpellCastProblem::ESpellCastProblem isImmuneBy(const IBonusBearer *obj) const; //checks for creature immunity / anything that prevent casting *at given hex* - doesn't take into acount general problems such as not having spellbook or mana points etc. - ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) const; + ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const; //internal, for use only by Mechanics classes. applying secondary skills ui32 calculateBonus(ui32 baseDamage, const CGHeroInstance * caster, const CStack * affectedCreature) const; @@ -202,7 +201,8 @@ public: ///calculate healed HP for all spells casted by hero ui32 calculateHealedHP(const CGHeroInstance * caster, const CStack * stack, const CStack * sacrificedStack = nullptr) const; - + ///selects from allStacks actually affected stacks + std::set getAffectedStacks(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, PlayerColor casterColor, int spellLvl, BattleHex destination, const CGHeroInstance * caster = nullptr) const; si32 getCost(const int skillLevel) const; @@ -289,6 +289,38 @@ private: ISpellMechanics * mechanics;//(!) do not serialize }; +class DLL_LINKAGE ISpellMechanics +{ +public: + + struct SpellTargetingContext + { + CBattleInfoCallback * cb; + + CSpell::TargetInfo ti; + }; + +public: + ISpellMechanics(CSpell * s); + virtual ~ISpellMechanics(){}; + + virtual std::set getAffectedStacks(SpellTargetingContext & ctx) const = 0; + + virtual ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const = 0; + + + /** \brief + * + * \param + * \return true if no error + * + */ + virtual bool adventureCast(SpellCastContext & context) const = 0; + virtual bool battleCast(SpellCastContext & context) const = 0; + +protected: + CSpell * owner; +}; bool DLL_LINKAGE isInScreenRange(const int3 ¢er, const int3 &pos); //for spells like Dimension Door diff --git a/lib/GameConstants.h b/lib/GameConstants.h index 3cc798a47..13262cb5a 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -402,8 +402,12 @@ namespace ESpellCastProblem namespace ECastingMode { - enum ECastingMode {HERO_CASTING, AFTER_ATTACK_CASTING, //also includes cast before attack - MAGIC_MIRROR, CREATURE_ACTIVE_CASTING, ENCHANTER_CASTING}; + enum ECastingMode + { + HERO_CASTING, AFTER_ATTACK_CASTING, //also includes cast before attack + MAGIC_MIRROR, CREATURE_ACTIVE_CASTING, ENCHANTER_CASTING, + SPELL_LIKE_ATTACK + }; } namespace EMarketMode diff --git a/lib/SpellMechanics.cpp b/lib/SpellMechanics.cpp index 8b84734a0..9191f9c44 100644 --- a/lib/SpellMechanics.cpp +++ b/lib/SpellMechanics.cpp @@ -14,15 +14,43 @@ #include "mapObjects/CGHeroInstance.h" #include "BattleState.h" +#include "NetPacks.h" + ///DefaultSpellMechanics -ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) + +std::set DefaultSpellMechanics::getAffectedStacks(SpellTargetingContext & ctx) const +{ + +} + + +ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const { //by default use general algorithm return owner->isImmuneBy(obj); } +bool DefaultSpellMechanics::adventureCast(SpellCastContext& context) const +{ + return false; //there is no general algorithm for castind adventure spells +} + +bool DefaultSpellMechanics::battleCast(SpellCastContext& context) const +{ + return false; //todo; DefaultSpellMechanics::battleCast +} + +///OffenciveSpellMechnics +bool OffenciveSpellMechnics::battleCast(SpellCastContext& context) const +{ + assert(owner->isOffensiveSpell()); + + //todo:OffenciveSpellMechnics::battleCast +} + + ///CloneMechanics -ESpellCastProblem::ESpellCastProblem CloneMechnics::isImmuneByStack(const CGHeroInstance* caster, ECastingMode::ECastingMode mode, const CStack * obj) +ESpellCastProblem::ESpellCastProblem CloneMechanics::isImmuneByStack(const CGHeroInstance* caster, const CStack * obj) const { //can't clone already cloned creature if (vstd::contains(obj->state, EBattleStackState::CLONED)) @@ -47,11 +75,11 @@ ESpellCastProblem::ESpellCastProblem CloneMechnics::isImmuneByStack(const CGHero return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; } //use default algorithm only if there is no mechanics-related problem - return DefaultSpellMechanics::isImmuneByStack(caster,mode,obj); + return DefaultSpellMechanics::isImmuneByStack(caster,obj); } ///DispellHelpfulMechanics -ESpellCastProblem::ESpellCastProblem DispellHelpfulMechanics::isImmuneByStack(const CGHeroInstance* caster, ECastingMode::ECastingMode mode, const CStack* obj) +ESpellCastProblem::ESpellCastProblem DispellHelpfulMechanics::isImmuneByStack(const CGHeroInstance* caster, const CStack* obj) const { TBonusListPtr spellBon = obj->getSpellBonuses(); bool hasPositiveSpell = false; @@ -69,11 +97,11 @@ ESpellCastProblem::ESpellCastProblem DispellHelpfulMechanics::isImmuneByStack(co } //use default algorithm only if there is no mechanics-related problem - return DefaultSpellMechanics::isImmuneByStack(caster,mode,obj); + return DefaultSpellMechanics::isImmuneByStack(caster,obj); } ///HypnotizeMechanics -ESpellCastProblem::ESpellCastProblem HypnotizeMechanics::isImmuneByStack(const CGHeroInstance* caster, ECastingMode::ECastingMode mode, const CStack* obj) +ESpellCastProblem::ESpellCastProblem HypnotizeMechanics::isImmuneByStack(const CGHeroInstance* caster, const CStack* obj) const { if(nullptr != caster) //do not resist hypnotize casted after attack, for example { @@ -85,12 +113,12 @@ ESpellCastProblem::ESpellCastProblem HypnotizeMechanics::isImmuneByStack(const C if (subjectHealth > maxHealth) return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; } - return DefaultSpellMechanics::isImmuneByStack(caster,mode,obj); + return DefaultSpellMechanics::isImmuneByStack(caster,obj); } ///SpecialRisingSpellMechanics -ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::isImmuneByStack(const CGHeroInstance* caster, ECastingMode::ECastingMode mode, const CStack* obj) +ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::isImmuneByStack(const CGHeroInstance* caster, const CStack* obj) const { // following does apply to resurrect and animate dead(?) only // for sacrifice health calculation and health limit check don't matter @@ -105,7 +133,7 @@ ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::isImmuneByStac return ESpellCastProblem::STACK_IMMUNE_TO_SPELL; } - return DefaultSpellMechanics::isImmuneByStack(caster,mode,obj); + return DefaultSpellMechanics::isImmuneByStack(caster,obj); } diff --git a/lib/SpellMechanics.h b/lib/SpellMechanics.h index a5a136911..39344f12c 100644 --- a/lib/SpellMechanics.h +++ b/lib/SpellMechanics.h @@ -16,32 +16,42 @@ class DefaultSpellMechanics: public ISpellMechanics { public: DefaultSpellMechanics(CSpell * s): ISpellMechanics(s){}; - ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) override; - bool adventureCast(SpellCastContext & context) override; - bool battleCast(SpellCastContext & context) override; + std::set getAffectedStacks(SpellTargetingContext & ctx) const override; + + ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override; + + bool adventureCast(SpellCastContext & context) const override; + bool battleCast(SpellCastContext & context) const override; }; -class CloneMechnics: public DefaultSpellMechanics +class OffenciveSpellMechnics: public DefaultSpellMechanics { public: - CloneMechnics(CSpell * s): DefaultSpellMechanics(s){}; - ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) override; + OffenciveSpellMechnics(CSpell * s): DefaultSpellMechanics(s){}; + bool battleCast(SpellCastContext & context) const override; +}; + +class CloneMechanics: public DefaultSpellMechanics +{ +public: + CloneMechanics(CSpell * s): DefaultSpellMechanics(s){}; + ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override; }; class DispellHelpfulMechanics: public DefaultSpellMechanics { public: DispellHelpfulMechanics(CSpell * s): DefaultSpellMechanics(s){}; - ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) override; + ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override; }; class HypnotizeMechanics: public DefaultSpellMechanics { public: HypnotizeMechanics(CSpell * s): DefaultSpellMechanics(s){}; - ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) override; -}; + ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override; +}; ///all rising spells class RisingSpellMechanics: public DefaultSpellMechanics @@ -56,7 +66,7 @@ class SpecialRisingSpellMechanics: public RisingSpellMechanics { public: SpecialRisingSpellMechanics(CSpell * s): RisingSpellMechanics(s){}; - ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, ECastingMode::ECastingMode mode, const CStack * obj) override; + ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override; }; class SacrificeMechanics: public RisingSpellMechanics diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 9b30afa43..692c99e4d 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -787,7 +787,8 @@ void CGameHandler::prepareAttack(BattleAttack &bat, const CStack *att, const CSt bat.bsa.front().flags |= BattleStackAttacked::EFFECT; bat.bsa.front().effect = VLC->spellh->objects.at(bonus->subtype)->mainEffectAnim; //hopefully it does not interfere with any other effect? - std::set attackedCreatures = gs->curB->getAffectedCreatures(SpellID(bonus->subtype).toSpell(), bonus->val, att->owner, targetHex); + std::set attackedCreatures = SpellID(bonus->subtype).toSpell()->getAffectedStacks(gs->curB, ECastingMode::SPELL_LIKE_ATTACK, att->owner, bonus->val, targetHex); + //TODO: get exact attacked hex for defender for(const CStack * stack : attackedCreatures) @@ -4018,31 +4019,9 @@ void CGameHandler::handleSpellCasting( SpellID spellID, int spellLvl, BattleHex //must be vector, as in Chain Lightning order matters std::vector attackedCres; //CStack vector is somewhat more suitable than ID vector - if (mode != ECastingMode::ENCHANTER_CASTING) - { - auto creatures = gs->curB->getAffectedCreatures(spell, spellLvl, casterColor, destination); - std::copy(creatures.begin(), creatures.end(), std::back_inserter(attackedCres)); - } - else //enchanter - hit all possible stacks - { - for (const CStack * stack : gs->curB->stacks) - { - /*if it's non negative spell and our unit or non positive spell and hostile unit */ - if((!spell->isNegative() && stack->owner == casterColor) - || (!spell->isPositive() && stack->owner != casterColor)) - { - if(stack->isValidTarget()) //TODO: allow dead targets somewhere in the future - { - attackedCres.push_back(stack); - } - } - } - } - - vstd::erase_if(attackedCres,[=](const CStack * s){ - return ESpellCastProblem::OK != spell->isImmuneByStack(caster,mode,s); - }); - + auto creatures = spell->getAffectedStacks(gs->curB, mode, casterColor, spellLvl, destination, caster); + std::copy(creatures.begin(), creatures.end(), std::back_inserter(attackedCres)); + for (auto cre : attackedCres) { sc.affectedCres.insert (cre->ID); @@ -4453,7 +4432,7 @@ void CGameHandler::handleSpellCasting( SpellID spellID, int spellLvl, BattleHex { if(battleStack->owner == gs->curB->sides.at(casterSide).color) //get enemy stacks which can be affected by this spell { - if (ESpellCastProblem::OK == spell->isImmuneByStack(nullptr, ECastingMode::MAGIC_MIRROR, battleStack)) + if (ESpellCastProblem::OK == spell->isImmuneByStack(nullptr, battleStack)) mirrorTargets.push_back(battleStack); } }