Browse Source

Extract adventure spell mechanics

AlexVinS 10 years ago
parent
commit
08d4f7991e
7 changed files with 419 additions and 223 deletions
  1. 5 0
      config/bonuses.json
  2. 3 1
      lib/CBonusTypeHandler.cpp
  3. 6 0
      lib/CSpellHandler.cpp
  4. 16 1
      lib/CSpellHandler.h
  5. 360 2
      lib/SpellMechanics.cpp
  6. 1 1
      lib/SpellMechanics.h
  7. 28 218
      server/CGameHandler.cpp

+ 5 - 0
config/bonuses.json

@@ -328,6 +328,11 @@
 			"icon":  "zvs/Lib1.res/E_MIND"
 		}
 	},
+	
+	"NONE":
+	{
+		"hidden": true
+	},
 
 	"NO_DISTANCE_PENALTY":
 	{

+ 3 - 1
lib/CBonusTypeHandler.cpp

@@ -127,7 +127,7 @@ CBonusTypeHandler::~CBonusTypeHandler()
 }
 
 std::string CBonusTypeHandler::bonusToString(const Bonus *bonus, const IBonusBearer *bearer, bool description) const
-{
+{	
 	auto getValue = [=](const std::string &name) -> std::string
 	{
 		if (name == "val")
@@ -156,6 +156,8 @@ std::string CBonusTypeHandler::bonusToString(const Bonus *bonus, const IBonusBea
 	};
 	
 	const CBonusType& bt = bonusTypes[bonus->type];	
+	if(bt.hidden)
+		return "";
 	const MacroString& macro = description ? bt.description : bt.name;
 	
 	return macro.build(getValue);	

+ 6 - 0
lib/CSpellHandler.cpp

@@ -113,6 +113,12 @@ void CSpell::afterCast(BattleInfo * battle, const BattleSpellCast * packet) cons
 	mechanics->afterCast(battle, packet);
 }
 
+bool CSpell::adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const
+{
+	assert(env);
+		
+	return mechanics->adventureCast(env, parameters);
+}
 
 void CSpell::battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const
 {

+ 16 - 1
lib/CSpellHandler.h

@@ -18,6 +18,8 @@
  *
  */
 
+class CGObjectInstance;
+
 class CSpell;
 class ISpellMechanics;
 
@@ -32,7 +34,9 @@ class BattleInfo;
 struct CPackForClient;
 struct BattleSpellCast;
 
+class CGameInfoCallback;
 class CRandomGenerator;
+class CMap;
 
 struct SpellSchoolInfo
 {
@@ -53,6 +57,11 @@ public:
 	
 	virtual CRandomGenerator & getRandomGenerator() const = 0;
 	virtual void complain(const std::string & problem) const = 0;
+	
+	virtual const CMap * getMap() const = 0;
+	virtual const CGameInfoCallback * getCb() const = 0;	
+	
+	virtual bool moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, PlayerColor asker = PlayerColor::NEUTRAL) const =0;	//TODO: remove
 };
 
 ///helper struct
@@ -73,6 +82,12 @@ public:
 	const BattleInfo * cb;		
 };
 
+struct DLL_LINKAGE AdventureSpellCastParameters
+{
+	const CGHeroInstance * caster;
+	int3 pos;	
+};
+
 enum class VerticalPosition : ui8{TOP, CENTER, BOTTOM};
 
 class DLL_LINKAGE CSpell
@@ -314,7 +329,7 @@ public:
 	///Server logic. Has write access to GameState via packets.
 	///May be executed on client side by (future) non-cheat-proof scripts.
 	
-	//void adventureCast() const; 
+	bool adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const; 
 	void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const; 	
 		
 public:	

+ 360 - 2
lib/SpellMechanics.cpp

@@ -18,6 +18,10 @@
 
 #include "NetPacks.h"
 
+#include "mapping/CMap.h"
+#include "CGameInfoCallback.h"
+#include "CGameState.h"
+
 namespace SRSLPraserHelpers
 {
 	static int XYToHex(int x, int y)
@@ -139,7 +143,7 @@ public:
 	
 	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override;
 	
-	//bool adventureCast(const SpellCastContext & context) const override; 
+	bool adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override final; 
 	void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const override;
 	
 	void afterCast(BattleInfo * battle, const BattleSpellCast * packet) const override;
@@ -149,8 +153,58 @@ protected:
 	
 	virtual int calculateDuration(const CGHeroInstance * caster, int usedSpellPower) const;
 	
+	///actual adventure cast implementation
+	virtual bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const;
+};
+
+///ADVENTURE SPELLS
+
+//todo: make configyrable
+class AdventureBonusingMechanics: public DefaultSpellMechanics 
+{
+public:	
+	AdventureBonusingMechanics(CSpell * s, Bonus::BonusType _bonusTypeID): DefaultSpellMechanics(s), bonusTypeID(_bonusTypeID){};	
+protected:
+	bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;	
+private:
+	Bonus::BonusType bonusTypeID;
+};
+
+class SummonBoatMechanics: public DefaultSpellMechanics 
+{
+public:
+	SummonBoatMechanics(CSpell * s): DefaultSpellMechanics(s){};
+protected:
+	bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;	
+};
+
+class ScuttleBoatMechanics: public DefaultSpellMechanics 
+{
+public:
+	ScuttleBoatMechanics(CSpell * s): DefaultSpellMechanics(s){};
+protected:
+	bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;	
+};
+
+class DimensionDoorMechanics: public DefaultSpellMechanics 
+{
+public:	
+	DimensionDoorMechanics(CSpell * s): DefaultSpellMechanics(s){};	
+protected:
+	bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;	
 };
 
+class TownPortalMechanics: public DefaultSpellMechanics 
+{
+public:	
+	TownPortalMechanics(CSpell * s): DefaultSpellMechanics(s){};	
+protected:
+	bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;	
+};
+
+
+///BATTLE SPELLS
+
 class AcidBreathDamageMechnics: public DefaultSpellMechanics
 {
 public:
@@ -323,7 +377,23 @@ ISpellMechanics * ISpellMechanics::createMechanics(CSpell * s)
 	case SpellID::SUMMON_AIR_ELEMENTAL:
 		return new SummonMechanics(s);
 	case SpellID::TELEPORT:
-		return new TeleportMechanics(s);
+		return new TeleportMechanics(s);	
+	case SpellID::SUMMON_BOAT:
+		return new SummonBoatMechanics(s);
+	case SpellID::SCUTTLE_BOAT:		
+		return new ScuttleBoatMechanics(s);		
+	case SpellID::DIMENSION_DOOR:
+		return new DimensionDoorMechanics(s);
+	case SpellID::FLY:
+		return new AdventureBonusingMechanics(s, Bonus::FLYING_MOVEMENT);
+	case SpellID::WATER_WALK:
+		return new AdventureBonusingMechanics(s, Bonus::WATER_WALKING);
+	case SpellID::TOWN_PORTAL:
+	
+	case SpellID::VISIONS:
+	case SpellID::VIEW_EARTH:
+	case SpellID::DISGUISE:
+	case SpellID::VIEW_AIR:	
 	default:		
 		if(s->isRisingSpell())
 			return new SpecialRisingSpellMechanics(s);
@@ -362,6 +432,55 @@ void DefaultSpellMechanics::afterCast(BattleInfo * battle, const BattleSpellCast
 	}	
 }
 
+bool DefaultSpellMechanics::adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const
+{
+	if(!owner->isAdventureSpell())
+	{
+		env->complain("Attempt to cast non adventure spell in adventure mode");
+		return false;
+	}
+	
+	const CGHeroInstance * caster = parameters.caster;
+	const int cost = caster->getSpellCost(owner);
+	
+	if(!caster->canCastThisSpell(owner))
+	{
+		env->complain("Hero cannot cast this spell!");
+		return false;		
+	}
+
+	if(caster->mana < cost)
+	{
+		env->complain("Hero doesn't have enough spell points to cast this spell!");
+		return false;		
+	}
+
+	{
+		AdvmapSpellCast asc;
+		asc.caster = caster;
+		asc.spellID = owner->id;
+		env->sendAndApply(&asc);		
+	}
+	
+	if(applyAdventureEffects(env, parameters))
+	{
+		SetMana sm;
+		sm.hid = caster->id;
+		sm.absolute = false;
+		sm.val = -cost;
+		env->sendAndApply(&sm);
+		return true;
+	}
+	return false;
+}
+
+bool DefaultSpellMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const
+{
+	//There is no generic algorithm of adventure cast
+	env->complain("Unimplemented adventure spell");
+	return false;		
+}
+
 
 void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const
 {
@@ -847,6 +966,245 @@ ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::isImmuneByStack(cons
 	return owner->isImmuneBy(obj);
 }
 
+///ADVENTURE SPELLS
+
+///AdventureBonusingMechanics
+bool AdventureBonusingMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const
+{
+	const int schoolLevel = parameters.caster->getSpellSchoolLevel(owner);	
+	const int subtype = schoolLevel >= 2 ? 1 : 2; //adv or expert
+
+	GiveBonus gb;
+	gb.id = parameters.caster->id.getNum();
+	gb.bonus = Bonus(Bonus::ONE_DAY, bonusTypeID, Bonus::SPELL_EFFECT, 0, owner->id, subtype);
+	env->sendAndApply(&gb);	
+	return true;
+}
+
+///SummonBoatMechanics
+bool SummonBoatMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const
+{
+	const int schoolLevel = parameters.caster->getSpellSchoolLevel(owner);	
+	//check if spell works at all
+	if(env->getRandomGenerator().nextInt(99) >= owner->getPower(schoolLevel)) //power is % chance of success
+	{
+		InfoWindow iw;
+		iw.player = parameters.caster->tempOwner;
+		iw.text.addTxt(MetaString::GENERAL_TXT, 336); //%s tried to summon a boat, but failed.
+		iw.text.addReplacement(parameters.caster->name);
+		env->sendAndApply(&iw);
+		return true;
+	}
+
+	//try to find unoccupied boat to summon
+	const CGBoat * nearest = nullptr;
+	double dist = 0;
+	int3 summonPos = parameters.caster->bestLocation();
+	if(summonPos.x < 0)
+	{
+		env->complain("There is no water tile available!");
+		return false;
+	}	
+	
+	for(const CGObjectInstance * obj : env->getMap()->objects)
+	{
+		if(obj && obj->ID == Obj::BOAT)
+		{
+			const CGBoat *b = static_cast<const CGBoat*>(obj);
+			if(b->hero) 
+				continue; //we're looking for unoccupied boat
+
+			double nDist = b->pos.dist2d(parameters.caster->getPosition());
+			if(!nearest || nDist < dist) //it's first boat or closer than previous
+			{
+				nearest = b;
+				dist = nDist;
+			}
+		}						
+	}
+
+	if(nullptr != nearest) //we found boat to summon
+	{
+		ChangeObjPos cop;
+		cop.objid = nearest->id;
+		cop.nPos = summonPos + int3(1,0,0);;
+		cop.flags = 1;
+		env->sendAndApply(&cop);
+	}
+	else if(schoolLevel < 2) //none or basic level -> cannot create boat :(
+	{
+		InfoWindow iw;
+		iw.player = parameters.caster->tempOwner;
+		iw.text.addTxt(MetaString::GENERAL_TXT, 335); //There are no boats to summon.
+		env->sendAndApply(&iw);
+	}
+	else //create boat
+	{
+		NewObject no;
+		no.ID = Obj::BOAT;
+		no.subID = parameters.caster->getBoatType();
+		no.pos = summonPos + int3(1,0,0);;
+		env->sendAndApply(&no);
+	}	
+	return true;	
+}
+
+///ScuttleBoatMechanics
+bool ScuttleBoatMechanics::applyAdventureEffects(const SpellCastEnvironment* env, AdventureSpellCastParameters& parameters) const
+{
+	const int schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
+	//check if spell works at all
+	if(env->getRandomGenerator().nextInt(99) >= owner->getPower(schoolLevel)) //power is % chance of success
+	{
+		InfoWindow iw;
+		iw.player = parameters.caster->tempOwner;
+		iw.text.addTxt(MetaString::GENERAL_TXT, 337); //%s tried to scuttle the boat, but failed
+		iw.text.addReplacement(parameters.caster->name);
+		env->sendAndApply(&iw);
+		return true;
+	}
+		
+	if(!env->getMap()->isInTheMap(parameters.pos))
+	{
+		env->complain("Invalid dst tile for scuttle!");
+		return false;
+	}
+
+	//TODO: test range, visibility
+	const TerrainTile *t = &env->getMap()->getTile(parameters.pos);
+	if(!t->visitableObjects.size() || t->visitableObjects.back()->ID != Obj::BOAT)
+	{
+		env->complain("There is no boat to scuttle!");
+		return false;
+	}
+		
+
+	RemoveObject ro;
+	ro.id = t->visitableObjects.back()->id;
+	env->sendAndApply(&ro);
+	return true;	
+}
+
+///DimensionDoorMechanics
+bool DimensionDoorMechanics::applyAdventureEffects(const SpellCastEnvironment* env, AdventureSpellCastParameters& parameters) const
+{
+	if(!env->getMap()->isInTheMap(parameters.pos))
+	{
+		env->complain("Destination is out of map!");
+		return false;
+	}	
+	
+	const TerrainTile * dest = env->getCb()->getTile(parameters.pos);
+	const TerrainTile * curr = env->getCb()->getTile(parameters.caster->getSightCenter());
+
+	if(nullptr == dest)
+	{
+		env->complain("Destination tile doesn't exist!");
+		return false;
+	}
+	
+	if(nullptr == curr)
+	{
+		env->complain("Source tile doesn't exist!");
+		return false;
+	}	
+		
+	if(parameters.caster->movement <= 0)
+	{
+		env->complain("Hero needs movement points to cast Dimension Door!");
+		return false;
+	}
+	
+	const int schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
+			
+	if(parameters.caster->getBonusesCount(Bonus::SPELL_EFFECT, SpellID::DIMENSION_DOOR) >= owner->getPower(schoolLevel)) //limit casts per turn
+	{
+		InfoWindow iw;
+		iw.player = parameters.caster->tempOwner;
+		iw.text.addTxt(MetaString::GENERAL_TXT, 338); //%s is not skilled enough to cast this spell again today.
+		iw.text.addReplacement(parameters.caster->name);
+		env->sendAndApply(&iw);
+		return true;
+	}
+
+	GiveBonus gb;
+	gb.id = parameters.caster->id.getNum();
+	gb.bonus = Bonus(Bonus::ONE_DAY, Bonus::NONE, Bonus::SPELL_EFFECT, 0, owner->id);
+	env->sendAndApply(&gb);
+
+	if(!dest->isClear(curr)) //wrong dest tile
+	{
+		InfoWindow iw;
+		iw.player = parameters.caster->tempOwner;
+		iw.text.addTxt(MetaString::GENERAL_TXT, 70); //Dimension Door failed!
+		env->sendAndApply(&iw);		
+	}
+	else if(env->moveHero(parameters.caster->id, parameters.pos + parameters.caster->getVisitableOffset(), true))
+	{
+		SetMovePoints smp;
+		smp.hid = parameters.caster->id;
+		smp.val = std::max<ui32>(0, parameters.caster->movement - 300);
+		env->sendAndApply(&smp);
+	}
+	return true; 	
+}
+
+///TownPortalMechanics
+bool TownPortalMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters& parameters) const
+{
+	if (!env->getMap()->isInTheMap(parameters.pos))
+	{
+		env->complain("Destination tile not present!");
+		return false;
+	}	
+
+	TerrainTile tile = env->getMap()->getTile(parameters.pos);
+	if (tile.visitableObjects.empty() || tile.visitableObjects.back()->ID != Obj::TOWN)
+	{
+		env->complain("Town not found for Town Portal!");	
+		return false;
+	}		
+
+	CGTownInstance * town = static_cast<CGTownInstance*>(tile.visitableObjects.back());
+	if (town->tempOwner != parameters.caster->tempOwner)
+	{
+		env->complain("Can't teleport to another player!");
+		return false;
+	}			
+	
+	if (town->visitingHero)
+	{
+		env->complain("Can't teleport to occupied town!");
+		return false;
+	}
+	
+	if (parameters.caster->getSpellSchoolLevel(owner) < 2)
+	{
+		si32 dist = town->pos.dist2dSQ(parameters.caster->pos);
+		ObjectInstanceID nearest = town->id; //nearest town's ID
+		for(const CGTownInstance * currTown : env->getCb()->getPlayer(parameters.caster->tempOwner)->towns)
+		{
+			si32 currDist = currTown->pos.dist2dSQ(parameters.caster->pos);
+			if (currDist < dist)
+			{
+				nearest = currTown->id;
+				dist = currDist;
+			}
+		}
+		if (town->id != nearest)
+		{
+			env->complain("This hero can only teleport to nearest town!");
+			return false;
+		}
+			
+	}
+	env->moveHero(parameters.caster->id, town->visitablePos() + parameters.caster->getVisitableOffset() ,1);	
+	return true;
+}
+
+
+///BATTLE SPELLS
+
 ///AcidBreathDamageMechnics
 void AcidBreathDamageMechnics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
 {

+ 1 - 1
lib/SpellMechanics.h

@@ -41,7 +41,7 @@ public:
 	
 	virtual ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const = 0;
                    
-	//virtual bool adventureCast(const SpellCastContext & context) const = 0; 
+	virtual bool adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const = 0; 
 	virtual void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const = 0; 	
 	
 	static ISpellMechanics * createMechanics(CSpell * s);

+ 28 - 218
server/CGameHandler.cpp

@@ -67,8 +67,11 @@ public:
 	void sendAndApply(CPackForClient * info) const override;	
 	CRandomGenerator & getRandomGenerator() const override;
 	void complain(const std::string & problem) const override;
+	const CMap * getMap() const override;
+	const CGameInfoCallback * getCb() const override;
+	bool moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, PlayerColor asker = PlayerColor::NEUTRAL) const override;	
 private:
-	CGameHandler * gh;	
+	mutable CGameHandler * gh;	
 };
 
 CondSh<bool> battleMadeAction;
