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)