فهرست منبع

Merge remote-tracking branch 'remotes/origin/develop' into feature/VCMIMapFormat1

AlexVinS 9 سال پیش
والد
کامیت
150fcc9422

+ 1 - 1
AI/StupidAI/StupidAI.cpp

@@ -108,7 +108,7 @@ BattleAction CStupidAI::activeStack( const CStack * stack )
 	if(stack->type->idNumber == CreatureID::CATAPULT)
 	{
 		BattleAction attack;
-		static const std::vector<int> wallHexes = {50, 183, 182, 130, 62, 29, 12, 95};
+		static const std::vector<int> wallHexes = {50, 183, 182, 130, 78, 29, 12, 95};
 
 		attack.destinationTile = *RandomGeneratorUtil::nextItem(wallHexes, CRandomGenerator::getDefault());
 		attack.actionType = Battle::CATAPULT;

+ 3 - 0
ChangeLog

@@ -11,6 +11,9 @@ GENERAL:
 * New cheat code:
 - vcmiglaurung - gives 5000 crystal dragons into each slot
 
+BATTLES:
+* Drawbridge mechanics implemented (animation still missing)
+
 ADVETURE AI:
 * Fixed AI trying to go through underground rock
 * Fixed several cases causing AI wandering aimlessly

+ 8 - 0
client/CPlayerInterface.cpp

@@ -1012,6 +1012,14 @@ void CPlayerInterface::battleObstaclePlaced(const CObstacleInstance &obstacle)
 	battleInt->obstaclePlaced(obstacle);
 }
 
+void CPlayerInterface::battleGateStateChanged(const EGateState state)
+{
+	EVENT_HANDLER_CALLED_BY_CLIENT;
+	BATTLE_EVENT_POSSIBLE_RETURN;
+
+	battleInt->gateStateChanged(state);
+}
+
 void CPlayerInterface::yourTacticPhase(int distance)
 {
 	THREAD_CREATED_BY_CLIENT;

+ 1 - 0
client/CPlayerInterface.h

@@ -220,6 +220,7 @@ public:
 	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 battleObstaclePlaced(const CObstacleInstance &obstacle) override;
+	void battleGateStateChanged(const EGateState state) override;
 	void yourTacticPhase(int distance) override;
 
 	//-------------//

+ 5 - 0
client/NetPacksClient.cpp

@@ -658,6 +658,11 @@ void BattleObstaclePlaced::applyCl(CClient * cl)
 	BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleObstaclePlaced, *obstacle);
 }
 
+void BattleUpdateGateState::applyFirstCl(CClient * cl)
+{
+	BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleGateStateChanged, state);
+}
+
 void BattleResult::applyFirstCl( CClient *cl )
 {
 	BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleEnd,this);

+ 92 - 48
client/battle/CBattleInterface.cpp

@@ -1218,9 +1218,14 @@ void CBattleInterface::stackIsCatapulting(const CatapultAttack & ca)
 
 	for(auto attackInfo : ca.attackedParts)
 	{
-		SDL_FreeSurface(siegeH->walls[attackInfo.attackedPart + 2]);
-		siegeH->walls[attackInfo.attackedPart + 2] = BitmapHandler::loadBitmap(
-			siegeH->getSiegeName(attackInfo.attackedPart + 2, curInt->cb->battleGetWallState(attackInfo.attackedPart)));
+		int wallId = attackInfo.attackedPart + 2;
+		//gate state changing handled separately
+		if(wallId == SiegeHelper::GATE)
+			continue;
+
+		SDL_FreeSurface(siegeH->walls[wallId]);
+		siegeH->walls[wallId] = BitmapHandler::loadBitmap(
+			siegeH->getSiegeName(wallId, curInt->cb->battleGetWallState(attackInfo.attackedPart)));
 	}
 }
 
@@ -2735,6 +2740,37 @@ void CBattleInterface::obstaclePlaced(const CObstacleInstance & oi)
 	//CCS->soundh->playSound(sound);
 }
 
+void CBattleInterface::gateStateChanged(const EGateState state)
+{
+	auto oldState = curInt->cb->battleGetGateState();
+	bool playSound = false;
+	int stateId = EWallState::NONE;
+	switch(state)
+	{
+	case EGateState::CLOSED:
+		if(oldState != EGateState::BLOCKED)
+			playSound = true;
+		break;
+	case EGateState::BLOCKED:
+		if(oldState != EGateState::CLOSED)
+			playSound = true;
+		break;
+	case EGateState::OPENED:
+		playSound = true;
+		stateId = EWallState::DAMAGED;
+		break;
+	case EGateState::DESTROYED:
+		stateId = EWallState::DESTROYED;
+		break;
+	}
+
+	SDL_FreeSurface(siegeH->walls[SiegeHelper::GATE]);
+	if(stateId != EWallState::NONE)
+		siegeH->walls[SiegeHelper::GATE] = BitmapHandler::loadBitmap(siegeH->getSiegeName(SiegeHelper::GATE, stateId));
+	if(playSound)
+		CCS->soundh->playSound(soundBase::DRAWBRG);
+}
+
 const CGHeroInstance * CBattleInterface::currentHero() const
 {
 	if(attackingHeroInstance->tempOwner == curInt->playerID)
@@ -2791,11 +2827,11 @@ void CBattleInterface::requestAutofightingAIToTakeAction()
 }
 
 CBattleInterface::SiegeHelper::SiegeHelper(const CGTownInstance *siegeTown, const CBattleInterface * _owner)
-  : owner(_owner), town(siegeTown)
+	: owner(_owner), town(siegeTown)
 {
 	for(int g = 0; g < ARRAY_COUNT(walls); ++g)
 	{
-		walls[g] = BitmapHandler::loadBitmap( getSiegeName(g) );
+		walls[g] = BitmapHandler::loadBitmap(getSiegeName(g));
 	}
 }
 
@@ -2834,75 +2870,83 @@ std::string CBattleInterface::SiegeHelper::getSiegeName(ui16 what, int state) co
 
 	switch(what)
 	{
-	case 0: //background
+	case SiegeHelper::BACKGROUND:
 		return prefix + "BACK.BMP";
-	case 1: //background wall
+	case SiegeHelper::BACKGROUND_WALL:
 		{
 			switch(town->town->faction->index)
 			{
-			case 5: case 4: case 1: case 6:
+			case ETownType::RAMPART:
+			case ETownType::NECROPOLIS:
+			case ETownType::DUNGEON:
+			case ETownType::STRONGHOLD:
 				return prefix + "TPW1.BMP";
 			default:
 				return prefix + "TPWL.BMP";
 			}
 		}
-	case 2: //keep
+	case SiegeHelper::KEEP:
 		return prefix + "MAN" + addit + ".BMP";
-	case 3: //bottom tower
+	case SiegeHelper::BOTTOM_TOWER:
 		return prefix + "TW1" + addit + ".BMP";
-	case 4: //bottom wall
+	case SiegeHelper::BOTTOM_WALL:
 		return prefix + "WA1" + addit + ".BMP";
-	case 5: //below gate
+	case SiegeHelper::WALL_BELLOW_GATE:
 		return prefix + "WA3" + addit + ".BMP";
-	case 6: //over gate
+	case SiegeHelper::WALL_OVER_GATE:
 		return prefix + "WA4" + addit + ".BMP";
-	case 7: //upper wall
+	case SiegeHelper::UPPER_WALL:
 		return prefix + "WA6" + addit + ".BMP";
-	case 8: //upper tower
+	case SiegeHelper::UPPER_TOWER:
 		return prefix + "TW2" + addit + ".BMP";
-	case 9: //gate
+	case SiegeHelper::GATE:
 		return prefix + "DRW" + addit + ".BMP";
-	case 10: //gate arch
+	case SiegeHelper::GATE_ARCH:
 		return prefix + "ARCH.BMP";
-	case 11: //bottom static wall
+	case SiegeHelper::BOTTOM_STATIC_WALL:
 		return prefix + "WA2.BMP";
-	case 12: //upper static wall
+	case SiegeHelper::UPPER_STATIC_WALL:
 		return prefix + "WA5.BMP";
-	case 13: //moat
+	case SiegeHelper::MOAT:
 		return prefix + "MOAT.BMP";
-	case 14: //mlip
+	case SiegeHelper::BACKGROUND_MOAT:
 		return prefix + "MLIP.BMP";
-	case 15: //keep creature cover
+	case SiegeHelper::KEEP_BATTLEMENT:
 		return prefix + "MANC.BMP";
-	case 16: //bottom turret creature cover
+	case SiegeHelper::BOTTOM_BATTLEMENT:
 		return prefix + "TW1C.BMP";
-	case 17: //upper turret creature cover
+	case SiegeHelper::UPPER_BATTLEMENT:
 		return prefix + "TW2C.BMP";
 	default:
 		return "";
 	}
 }
 