@@ -4970,222 +4973,12 @@ void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat )
 bool CGameHandler::castSpell(const CGHeroInstance *h, SpellID spellID, const int3 &pos)
 {
 	const CSpell *s = spellID.toSpell();
-	int cost = h->getSpellCost(s);
-	int schoolLevel = h->getSpellSchoolLevel(s);
-
-	if(!h->canCastThisSpell(s))
-		COMPLAIN_RET("Hero cannot cast this spell!");
-	if(h->mana < cost)
-		COMPLAIN_RET("Hero doesn't have enough spell points to cast this spell!");
-	if(s->combatSpell)
-		COMPLAIN_RET("This function can be used only for adventure map spells!");
-
-	AdvmapSpellCast asc;
-	asc.caster = h;
-	asc.spellID = spellID;
-	sendAndApply(&asc);
-
-	switch(spellID)
-	{
-	case SpellID::SUMMON_BOAT:
-		{
-			//check if spell works at all
-			if(gs->getRandomGenerator().nextInt(99) >= s->getPower(schoolLevel)) //power is % chance of success
-			{
-				InfoWindow iw;
-				iw.player = h->tempOwner;
-				iw.text.addTxt(MetaString::GENERAL_TXT, 336); //%s tried to summon a boat, but failed.
-				iw.text.addReplacement(h->name);
-				sendAndApply(&iw);
-				break;
-			}
-
-			//try to find unoccupied boat to summon
-			const CGBoat *nearest = nullptr;
-			double dist = 0;
-			int3 summonPos = h->bestLocation();
-			if(summonPos.x < 0)
-				COMPLAIN_RET("There is no water tile available!");
-
-			for(const CGObjectInstance *obj : gs->map->objects)
-			{
-				if(obj && obj->ID == Obj::BOAT)
-				{
-					const CGBoat *b = static_cast<const CGBoat*>(obj);
-					if(b->hero) continue; //we're looking for unoccupied boat
-
-					double nDist = distance(b->pos, h->getPosition());
-					if(!nearest || nDist < dist) //it's first boat or closer than previous
-					{
-						nearest = b;
-						dist = nDist;
-					}
-				}
-			}
-
-			if(nearest) //we found boat to summon
-			{
-				ChangeObjPos cop;
-				cop.objid = nearest->id;
-				cop.nPos = summonPos + int3(1,0,0);;
-				cop.flags = 1;
-				sendAndApply(&cop);
-			}
-			else if(schoolLevel < 2) //none or basic level -> cannot create boat :(
-			{
-				InfoWindow iw;
-				iw.player = h->tempOwner;
-				iw.text.addTxt(MetaString::GENERAL_TXT, 335); //There are no boats to summon.
-				sendAndApply(&iw);
-			}
-			else //create boat
-			{
-				NewObject no;
-				no.ID = Obj::BOAT;
-				no.subID = h->getBoatType();
-				no.pos = summonPos + int3(1,0,0);;
-				sendAndApply(&no);
-			}
-			break;
-		}
-
-	case SpellID::SCUTTLE_BOAT:
-		{
-			//check if spell works at all
-			if(gs->getRandomGenerator().nextInt(99) >= s->getPower(schoolLevel)) //power is % chance of success
-			{
-				InfoWindow iw;
-				iw.player = h->tempOwner;
-				iw.text.addTxt(MetaString::GENERAL_TXT, 337); //%s tried to scuttle the boat, but failed
-				iw.text.addReplacement(h->name);
-				sendAndApply(&iw);
-				break;
-			}
-			if(!gs->map->isInTheMap(pos))
-				COMPLAIN_RET("Invalid dst tile for scuttle!");
-
-			//TODO: test range, visibility
-			const TerrainTile *t = &gs->map->getTile(pos);
-			if(!t->visitableObjects.size() || t->visitableObjects.back()->ID != Obj::BOAT)
-				COMPLAIN_RET("There is no boat to scuttle!");
-
-			RemoveObject ro;
-			ro.id = t->visitableObjects.back()->id;
-			sendAndApply(&ro);
-			break;
-		}
-	case SpellID::DIMENSION_DOOR:
-		{
-			const TerrainTile *dest = getTile(pos);
-			const TerrainTile *curr = getTile(h->getSightCenter());
-
-			if(!dest)
-				COMPLAIN_RET("Destination tile doesn't exist!");
-			if(!h->movement)
-				COMPLAIN_RET("Hero needs movement points to cast Dimension Door!");
-			if(h->getBonusesCount(Bonus::SPELL_EFFECT, SpellID::DIMENSION_DOOR) >= s->getPower(schoolLevel)) //limit casts per turn
-			{
-				InfoWindow iw;
-				iw.player = h->tempOwner;
-				iw.text.addTxt(MetaString::GENERAL_TXT, 338); //%s is not skilled enough to cast this spell again today.
-				iw.text.addReplacement(h->name);
-				sendAndApply(&iw);
-				break;
-			}
-
-			GiveBonus gb;
-			gb.id = h->id.getNum();
-			gb.bonus = Bonus(Bonus::ONE_DAY, Bonus::NONE, Bonus::SPELL_EFFECT, 0, SpellID::DIMENSION_DOOR);
-			sendAndApply(&gb);
-
-			if(!dest->isClear(curr)) //wrong dest tile
-			{
-				InfoWindow iw;
-				iw.player = h->tempOwner;
-				iw.text.addTxt(MetaString::GENERAL_TXT, 70); //Dimension Door failed!
-				sendAndApply(&iw);
-				break;
-			}
-
-			if (moveHero(h->id, pos + h->getVisitableOffset(), true))
-			{
-				SetMovePoints smp;
-				smp.hid = h->id;
-				smp.val = std::max<ui32>(0, h->movement - 300);
-				sendAndApply(&smp);
-			}
-		}
-		break;
-	case SpellID::FLY:
-		{
-			int subtype = schoolLevel >= 2 ? 1 : 2; //adv or expert
-
-			GiveBonus gb;
-			gb.id = h->id.getNum();
-			gb.bonus = Bonus(Bonus::ONE_DAY, Bonus::FLYING_MOVEMENT, Bonus::SPELL_EFFECT, 0, SpellID::FLY, subtype);
-			sendAndApply(&gb);
-		}
-		break;
-	case SpellID::WATER_WALK:
-		{
-			int subtype = schoolLevel >= 2 ? 1 : 2; //adv or expert
-
-			GiveBonus gb;
-			gb.id = h->id.getNum();
-			gb.bonus = Bonus(Bonus::ONE_DAY, Bonus::WATER_WALKING, Bonus::SPELL_EFFECT, 0, SpellID::WATER_WALK, subtype);
-			sendAndApply(&gb);
-		}
-		break;
-
-	case SpellID::TOWN_PORTAL:
-		{
-			if (!gs->map->isInTheMap(pos))
-				COMPLAIN_RET("Destination tile not present!")
-			TerrainTile tile = gs->map->getTile(pos);
-			if (tile.visitableObjects.empty() || tile.visitableObjects.back()->ID != Obj::TOWN )
-				COMPLAIN_RET("Town not found for Town Portal!");
-
-			CGTownInstance * town = static_cast<CGTownInstance*>(tile.visitableObjects.back());
-			if (town->tempOwner != h->tempOwner)
-				COMPLAIN_RET("Can't teleport to another player!");
-			if (town->visitingHero)
-				COMPLAIN_RET("Can't teleport to occupied town!");
-
-			if (h->getSpellSchoolLevel(s) < 2)
-			{
-				si32 dist = town->pos.dist2dSQ(h->pos);
-				ObjectInstanceID nearest = town->id; //nearest town's ID
-				for(const CGTownInstance * currTown : gs->getPlayer(h->tempOwner)->towns)
-				{
-					si32 currDist = currTown->pos.dist2dSQ(h->pos);
-					if (currDist < dist)
-					{
-						nearest = currTown->id;
-						dist = currDist;
-					}
-				}
-				if (town->id != nearest)
-					COMPLAIN_RET("This hero can only teleport to nearest town!")
-			}
-			moveHero(h->id, town->visitablePos() + h->getVisitableOffset() ,1);
-		}
-		break;
-
-	case SpellID::VISIONS:
-	case SpellID::VIEW_EARTH:
-	case SpellID::DISGUISE:
-	case SpellID::VIEW_AIR:
-	default:
-		COMPLAIN_RET("This spell is not implemented yet!");
-	}
-
-	SetMana sm;
-	sm.hid = h->id;
-	sm.absolute = false;
-	sm.val = -cost;
-	sendAndApply(&sm);
-
-	return true;
+	
+	AdventureSpellCastParameters p;
+	p.caster = h;
+	p.pos = pos;
+	
+	return s->adventureCast(spellEnv, p);
 }
 
 void CGameHandler::visitObjectOnTile(const TerrainTile &t, const CGHeroInstance * h)
@@ -6010,7 +5803,7 @@ CGameHandler::FinishingBattleHelper::FinishingBattleHelper()
 	winnerHero = loserHero = nullptr;
 }
 
-
+///ServerSpellCastEnvironment
 ServerSpellCastEnvironment::ServerSpellCastEnvironment(CGameHandler * gh): gh(gh)
 {
 	
@@ -6030,3 +5823,20 @@ void ServerSpellCastEnvironment::complain(const std::string& problem) const
 {
 	gh->complain(problem);
 }
+
+const CGameInfoCallback * ServerSpellCastEnvironment::getCb() const
+{
+	return gh;
+}
+
+
+const CMap * ServerSpellCastEnvironment::getMap() const
+{
+	return gh->gameState()->map;
+}
+
+bool ServerSpellCastEnvironment::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, PlayerColor asker) const
+{
+	return gh->moveHero(hid, dst, teleporting, asker);
+}
+