Selaa lähdekoodia

Advanced work on support for Quicksand and Land Mine spells.

Michał W. Urbańczyk 13 vuotta sitten
vanhempi
sitoutus
5449546447

+ 10 - 0
Global.h

@@ -324,6 +324,16 @@ namespace vstd
 	{
 	{
 		return std::unique_ptr<T>(new T(std::forward<Arg1>(arg1), std::forward<Arg2>(arg2)));
 		return std::unique_ptr<T>(new T(std::forward<Arg1>(arg1), std::forward<Arg2>(arg2)));
 	}
 	}
+
+	template <typename Container>
+	typename Container::const_reference circularAt(const Container &r, size_t index)
+	{
+		assert(r.size());
+		index %= r.size();
+		auto itr = std::begin(r);
+		std::advance(itr, index);
+		return *itr;
+	}
 }
 }
 
 
 using std::shared_ptr;
 using std::shared_ptr;

+ 1 - 1
client/BattleInterface/CBattleAnimations.cpp

@@ -903,7 +903,7 @@ void CShootingAnimation::endAnim()
 }
 }
 
 
 CSpellEffectAnimation::CSpellEffectAnimation(CBattleInterface * _owner, ui32 _effect, BattleHex _destTile, int _dx, int _dy, bool _Vflip)
 CSpellEffectAnimation::CSpellEffectAnimation(CBattleInterface * _owner, ui32 _effect, BattleHex _destTile, int _dx, int _dy, bool _Vflip)
-:CBattleAnimation(_owner), effect(_effect), destTile(_destTile), customAnim(""), dx(_dx), dy(_dy), Vflip(_Vflip) 
+:CBattleAnimation(_owner), effect(_effect), destTile(_destTile), customAnim(""), x(0), y(0), dx(_dx), dy(_dy), Vflip(_Vflip) 
 {}
 {}
 
 
 CSpellEffectAnimation::CSpellEffectAnimation(CBattleInterface * _owner, std::string _customAnim, int _x, int _y, int _dx, int _dy, bool _Vflip)
 CSpellEffectAnimation::CSpellEffectAnimation(CBattleInterface * _owner, std::string _customAnim, int _x, int _y, int _dx, int _dy, bool _Vflip)

+ 89 - 17
client/BattleInterface/CBattleInterface.cpp

@@ -341,7 +341,7 @@ CBattleInterface::CBattleInterface(const CCreatureSet * army1, const CCreatureSe
 		int ID = obst[t].ID;
 		int ID = obst[t].ID;
 		std::string gfxName = obst[t].getInfo().defName;
 		std::string gfxName = obst[t].getInfo().defName;
 
 
-		if(!obst[t].isAbsoluteObstacle)
+		if(obst[t].obstacleType == CObstacleInstance::USUAL)
 		{
 		{
 			idToObstacle[ID] = CDefHandler::giveDef(gfxName);
 			idToObstacle[ID] = CDefHandler::giveDef(gfxName);
 			for(size_t n = 0; n < idToObstacle[ID]->ourImages.size(); ++n)
 			for(size_t n = 0; n < idToObstacle[ID]->ourImages.size(); ++n)
@@ -355,6 +355,9 @@ CBattleInterface::CBattleInterface(const CCreatureSet * army1, const CCreatureSe
 		}
 		}
 	}
 	}
 
 