-/// What: 1. background wall, 2. keep, 3. bottom tower, 4. bottom wall, 5. wall below gate,
-/// 6. wall over gate, 7. upper wall, 8. upper tower, 9. gate, 10. gate arch, 11. bottom static wall, 12. upper static wall, 13. moat, 14. mlip,
-/// 15. keep turret cover, 16. lower turret cover, 17. upper turret cover
 void CBattleInterface::SiegeHelper::printPartOfWall(SDL_Surface * to, int what)
 {
 	Point pos = Point(-1, -1);
-	auto & ci = owner->siegeH->town->town->clientInfo;
+	auto & ci = town->town->clientInfo;
 
-	if (what >= 1 && what <= 17)
+	if (vstd::iswithin(what, 1, 17))
 	{
 		pos.x = ci.siegePositions[what].x + owner->pos.x;
 		pos.y = ci.siegePositions[what].y + owner->pos.y;
 	}
 
 	if (town->town->faction->index == ETownType::TOWER
-	    && (what == 13 || what == 14))
+		&& (what == SiegeHelper::MOAT || what == SiegeHelper::BACKGROUND_MOAT))
 		return; // no moat in Tower. TODO: remove hardcode somehow?
 
 	if(pos.x != -1)
 	{
+		//gate have no displayed bitmap when drawbridge is raised
+		if(what == SiegeHelper::GATE)
+		{
+			auto gateState = owner->curInt->cb->battleGetGateState();
+			if(gateState != EGateState::OPENED && gateState != EGateState::DESTROYED)
+				return;
+		}
+
 		blitAt(walls[what], pos.x, pos.y, to);
 	}
 }
@@ -3004,7 +3048,7 @@ void CBattleInterface::showAbsoluteObstacles(SDL_Surface * to)
 			blitAt(getObstacleImage(*oi), pos.x + oi->getInfo().width, pos.y + oi->getInfo().height, to);
 
 	if (siegeH && siegeH->town->hasBuilt(BuildingID::CITADEL))
-		siegeH->printPartOfWall(to, 14); // show moat background
+		siegeH->printPartOfWall(to, SiegeHelper::BACKGROUND_MOAT);
 }
 
 void CBattleInterface::showHighlightedHexes(SDL_Surface * to)
@@ -3415,29 +3459,29 @@ BattleObjectsByHex CBattleInterface::sortObjectsByHex()
 	// Sort wall parts
 	if (siegeH)
 	{
-		sorted.beforeAll.walls.push_back(1);  // 1. background wall
-		sorted.hex[135].walls.push_back(2);   // 2. keep
-		sorted.afterAll.walls.push_back(3);   // 3. bottom tower
-		sorted.hex[182].walls.push_back(4);   // 4. bottom wall
-		sorted.hex[130].walls.push_back(5);   // 5. wall below gate,
-		sorted.hex[62].walls.push_back(6);    // 6. wall over gate
-		sorted.hex[12].walls.push_back(7);    // 7. upper wall
-		sorted.beforeAll.walls.push_back(8);  // 8. upper tower
-		//sorted.hex[94].walls.push_back(9);  // 9. gate // Not implemented it seems
-		sorted.hex[112].walls.push_back(10);  // 10. gate arch
-		sorted.hex[165].walls.push_back(11);  // 11. bottom static wall
-		sorted.hex[45].walls.push_back(12);   // 12. upper static wall
+		sorted.beforeAll.walls.push_back(SiegeHelper::BACKGROUND_WALL);
+		sorted.hex[135].walls.push_back(SiegeHelper::KEEP);
+		sorted.afterAll.walls.push_back(SiegeHelper::BOTTOM_TOWER);
+		sorted.hex[182].walls.push_back(SiegeHelper::BOTTOM_WALL);
+		sorted.hex[130].walls.push_back(SiegeHelper::WALL_BELLOW_GATE);
+		sorted.hex[78].walls.push_back(SiegeHelper::WALL_OVER_GATE);
+		sorted.hex[12].walls.push_back(SiegeHelper::UPPER_WALL);
+		sorted.beforeAll.walls.push_back(SiegeHelper::UPPER_TOWER);
+		sorted.hex[94].walls.push_back(SiegeHelper::GATE);
+		sorted.hex[112].walls.push_back(SiegeHelper::GATE_ARCH);
+		sorted.hex[165].walls.push_back(SiegeHelper::BOTTOM_STATIC_WALL);
+		sorted.hex[45].walls.push_back(SiegeHelper::UPPER_STATIC_WALL);
 
 		if (siegeH && siegeH->town->hasBuilt(BuildingID::CITADEL))
 		{
-			sorted.beforeAll.walls.push_back(13); // 13. moat
-			//sorted.beforeAll.walls.push_back(14); // 14. mlip (moat background terrain), blit as absolute obstacle
-			sorted.hex[135].walls.push_back(15);  // 15. keep turret cover
+			sorted.beforeAll.walls.push_back(SiegeHelper::MOAT);
+			//sorted.beforeAll.walls.push_back(SiegeHelper::BACKGROUND_MOAT); // blit as absolute obstacle
+			sorted.hex[135].walls.push_back(SiegeHelper::KEEP_BATTLEMENT);
 		}
 		if (siegeH && siegeH->town->hasBuilt(BuildingID::CASTLE))
 		{
-			sorted.afterAll.walls.push_back(16);  // 16. lower turret cover
-			sorted.beforeAll.walls.push_back(17); // 17. upper turret cover
+			sorted.afterAll.walls.push_back(SiegeHelper::BOTTOM_BATTLEMENT);
+			sorted.beforeAll.walls.push_back(SiegeHelper::UPPER_BATTLEMENT);
 		}
 	}
 	return sorted;

+ 28 - 10
client/battle/CBattleInterface.h

@@ -148,7 +148,7 @@ private:
 	void activateStack(); //sets activeStack to stackToActivate etc. //FIXME: No, it's not clear at all
 	std::vector<BattleHex> occupyableHexes, //hexes available for active stack
 		attackableHexes; //hexes attackable by active stack
-    bool stackCountOutsideHexes[GameConstants::BFIELD_SIZE]; // hexes that when in front of a unit cause it's amount box to move back
+	bool stackCountOutsideHexes[GameConstants::BFIELD_SIZE]; // hexes that when in front of a unit cause it's amount box to move back
 	BattleHex previouslyHoveredHex; //number of hex that was hovered by the cursor a while ago
 	BattleHex currentlyHoveredHex; //number of hex that is supposed to be hovered (for a while it may be inappropriately set, but will be renewed soon)
 	int attackingHex; //hex from which the stack would perform attack with current cursor
@@ -198,16 +198,32 @@ private:
 		SiegeHelper(const CGTownInstance * siegeTown, const CBattleInterface * _owner); //c-tor
 		~SiegeHelper(); //d-tor
 
-		//filename getters
-		//what: 0 - background,  1 - background wall,  2 - keep,          3 - bottom tower,  4 - bottom wall,
-		//      5 - below gate,  6 - over gate,        7 - upper wall,    8 - upper tower,   9 - gate,
-		//      10 - gate arch, 11 - bottom static    12 - upper static, 13 - moat,         14 - moat background,
-		//      15 - keep battlement, 16 - bottom battlement, 17 - upper battlement;
-		//      state uses EWallState enum
 		std::string getSiegeName(ui16 what) const;
