diff --git a/config/schemas/spell.json b/config/schemas/spell.json index dff3bc2df..d4b00487b 100644 --- a/config/schemas/spell.json +++ b/config/schemas/spell.json @@ -171,6 +171,10 @@ "type" : "boolean", "description" : "If used as creature spell, unit can cast this spell on itself" }, + "canCastWithoutSkip" : { + "type" : "boolean", + "description" : "If used the creature will not skip the turn after casting a spell." + }, "gainChance" : { "type" : "object", "description" : "Chance for this spell to appear in Mage Guild of a specific faction", diff --git a/docs/modders/Entities_Format/Spell_Format.md b/docs/modders/Entities_Format/Spell_Format.md index 5e17e817e..41461b606 100644 --- a/docs/modders/Entities_Format/Spell_Format.md +++ b/docs/modders/Entities_Format/Spell_Format.md @@ -64,6 +64,9 @@ // If true, then creature capable of casting this spell can cast this spell on itself // If false, then creature can only cast this spell on other units "canCastOnSelf" : false, + + // If true the creature will not skip the turn after casting a spell + "canCastWithoutSkip": false, // If true, spell won't be available on a map without water "onlyOnWaterMap" : true, diff --git a/include/vcmi/spells/Spell.h b/include/vcmi/spells/Spell.h index 1822639e1..6b40bf258 100644 --- a/include/vcmi/spells/Spell.h +++ b/include/vcmi/spells/Spell.h @@ -45,6 +45,7 @@ public: virtual bool hasSchool(SpellSchool school) const = 0; virtual bool canCastOnSelf() const = 0; + virtual bool canCastWithoutSkip() const = 0; virtual void forEachSchool(const SchoolCallback & cb) const = 0; virtual int32_t getCost(const int32_t skillLevel) const = 0; diff --git a/lib/battle/CUnitState.cpp b/lib/battle/CUnitState.cpp index de2704058..131fc2a23 100644 --- a/lib/battle/CUnitState.cpp +++ b/lib/battle/CUnitState.cpp @@ -330,6 +330,7 @@ CUnitState::CUnitState(): drainedMana(false), fear(false), hadMorale(false), + castSpellThisTurn(false), ghost(false), ghostPending(false), movedThisRound(false), @@ -362,6 +363,7 @@ CUnitState & CUnitState::operator=(const CUnitState & other) drainedMana = other.drainedMana; fear = other.fear; hadMorale = other.hadMorale; + castSpellThisTurn = other.castSpellThisTurn; ghost = other.ghost; ghostPending = other.ghostPending; movedThisRound = other.movedThisRound; @@ -532,7 +534,7 @@ bool CUnitState::hasClone() const bool CUnitState::canCast() const { - return casts.canUse(1);//do not check specific cast abilities here + return casts.canUse(1) && !castSpellThisTurn;//do not check specific cast abilities here } bool CUnitState::isCaster() const @@ -748,6 +750,7 @@ void CUnitState::serializeJson(JsonSerializeFormat & handler) handler.serializeBool("drainedMana", drainedMana); handler.serializeBool("fear", fear); handler.serializeBool("hadMorale", hadMorale); + handler.serializeBool("castSpellThisTurn", castSpellThisTurn); handler.serializeBool("ghost", ghost); handler.serializeBool("ghostPending", ghostPending); handler.serializeBool("moved", movedThisRound); @@ -782,6 +785,7 @@ void CUnitState::reset() drainedMana = false; fear = false; hadMorale = false; + castSpellThisTurn = false; ghost = false; ghostPending = false; movedThisRound = false; @@ -864,6 +868,7 @@ void CUnitState::afterNewRound() waitedThisTurn = false; movedThisRound = false; hadMorale = false; + castSpellThisTurn = false; fear = false; drainedMana = false; counterAttacks.reset(); diff --git a/lib/battle/CUnitState.h b/lib/battle/CUnitState.h index 02422e2e1..b5451ba89 100644 --- a/lib/battle/CUnitState.h +++ b/lib/battle/CUnitState.h @@ -141,6 +141,7 @@ public: bool drainedMana; bool fear; bool hadMorale; + bool castSpellThisTurn; bool ghost; bool ghostPending; bool movedThisRound; diff --git a/lib/networkPacks/NetPacksLib.cpp b/lib/networkPacks/NetPacksLib.cpp index bccbbef61..43309a8c5 100644 --- a/lib/networkPacks/NetPacksLib.cpp +++ b/lib/networkPacks/NetPacksLib.cpp @@ -2209,6 +2209,7 @@ void StartAction::applyGs(CGameState *gs) st->waiting = false; st->defendingAnim = false; st->movedThisRound = true; + st->castSpellThisTurn = ba.actionType == EActionType::MONSTER_SPELL; break; } } diff --git a/lib/spells/CSpellHandler.cpp b/lib/spells/CSpellHandler.cpp index 05d639f62..4e3db1a5c 100644 --- a/lib/spells/CSpellHandler.cpp +++ b/lib/spells/CSpellHandler.cpp @@ -298,6 +298,11 @@ bool CSpell::canCastOnSelf() const return castOnSelf; } +bool CSpell::canCastWithoutSkip() const +{ + return castWithoutSkip; +} + const std::string & CSpell::getIconImmune() const { return iconImmune; @@ -779,6 +784,7 @@ std::shared_ptr CSpellHandler::loadFromJson(const std::string & scope, c } spell->castOnSelf = json["canCastOnSelf"].Bool(); + spell->castWithoutSkip = json["canCastWithoutSkip"].Bool(); spell->level = static_cast(json["level"].Integer()); spell->power = static_cast(json["power"].Integer()); diff --git a/lib/spells/CSpellHandler.h b/lib/spells/CSpellHandler.h index 32b91f3fb..c16e10ceb 100644 --- a/lib/spells/CSpellHandler.h +++ b/lib/spells/CSpellHandler.h @@ -167,6 +167,7 @@ public: bool hasSchool(SpellSchool school) const override; bool canCastOnSelf() const override; + bool canCastWithoutSkip() const override; /** * Calls cb for each school this spell belongs to @@ -296,6 +297,7 @@ private: bool combat; //is this spell combat (true) or adventure (false) bool creatureAbility; //if true, only creatures can use this spell bool castOnSelf; // if set, creature caster can cast this spell on itself + bool castWithoutSkip; // if set the creature will not skip the turn after casting a spell si8 positiveness; //1 if spell is positive for influenced stacks, 0 if it is indifferent, -1 if it's negative std::unique_ptr mechanics;//(!) do not serialize diff --git a/server/battles/BattleFlowProcessor.cpp b/server/battles/BattleFlowProcessor.cpp index c8beebb63..ce70d21fd 100644 --- a/server/battles/BattleFlowProcessor.cpp +++ b/server/battles/BattleFlowProcessor.cpp @@ -565,6 +565,19 @@ void BattleFlowProcessor::onActionMade(const CBattleInfoCallback & battle, const if(battle.battleGetTacticDist() != 0) return; + // creature will not skip the turn after casting a spell if spell uses canCastWithoutSkip + if(ba.actionType == EActionType::MONSTER_SPELL) + { + assert(activeStack != nullptr); + assert(actedStack != nullptr); + + if(actedStack->castSpellThisTurn && SpellID(ba.spell).toSpell()->canCastWithoutSkip()) + { + setActiveStack(battle, actedStack); + return; + } + } + if (ba.isUnitAction()) { assert(activeStack != nullptr); diff --git a/test/mock/mock_spells_Spell.h b/test/mock/mock_spells_Spell.h index de03559eb..2d0ae31d1 100644 --- a/test/mock/mock_spells_Spell.h +++ b/test/mock/mock_spells_Spell.h @@ -47,6 +47,7 @@ public: MOCK_CONST_METHOD0(isSpecial, bool()); MOCK_CONST_METHOD0(isMagical, bool()); MOCK_CONST_METHOD0(canCastOnSelf, bool()); + MOCK_CONST_METHOD0(canCastWithoutSkip, bool()); MOCK_CONST_METHOD1(hasSchool, bool(SpellSchool)); MOCK_CONST_METHOD1(forEachSchool, void(const SchoolCallback &)); MOCK_CONST_METHOD0(getCastSound, const std::string &());