+	quicksand = CDefHandler::giveDef("C17SPE1.DEF");
+	landMine = CDefHandler::giveDef("C09SPF1.DEF");
+
 	for (int i = 0; i < bfield.size(); i++)
 	for (int i = 0; i < bfield.size(); i++)
 	{
 	{
 		children.push_back(&bfield[i]);
 		children.push_back(&bfield[i]);
@@ -423,6 +426,9 @@ CBattleInterface::~CBattleInterface()
 	for(std::map< int, CDefHandler * >::iterator g=idToObstacle.begin(); g!=idToObstacle.end(); ++g)
 	for(std::map< int, CDefHandler * >::iterator g=idToObstacle.begin(); g!=idToObstacle.end(); ++g)
 		delete g->second;
 		delete g->second;
 
 
+	delete quicksand;
+	delete landMine;
+
 	delete siegeH;
 	delete siegeH;
 
 
 	//TODO: play AI tracks if battle was during AI turn
 	//TODO: play AI tracks if battle was during AI turn
@@ -636,7 +642,7 @@ void CBattleInterface::show(SDL_Surface * to)
 	for(size_t b = 0; b < obstacles.size(); ++b)
 	for(size_t b = 0; b < obstacles.size(); ++b)
 	{
 	{
 		const CObstacleInstance &oi = obstacles[b];
 		const CObstacleInstance &oi = obstacles[b];
-		if(!oi.isAbsoluteObstacle)
+		if(oi.obstacleType != CObstacleInstance::ABSOLUTE_OBSTACLE)
 		{
 		{
 			//BattleHex position = CGI->heroh->obstacles.find(obstacles[b].ID)->second.getMaxBlocked(obstacles[b].pos);
 			//BattleHex position = CGI->heroh->obstacles.find(obstacles[b].ID)->second.getMaxBlocked(obstacles[b].pos);
 			hexToObstacle.insert(std::make_pair(oi.pos, b));
 			hexToObstacle.insert(std::make_pair(oi.pos, b));
@@ -717,8 +723,8 @@ void CBattleInterface::show(SDL_Surface * to)
 	{
 	{
 		for(int b = 0; b < GameConstants::BFIELD_SIZE; ++b) //showing alive stacks
 		for(int b = 0; b < GameConstants::BFIELD_SIZE; ++b) //showing alive stacks
 		{
 		{
-			showAliveStacks(stackAliveByHex, b, &flyingStacks, to);
 			showObstacles(&hexToObstacle, obstacles, b, to);
 			showObstacles(&hexToObstacle, obstacles, b, to);
+			showAliveStacks(stackAliveByHex, b, &flyingStacks, to);
 		}
 		}
 	}
 	}
 	// Siege drawing
 	// Siege drawing
@@ -786,8 +792,8 @@ void CBattleInterface::show(SDL_Surface * to)
 				for (int k = xMin; k <= xMax; k++)
 				for (int k = xMin; k <= xMax; k++)
 				{
 				{
 					int hex = j * 17 + k;
 					int hex = j * 17 + k;
-					showAliveStacks(stackAliveByHex, hex, &flyingStacks, to);
 					showObstacles(&hexToObstacle, obstacles, hex, to);
 					showObstacles(&hexToObstacle, obstacles, hex, to);
+					showAliveStacks(stackAliveByHex, hex, &flyingStacks, to);
 					showPieceOfWall(to, hex, stacks);
 					showPieceOfWall(to, hex, stacks);
 				}
 				}
 
 
@@ -904,15 +910,9 @@ void CBattleInterface::showObstacles(std::multimap<BattleHex, int> *hexToObstacl
 	for(std::multimap<BattleHex, int>::const_iterator it = obstRange.first; it != obstRange.second; ++it)
 	for(std::multimap<BattleHex, int>::const_iterator it = obstRange.first; it != obstRange.second; ++it)
 	{
 	{
 		CObstacleInstance & curOb = obstacles[it->second];
 		CObstacleInstance & curOb = obstacles[it->second];
-		std::vector<Cimage> &images = idToObstacle[curOb.ID]->ourImages; //reference to animation of obstacle
-		Rect r = hexPosition(hex);
-		int offset = images.front().bitmap->h % 42;
-		if(curOb.getInfo().blockedTiles.front() < 0)
-			offset -= 42;
-
-		r.y += 42 - images.front().bitmap->h + offset;
-		//r.y -= cellShade->h*CGI->heroh->obstacles.find(curOb.ID)->second.height - images.front().bitmap->h;
-		blitAt(images[((animCount+1)/(4/getAnimSpeed()))%images.size()].bitmap, r.x, r.y, to);
+		SDL_Surface *toBlit = imageOfObstacle(curOb);
+		Point p = whereToBlitObstacleImage(toBlit, curOb);
+		blitAt(toBlit, p.x, p.y, to);
 	}
 	}
 }
 }
 
 
@@ -1957,12 +1957,12 @@ void CBattleInterface::activateStack()
 		creatureSpellToCast = -1;
 		creatureSpellToCast = -1;
 	}
 	}
 
 
-	GH.fakeMouseMove();
 
 
 	if(!pendingAnims.size() && !active)
 	if(!pendingAnims.size() && !active)
 		activate();
 		activate();
 
 
 	getPossibleActionsForStack (activeStack);
 	getPossibleActionsForStack (activeStack);
+	GH.fakeMouseMove();
 }
 }
 
 
 double CBattleInterface::getAnimSpeedMultiplier() const
 double CBattleInterface::getAnimSpeedMultiplier() const
@@ -2279,8 +2279,8 @@ void CBattleInterface::redrawBackgroundWithHexes(const CStack * activeStack)
 
 
 	//draw absolute obstacles (cliffs and so on)
 	//draw absolute obstacles (cliffs and so on)
 	BOOST_FOREACH(const CObstacleInstance &oi, curInt->cb->battleGetAllObstacles())
 	BOOST_FOREACH(const CObstacleInstance &oi, curInt->cb->battleGetAllObstacles())
-		if(oi.isAbsoluteObstacle)
-			blitAt(idToAbsoluteObstacle[oi.ID], oi.getInfo().width, oi.getInfo().height, backgroundWithHexes);
+		if(oi.obstacleType == CObstacleInstance::ABSOLUTE_OBSTACLE)
+			blitAt(imageOfObstacle(oi), oi.getInfo().width, oi.getInfo().height, backgroundWithHexes);
 
 
 	if(settings["battle"]["cellBorders"].Bool())
 	if(settings["battle"]["cellBorders"].Bool())
 		CSDL_Ext::blit8bppAlphaTo24bpp(cellBorders, NULL, backgroundWithHexes, NULL);
 		CSDL_Ext::blit8bppAlphaTo24bpp(cellBorders, NULL, backgroundWithHexes, NULL);
@@ -2418,7 +2418,8 @@ void CBattleInterface::endAction(const BattleAction* action)
 		else
 		else
 			attackingHero->setPhase(0);
 			attackingHero->setPhase(0);
 	}
 	}
-	if(action->actionType == BattleAction::WALK && creAnims[action->stackNumber]->getType() != 2) //walk or walk & attack
+
+	if(stack && action->actionType == BattleAction::WALK && creAnims[action->stackNumber]->getType() != 2) //walk or walk & attack
 	{
 	{
 		pendingAnims.push_back(std::make_pair(new CMovementEndAnimation(this, stack, action->destinationTile), false));
 		pendingAnims.push_back(std::make_pair(new CMovementEndAnimation(this, stack, action->destinationTile), false));
 	}
 	}
@@ -3246,6 +3247,77 @@ Rect CBattleInterface::hexPosition(BattleHex hex) const
 	return Rect(x, y, w, h);
 	return Rect(x, y, w, h);
 }
 }
 
 