-		std::string getSiegeName(ui16 what, int state) const;
-
-		void printPartOfWall(SDL_Surface * to, int what);//what: 1 - background wall, 2 - keep, 3 - bottom tower, 4 - bottom wall, 5 - below gate, 6 - over gate, 7 - upper wall, 8 - uppert tower, 9 - gate, 10 - gate arch, 11 - bottom static wall, 12 - upper static wall, 15 - keep creature cover, 16 - bottom turret creature cover, 17 - upper turret creature cover
+		std::string getSiegeName(ui16 what, int state) const; // state uses EWallState enum
+
+		void printPartOfWall(SDL_Surface * to, int what);
+
+		enum EWallVisual
+		{
+			BACKGROUND = 0,
+			BACKGROUND_WALL = 1,
+			KEEP,
+			BOTTOM_TOWER,
+			BOTTOM_WALL,
+			WALL_BELLOW_GATE,
+			WALL_OVER_GATE,
+			UPPER_WALL,
+			UPPER_TOWER,
+			GATE,
+			GATE_ARCH,
+			BOTTOM_STATIC_WALL,
+			UPPER_STATIC_WALL,
+			MOAT,
+			BACKGROUND_MOAT,
+			KEEP_BATTLEMENT,
+			BOTTOM_BATTLEMENT,
+			UPPER_BATTLEMENT
+		};
 
 		friend class CBattleInterface;
 	} * siegeH;
@@ -341,6 +357,8 @@ public:
 	BattleHex fromWhichHexAttack(BattleHex myNumber);
 	void obstaclePlaced(const CObstacleInstance & oi);
 
+	void gateStateChanged(const EGateState state);
+
 	const CGHeroInstance * currentHero() const;
 	InfoAboutHero enemyHero() const;
 

+ 2 - 0
lib/BattleState.cpp

@@ -327,6 +327,8 @@ BattleInfo * BattleInfo::setupBattle( int3 tile, ETerrainType terrain, BFieldTyp
 	//setting up siege obstacles
 	if (town && town->hasFort())
 	{
+		curB->si.gateState = EGateState::CLOSED;
+
 		for (int b = 0; b < curB->si.wallState.size(); ++b)
 		{
 			curB->si.wallState[b] = EWallState::INTACT;

+ 2 - 1
lib/BattleState.h

@@ -33,6 +33,7 @@ class CRandomGenerator;
 struct DLL_LINKAGE SiegeInfo
 {
 	std::array<si8, EWallPart::PARTS_COUNT> wallState;
+	EGateState gateState;
 
 	// return EWallState decreased by value of damage points
 	static EWallState::EWallState applyDamage(EWallState::EWallState state, unsigned int value)
@@ -51,7 +52,7 @@ struct DLL_LINKAGE SiegeInfo
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
-		h & wallState;
+		h & wallState & gateState;
 	}
 };
 

+ 33 - 8
lib/CBattleCallback.cpp

@@ -62,13 +62,13 @@ namespace SiegeStuffThatShouldBeMovedToHandlers //  <=== TODO
 		std::make_pair(183, EWallPart::BOTTOM_TOWER),
 		std::make_pair(182, EWallPart::BOTTOM_WALL),
 		std::make_pair(130, EWallPart::BELOW_GATE),
-		std::make_pair(62,  EWallPart::OVER_GATE),
+		std::make_pair(78,  EWallPart::OVER_GATE),
 		std::make_pair(29,  EWallPart::UPPER_WALL),
 		std::make_pair(12,  EWallPart::UPPER_TOWER),
 		std::make_pair(95,  EWallPart::INDESTRUCTIBLE_PART_OF_GATE),
 		std::make_pair(96,  EWallPart::GATE),
 		std::make_pair(45,  EWallPart::INDESTRUCTIBLE_PART),
-		std::make_pair(78,  EWallPart::INDESTRUCTIBLE_PART),
+		std::make_pair(62,  EWallPart::INDESTRUCTIBLE_PART),
 		std::make_pair(112, EWallPart::INDESTRUCTIBLE_PART),
 		std::make_pair(147, EWallPart::INDESTRUCTIBLE_PART)
 	};
@@ -442,6 +442,15 @@ si8 CBattleInfoEssentials::battleGetWallState(int partOfWall) const
 	return getBattle()->si.wallState[partOfWall];
 }
 
+EGateState CBattleInfoEssentials::battleGetGateState() const
+{
+	RETURN_IF_NOT_BATTLE(EGateState::NONE);
+	if(getBattle()->town == nullptr || getBattle()->town->fortLevel() == CGTownInstance::NONE)
+		return EGateState::NONE;
+
+	return getBattle()->si.gateState;
+}
+
 si8 CBattleInfoCallback::battleHasWallPenalty( const CStack * stack, BattleHex destHex ) const
 {
 	return battleHasWallPenalty(stack, stack->position, destHex);
@@ -1134,9 +1143,20 @@ AccessibilityInfo CBattleInfoCallback::getAccesibility() const
 	}
 
 	//gate -> should be before stacks
-	if(battleGetSiegeLevel() > 0 && battleGetWallState(EWallPart::GATE) != EWallState::DESTROYED)
+	if(battleGetSiegeLevel() > 0)
 	{
-		ret[95] = ret[96] = EAccessibility::GATE; //block gate's hexes
+		EAccessibility::EAccessibility accessability = EAccessibility::ACCESSIBLE;
+		switch(battleGetGateState())
+		{
+		case EGateState::CLOSED:
+			accessability = EAccessibility::GATE;
+			break;
+
+		case EGateState::BLOCKED:
+			accessability = EAccessibility::UNAVAILABLE;
+			break;
+		}
+		ret[ESiegeHex::GATE_OUTER] = ret[ESiegeHex::GATE_INNER] = accessability;
 	}
 
 	//tiles occupied by standing stacks
@@ -1157,14 +1177,19 @@ AccessibilityInfo CBattleInfoCallback::getAccesibility() const
 	//walls
 	if(battleGetSiegeLevel() > 0)
 	{
-		static const int permanentlyLocked[] = {12, 45, 78, 112, 147, 165};
+		static const int permanentlyLocked[] = {12, 45, 62, 112, 147, 165};
 		for(auto hex : permanentlyLocked)
 			ret[hex] = EAccessibility::UNAVAILABLE;
 
 		//TODO likely duplicated logic
-		static const std::pair<int, BattleHex> lockedIfNotDestroyed[] = //(which part of wall, which hex is blocked if this part of wall is not destroyed
-			{std::make_pair(2, BattleHex(182)), std::make_pair(3, BattleHex(130)),
-			std::make_pair(4, BattleHex(62)), std::make_pair(5, BattleHex(29))};
+		static const std::pair<int, BattleHex> lockedIfNotDestroyed[] =
+		{
+			//which part of wall, which hex is blocked if this part of wall is not destroyed
+			std::make_pair(2, BattleHex(ESiegeHex::DESTRUCTIBLE_WALL_4)),
+			std::make_pair(3, BattleHex(ESiegeHex::DESTRUCTIBLE_WALL_3)),
+			std::make_pair(4, BattleHex(ESiegeHex::DESTRUCTIBLE_WALL_2)),
+			std::make_pair(5, BattleHex(ESiegeHex::DESTRUCTIBLE_WALL_1))
+		};
 
 		for(auto & elem : lockedIfNotDestroyed)
 		{

+ 1 - 0
lib/CBattleCallback.h

@@ -199,6 +199,7 @@ public:
 	// for determining state of a part of the wall; format: parameter [0] - keep, [1] - bottom tower, [2] - bottom wall,
 	// [3] - below gate, [4] - over gate, [5] - upper wall, [6] - uppert tower, [7] - gate; returned value: 1 - intact, 2 - damaged, 3 - destroyed; 0 - no battle
 	si8 battleGetWallState(int partOfWall) const;
+	EGateState battleGetGateState() const;
 
 	//helpers
 	///returns all stacks, alive or dead or undead or mechanical :)

+ 23 - 0
lib/GameConstants.h

@@ -513,6 +513,29 @@ namespace EWallState
 	};
 }
 
+enum class EGateState : ui8
+{
+	NONE,
+	CLOSED,
+	BLOCKED, //dead or alive stack blocking from outside
+	OPENED,
+	DESTROYED
+};
+
+namespace ESiegeHex
+{
+	enum ESiegeHex : si16
+	{
+		DESTRUCTIBLE_WALL_1 = 29,
+		DESTRUCTIBLE_WALL_2 = 78,
+		DESTRUCTIBLE_WALL_3 = 130,
+		DESTRUCTIBLE_WALL_4 = 182,
+		GATE_BRIDGE = 94,
+		GATE_OUTER = 95,
+		GATE_INNER = 96
+	};
+}
+
 namespace ETileType
 {
 	enum ETileType

+ 1 - 0
lib/IGameEventsReceiver.h

@@ -69,6 +69,7 @@ public:
 	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 battleObstaclePlaced(const CObstacleInstance &obstacle){};
+	virtual void battleGateStateChanged(const EGateState state){};
 };
 
 class DLL_LINKAGE IGameEventsReceiver

