diff --git a/cmake_modules/VCMI_lib.cmake b/cmake_modules/VCMI_lib.cmake index 3e6c2cf2f..ef1bae129 100644 --- a/cmake_modules/VCMI_lib.cmake +++ b/cmake_modules/VCMI_lib.cmake @@ -133,6 +133,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/spells/BonusCaster.cpp ${MAIN_LIB_DIR}/spells/CSpellHandler.cpp ${MAIN_LIB_DIR}/spells/ISpellMechanics.cpp + ${MAIN_LIB_DIR}/spells/ObstacleCasterProxy.cpp ${MAIN_LIB_DIR}/spells/Problem.cpp ${MAIN_LIB_DIR}/spells/ProxyCaster.cpp ${MAIN_LIB_DIR}/spells/TargetCondition.cpp diff --git a/lib/spells/ObstacleCasterProxy.cpp b/lib/spells/ObstacleCasterProxy.cpp new file mode 100644 index 000000000..8d1a74b12 --- /dev/null +++ b/lib/spells/ObstacleCasterProxy.cpp @@ -0,0 +1,98 @@ +/* + * ObstacleCasterProxy.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "ObstacleCasterProxy.h" + +VCMI_LIB_NAMESPACE_BEGIN + +namespace spells +{ + +ObstacleCasterProxy::ObstacleCasterProxy(PlayerColor owner_, const Caster * hero_, const SpellCreatedObstacle * obs_): + ProxyCaster(hero_), + owner(std::move(owner_)), + obs(*obs_) +{ +} + +int32_t ObstacleCasterProxy::getCasterUnitId() const +{ + if(actualCaster) + return actualCaster->getCasterUnitId(); + else + return -1; +} + +int32_t ObstacleCasterProxy::getSpellSchoolLevel(const Spell * spell, int32_t * outSelectedSchool) const +{ + return obs.spellLevel; +} + +int32_t ObstacleCasterProxy::getEffectLevel(const Spell * spell) const +{ + return obs.spellLevel; +} + +int64_t ObstacleCasterProxy::getSpellBonus(const Spell * spell, int64_t base, const battle::Unit * affectedStack) const +{ + if(actualCaster) + return std::max<int64_t>(actualCaster->getSpellBonus(spell, base, affectedStack), obs.minimalDamage); + else + return std::max<int64_t>(base, obs.minimalDamage); +} + +int64_t ObstacleCasterProxy::getSpecificSpellBonus(const Spell * spell, int64_t base) const +{ + if(actualCaster) + return actualCaster->getSpecificSpellBonus(spell, base); + else + return base; +} + +int32_t ObstacleCasterProxy::getEffectPower(const Spell * spell) const +{ + return obs.casterSpellPower; +} + +int32_t ObstacleCasterProxy::getEnchantPower(const Spell * spell) const +{ + return obs.casterSpellPower; +} + +int64_t ObstacleCasterProxy::getEffectValue(const Spell * spell) const +{ + if(actualCaster) + return std::max(static_cast<int64_t>(obs.minimalDamage), actualCaster->getEffectValue(spell)); + else + return obs.minimalDamage; +} + +PlayerColor ObstacleCasterProxy::getCasterOwner() const +{ + return owner; +} + +void ObstacleCasterProxy::getCasterName(MetaString & text) const +{ + logGlobal->error("Unexpected call to ObstacleCasterProxy::getCasterName"); +} + +void ObstacleCasterProxy::getCastDescription(const Spell * spell, const std::vector<const battle::Unit *> & attacked, MetaString & text) const +{ + //do nothing +} + +void ObstacleCasterProxy::spendMana(ServerCallback * server, const int spellCost) const +{ + //do nothing +} + +} +VCMI_LIB_NAMESPACE_END \ No newline at end of file diff --git a/lib/spells/ObstacleCasterProxy.h b/lib/spells/ObstacleCasterProxy.h new file mode 100644 index 000000000..4e941701c --- /dev/null +++ b/lib/spells/ObstacleCasterProxy.h @@ -0,0 +1,44 @@ +/* + * ObstacleCasterProxy.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "ProxyCaster.h" +#include "../lib/NetPacksBase.h" +#include "../battle/BattleHex.h" +#include "../battle/CObstacleInstance.h" + +VCMI_LIB_NAMESPACE_BEGIN +namespace spells +{ + +class DLL_LINKAGE ObstacleCasterProxy : public ProxyCaster +{ +public: + ObstacleCasterProxy(PlayerColor owner_, const Caster * hero_, const SpellCreatedObstacle * obs_); + + int32_t getCasterUnitId() const override; + int32_t getSpellSchoolLevel(const Spell * spell, int32_t * outSelectedSchool = nullptr) const override; + int32_t getEffectLevel(const Spell * spell) const override; + int64_t getSpellBonus(const Spell * spell, int64_t base, const battle::Unit * affectedStack) const override; + int64_t getSpecificSpellBonus(const Spell * spell, int64_t base) const override; + int32_t getEffectPower(const Spell * spell) const override; + int32_t getEnchantPower(const Spell * spell) const override; + int64_t getEffectValue(const Spell * spell) const override; + PlayerColor getCasterOwner() const override; + void getCasterName(MetaString & text) const override; + void getCastDescription(const Spell * spell, const std::vector<const battle::Unit *> & attacked, MetaString & text) const override; + void spendMana(ServerCallback * server, const int spellCost) const override; + +private: + const PlayerColor owner; + const SpellCreatedObstacle & obs; +}; + +}// +VCMI_LIB_NAMESPACE_END \ No newline at end of file diff --git a/lib/spells/effects/Obstacle.cpp b/lib/spells/effects/Obstacle.cpp index 63d3c48ea..e1451870c 100644 --- a/lib/spells/effects/Obstacle.cpp +++ b/lib/spells/effects/Obstacle.cpp @@ -121,7 +121,7 @@ void Obstacle::adjustAffectedHexes(std::set<BattleHex> & hexes, const Mechanics bool Obstacle::applicable(Problem & problem, const Mechanics * m) const { - if(hidden && m->battle()->battleHasNativeStack(m->battle()->otherSide(m->casterSide))) + if(hidden && !hideNative && m->battle()->battleHasNativeStack(m->battle()->otherSide(m->casterSide))) return m->adaptProblem(ESpellCastProblem::NO_APPROPRIATE_TARGET, problem); return LocationEffect::applicable(problem, m); diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 21fb3b73a..c14d2b180 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -22,6 +22,7 @@ #include "../lib/spells/BonusCaster.h" #include "../lib/spells/CSpellHandler.h" #include "../lib/spells/ISpellMechanics.h" +#include "../lib/spells/ObstacleCasterProxy.h" #include "../lib/spells/Problem.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CTownHandler.h" @@ -93,102 +94,6 @@ private: CGameHandler * gh; }; -VCMI_LIB_NAMESPACE_BEGIN -namespace spells -{ - -class ObstacleCasterProxy : public Caster -{ -public: - ObstacleCasterProxy(const PlayerColor owner_, const CGHeroInstance * hero_, const SpellCreatedObstacle * obs_) - : owner(owner_), - hero(hero_), - obs(obs_) - { - }; - - ~ObstacleCasterProxy() = default; - - int32_t getCasterUnitId() const override - { - if(hero) - return hero->getCasterUnitId(); - else - return -1; - } - - int32_t getSpellSchoolLevel(const Spell * spell, int32_t * outSelectedSchool = nullptr) const override - { - return obs->spellLevel; - } - - int32_t getEffectLevel(const Spell * spell) const override - { - return obs->spellLevel; - } - - int64_t getSpellBonus(const Spell * spell, int64_t base, const battle::Unit * affectedStack) const override - { - if(hero) - return hero->getSpellBonus(spell, base, affectedStack); - else - return base; - } - - int64_t getSpecificSpellBonus(const Spell * spell, int64_t base) const override - { - if(hero) - return hero->getSpecificSpellBonus(spell, base); - else - return base; - } - - int32_t getEffectPower(const Spell * spell) const override - { - return obs->casterSpellPower; - } - - int32_t getEnchantPower(const Spell * spell) const override - { - return obs->casterSpellPower; - } - - int64_t getEffectValue(const Spell * spell) const override - { - if(hero) - return hero->getEffectValue(spell); - else - return 0; - } - - PlayerColor getCasterOwner() const override - { - return owner; - } - - void getCasterName(MetaString & text) const override - { - logGlobal->error("Unexpected call to ObstacleCasterProxy::getCasterName"); - } - - void getCastDescription(const Spell * spell, const std::vector<const battle::Unit *> & attacked, MetaString & text) const override - { - logGlobal->error("Unexpected call to ObstacleCasterProxy::getCastDescription"); - } - - void spendMana(ServerCallback * server, const int spellCost) const override - { - logGlobal->error("Unexpected call to ObstacleCasterProxy::spendMana"); - } - -private: - const CGHeroInstance * hero; - const PlayerColor owner; - const SpellCreatedObstacle * obs; -}; - -}// -VCMI_LIB_NAMESPACE_END CondSh<bool> battleMadeAction(false); CondSh<BattleResult *> battleResult(nullptr); @@ -5333,27 +5238,18 @@ bool CGameHandler::handleDamageFromObstacle(const CStack * curStack, bool stackI if(spellObstacle->triggersEffects()) { const bool oneTimeObstacle = spellObstacle->removeOnTrigger; - - //hidden obstacle triggers effects until revealed - if(!(spellObstacle->hidden && gs->curB->battleIsObstacleVisibleForSide(*obstacle, (BattlePerspective::BattlePerspective)side))) + auto revealObstacles = [&](const SpellCreatedObstacle & spellObstacle) -> void { - const CGHeroInstance * hero = gs->curB->battleGetFightingHero(spellObstacle->casterSide); - spells::ObstacleCasterProxy caster(gs->curB->sides.at(spellObstacle->casterSide).color, hero, spellObstacle); - - const CSpell * sp = SpellID(spellObstacle->ID).toSpell(); - if(!sp) - COMPLAIN_RET("Invalid obstacle instance"); - // For the hidden spell created obstacles, e.g. QuickSand, it should be revealed after taking damage ObstacleChanges changeInfo; - changeInfo.id = spellObstacle->uniqueID; + changeInfo.id = spellObstacle.uniqueID; if (oneTimeObstacle) changeInfo.operation = ObstacleChanges::EOperation::REMOVE; else changeInfo.operation = ObstacleChanges::EOperation::UPDATE; SpellCreatedObstacle changedObstacle; - changedObstacle.uniqueID = spellObstacle->uniqueID; + changedObstacle.uniqueID = changeInfo.id; changedObstacle.revealed = true; changeInfo.data.clear(); @@ -5363,10 +5259,26 @@ bool CGameHandler::handleDamageFromObstacle(const CStack * curStack, bool stackI BattleObstaclesChanged bocp; bocp.changes.emplace_back(changeInfo); sendAndApply(&bocp); - - spells::BattleCast battleCast(gs->curB, &caster, spells::Mode::HERO, sp); - battleCast.applyEffects(spellEnv, spells::Target(1, spells::Destination(curStack)), true); + }; + auto shouldReveal = !spellObstacle->hidden || !gs->curB->battleIsObstacleVisibleForSide(*obstacle, (BattlePerspective::BattlePerspective)side); + const auto * hero = gs->curB->battleGetFightingHero(spellObstacle->casterSide); + auto caster = spells::ObstacleCasterProxy(gs->curB->sides.at(spellObstacle->casterSide).color, hero, spellObstacle); + const auto * sp = SpellID(spellObstacle->ID).toSpell(); + if(sp) + { + auto cast = spells::BattleCast(gs->curB, &caster, spells::Mode::PASSIVE, sp); + spells::detail::ProblemImpl ignored; + auto target = spells::Target(1, spells::Destination(curStack)); + if(sp->battleMechanics(&cast)->canBeCastAt(target, ignored)) // Obstacles should not be revealed by immune creatures + { + if(shouldReveal) { //hidden obstacle triggers effects after revealed + revealObstacles(*spellObstacle); + cast.cast(spellEnv, target); + } + } } + else if(shouldReveal) + revealObstacles(*spellObstacle); } } else if(obstacle->obstacleType == CObstacleInstance::MOAT)