+SDL_Surface * CBattleInterface::imageOfObstacle(const CObstacleInstance &oi) const
+{
+	int frameIndex = (animCount+1) / (40/getAnimSpeed());
+	switch(oi.obstacleType)
+	{
+	case CObstacleInstance::USUAL:
+		return vstd::circularAt(idToObstacle.find(oi.ID)->second->ourImages, frameIndex).bitmap;
+	case CObstacleInstance::ABSOLUTE_OBSTACLE:
+		return idToAbsoluteObstacle.find(oi.ID)->second;
+	case CObstacleInstance::QUICKSAND:
+		return vstd::circularAt(quicksand->ourImages, frameIndex).bitmap;
+	case CObstacleInstance::LAND_MINE:
+		return vstd::circularAt(landMine->ourImages, frameIndex).bitmap;
+	default:
+		assert(0);
+	}
+}
+
+void CBattleInterface::obstaclePlaced(const CObstacleInstance & oi)
+{
+	//so when multiple obstacles are added, they show up one after another
+	waitForAnims();
+
+	int effectID = -1;
+
+	switch(oi.obstacleType)
+	{
+	case CObstacleInstance::QUICKSAND:
+		effectID = 55;
+		break;
+	case CObstacleInstance::LAND_MINE:
+		effectID = 47;
+		break;
+		//TODO firewall, force field
+	default:
+		tlog1 << "I don't know how to animate appearing obstacle of type " << (int)oi.obstacleType << std::endl;
+		return;
+	}
+
+	if(graphics->battleACToDef[effectID].empty())
+	{
+		tlog1 << "Cannot find def for effect type " << effectID << std::endl;
+		return;
+	}
+
+	std::string defname = graphics->battleACToDef[effectID].front();
+
+	//we assume here that effect graphics have the same size as the usual obstacle image
+	// -> if we know how to blit obstacle, let's blit the effect in the same place
+	Point whereTo = whereToBlitObstacleImage(imageOfObstacle(oi), oi); 
+	addNewAnim(new CSpellEffectAnimation(this, defname, whereTo.x, whereTo.y));
+}
+
+Point CBattleInterface::whereToBlitObstacleImage(SDL_Surface *image, const CObstacleInstance &obstacle) const
+{
+	int offset = image->h % 42;
+	if(obstacle.obstacleType == CObstacleInstance::USUAL)
+	{
+		if(obstacle.getInfo().blockedTiles.front() < 0)
+			offset -= 42;
+	}
+	else if(obstacle.obstacleType == CObstacleInstance::QUICKSAND)
+	{
+		offset -= 42;
+	}
+
+	Rect r = hexPosition(obstacle.pos);
+	r.y += 42 - image->h + offset;
+	return r.topLeft();
+}
+
 std::string CBattleInterface::SiegeHelper::townTypeInfixes[GameConstants::F_NUMBER] = {"CS", "RM", "TW", "IN", "NC", "DN", "ST", "FR", "EL"};
 std::string CBattleInterface::SiegeHelper::townTypeInfixes[GameConstants::F_NUMBER] = {"CS", "RM", "TW", "IN", "NC", "DN", "ST", "FR", "EL"};
 
 
 CBattleInterface::SiegeHelper::SiegeHelper(const CGTownInstance *siegeTown, const CBattleInterface * _owner)
 CBattleInterface::SiegeHelper::SiegeHelper(const CGTownInstance *siegeTown, const CBattleInterface * _owner)

+ 7 - 1
client/BattleInterface/CBattleInterface.h

@@ -112,6 +112,9 @@ private:
 	std::map< int, CDefHandler * > idToProjectile; //projectiles of creatures (creatureID, defhandler)
 	std::map< int, CDefHandler * > idToProjectile; //projectiles of creatures (creatureID, defhandler)
 	std::map< int, CDefHandler * > idToObstacle; //obstacles located on the battlefield
 	std::map< int, CDefHandler * > idToObstacle; //obstacles located on the battlefield
 	std::map< int, SDL_Surface * > idToAbsoluteObstacle; //obstacles located on the battlefield
 	std::map< int, SDL_Surface * > idToAbsoluteObstacle; //obstacles located on the battlefield
+	CDefHandler *landMine;
+	CDefHandler *quicksand;
+
 	std::map< int, bool > creDir; // <creatureID, if false reverse creature's animation>
 	std::map< int, bool > creDir; // <creatureID, if false reverse creature's animation>
 	ui8 animCount;
 	ui8 animCount;
 	const CStack * activeStack; //number of active stack; NULL - no one
 	const CStack * activeStack; //number of active stack; NULL - no one
@@ -153,6 +156,8 @@ private:
 	void showAliveStacks(std::vector<const CStack *> *aliveStacks, int hex, std::vector<const CStack *> *flyingStacks, SDL_Surface *to); // loops through all stacks at a given hex position
 	void showAliveStacks(std::vector<const CStack *> *aliveStacks, int hex, std::vector<const CStack *> *flyingStacks, SDL_Surface *to); // loops through all stacks at a given hex position
 	void showPieceOfWall(SDL_Surface * to, int hex, const std::vector<const CStack*> & stacks); //helper function for show
 	void showPieceOfWall(SDL_Surface * to, int hex, const std::vector<const CStack*> & stacks); //helper function for show
 	void showObstacles(std::multimap<BattleHex, int> *hexToObstacle, std::vector<CObstacleInstance> &obstacles, int hex, SDL_Surface *to); // show all obstacles at a given hex position
 	void showObstacles(std::multimap<BattleHex, int> *hexToObstacle, std::vector<CObstacleInstance> &obstacles, int hex, SDL_Surface *to); // show all obstacles at a given hex position
+	SDL_Surface *imageOfObstacle(const CObstacleInstance &oi) const;
+	Point whereToBlitObstacleImage(SDL_Surface *image, const CObstacleInstance &obstacle) const;
 	void redrawBackgroundWithHexes(const CStack * activeStack);
 	void redrawBackgroundWithHexes(const CStack * activeStack);
 	void printConsoleAttacked(const CStack * defender, int dmg, int killed, const CStack * attacker, bool Multiple);
 	void printConsoleAttacked(const CStack * defender, int dmg, int killed, const CStack * attacker, bool Multiple);
 
 
@@ -270,7 +275,8 @@ public:
 	bool canStackMoveHere (const CStack * sactive, BattleHex MyNumber); //TODO: move to BattleState / callback
 	bool canStackMoveHere (const CStack * sactive, BattleHex MyNumber); //TODO: move to BattleState / callback
 
 
 	BattleHex fromWhichHexAttack(BattleHex myNumber);
 	BattleHex fromWhichHexAttack(BattleHex myNumber);
-	
+	void obstaclePlaced(const CObstacleInstance & oi);
+
 	friend class CPlayerInterface;
 	friend class CPlayerInterface;
 	friend class CAdventureMapButton;
 	friend class CAdventureMapButton;
 	friend class CInGameConsole;
 	friend class CInGameConsole;

+ 11 - 0
client/CPlayerInterface.cpp

@@ -910,6 +910,17 @@ void CPlayerInterface::battleAttack(const BattleAttack *ba)
 	}
 	}
 }
 }
 
 