+ 15 - 0
lib/NetPacks.h

@@ -1689,6 +1689,21 @@ struct BattleObstaclePlaced : public CPackForClient //3020
 	}
 };
 
+struct BattleUpdateGateState : public CPackForClient//3021
+{
+	BattleUpdateGateState(){type = 3021;};
+
+	void applyFirstCl(CClient *cl);
+
+	DLL_LINKAGE void applyGs(CGameState *gs);
+
+	EGateState state;
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & state;
+	}
+};
+
 
 struct ShowInInfobox : public CPackForClient //107
 {

+ 6 - 0
lib/NetPacksLib.cpp

@@ -1225,6 +1225,12 @@ DLL_LINKAGE void BattleObstaclePlaced::applyGs( CGameState *gs )
 	gs->curB->obstacles.push_back(obstacle);
 }
 
+DLL_LINKAGE void BattleUpdateGateState::applyGs(CGameState *gs)
+{
+	if(gs->curB)
+		gs->curB->si.gateState = state;
+}
+
 void BattleResult::applyGs( CGameState *gs )
 {
 	for (CStack *s : gs->curB->stacks)

+ 1 - 0
lib/registerTypes/RegisterTypes.h

@@ -270,6 +270,7 @@ void registerTypesClientPacks2(Serializer &s)
 	s.template registerType<CPackForClient, SetStackEffect>();
 	s.template registerType<CPackForClient, BattleTriggerEffect>();
 	s.template registerType<CPackForClient, BattleObstaclePlaced>();
+	s.template registerType<CPackForClient, BattleUpdateGateState>();
 	s.template registerType<CPackForClient, BattleSetStackProperty>();
 	s.template registerType<CPackForClient, StacksInjured>();
 	s.template registerType<CPackForClient, BattleResultsApplied>();

+ 1 - 1
lib/rmg/CMapGenOptions.cpp

@@ -339,7 +339,7 @@ void CMapGenOptions::updateCompOnlyPlayers()
 
 	// Add some comp only players if necessary
 	int compOnlyPlayersToAdd = getPlayerCount() - players.size();
-	
+
 	if (compOnlyPlayersToAdd < 0)
 	{
 		logGlobal->errorStream() << boost::format("Incorrect number of players to add. Requested players %d, current players %d") % playerCount % players.size();

+ 4 - 4
lib/rmg/CMapGenerator.h

@@ -55,7 +55,7 @@ public:
 	~CMapGenerator(); // required due to std::unique_ptr
 
 	std::unique_ptr<CMap> generate(CMapGenOptions * mapGenOptions, int RandomSeed = std::time(nullptr));
-	
+
 	CMapGenOptions * mapGenOptions;
 	std::unique_ptr<CMap> map;
 	CRandomGenerator rand;
@@ -74,14 +74,14 @@ public:
 	bool isFree(const int3 &tile) const;
 	bool isUsed(const int3 &tile) const;
 	bool isRoad(const int3 &tile) const;
-	
+
 	void setOccupied(const int3 &tile, ETileType::ETileType state);
 	void setRoad(const int3 &tile, ERoadType::ERoadType roadType);
-	
+
 	CTileInfo getTile(const int3 & tile) const;
 	bool isAllowedSpell(SpellID sid) const;
 
-	float getNearestObjectDistance(const int3 &tile) const; 
+	float getNearestObjectDistance(const int3 &tile) const;
 	void setNearestObjectDistance(int3 &tile, float value);
 
 	int getNextMonlithIndex();

+ 5 - 5
lib/rmg/CRmgTemplateStorage.cpp

@@ -196,7 +196,7 @@ void CRmgTemplateStorage::loadObject(std::string scope, std::string name, const
 				{
 					zone->setMinesAmount (mineInfo.first, mineInfo.second);
 				}
-					
+
 			}
 		}
 
@@ -239,8 +239,8 @@ CRmgTemplate::CSize CRmgTemplateStorage::parseMapTemplateSize(const std::string
 
 	std::vector<std::string> parts;
 	boost::split(parts, text, boost::is_any_of("+"));
-	static const std::map<std::string, int> mapSizeMapping = 
-	{ 
+	static const std::map<std::string, int> mapSizeMapping =
+	{
 		{"s", CMapHeader::MAP_SIZE_SMALL},
 		{"m", CMapHeader::MAP_SIZE_MIDDLE},
 		{"l", CMapHeader::MAP_SIZE_LARGE},
@@ -269,12 +269,12 @@ CRmgTemplate::CSize CRmgTemplateStorage::parseMapTemplateSize(const std::string
 
 ETemplateZoneType::ETemplateZoneType CRmgTemplateStorage::parseZoneType(const std::string & type) const
 {
-	static const std::map<std::string, ETemplateZoneType::ETemplateZoneType> zoneTypeMapping = 
+	static const std::map<std::string, ETemplateZoneType::ETemplateZoneType> zoneTypeMapping =
 	{
 		{"playerStart", ETemplateZoneType::PLAYER_START},
 		{"cpuStart", ETemplateZoneType::CPU_START},
 		{"treasure", ETemplateZoneType::TREASURE},
-		{"junction", ETemplateZoneType::JUNCTION},		
+		{"junction", ETemplateZoneType::JUNCTION},
 	};
 	auto it = zoneTypeMapping.find(type);
 	if(it == zoneTypeMapping.end()) throw std::runtime_error("Zone type unknown.");

+ 5 - 5
lib/rmg/CRmgTemplateZone.h

@@ -47,12 +47,12 @@ public:
 	bool isPossible() const;
 	bool isFree() const;
 	bool isUsed() const;
-	bool isRoad() const;	
+	bool isRoad() const;
 	void setOccupied(ETileType::ETileType value);
 	ETerrainType getTerrainType() const;
 	ETileType::ETileType getTileType() const;
 	void setTerrainType(ETerrainType value);
-	
+
 	void setRoadType(ERoadType::ERoadType value);
 private:
 	float nearestObjectDistance;
@@ -175,7 +175,7 @@ public:
 	bool crunchPath(CMapGenerator* gen, const int3 &src, const int3 &dst, bool onlyStraight, std::set<int3>* clearedTiles = nullptr);
 	bool connectPath(CMapGenerator* gen, const int3& src, bool onlyStraight);
 	bool connectWithCenter(CMapGenerator* gen, const int3& src, bool onlyStraight);
-	
+
 	std::vector<int3> getAccessibleOffsets (CMapGenerator* gen, CGObjectInstance* object);
 
 	void addConnection(TRmgTemplateZoneId otherZone);
@@ -229,11 +229,11 @@ private:
 	std::set<int3> possibleTiles; //optimization purposes for treasure generation
 	std::vector<TRmgTemplateZoneId> connections; //list of adjacent zones
 	std::set<int3> freePaths; //core paths of free tiles that all other objects will be linked to
-	
+
 	std::set<int3> roadNodes; //tiles to be connected with roads
 	std::set<int3> roads; //all tiles with roads
 	std::set<int3> tilesToConnectLater; //will be connected after paths are fractalized
-	
+
 	bool createRoad(CMapGenerator* gen, const int3 &src, const int3 &dst);
 	void drawRoads(CMapGenerator * gen); //actually updates tiles
 

+ 8 - 8
lib/spells/AdventureSpellMechanics.cpp

@@ -206,9 +206,9 @@ ESpellCastResult TownPortalMechanics::applyAdventureEffects(const SpellCastEnvir
 	}
 
 	CGTownInstance * town = static_cast<CGTownInstance*>(tile.visitableObjects.back());
-	
+
 	const auto relations = env->getCb()->getPlayerRelations(town->tempOwner, parameters.caster->tempOwner);
-	
+
 	if(relations == PlayerRelations::ENEMIES)
 	{
 		env->complain("Can't teleport to enemy!");
@@ -241,21 +241,21 @@ ESpellCastResult TownPortalMechanics::applyAdventureEffects(const SpellCastEnvir
 		}
 
 	}
-	
+
 	const int movementCost = GameConstants::BASE_MOVEMENT_COST * ((parameters.caster->getSpellSchoolLevel(owner) >= 3) ? 2 : 3);
-	
+
 	if(parameters.caster->movement < movementCost)
 	{
 		env->complain("This hero has not enough movement points!");
-		return ESpellCastResult::ERROR;		
+		return ESpellCastResult::ERROR;
 	}
-	
+
 	if(env->moveHero(parameters.caster->id, town->visitablePos() + parameters.caster->getVisitableOffset() ,1))
 	{
 		SetMovePoints smp;
 		smp.hid = parameters.caster->id;
 		smp.val = std::max<ui32>(0, parameters.caster->movement - movementCost);
-		env->sendAndApply(&smp);		
+		env->sendAndApply(&smp);
 	}
 	return ESpellCastResult::OK;
 }
@@ -271,7 +271,7 @@ ESpellCastResult ViewMechanics::applyAdventureEffects(const SpellCastEnvironment
 	for(const CGObjectInstance * obj : env->getMap()->objects)
 	{
 		//todo:we need to send only not visible objects
-		
+
 		if(obj)//for some reason deleted object remain as empty pointer
 			if(filterObject(obj, spellLevel))
 				pack.objectPositions.push_back(ObjectPosInfo(obj));

+ 21 - 21
lib/spells/BattleSpellMechanics.cpp

@@ -29,7 +29,7 @@ void HealingSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env,
 	for(auto & attackedCre : ctx.attackedCres)
 	{
 		StacksHealedOrResurrected::HealInfo hi;
-		hi.stackID = (attackedCre)->ID;		
+		hi.stackID = (attackedCre)->ID;
 		int stackHPgained = parameters.caster->getSpellBonus(owner, hpGained, attackedCre);
 		hi.healedHP = attackedCre->calculateHealedHealthPoints(stackHPgained, resurrect);
 		hi.lowLevelResurrection = (healLevel == EHealLevel::RESURRECT);
@@ -54,11 +54,11 @@ void AntimagicMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast
 	doDispell(battle, packet, [this](const Bonus * b) -> bool
 	{
 		if(b->source == Bonus::SPELL_EFFECT)
-		{				
+		{
 			return b->sid != owner->id; //effect from this spell
 		}
 		return false; //not a spell effect
-	});	
+	});
 }
 
 ///ChainLightningMechanics
@@ -125,12 +125,12 @@ void CloneMechanics::applyBattleEffects(const SpellCastEnvironment * env, const
 	ssp.val = 0;
 	ssp.absolute = 1;
 	env->sendAndApply(&ssp);
-	
+
 	ssp.stackID = clonedStack->ID;
 	ssp.which = BattleSetStackProperty::HAS_CLONE;
 	ssp.val = bsa.newStackID;
 	ssp.absolute = 1;
-	env->sendAndApply(&ssp);	
+	env->sendAndApply(&ssp);
 }
 
 ESpellCastProblem::ESpellCastProblem CloneMechanics::isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const
@@ -172,7 +172,7 @@ void CureMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * pac
 			CSpell * sp = SpellID(b->sid).toSpell();
 			return sp->isNegative();
 		}
-		return false; //not a spell effect		
+		return false; //not a spell effect
 	});
 }
 
@@ -193,9 +193,9 @@ ESpellCastProblem::ESpellCastProblem DispellMechanics::isImmuneByStack(const ISp
 	{
 		//just in case
 		if(!obj->alive())
-			return ESpellCastProblem::WRONG_SPELL_TARGET;			
+			return ESpellCastProblem::WRONG_SPELL_TARGET;
 	}
-	//DISPELL ignores all immunities, except specific absolute immunity 
+	//DISPELL ignores all immunities, except specific absolute immunity
 	{
 		//SPELL_IMMUNITY absolute case
 		std::stringstream cachingStr;
@@ -210,7 +210,7 @@ ESpellCastProblem::ESpellCastProblem DispellMechanics::isImmuneByStack(const ISp
 		if(obj->hasBonus(Selector::sourceType(Bonus::SPELL_EFFECT), cachingStr.str()))
 		{
 			return ESpellCastProblem::OK;
-		}		
+		}
 	}
 	return ESpellCastProblem::WRONG_SPELL_TARGET;
 	//any other immunities are ignored - do not execute default algorithm
@@ -337,7 +337,7 @@ ESpellCastProblem::ESpellCastProblem EarthquakeMechanics::canBeCast(const CBattl
 	{
 		return ESpellCastProblem::NO_APPROPRIATE_TARGET;
 	}
-	
+
 	CSpell::TargetInfo ti(owner, 0);//TODO: use real spell level
 	if(ti.smart)
 	{
@@ -353,7 +353,7 @@ ESpellCastProblem::ESpellCastProblem EarthquakeMechanics::canBeCast(const CBattl
 ESpellCastProblem::ESpellCastProblem HypnotizeMechanics::isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const
 {
 	//todo: maybe do not resist on passive cast
-	if(nullptr != caster) 
+	if(nullptr != caster)
 	{
 		//TODO: what with other creatures casting hypnotize, Faerie Dragons style?
 		ui64 subjectHealth = (obj->count - 1) * obj->MaxHealth() + obj->firstHPleft;
@@ -521,7 +521,7 @@ HealingSpellMechanics::EHealLevel RisingSpellMechanics::getHealLevel(int effectL
 	//this may be even distinct class
 	if((effectLevel <= 1) && (owner->id == SpellID::RESURRECTION))
 		return EHealLevel::RESURRECT;
-	
+
 	return EHealLevel::TRUE_RESURRECT;
 }
 
@@ -532,9 +532,9 @@ ESpellCastProblem::ESpellCastProblem SacrificeMechanics::canBeCast(const CBattle
 
 	bool targetExists = false;
 	bool targetToSacrificeExists = false;
-	
+
 	const CGHeroInstance * caster = nullptr; //todo: use ISpellCaster
-	
+
 	if(cb->battleHasHero(cb->playerToSide(player)))
 		caster = cb->battleGetFightingHero(cb->playerToSide(player));
 
@@ -624,7 +624,7 @@ ESpellCastProblem::ESpellCastProblem SpecialRisingSpellMechanics::isImmuneByStac
 		return ESpellCastProblem::STACK_IMMUNE_TO_SPELL;
 
 	//FIXME: Archangels can cast immune stack and this should be applied for them and not hero
-//	if(caster) 
+//	if(caster)
 //	{
 //		auto maxHealth = calculateHealedHP(caster, obj, nullptr);
 //		if (maxHealth < obj->MaxHealth()) //must be able to rise at least one full creature
@@ -640,16 +640,16 @@ ESpellCastProblem::ESpellCastProblem SummonMechanics::canBeCast(const CBattleInf
 	const ui8 side = cb->playerToSide(player);
 
 	//check if there are summoned elementals of other type
-	
+
 	auto otherSummoned = cb->battleGetStacksIf([side, this](const CStack * st)
 	{
 		return (st->attackerOwned == !side)
 			&& (vstd::contains(st->state, EBattleStackState::SUMMONED))
 			&& (st->getCreature()->idNumber != creatureToSummon);
 	});
-	
+
 	if(!otherSummoned.empty())
-		return ESpellCastProblem::ANOTHER_ELEMENTAL_SUMMONED;	
+		return ESpellCastProblem::ANOTHER_ELEMENTAL_SUMMONED;
 
 	return ESpellCastProblem::OK;
 }
@@ -692,7 +692,7 @@ void TeleportMechanics::applyBattleEffects(const SpellCastEnvironment * env, con
 		if(!destination.isValid())
 		{
 			env->complain("TeleportMechanics: invalid teleport destination");
-			return;			
+			return;
 		}
 		BattleStackMoved bsm;
 		bsm.distance = -1;
@@ -701,7 +701,7 @@ void TeleportMechanics::applyBattleEffects(const SpellCastEnvironment * env, con
 		tiles.push_back(destination);
 		bsm.tilesToMove = tiles;
 		bsm.teleporting = true;
-		env->sendAndApply(&bsm);		
+		env->sendAndApply(&bsm);
 	}
 	else
 	{
@@ -714,7 +714,7 @@ void TeleportMechanics::applyBattleEffects(const SpellCastEnvironment * env, con
 		bsm.tilesToMove = tiles;
 		bsm.teleporting = true;
 		env->sendAndApply(&bsm);
-	}		
+	}
 }
 
 

+ 7 - 7
lib/spells/BattleSpellMechanics.h

@@ -15,14 +15,14 @@
 class DLL_LINKAGE HealingSpellMechanics : public DefaultSpellMechanics
 {
 public:
-	enum class EHealLevel 
+	enum class EHealLevel
 	{
 		HEAL,
 		RESURRECT,
 		TRUE_RESURRECT
 	};
 
-	HealingSpellMechanics(CSpell * s): DefaultSpellMechanics(s){};	
+	HealingSpellMechanics(CSpell * s): DefaultSpellMechanics(s){};
 protected:
 	void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
 	virtual int calculateHealedHP(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const;
@@ -32,9 +32,9 @@ protected:
 class DLL_LINKAGE AntimagicMechanics : public DefaultSpellMechanics
 {
 public:
-	AntimagicMechanics(CSpell * s): DefaultSpellMechanics(s){};	
-	
-	void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override final;	
+	AntimagicMechanics(CSpell * s): DefaultSpellMechanics(s){};
+
+	void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override final;
 };
 
 class DLL_LINKAGE ChainLightningMechanics : public DefaultSpellMechanics
@@ -59,7 +59,7 @@ public:
 	CureMechanics(CSpell * s): HealingSpellMechanics(s){};
 
 	void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override final;
-	
+
 	EHealLevel getHealLevel(int effectLevel) const override final;
 };
 
@@ -130,7 +130,7 @@ public:
 	ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const override;
 protected:
 	void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
-	int calculateHealedHP(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;	
+	int calculateHealedHP(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const override;
 };
 
 ///all rising spells but SACRIFICE

+ 5 - 5
lib/spells/CDefaultSpellMechanics.h

@@ -19,7 +19,7 @@ class StacksInjured;
 struct SpellCastContext
 {
 	SpellCastContext(std::vector<const CStack *> & attackedCres, BattleSpellCast & sc, StacksInjured & si):
-		attackedCres(attackedCres), sc(sc), si(si)	
+		attackedCres(attackedCres), sc(sc), si(si)
 	{
 	};
 	std::vector<const CStack *> & attackedCres;
@@ -43,21 +43,21 @@ public:
 	std::set<const CStack *> getAffectedStacks(SpellTargetingContext & ctx) const override;
 
 	ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const override;
-	
+
 	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const override;
 
 	virtual void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const override;
 	bool adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override final;
 	void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const override final;
 
-	void battleLogSingleTarget(std::vector<std::string> & logLines, const BattleSpellCast * packet, 
-		const std::string & casterName, const CStack * attackedStack, bool & displayDamage) const override;	
+	void battleLogSingleTarget(std::vector<std::string> & logLines, const BattleSpellCast * packet,
+		const std::string & casterName, const CStack * attackedStack, bool & displayDamage) const override;
 protected:
 	virtual void applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const;
 
 	///actual adventure cast implementation
 	virtual ESpellCastResult applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const;
-	
+
 	void doDispell(BattleInfo * battle, const BattleSpellCast * packet, const CSelector & selector) const;
 private:
 	void castMagicMirror(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const;

+ 7 - 7
lib/spells/CSpellHandler.h

@@ -68,7 +68,7 @@ public:
 	{
 		std::string resourceName;
 		VerticalPosition verticalPosition;
-		int pause; 
+		int pause;
 
 		AnimationItem();
 
@@ -78,7 +78,7 @@ public:
 			if(version >= 754) //save format backward compatibility
 			{
 				h & pause;
-			}			
+			}
 			else if(!h.saving)
 			{
 				pause = 0;
@@ -128,8 +128,8 @@ public:
 		std::string range;
 
 		std::vector<Bonus> effects;
-		
-		std::vector<Bonus *> effectsTmp; //TODO: this should replace effects 
+
+		std::vector<Bonus *> effectsTmp; //TODO: this should replace effects
 
 		LevelInfo();
 		~LevelInfo();
@@ -265,13 +265,13 @@ public:
 	friend class Graphics;
 public:
 	///internal interface (for callbacks)
-	
+
 	///Checks general but spell-specific problems for all casting modes. Use only during battle.
 	ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const;
 
 	///checks for creature immunity / anything that prevent casting *at given hex* - doesn't take into account general problems such as not having spellbook or mana points etc.
 	ESpellCastProblem::ESpellCastProblem isImmuneAt(const CBattleInfoCallback * cb, const ISpellCaster * caster, ECastingMode::ECastingMode mode, BattleHex destination) const;
-	
+
 	///checks for creature immunity / anything that prevent casting *at given target* - doesn't take into account general problems such as not having spellbook or mana points etc.
 	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const;
 public:
@@ -295,7 +295,7 @@ public://internal, for use only by Mechanics classes
 	///applies caster`s secondary skills and affectedCreature`s to raw damage
 	int adjustRawDamage(const ISpellCaster * caster, const CStack * affectedCreature, int rawDamage) const;
 	///returns raw damage or healed HP
-	int calculateRawEffectValue(int effectLevel, int effectPower) const;		
+	int calculateRawEffectValue(int effectLevel, int effectPower) const;
 	///generic immunity calculation
 	ESpellCastProblem::ESpellCastProblem internalIsImmune(const ISpellCaster * caster, const CStack *obj) const;
 

+ 3 - 3
lib/spells/CreatureSpellMechanics.cpp

@@ -48,7 +48,7 @@ void DeathStareMechanics::applyBattleEffects(const SpellCastEnvironment * env, c
 		BattleStackAttacked bsa;
 		bsa.flags |= BattleStackAttacked::SPELL_EFFECT;
 		bsa.spellID = owner->id;
-		bsa.damageAmount = parameters.effectPower * (attackedCre)->valOfBonuses(Bonus::STACK_HEALTH);//todo: move here all DeathStare calculation 
+		bsa.damageAmount = parameters.effectPower * (attackedCre)->valOfBonuses(Bonus::STACK_HEALTH);//todo: move here all DeathStare calculation
 		bsa.stackAttacked = (attackedCre)->ID;
 		bsa.attackerID = -1;
 		(attackedCre)->prepareAttacked(bsa, env->getRandomGenerator());
@@ -60,8 +60,8 @@ void DeathStareMechanics::applyBattleEffects(const SpellCastEnvironment * env, c
 void DispellHelpfulMechanics::applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const
 {
 	DefaultSpellMechanics::applyBattle(battle, packet);
-	
-	doDispell(battle, packet, Selector::positiveSpellEffects);	
+
+	doDispell(battle, packet, Selector::positiveSpellEffects);
 }
 
 ESpellCastProblem::ESpellCastProblem DispellHelpfulMechanics::isImmuneByStack(const ISpellCaster * caster,  const CStack * obj) const

+ 9 - 9
lib/spells/ISpellMechanics.cpp

@@ -24,14 +24,14 @@ BattleSpellCastParameters::Destination::Destination(const CStack * destination):
 	stackValue(destination),
 	hexValue(destination->position)
 {
-	
+
 }
 
 BattleSpellCastParameters::Destination::Destination(const BattleHex & destination):
 	stackValue(nullptr),
-	hexValue(destination)	
+	hexValue(destination)
 {
-	
+
 }
 
 BattleSpellCastParameters::BattleSpellCastParameters(const BattleInfo * cb, const ISpellCaster * caster, const CSpell * spell)
@@ -68,10 +68,10 @@ void BattleSpellCastParameters::prepare(const CSpell * spell)
 	effectPower = caster->getEffectPower(spell);
 	effectValue = caster->getEffectValue(spell);
 	enchantPower = caster->getEnchantPower(spell);
-	
+
 	vstd::amax(spellLvl, 0);
 	vstd::amax(effectLevel, 0);
-	vstd::amax(enchantPower, 0);	
+	vstd::amax(enchantPower, 0);
 	vstd::amax(enchantPower, 0);
 	vstd::amax(effectValue, 0);
 }
@@ -118,11 +118,11 @@ ISpellMechanics * ISpellMechanics::createMechanics(CSpell * s)
 	case SpellID::SACRIFICE:
 		return new SacrificeMechanics(s);
 	case SpellID::SUMMON_FIRE_ELEMENTAL:
-		return new SummonMechanics(s, CreatureID::FIRE_ELEMENTAL);			
+		return new SummonMechanics(s, CreatureID::FIRE_ELEMENTAL);
 	case SpellID::SUMMON_EARTH_ELEMENTAL:
-		return new SummonMechanics(s, CreatureID::EARTH_ELEMENTAL);		
+		return new SummonMechanics(s, CreatureID::EARTH_ELEMENTAL);
 	case SpellID::SUMMON_WATER_ELEMENTAL:
-		return new SummonMechanics(s, CreatureID::WATER_ELEMENTAL);		
+		return new SummonMechanics(s, CreatureID::WATER_ELEMENTAL);
 	case SpellID::SUMMON_AIR_ELEMENTAL:
 		return new SummonMechanics(s, CreatureID::AIR_ELEMENTAL);
 	case SpellID::TELEPORT:
@@ -151,4 +151,4 @@ ISpellMechanics * ISpellMechanics::createMechanics(CSpell * s)
 			return new DefaultSpellMechanics(s);
 	}
 }
-		
+

+ 12 - 12
lib/spells/ISpellMechanics.h

@@ -34,13 +34,13 @@ public:
 struct DLL_LINKAGE BattleSpellCastParameters
 {
 public:
-	///Single spell destination. 
+	///Single spell destination.
 	/// (assumes that anything but battle stack can share same hex)
 	struct DLL_LINKAGE Destination
 	{
-		explicit Destination(const CStack * destination); 
+		explicit Destination(const CStack * destination);
 		explicit Destination(const BattleHex & destination);
-		
+
 		const CStack * stackValue;
 		const BattleHex hexValue;
 	};
@@ -49,10 +49,10 @@ public:
 	void aimToHex(const BattleHex & destination);
 	void aimToStack(const CStack * destination);
 	BattleHex getFirstDestinationHex() const;
-	
+
 	const BattleInfo * cb;
 	const ISpellCaster * caster;
-	const PlayerColor casterColor;	
+	const PlayerColor casterColor;
 	const ui8 casterSide;
 
 	std::vector<Destination> destinations;
@@ -63,7 +63,7 @@ public:
 	const CStack * selectedStack;//deprecated
 
 	///spell school level
-	int spellLvl;	
+	int spellLvl;
 	///spell school level to use for effects
 	int effectLevel;
 	///actual spell-power affecting effect values
@@ -72,7 +72,7 @@ public:
 	int enchantPower;
 	///for Archangel-like casting
 	int effectValue;
-private:	
+private:
 	void prepare(const CSpell * spell);
 };
 
@@ -105,18 +105,18 @@ public:
 
 	virtual std::vector<BattleHex> rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool * outDroppedHexes = nullptr) const = 0;
 	virtual std::set<const CStack *> getAffectedStacks(SpellTargetingContext & ctx) const = 0;
-	
+
 	virtual ESpellCastProblem::ESpellCastProblem canBeCast(const CBattleInfoCallback * cb, PlayerColor player) const = 0;
-	
+
 	virtual ESpellCastProblem::ESpellCastProblem isImmuneByStack(const ISpellCaster * caster, const CStack * obj) const = 0;
-	
+
 	virtual void applyBattle(BattleInfo * battle, const BattleSpellCast * packet) const = 0;
 	virtual bool adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const = 0;
 	virtual void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const = 0;
 
-	virtual void battleLogSingleTarget(std::vector<std::string> & logLines, const BattleSpellCast * packet, 
+	virtual void battleLogSingleTarget(std::vector<std::string> & logLines, const BattleSpellCast * packet,
 		const std::string & casterName, const CStack * attackedStack, bool & displayDamage) const = 0;
-	
+
 	static ISpellMechanics * createMechanics(CSpell * s);
 protected:
 	CSpell * owner;

+ 5 - 5
lib/spells/Magic.h

@@ -23,15 +23,15 @@ class DLL_LINKAGE ISpellCaster
 {
 public:
 	virtual ~ISpellCaster(){};
-	
+
 	/// returns level on which given spell would be cast by this(0 - none, 1 - basic etc);
-	/// caster may not know this spell at all 
+	/// caster may not know this spell at all
 	/// optionally returns number of selected school by arg - 0 - air magic, 1 - fire magic, 2 - water magic, 3 - earth magic
-	virtual ui8 getSpellSchoolLevel(const CSpell * spell, int *outSelectedSchool = nullptr) const = 0;	
+	virtual ui8 getSpellSchoolLevel(const CSpell * spell, int *outSelectedSchool = nullptr) const = 0;
 
 	///applying sorcery secondary skill etc
 	virtual ui32 getSpellBonus(const CSpell * spell, ui32 base, const CStack * affectedStack) const = 0;
-	
+
 	///default spell school level for effect calculation
 	virtual int getEffectLevel(const CSpell * spell) const = 0;
 
@@ -43,6 +43,6 @@ public:
 
 	///damage/heal override(ignores spell configuration, effect level and effect power)
 	virtual int getEffectValue(const CSpell * spell) const = 0;
-	
+
 	virtual const PlayerColor getOwner() const = 0;
 };

+ 200 - 18
server/CGameHandler.cpp

@@ -1005,7 +1005,8 @@ int CGameHandler::moveStack(int stack, BattleHex dest)
 		assert(gs->curB->isInTacticRange(dest));
 	}
 
-	if(curStack->position == dest)
+	auto start = curStack->position;
+	if(start == dest)
 		return 0;
 
 	//initing necessary tables
@@ -1032,16 +1033,60 @@ int CGameHandler::moveStack(int stack, BattleHex dest)
 		return 0;
 	}
 
-	std::pair< std::vector<BattleHex>, int > path = gs->curB->getPath(curStack->position, dest, curStack);
+	bool canUseGate = false;
+	auto dbState = gs->curB->si.gateState;
+	if(battleGetSiegeLevel() > 0 && !curStack->attackerOwned &&
+		dbState != EGateState::DESTROYED &&
+		dbState != EGateState::BLOCKED)
+	{
+		canUseGate = true;
+	}
+
+	std::pair< std::vector<BattleHex>, int > path = gs->curB->getPath(start, dest, curStack);
 
 	ret = path.second;
 
 	int creSpeed = gs->curB->tacticDistance ? GameConstants::BFIELD_SIZE : curStack->Speed();
 
+	auto isGateDrawbridgeHex = [&](BattleHex hex) -> bool
+	{
+		if(gs->curB->town->subID == ETownType::FORTRESS && hex == ESiegeHex::GATE_BRIDGE)
+			return true;
+		if(hex == ESiegeHex::GATE_OUTER)
+			return true;
+		if(hex == ESiegeHex::GATE_INNER)
+			return true;
+
+		return false;
+	};
+
+	auto occupyGateDrawbridgeHex = [&](BattleHex hex) -> bool
+	{
+		if(isGateDrawbridgeHex(hex))
+			return true;
+
+		if(curStack->doubleWide())
+		{
+			BattleHex otherHex = curStack->occupiedHex(hex);
+			if(otherHex.isValid() && isGateDrawbridgeHex(otherHex))
+				return true;
+		}
+
+		return false;
+	};
+
 	if(curStack->hasBonusOfType(Bonus::FLYING))
 	{
 		if(path.second <= creSpeed && path.first.size() > 0)
 		{
+			if(canUseGate && dbState != EGateState::OPENED &&
+				occupyGateDrawbridgeHex(dest))
+			{
+				BattleUpdateGateState db;
+				db.state = EGateState::OPENED;
+				sendAndApply(&db);
+			}
+
 			//inform clients about move
 			BattleStackMoved sm;
 			sm.stack = curStack->ID;
@@ -1059,6 +1104,72 @@ int CGameHandler::moveStack(int stack, BattleHex dest)
 		std::vector<BattleHex> tiles;
 		const int tilesToMove = std::max((int)(path.first.size() - creSpeed), 0);
 		int v = path.first.size()-1;
+		path.first.push_back(start);
+
+		// check if gate need to be open or closed at some point
+		BattleHex openGateAtHex, gateMayCloseAtHex;
+		if(canUseGate)
+		{
+			for(int i = path.first.size()-1; i >= 0; i--)
+			{
+				auto needOpenGates = [&](BattleHex hex) -> bool
+				{
+					if(gs->curB->town->subID == ETownType::FORTRESS && hex == ESiegeHex::GATE_BRIDGE)
+						return true;
+					if(hex == ESiegeHex::GATE_BRIDGE && i-1 >= 0 && path.first[i-1] == ESiegeHex::GATE_OUTER)
+						return true;
+					else if(hex == ESiegeHex::GATE_OUTER || hex == ESiegeHex::GATE_INNER)
+						return true;
+
+					return false;
+				};
+
+				auto hex = path.first[i];
+				if(!openGateAtHex.isValid() && dbState != EGateState::OPENED)
+				{
+					if(needOpenGates(hex))
+						openGateAtHex = path.first[i+1];
+
+					//TODO we need find batter way to handle double-wide stacks
+					//currently if only second occupied stack part is standing on gate / bridge hex then stack will start to wait for bridge to lower before it's needed. Though this is just a visual bug.
+					if(curStack->doubleWide())
+					{
+						BattleHex otherHex = curStack->occupiedHex(hex);
+						if(otherHex.isValid() && needOpenGates(otherHex))
+							openGateAtHex = path.first[i+2];
+					}
+
+					//gate may be opened and then closed during stack movement, but not other way around
+					if(openGateAtHex.isValid())
+						dbState = EGateState::OPENED;
+				}
+
+				if(!gateMayCloseAtHex.isValid() && dbState != EGateState::CLOSED)
+				{
+					if(hex == ESiegeHex::GATE_INNER && i-1 >= 0 && path.first[i-1] != ESiegeHex::GATE_OUTER)
+					{
+						gateMayCloseAtHex = path.first[i-1];
+					}
+					if(gs->curB->town->subID == ETownType::FORTRESS)
+					{
+						if(hex == ESiegeHex::GATE_BRIDGE && i-1 >= 0 && path.first[i-1] != ESiegeHex::GATE_OUTER)
+						{
+							gateMayCloseAtHex = path.first[i-1];
+						}
+						else if(hex == ESiegeHex::GATE_OUTER && i-1 >= 0 &&
+							path.first[i-1] != ESiegeHex::GATE_INNER &&
+							path.first[i-1] != ESiegeHex::GATE_BRIDGE)
+						{
+							gateMayCloseAtHex = path.first[i-1];
+						}
+					}
+					else if(hex == ESiegeHex::GATE_OUTER && i-1 >= 0 && path.first[i-1] != ESiegeHex::GATE_INNER)
+					{
+						gateMayCloseAtHex = path.first[i-1];
+					}
+				}
+			}
+		}
 
 		bool stackIsMoving = true;
 
@@ -1070,22 +1181,35 @@ int CGameHandler::moveStack(int stack, BattleHex dest)
 				break;
 			}
 
-			for(bool obstacleHit = false; (!obstacleHit) && (v >= tilesToMove); --v)
+			bool gateStateChanging = false;
+			//special handling for opening gate on from starting hex
+			if(openGateAtHex.isValid() && openGateAtHex == start)
+				gateStateChanging = true;
+			else
 			{
-				BattleHex hex = path.first[v];
-				tiles.push_back(hex);
-
-				//if we walked onto something, finalize this portion of stack movement check into obstacle
-				if((obstacle = battleGetObstacleOnPos(hex, false)))
-					obstacleHit = true;
-
-				if(curStack->doubleWide())
+				for(bool obstacleHit = false; (!obstacleHit) && (!gateStateChanging) && (v >= tilesToMove); --v)
 				{
-					BattleHex otherHex = curStack->occupiedHex(hex);
+					BattleHex hex = path.first[v];
+					tiles.push_back(hex);
 
-					//two hex creature hit obstacle by backside
-					if(otherHex.isValid() && ((obstacle2 = battleGetObstacleOnPos(otherHex, false))))
+					if((openGateAtHex.isValid() && openGateAtHex == hex) ||
+						(gateMayCloseAtHex.isValid() && gateMayCloseAtHex == hex))
+					{
+						gateStateChanging = true;
+					}
+
+					//if we walked onto something, finalize this portion of stack movement check into obstacle
+					if((obstacle = battleGetObstacleOnPos(hex, false)))
 						obstacleHit = true;
+
+					if(curStack->doubleWide())
+					{
+						BattleHex otherHex = curStack->occupiedHex(hex);
+
+						//two hex creature hit obstacle by backside
+						if(otherHex.isValid() && ((obstacle2 = battleGetObstacleOnPos(otherHex, false))))
+							obstacleHit = true;
+					}
 				}
 			}
 
@@ -1121,6 +1245,26 @@ int CGameHandler::moveStack(int stack, BattleHex dest)
 				processObstacle(obstacle);
 				if(curStack->alive())
 					processObstacle(obstacle2);
+
+				if(gateStateChanging)
+				{
+					if(curStack->position == openGateAtHex)
+					{
+						openGateAtHex = BattleHex();
+						//only open gate if stack is still alive
+						if(curStack->alive())
+						{
+							BattleUpdateGateState db;
+							db.state = EGateState::OPENED;
+							sendAndApply(&db);
+						}
+					}
+					else if(curStack->position == gateMayCloseAtHex)
+					{
+						gateMayCloseAtHex = BattleHex();
+						updateGateState();
+					}
+				}
 			}
 			else
 				//movement finished normally: we reached destination
@@ -1731,8 +1875,13 @@ void CGameHandler::setupBattle( int3 tile, const CArmedInstance *armies[2], cons
 	sendAndApply(&bs);
 }
 
-void CGameHandler::checkForBattleEnd()
+void CGameHandler::checkBattleStateChanges()
 {
+	//check if drawbridge state need to be changes
+	if(battleGetSiegeLevel() > 0)
+		updateGateState();
+
+	//check if battle ended
 	if(auto result = battleIsFinished())
 	{
 		setBattleResult(BattleResult::NORMAL, *result);
@@ -3466,6 +3615,39 @@ bool CGameHandler::queryReply(QueryID qid, ui32 answer, PlayerColor player)
 
 static EndAction end_action;
 
+void CGameHandler::updateGateState()
+{
+	BattleUpdateGateState db;
+	db.state = gs->curB->si.gateState;
+	if(gs->curB->si.wallState[EWallPart::GATE] == EWallState::DESTROYED)
+	{
+		db.state = EGateState::DESTROYED;
+	}
+	else if(db.state == EGateState::OPENED)
+	{
+		if(!gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_OUTER), false) &&
+			!gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_INNER), false))
+		{
+			if(gs->curB->town->subID == ETownType::FORTRESS)
+			{
+				if(!gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_BRIDGE), false))
+					db.state = EGateState::CLOSED;
+			}
+			else if(gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_BRIDGE)))
+				db.state = EGateState::BLOCKED;
+			else
+				db.state = EGateState::CLOSED;
+		}
+	}
+	else if(gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_BRIDGE), false))
+		db.state = EGateState::BLOCKED;
+	else
+		db.state = EGateState::CLOSED;
+
+	if(db.state != gs->curB->si.gateState)
+		sendAndApply(&db);
+}
+
 bool CGameHandler::makeBattleAction( BattleAction &ba )
 {
 	bool ok = true;
@@ -4218,7 +4400,7 @@ bool CGameHandler::makeCustomAction( BattleAction &ba )
 			{
 				battleMadeAction.setn(true);
 			}
-			checkForBattleEnd();
+			checkBattleStateChanges();
 			if(battleResult.get())
 			{
 				battleMadeAction.setn(true);
@@ -5605,7 +5787,7 @@ void CGameHandler::runBattle()
 					break;
 				}
 				//we're after action, all results applied
-				checkForBattleEnd(); //check if this action ended the battle
+				checkBattleStateChanges(); //check if this action ended the battle
 
 				if(next != nullptr)
 				{
@@ -5657,7 +5839,7 @@ bool CGameHandler::makeAutomaticAction(const CStack *stack, BattleAction &ba)
 	sendAndApply(&bsa);
 
 	bool ret = makeBattleAction(ba);
-	checkForBattleEnd();
+	checkBattleStateChanges();
 	return ret;
 }
 

+ 2 - 1
server/CGameHandler.h

@@ -115,7 +115,7 @@ public:
 	void endBattle(int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2); //ends battle
 	void prepareAttack(BattleAttack &bat, const CStack *att, const CStack *def, int distance, int targetHex); //distance - number of hexes travelled before attacking
 	void applyBattleEffects(BattleAttack &bat, const CStack *att, const CStack *def, int distance, bool secondary); //damage, drain life & fire shield
-	void checkForBattleEnd();
+	void checkBattleStateChanges();
 	void setupBattle(int3 tile, const CArmedInstance *armies[2], const CGHeroInstance *heroes[2], bool creatureBank, const CGTownInstance *town);
 	void setBattleResult(BattleResult::EResult resultType, int victoriusSide);
 	void duelFinished();
@@ -203,6 +203,7 @@ public:
 	PlayerColor getPlayerAt(CConnection *c) const;
 
 	void playerMessage( PlayerColor player, const std::string &message, ObjectInstanceID currObj);
+	void updateGateState();
 	bool makeBattleAction(BattleAction &ba);
 	bool makeAutomaticAction(const CStack *stack, BattleAction &ba); //used when action is taken by stack without volition of player (eg. unguided catapult attack)
 	bool makeCustomAction(BattleAction &ba);