+void CPlayerInterface::battleObstaclesPlaced(const CObstacleInstance &obstacle)
+{
+	EVENT_HANDLER_CALLED_BY_CLIENT;
+	if(LOCPLINT != this)
+	{ //another local interface should do this
+		return;
+	}
+
+	battleInt->obstaclePlaced(obstacle);
+}
+
 void CPlayerInterface::yourTacticPhase(int distance)
 void CPlayerInterface::yourTacticPhase(int distance)
 {
 {
 	THREAD_CREATED_BY_CLIENT;
 	THREAD_CREATED_BY_CLIENT;

+ 1 - 0
client/CPlayerInterface.h

@@ -197,6 +197,7 @@ public:
 	void battleObstaclesRemoved(const std::set<si32> & removedObstacles) OVERRIDE; //called when a certain set  of obstacles is removed from batlefield; IDs of them are given
 	void battleObstaclesRemoved(const std::set<si32> & removedObstacles) OVERRIDE; //called when a certain set  of obstacles is removed from batlefield; IDs of them are given
 	void battleCatapultAttacked(const CatapultAttack & ca) OVERRIDE; //called when catapult makes an attack
 	void battleCatapultAttacked(const CatapultAttack & ca) OVERRIDE; //called when catapult makes an attack
 	void battleStacksRemoved(const BattleStacksRemoved & bsr) OVERRIDE; //called when certain stack is completely removed from battlefield
 	void battleStacksRemoved(const BattleStacksRemoved & bsr) OVERRIDE; //called when certain stack is completely removed from battlefield
+	void battleObstaclePlaced(const CObstacleInstance &obstacle) OVERRIDE;
 	void yourTacticPhase(int distance) OVERRIDE;
 	void yourTacticPhase(int distance) OVERRIDE;
 
 
 	//-------------//
 	//-------------//

+ 5 - 0
client/NetPacksClient.cpp

@@ -595,6 +595,11 @@ void BattleTriggerEffect::applyCl(CClient * cl)
 	BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleTriggerEffect, *this);
 	BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleTriggerEffect, *this);
 }
 }
 
 
+void BattleObstaclePlaced::applyCl(CClient * cl)
+{
+	BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleObstaclesPlaced, obstacle);
+}
+
 void BattleResult::applyFirstCl( CClient *cl )
 void BattleResult::applyFirstCl( CClient *cl )
 {
 {
 	BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleEnd,this);
 	BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleEnd,this);

+ 22 - 3
lib/BattleState.cpp

@@ -229,6 +229,8 @@ bool BattleInfo::isAccessible(BattleHex hex, bool * accessibility, bool twoHex,
 
 
 void BattleInfo::makeBFS(BattleHex start, bool *accessibility, BattleHex *predecessor, int *dists, bool twoHex, bool attackerOwned, bool flying, bool fillPredecessors) const //both pointers must point to the at least 187-elements int arrays
 void BattleInfo::makeBFS(BattleHex start, bool *accessibility, BattleHex *predecessor, int *dists, bool twoHex, bool attackerOwned, bool flying, bool fillPredecessors) const //both pointers must point to the at least 187-elements int arrays
 {
 {
+	std::set<BattleHex> quicksands = getQuicksands(!attackerOwned);
+
 	//inits
 	//inits
 	for(int b=0; b<GameConstants::BFIELD_SIZE; ++b)
 	for(int b=0; b<GameConstants::BFIELD_SIZE; ++b)
 		predecessor[b] = -1;
 		predecessor[b] = -1;
@@ -244,6 +246,9 @@ void BattleInfo::makeBFS(BattleHex start, bool *accessibility, BattleHex *predec
 		std::pair<BattleHex, bool> curHex = hexq.front();
 		std::pair<BattleHex, bool> curHex = hexq.front();
 		std::vector<BattleHex> neighbours = curHex.first.neighbouringTiles();
 		std::vector<BattleHex> neighbours = curHex.first.neighbouringTiles();
 		hexq.pop();
 		hexq.pop();
+		if(curHex.first != start && !flying && vstd::contains(quicksands, curHex.first)) //walking stack can't step past the quicksands
+			continue;
+
 		for(ui32 nr=0; nr<neighbours.size(); nr++)
 		for(ui32 nr=0; nr<neighbours.size(); nr++)
 		{
 		{
 			curNext = neighbours[nr]; //if(!accessibility[curNext] || (dists[curHex]+1)>=dists[curNext])
 			curNext = neighbours[nr]; //if(!accessibility[curNext] || (dists[curHex]+1)>=dists[curNext])
@@ -1808,7 +1813,7 @@ BattleInfo * BattleInfo::setupBattle( int3 tile, int terrain, int terType, const
 			try
 			try
 			{
 			{
 				CObstacleInstance coi;
 				CObstacleInstance coi;
-				coi.isAbsoluteObstacle = true;
+				coi.obstacleType = CObstacleInstance::ABSOLUTE_OBSTACLE;
 				coi.ID = obidgen.getSuchNumber(appropriateAbsoluteObstacle);
 				coi.ID = obidgen.getSuchNumber(appropriateAbsoluteObstacle);
 				coi.uniqueID = curB->obstacles.size();
 				coi.uniqueID = curB->obstacles.size();
 				curB->obstacles.push_back(coi);
 				curB->obstacles.push_back(coi);
@@ -1819,7 +1824,7 @@ BattleInfo * BattleInfo::setupBattle( int3 tile, int terrain, int terType, const
 			}
 			}
 			catch(RangeGenerator::ExhaustedPossibilities &)
 			catch(RangeGenerator::ExhaustedPossibilities &)
 			{
 			{
-				//silently ignore, if we can't place absolute obstacle, we'll go wityh the usual ones
+				//silently ignore, if we can't place absolute obstacle, we'll go with the usual ones
 			}
 			}
 		}
 		}
 
 
@@ -2141,7 +2146,7 @@ ESpellCastProblem::ESpellCastProblem BattleInfo::battleCanCastThisSpellHere( int
 	{
 	{
 		if(!deadStack && !aliveStack)
 		if(!deadStack && !aliveStack)
 			return ESpellCastProblem::NO_APPROPRIATE_TARGET;
 			return ESpellCastProblem::NO_APPROPRIATE_TARGET;
-		if(spell->id == Spells::ANIMATE_DEAD  &&  !deadStack->hasBonusOfType(Bonus::UNDEAD)) 
+		if(spell->id == Spells::ANIMATE_DEAD  &&  deadStack  &&  !deadStack->hasBonusOfType(Bonus::UNDEAD)) 
 			return ESpellCastProblem::NO_APPROPRIATE_TARGET;
 			return ESpellCastProblem::NO_APPROPRIATE_TARGET;
 		if(deadStack && deadStack->owner != player) //you can resurrect only your own stacks //FIXME: it includes alive stacks as well
 		if(deadStack && deadStack->owner != player) //you can resurrect only your own stacks //FIXME: it includes alive stacks as well
 			return ESpellCastProblem::NO_APPROPRIATE_TARGET;
 			return ESpellCastProblem::NO_APPROPRIATE_TARGET;
@@ -2577,6 +2582,20 @@ int BattleInfo::battlefieldTypeToBI(int bfieldType)
 	return BattlefieldBI::NONE;
 	return BattlefieldBI::NONE;
 }
 }
 
 
+std::set<BattleHex> BattleInfo::getQuicksands(bool whichSidePerspective) const
+{
+	std::set<BattleHex> ret;
+	BOOST_FOREACH(const CObstacleInstance &oi, obstacles)
+	{
+		if(oi.obstacleType == CObstacleInstance::QUICKSAND 
+			&& oi.visibleForSide(whichSidePerspective)) //quicksands are visible to the caster or if owned unit stepped into that partcular patch
+		{
+			range::copy(oi.getAffectedTiles(), std::inserter(ret, ret.begin()));
+		}
+	}
+	return ret;
+}
+
 CStack::CStack(const CStackInstance *Base, int O, int I, bool AO, int S)
 CStack::CStack(const CStackInstance *Base, int O, int I, bool AO, int S)
 	: base(Base), ID(I), owner(O), slot(S), attackerOwned(AO),   
 	: base(Base), ID(I), owner(O), slot(S), attackerOwned(AO),   
 	counterAttacks(1)
 	counterAttacks(1)

+ 2 - 0
lib/BattleState.h

@@ -98,8 +98,10 @@ struct DLL_LINKAGE BattleInfo : public CBonusSystemNode
 	std::pair< std::vector<BattleHex>, int > getPath(BattleHex start, BattleHex dest, bool*accessibility, bool flyingCreature, bool twoHex, bool attackerOwned); //returned value: pair<path, length>; length may be different than number of elements in path since flying vreatures jump between distant hexes
 	std::pair< std::vector<BattleHex>, int > getPath(BattleHex start, BattleHex dest, bool*accessibility, bool flyingCreature, bool twoHex, bool attackerOwned); //returned value: pair<path, length>; length may be different than number of elements in path since flying vreatures jump between distant hexes
 	std::vector<BattleHex> getAccessibility(const CStack * stack, bool addOccupiable, std::vector<BattleHex> * attackable = NULL, bool forPassingBy = false) const; //returns vector of accessible tiles (taking into account the creature range)
 	std::vector<BattleHex> getAccessibility(const CStack * stack, bool addOccupiable, std::vector<BattleHex> * attackable = NULL, bool forPassingBy = false) const; //returns vector of accessible tiles (taking into account the creature range)
 
 
+	bool isObstacleVisibleForSide(const CObstacleInstance &obstacle, ui8 side) const;
 	bool isObstacleOnTile(BattleHex tile) const;
 	bool isObstacleOnTile(BattleHex tile) const;
 	bool isStackBlocked(const CStack * stack) const; //returns true if there is neighboring enemy stack
 	bool isStackBlocked(const CStack * stack) const; //returns true if there is neighboring enemy stack
+	std::set<BattleHex> getQuicksands(bool whichSidePerspective) const;
 
 
 	ui32 calculateDmg(const CStack* attacker, const CStack* defender, const CGHeroInstance * attackerHero, const CGHeroInstance * defendingHero, bool shooting, ui8 charge, bool lucky, bool deathBlow, bool ballistaDoubleDmg); //charge - number of hexes travelled before attack (for champion's jousting)
 	ui32 calculateDmg(const CStack* attacker, const CStack* defender, const CGHeroInstance * attackerHero, const CGHeroInstance * defendingHero, bool shooting, ui8 charge, bool lucky, bool deathBlow, bool ballistaDoubleDmg); //charge - number of hexes travelled before attack (for champion's jousting)
 	TDmgRange calculateDmgRange(const CStack* attacker, const CStack* defender, TQuantity attackerCount, TQuantity defenderCount, const CGHeroInstance * attackerHero, const CGHeroInstance * defendingHero, bool shooting, ui8 charge, bool lucky, bool deathBlow, bool ballistaDoubleDmg) const; //charge - number of hexes travelled before attack (for champion's jousting); returns pair <min dmg, max dmg>
 	TDmgRange calculateDmgRange(const CStack* attacker, const CStack* defender, TQuantity attackerCount, TQuantity defenderCount, const CGHeroInstance * attackerHero, const CGHeroInstance * defendingHero, bool shooting, ui8 charge, bool lucky, bool deathBlow, bool ballistaDoubleDmg) const; //charge - number of hexes travelled before attack (for champion's jousting); returns pair <min dmg, max dmg>

+ 5 - 3
lib/CGameState.cpp

@@ -1578,14 +1578,16 @@ void CGameState::init(StartInfo * si)
 
 
 	map->checkForObjectives(); //needs to be run when all objects are properly placed
 	map->checkForObjectives(); //needs to be run when all objects are properly placed
 
 
+	int seedAfterInit = ran();
+	tlog0 << "Seed after init is " << seedAfterInit << " (before was " << scenarioOps->seedToBeUsed << ")" << std::endl;
 	if(scenarioOps->seedPostInit > 0)
 	if(scenarioOps->seedPostInit > 0)
 	{
 	{
-		int actualSeed = ran();
-		assert(scenarioOps->seedPostInit == actualSeed); //RNG must be in the same state on all machines when initialization is done (otherwise we have desync)
+		//RNG must be in the same state on all machines when initialization is done (otherwise we have desync)
+		assert(scenarioOps->seedPostInit == seedAfterInit); 
 	}
 	}
 	else
 	else
 	{
 	{
-		scenarioOps->seedPostInit = ran(); //store the post init "seed"
+		scenarioOps->seedPostInit = seedAfterInit; //store the post init "seed"
 	}
 	}
 }
 }
 
 

+ 56 - 5
lib/CObstacleInstance.cpp

@@ -5,18 +5,69 @@
 
 
 CObstacleInstance::CObstacleInstance()
 CObstacleInstance::CObstacleInstance()
 {
 {
-	isAbsoluteObstacle = false;
+	obstacleType = USUAL;
+	casterSide = -1;
+	spellLevel = -1;
+	turnsRemaining = -1;
+	visibleForAnotherSide = -1;
 }
 }
 
 
 const CObstacleInfo & CObstacleInstance::getInfo() const
 const CObstacleInfo & CObstacleInstance::getInfo() const
 {
 {
-	if(isAbsoluteObstacle)
+	switch(obstacleType)
+	{
+	case ABSOLUTE_OBSTACLE:
 		return VLC->heroh->absoluteObstacles[ID];
 		return VLC->heroh->absoluteObstacles[ID];
+	case USUAL:
+		return VLC->heroh->obstacles[ID];
+	default:
+		assert(0);
+	}
 
 
-	return VLC->heroh->obstacles[ID];
 }
 }
 
 
 std::vector<BattleHex> CObstacleInstance::getBlocked() const
 std::vector<BattleHex> CObstacleInstance::getBlocked() const
 {
 {
-	return getInfo().getBlocked(pos);
-}
+	switch(obstacleType)
+	{
+	case ABSOLUTE_OBSTACLE:
+	case USUAL:
+		return getInfo().getBlocked(pos);
+		//TODO Force Field
+	}
+
+	return std::vector<BattleHex>();
+}
+
+std::vector<BattleHex> CObstacleInstance::getAffectedTiles() const
+{
+	switch(obstacleType)
+	{
+	case QUICKSAND:
+	case LAND_MINE:
+		return std::vector<BattleHex>(1, pos);
+		//TODO Fire Wall
+	}
+	return std::vector<BattleHex>();
+}
+
+bool CObstacleInstance::spellGenerated() const
+{
+	if(obstacleType == USUAL  ||  obstacleType == ABSOLUTE_OBSTACLE)
+		return false;
+
+	return true;
+}
+
+bool CObstacleInstance::visibleForSide(ui8 side) const
+{
+	switch(obstacleType)
+	{
+	case ABSOLUTE_OBSTACLE:
+	case USUAL:
+		return true;
+	default:
+		//we hide mines and not discovered quicksands
+		return casterSide == side  ||  visibleForAnotherSide;
+	}
+}

+ 20 - 4
lib/CObstacleInstance.h

@@ -9,15 +9,31 @@ struct DLL_LINKAGE CObstacleInstance
 	si32 ID; //ID of obstacle (defines type of it)
 	si32 ID; //ID of obstacle (defines type of it)
 	BattleHex pos; //position on battlefield
 	BattleHex pos; //position on battlefield
 
 
-	ui8 isAbsoluteObstacle; //if true, then position is meaningless
+	enum EObstacleType
+	{
+		//ABSOLUTE needs an underscore because it's a Win 
+		USUAL, ABSOLUTE_OBSTACLE, QUICKSAND, LAND_MINE, FORCE_FIELD, FIRE_WALL
+	};
 
 
-	CObstacleInstance();
+	ui8 obstacleType; //if true, then position is meaningless
 
 
-	const CObstacleInfo &getInfo() const;
+	//used only for spell-created obtsacles
+	si8 turnsRemaining;
+	si8 spellLevel;
+	si8 casterSide; //0 - obstacle created by attacker; 1 - by defender
+	si8 visibleForAnotherSide;
+
+	CObstacleInstance();
+	bool spellGenerated() const;
+	const CObstacleInfo &getInfo() const; //allowed only when not generated by spell (usual or absolute)
 	std::vector<BattleHex> getBlocked() const;
 	std::vector<BattleHex> getBlocked() const;
+	std::vector<BattleHex> getAffectedTiles() const; //to be  used with quicksands / fire walls /land mines
+	
+	bool visibleForSide(ui8 side) const; //0 attacker
 
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 	{
-		h & ID & pos & isAbsoluteObstacle & uniqueID;
+		h & ID & pos & obstacleType & uniqueID;
+		h & turnsRemaining & spellLevel & casterSide & visibleForAnotherSide;
 	}
 	}
 };
 };

+ 23 - 7
lib/IGameCallback.cpp

@@ -208,10 +208,17 @@ int CBattleInfoCallback::battleGetBattlefieldType()
 std::vector<CObstacleInstance> CBattleInfoCallback::battleGetAllObstacles()
 std::vector<CObstacleInstance> CBattleInfoCallback::battleGetAllObstacles()
 {
 {
 	//boost::shared_lock<boost::shared_mutex> lock(*gs->mx);
 	//boost::shared_lock<boost::shared_mutex> lock(*gs->mx);
+	std::vector<CObstacleInstance> ret;
 	if(gs->curB)
 	if(gs->curB)
-		return gs->curB->obstacles;
-	else
-		return std::vector<CObstacleInstance>();
+	{
+		BOOST_FOREACH(const CObstacleInstance &oi, gs->curB->obstacles)
+		{
+			if(player < 0 || oi.visibleForSide(battleGetMySide()))
+				ret.push_back(oi);
+		}
+	}
+
+	return ret;
 }
 }
 
 
 const CStack* CBattleInfoCallback::battleGetStackByID(int ID, bool onlyAlive)
 const CStack* CBattleInfoCallback::battleGetStackByID(int ID, bool onlyAlive)
@@ -405,14 +412,23 @@ const CGHeroInstance * CBattleInfoCallback::battleGetFightingHero(ui8 side) cons
 	return gs->curB->heroes[side];
 	return gs->curB->heroes[side];
 }
 }
 
 
-bool CBattleInfoCallback::battleIsBlockedByObstacle(BattleHex tile)
+unique_ptr<CObstacleInstance> CBattleInfoCallback::battleGetObstacleOnPos(BattleHex tile, bool onlyBlocking /*= true*/)
 {
 {
 	if(!gs->curB)
 	if(!gs->curB)
-		return 0;
+		return NULL;
 
 
-	return gs->curB->isObstacleOnTile(tile);
-}
 
 
+	BOOST_FOREACH(const CObstacleInstance &obs, battleGetAllObstacles())
+	{
+		if(vstd::contains(obs.getBlocked(), tile)
+			|| (!onlyBlocking  &&  vstd::contains(obs.getAffectedTiles(), tile)))
+		{
+			return make_unique<CObstacleInstance>(obs);
+		}
+	}
+
+	return NULL;
+}
 
 
 CGameState *const CPrivilagedInfoCallback::gameState ()
 CGameState *const CPrivilagedInfoCallback::gameState ()
 { 
 { 

+ 1 - 1
lib/IGameCallback.h

@@ -95,7 +95,7 @@ public:
 	//battle
 	//battle
 	int battleGetBattlefieldType(); //   1. sand/shore   2. sand/mesas   3. dirt/birches   4. dirt/hills   5. dirt/pines   6. grass/hills   7. grass/pines   8. lava   9. magic plains   10. snow/mountains   11. snow/trees   12. subterranean   13. swamp/trees   14. fiery fields   15. rock lands   16. magic clouds   17. lucid pools   18. holy ground   19. clover field   20. evil fog   21. "favourable winds" text on magic plains background   22. cursed ground   23. rough   24. ship to ship   25. ship
 	int battleGetBattlefieldType(); //   1. sand/shore   2. sand/mesas   3. dirt/birches   4. dirt/hills   5. dirt/pines   6. grass/hills   7. grass/pines   8. lava   9. magic plains   10. snow/mountains   11. snow/trees   12. subterranean   13. swamp/trees   14. fiery fields   15. rock lands   16. magic clouds   17. lucid pools   18. holy ground   19. clover field   20. evil fog   21. "favourable winds" text on magic plains background   22. cursed ground   23. rough   24. ship to ship   25. ship
 	//int battleGetObstaclesAtTile(BattleHex tile); //returns bitfield
 	//int battleGetObstaclesAtTile(BattleHex tile); //returns bitfield
-	bool battleIsBlockedByObstacle(BattleHex tile);
+	unique_ptr<CObstacleInstance> battleGetObstacleOnPos(BattleHex tile, bool onlyBlocking = true);
 	std::vector<CObstacleInstance> battleGetAllObstacles(); //returns all obstacles on the battlefield
 	std::vector<CObstacleInstance> battleGetAllObstacles(); //returns all obstacles on the battlefield
 	const CStack * battleGetStackByID(int ID, bool onlyAlive = true); //returns stack info by given ID
 	const CStack * battleGetStackByID(int ID, bool onlyAlive = true); //returns stack info by given ID
 	const CStack * battleGetStackByPos(BattleHex pos, bool onlyAlive = true); //returns stack info by given pos
 	const CStack * battleGetStackByPos(BattleHex pos, bool onlyAlive = true); //returns stack info by given pos

+ 2 - 1
lib/IGameEventsReceiver.h

@@ -34,7 +34,7 @@ struct BattleAttack;
 struct SetStackEffect;
 struct SetStackEffect;
 struct BattleTriggerEffect;
 struct BattleTriggerEffect;
 class CComponent;
 class CComponent;
-
+struct CObstacleInstance;
 
 
 class DLL_LINKAGE IBattleEventsReceiver
 class DLL_LINKAGE IBattleEventsReceiver
 {
 {
@@ -56,6 +56,7 @@ public:
 	virtual void battleObstaclesRemoved(const std::set<si32> & removedObstacles){}; //called when a certain set  of obstacles is removed from batlefield; IDs of them are given
 	virtual void battleObstaclesRemoved(const std::set<si32> & removedObstacles){}; //called when a certain set  of obstacles is removed from batlefield; IDs of them are given
 	virtual void battleCatapultAttacked(const CatapultAttack & ca){}; //called when catapult makes an attack
 	virtual void battleCatapultAttacked(const CatapultAttack & ca){}; //called when catapult makes an attack
 	virtual void battleStacksRemoved(const BattleStacksRemoved & bsr){}; //called when certain stack is completely removed from battlefield
 	virtual void battleStacksRemoved(const BattleStacksRemoved & bsr){}; //called when certain stack is completely removed from battlefield
+	virtual void battleObstaclePlaced(const CObstacleInstance &obstacle){};
 };
 };
 
 
 class DLL_LINKAGE IGameEventsReceiver
 class DLL_LINKAGE IGameEventsReceiver

+ 17 - 0
lib/NetPacks.h

@@ -10,6 +10,7 @@
 #include "ConstTransitivePtr.h"
 #include "ConstTransitivePtr.h"
 #include "int3.h"
 #include "int3.h"
 #include "ResourceSet.h"
 #include "ResourceSet.h"
+#include "CObstacleInstance.h"
 
 
 /*
 /*
  * NetPacks.h, part of VCMI engine
  * NetPacks.h, part of VCMI engine
@@ -1592,6 +1593,22 @@ struct BattleTriggerEffect : public CPackForClient //3019
 	}
 	}
 };
 };
 
 
+struct BattleObstaclePlaced : public CPackForClient //3020
+{ //activated at the beginning of turn
+	BattleObstaclePlaced(){type = 3020;};
+
+	DLL_LINKAGE void applyGs(CGameState *gs); //effect
+	void applyCl(CClient *cl); //play animations & stuff
+
+	CObstacleInstance obstacle;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & obstacle;
+	}
+};
+
+
 struct ShowInInfobox : public CPackForClient //107
 struct ShowInInfobox : public CPackForClient //107
 {
 {
 	ShowInInfobox(){type = 107;};
 	ShowInInfobox(){type = 107;};

+ 19 - 1
lib/NetPacksLib.cpp

@@ -975,6 +975,11 @@ DLL_LINKAGE void BattleTriggerEffect::applyGs( CGameState *gs )
 	}
 	}
 }
 }
 
 
+DLL_LINKAGE void BattleObstaclePlaced::applyGs( CGameState *gs )
+{
+	gs->curB->obstacles.push_back(obstacle);
+}
+
 void BattleResult::applyGs( CGameState *gs )
 void BattleResult::applyGs( CGameState *gs )
 {
 {
 	//stack with SUMMONED flag but coming from garrison -> most likely resurrected, needs to be removed
 	//stack with SUMMONED flag but coming from garrison -> most likely resurrected, needs to be removed
@@ -1015,7 +1020,20 @@ void BattleResult::applyGs( CGameState *gs )
 
 
 void BattleStackMoved::applyGs( CGameState *gs )
 void BattleStackMoved::applyGs( CGameState *gs )
 {
 {
-	gs->curB->getStack(stack)->position = tilesToMove.back();
+	CStack *s = gs->curB->getStack(stack);
+	BattleHex dest = tilesToMove.back();
+
+	//if unit ended movement on quicksands that were created by enemy, that quicksand patch becomes visible for owner
+	BOOST_FOREACH(CObstacleInstance &oi, gs->curB->obstacles)
+	{
+		if(oi.obstacleType == CObstacleInstance::QUICKSAND
+		&& vstd::contains(oi.getAffectedTiles(), tilesToMove.back())
+		&& oi.casterSide != !s->attackerOwned)
+		{
+			oi.visibleForAnotherSide = true;
+		}
+	}
+	s->position = dest;
 }
 }
 
 
 DLL_LINKAGE void BattleStackAttacked::applyGs( CGameState *gs )
 DLL_LINKAGE void BattleStackAttacked::applyGs( CGameState *gs )

+ 1 - 0
lib/RegisterTypes.h

@@ -152,6 +152,7 @@ void registerTypes2(Serializer &s)
 	s.template registerType<BattleSpellCast>();
 	s.template registerType<BattleSpellCast>();
 	s.template registerType<SetStackEffect>();
 	s.template registerType<SetStackEffect>();
 	s.template registerType<BattleTriggerEffect>();
 	s.template registerType<BattleTriggerEffect>();
+	s.template registerType<BattleObstaclePlaced>();
 	s.template registerType<BattleSetStackProperty>();
 	s.template registerType<BattleSetStackProperty>();
 	s.template registerType<StacksInjured>();
 	s.template registerType<StacksInjured>();
 	s.template registerType<BattleResultsApplied>();
 	s.template registerType<BattleResultsApplied>();

+ 91 - 3
server/CGameHandler.cpp

@@ -858,11 +858,26 @@ int CGameHandler::moveStack(int stack, BattleHex dest)
 	else //for non-flying creatures
 	else //for non-flying creatures
 	{
 	{
 		// send one package with the creature path information
 		// send one package with the creature path information
+
+		unique_ptr<CObstacleInstance> obstacle = NULL; //obstacle that interrupted movement
 		std::vector<BattleHex> tiles;
 		std::vector<BattleHex> tiles;
 		int tilesToMove = std::max((int)(path.first.size() - creSpeed), 0);
 		int tilesToMove = std::max((int)(path.first.size() - creSpeed), 0);
-		for(int v=path.first.size()-1; v>=tilesToMove; --v)
+		int v = path.first.size()-1;
+
+startWalking:
+		for(; v >= tilesToMove; --v)
 		{
 		{
-			tiles.push_back(path.first[v]);	
+			BattleHex hex = path.first[v];
+			tiles.push_back(hex);	
+
+			//if there are quicksands, we stop
+			if(obstacle = battleGetObstacleOnPos(hex, false))
+			{
+				if(!obstacle->obstacleType == CObstacleInstance::LAND_MINE || obstacle->casterSide != !curStack->attackerOwned) //if it's mine, make boom only if enemy planted it
+					break;
+				else
+					obstacle = NULL;
+			}
 		}
 		}
 	
 	
 		if (tiles.size() > 0)
 		if (tiles.size() > 0)
@@ -874,6 +889,34 @@ int CGameHandler::moveStack(int stack, BattleHex dest)
 			sm.tilesToMove = tiles;
 			sm.tilesToMove = tiles;
 			sendAndApply(&sm);
 			sendAndApply(&sm);
 		}
 		}
+
+		if(obstacle && obstacle->obstacleType == CObstacleInstance::LAND_MINE)
+		{
+			//TODO make POOF and deal dmg
+
+			BattleStackAttacked bsa;
+			bsa.flags |= BattleStackAttacked::EFFECT;
+			bsa.effect = 82;
+			bsa.damageAmount = 50; //TODO TODO TODO
+			bsa.stackAttacked = curStack->ID;
+			bsa.attackerID = -1;
+			curStack->prepareAttacked(bsa);
+			//sendAndApply(&bsa);
+			StacksInjured si;
+			si.stacks.push_back(bsa);
+			sendAndApply(&si);
+
+			ObstaclesRemoved or;
+			or.obstacles.insert(obstacle->uniqueID);
+			sendAndApply(&or);
+
+			//if stack didn't die in explosion, continue movement
+			if(curStack->alive())
+			{
+				tiles.clear();
+				goto startWalking; //TODO it's so evil
+			}
+		}
 	}
 	}
 
 
 	return ret;
 	return ret;
@@ -3685,7 +3728,52 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex dest
 
 
 	//applying effects
 	//applying effects
 	switch (spellID)
 	switch (spellID)
-	{	//damage spells
+	{	
+	case Spells::QUICKSAND:
+	case Spells::LAND_MINE:
+		{
+
+			const int baseUniqueID = gs->curB->obstacles.size() 
+				? (gs->curB->obstacles.back().uniqueID+1) 
+				: 0;
+
+
+			std::vector<BattleHex> availableTiles;
+			for(int i = 0; i < GameConstants::BFIELD_SIZE; i += 1)
+			{
+				BattleHex hex = i;
+				if(hex.getX() > 2 && hex.getX() < 14 && !battleGetStackByPos(hex, false) & !battleGetObstacleOnPos(hex, false))
+					availableTiles.push_back(hex);
+			}
+			range::random_shuffle(availableTiles);
+
+			const int patchesForSkill[] = {4, 4, 6, 8};
+			int patchesToPut = patchesForSkill[spellLvl];
+			vstd::amin(patchesToPut, availableTiles.size());
+			for (int i = 0; i < patchesToPut; i++)
+			{
+				CObstacleInstance coi;
+				coi.pos = availableTiles[i];
+				coi.casterSide = casterSide;
+				coi.obstacleType = (spellID == Spells::QUICKSAND) 
+					? CObstacleInstance::QUICKSAND 
+					: CObstacleInstance::LAND_MINE;
+				coi.ID = spellID;
+				coi.spellLevel = spellLvl;
+				coi.turnsRemaining = -1;
+				coi.uniqueID = baseUniqueID + i;
+				coi.visibleForAnotherSide = false;
+
+				BattleObstaclePlaced bop;
+				bop.obstacle = coi;
+				sendAndApply(&bop);
+			}
+
+		}
+
+		break;
+		
+	//damage spells
 	case Spells::MAGIC_ARROW:
 	case Spells::MAGIC_ARROW:
 	case Spells::ICE_BOLT:
 	case Spells::ICE_BOLT:
 	case Spells::LIGHTNING_BOLT:
 	case Spells::LIGHTNING_BOLT: