Bladeren bron

Separated siege-related part of BattleInterface into separate class

- added constants for BattleHex'es of castle towers
Ivan Savenko 2 jaren geleden
bovenliggende
commit
a65dd0726d

+ 2 - 4
client/battle/CBattleAnimations.cpp

@@ -15,6 +15,7 @@
 #include "CBattleInterfaceClasses.h"
 #include "CBattleInterface.h"
 #include "CBattleProjectileController.h"
+#include "CBattleSiegeController.h"
 #include "CCreatureAnimation.h"
 
 #include "../CGameInfo.h"
@@ -734,10 +735,7 @@ bool CShootingAnimation::init()
 	const CCreature *shooterInfo = shooter->getCreature();
 
 	if(shooterInfo->idNumber == CreatureID::ARROW_TOWERS)
-	{
-		CreatureID creID = owner->siegeH->town->town->clientInfo.siegeShooter;
-		shooterInfo = CGI->creh->objects[creID];
-	}
+		shooterInfo = owner->siegeController->turretCreature();
 
 	Point shooterPos;
 	Point shotPos;

+ 23 - 327
client/battle/CBattleInterface.cpp

@@ -14,6 +14,7 @@
 #include "CBattleInterfaceClasses.h"
 #include "CCreatureAnimation.h"
 #include "CBattleProjectileController.h"
+#include "CBattleSiegeController.h"
 
 #include "../CBitmapHandler.h"
 #include "../CGameInfo.h"
@@ -109,7 +110,7 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet
 	activeStack(nullptr), mouseHoveredStack(nullptr), stackToActivate(nullptr), selectedStack(nullptr), previouslyHoveredHex(-1),
 	currentlyHoveredHex(-1), attackingHex(-1), stackCanCastSpell(false), creatureCasting(false), spellDestSelectMode(false), spellToCast(nullptr), sp(nullptr),
 	creatureSpellToCast(-1),
-	siegeH(nullptr), attackerInt(att), defenderInt(defen), curInt(att), animIDhelper(0),
+	attackerInt(att), defenderInt(defen), curInt(att), animIDhelper(0),
 	myTurn(false), moveStarted(false), moveSoundHander(-1), bresult(nullptr), battleActionsStarted(false)
 {
 	OBJ_CONSTRUCTION;
@@ -164,9 +165,7 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet
 	//preparing siege info
 	const CGTownInstance *town = curInt->cb->battleGetDefendedTown();
 	if(town && town->hasFort())
-	{
-		siegeH = new SiegeHelper(town, this);
-	}
+		siegeController.reset(new CBattleSiegeController(this, town));
 
 	CPlayerInterface::battleInt = this;
 
@@ -180,30 +179,7 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet
 	}
 
 	//preparing menu background and terrain
-	if(siegeH)
-	{
-		background = BitmapHandler::loadBitmap( siegeH->getSiegeName(0), false );
-		ui8 siegeLevel = curInt->cb->battleGetSiegeLevel();
-		if (siegeLevel >= 2) //citadel or castle
-		{
-			//print moat/mlip
-			SDL_Surface *moat = BitmapHandler::loadBitmap( siegeH->getSiegeName(13) ),
-				* mlip = BitmapHandler::loadBitmap( siegeH->getSiegeName(14) );
-
-			auto & info = siegeH->town->town->clientInfo;
-			Point moatPos(info.siegePositions[13].x, info.siegePositions[13].y);
-			Point mlipPos(info.siegePositions[14].x, info.siegePositions[14].y);
-
-			if (moat) //eg. tower has no moat
-				blitAt(moat, moatPos.x,moatPos.y, background);
-			if (mlip) //eg. tower has no mlip
-				blitAt(mlip, mlipPos.x, mlipPos.y, background);
-
-			SDL_FreeSurface(moat);
-			SDL_FreeSurface(mlip);
-		}
-	}
-	else
+	if(!siegeController)
 	{
 		auto bfieldType = curInt->cb->battleGetBattlefieldType();
 
@@ -451,8 +427,6 @@ CBattleInterface::~CBattleInterface()
 	SDL_FreeSurface(cellBorder);
 	SDL_FreeSurface(cellShade);
 
-	delete siegeH;
-
 	//TODO: play AI tracks if battle was during AI turn
 	//if (!curInt->makingTurn)
 	//CCS->musich->playMusicFromSet(CCS->musich->aiMusics, -1);
@@ -971,31 +945,14 @@ void CBattleInterface::unitAdded(const CStack * stack)
 
 	if(stack->initialPosition < 0) //turret
 	{
-		const CCreature *turretCreature = CGI->creh->objects[siegeH->town->town->clientInfo.siegeShooter];
-
-		creAnims[stack->ID] = AnimationControls::getAnimation(turretCreature);
+		assert(siegeController);
 
-		// Turret positions are read out of the config/wall_pos.txt
-		int posID = 0;
-		switch (stack->initialPosition)
-		{
-		case -2: // keep creature
-			posID = 18;
-			break;
-		case -3: // bottom creature
-			posID = 19;
-			break;
-		case -4: // upper creature
-			posID = 20;
-			break;
-		}
+		const CCreature *turretCreature = siegeController->turretCreature();
 
-		if (posID != 0)
-		{
-			coords.x = siegeH->town->town->clientInfo.siegePositions[posID].x + this->pos.x;
-			coords.y = siegeH->town->town->clientInfo.siegePositions[posID].y + this->pos.y;
-		}
+		creAnims[stack->ID] = AnimationControls::getAnimation(turretCreature);
 		creAnims[stack->ID]->pos.h = 225;
+
+		coords = siegeController->turretCreaturePosition(stack->initialPosition);
 	}
 	else
 	{
@@ -1170,16 +1127,6 @@ bool CBattleInterface::isTileAttackable(const BattleHex & number) const
 	return false;
 }
 
-bool CBattleInterface::isCatapultAttackable(BattleHex hex) const
-{
-	if (!siegeH || tacticsMode) return false;
-
-	auto wallPart = curInt->cb->battleHexToWallPart(hex);
-	if (!curInt->cb->isWallPartPotentiallyAttackable(wallPart)) return false;
-
-	auto state = curInt->cb->battleGetWallState(static_cast<int>(wallPart));
-	return state != EWallState::DESTROYED && state != EWallState::NONE;
-}
 
 const CGHeroInstance * CBattleInterface::getActiveHero()
 {
@@ -1204,38 +1151,14 @@ void CBattleInterface::hexLclicked(int whichOne)
 
 void CBattleInterface::stackIsCatapulting(const CatapultAttack & ca)
 {
-	if (ca.attacker != -1)
-	{
-		const CStack *stack = curInt->cb->battleGetStackByID(ca.attacker);
-		for (auto attackInfo : ca.attackedParts)
-		{
-			addNewAnim(new CShootingAnimation(this, stack, attackInfo.destinationTile, nullptr, true, attackInfo.damageDealt));
-		}
-	}
-	else
-	{
-		//no attacker stack, assume spell-related (earthquake) - only hit animation
-		for (auto attackInfo : ca.attackedParts)
-		{
-			Point destPos = CClickableHex::getXYUnitAnim(attackInfo.destinationTile, nullptr, this) + Point(99, 120);
-
-			addNewAnim(new CEffectAnimation(this, "SGEXPL.DEF", destPos.x, destPos.y));
-		}
-	}
-
-	waitForAnims();
-
-	for (auto attackInfo : ca.attackedParts)
-	{
-		int wallId = attackInfo.attackedPart + 2;
-		//gate state changing handled separately
-		if (wallId == SiegeHelper::GATE)
-			continue;
+	if (siegeController)
+		siegeController->stackIsCatapulting(ca);
+}
 
-		SDL_FreeSurface(siegeH->walls[wallId]);
-		siegeH->walls[wallId] = BitmapHandler::loadBitmap(
-			siegeH->getSiegeName(wallId, curInt->cb->battleGetWallState(attackInfo.attackedPart)));
-	}
+void CBattleInterface::gateStateChanged(const EGateState state)
+{
+	if (siegeController)
+		siegeController->gateStateChanged(state);
 }
 
 void CBattleInterface::battleFinished(const BattleResult& br)
@@ -2178,7 +2101,7 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
 				}
 				break;
 			case PossiblePlayerBattleAction::CATAPULT:
-				if (isCatapultAttackable(myNumber))
+				if (siegeController && siegeController->isCatapultAttackable(myNumber))
 					legalAction = true;
 				break;
 			case PossiblePlayerBattleAction::HEAL:
@@ -2743,39 +2666,6 @@ 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;
-	}
-
-	if (oldState != EGateState::NONE && oldState != EGateState::CLOSED && oldState != EGateState::BLOCKED)
-		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)
@@ -2835,138 +2725,6 @@ void CBattleInterface::requestAutofightingAIToTakeAction()
 	aiThread.detach();
 }
 
-CBattleInterface::SiegeHelper::SiegeHelper(const CGTownInstance *siegeTown, const CBattleInterface *_owner)
-	: owner(_owner), town(siegeTown)
-{
-	for (int g = 0; g < ARRAY_COUNT(walls); ++g)
-	{
-		if (g != SiegeHelper::GATE)
-			walls[g] = BitmapHandler::loadBitmap(getSiegeName(g));
-	}
-}
-
-CBattleInterface::SiegeHelper::~SiegeHelper()
-{
-	auto gateState = owner->curInt->cb->battleGetGateState();
-	for (int g = 0; g < ARRAY_COUNT(walls); ++g)
-	{
-		if (g != SiegeHelper::GATE || (gateState != EGateState::NONE && gateState != EGateState::CLOSED && gateState != EGateState::BLOCKED))
-			SDL_FreeSurface(walls[g]);
-	}
-}
-
-std::string CBattleInterface::SiegeHelper::getSiegeName(ui16 what) const
-{
-	return getSiegeName(what, EWallState::INTACT);
-}
-
-std::string CBattleInterface::SiegeHelper::getSiegeName(ui16 what, int state) const
-{
-	auto getImageIndex = [&]() -> int
-	{
-		switch (state)
-		{
-		case EWallState::INTACT : return 1;
-		case EWallState::DAMAGED :
-			if(what == 2 || what == 3 || what == 8) // towers don't have separate image here - INTACT and DAMAGED is 1, DESTROYED is 2
-				return 1;
-			else
-				return 2;
-		case EWallState::DESTROYED :
-			if (what == 2 || what == 3 || what == 8)
-				return 2;
-			else
-				return 3;
-		}
-		return 1;
-	};
-
-	const std::string & prefix = town->town->clientInfo.siegePrefix;
-	std::string addit = boost::lexical_cast<std::string>(getImageIndex());
-
-	switch(what)
-	{
-	case SiegeHelper::BACKGROUND:
-		return prefix + "BACK.BMP";
-	case SiegeHelper::BACKGROUND_WALL:
-		{
-			switch(town->town->faction->index)
-			{
-			case ETownType::RAMPART:
-			case ETownType::NECROPOLIS:
-			case ETownType::DUNGEON:
-			case ETownType::STRONGHOLD:
-				return prefix + "TPW1.BMP";
-			default:
-				return prefix + "TPWL.BMP";
-			}
-		}
-	case SiegeHelper::KEEP:
-		return prefix + "MAN" + addit + ".BMP";
-	case SiegeHelper::BOTTOM_TOWER:
-		return prefix + "TW1" + addit + ".BMP";
-	case SiegeHelper::BOTTOM_WALL:
-		return prefix + "WA1" + addit + ".BMP";
-	case SiegeHelper::WALL_BELLOW_GATE:
-		return prefix + "WA3" + addit + ".BMP";
-	case SiegeHelper::WALL_OVER_GATE:
-		return prefix + "WA4" + addit + ".BMP";
-	case SiegeHelper::UPPER_WALL:
-		return prefix + "WA6" + addit + ".BMP";
-	case SiegeHelper::UPPER_TOWER:
-		return prefix + "TW2" + addit + ".BMP";
-	case SiegeHelper::GATE:
-		return prefix + "DRW" + addit + ".BMP";
-	case SiegeHelper::GATE_ARCH:
-		return prefix + "ARCH.BMP";
-	case SiegeHelper::BOTTOM_STATIC_WALL:
-		return prefix + "WA2.BMP";
-	case SiegeHelper::UPPER_STATIC_WALL:
-		return prefix + "WA5.BMP";
-	case SiegeHelper::MOAT:
-		return prefix + "MOAT.BMP";
-	case SiegeHelper::BACKGROUND_MOAT:
-		return prefix + "MLIP.BMP";
-	case SiegeHelper::KEEP_BATTLEMENT:
-		return prefix + "MANC.BMP";
-	case SiegeHelper::BOTTOM_BATTLEMENT:
-		return prefix + "TW1C.BMP";
-	case SiegeHelper::UPPER_BATTLEMENT:
-		return prefix + "TW2C.BMP";
-	default:
-		return "";
-	}
-}
-
-void CBattleInterface::SiegeHelper::printPartOfWall(SDL_Surface *to, int what)
-{
-	Point pos = Point(-1, -1);
-	auto & ci = town->town->clientInfo;
-
-	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 == 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);
-	}
-}
-
 void CBattleInterface::showAll(SDL_Surface *to)
 {
 	show(to);
@@ -3041,9 +2799,8 @@ void CBattleInterface::showAbsoluteObstacles(SDL_Surface * to)
 		}
 	}
 
-
-	if (siegeH && siegeH->town->hasBuilt(BuildingID::CITADEL))
-		siegeH->printPartOfWall(to, SiegeHelper::BACKGROUND_MOAT);
+	if ( siegeController )
+		siegeController->showAbsoluteObstacles(to);
 }
 
 void CBattleInterface::showHighlightedHexes(SDL_Surface *to)
@@ -3143,7 +2900,8 @@ void CBattleInterface::showBattlefieldObjects(SDL_Surface *to)
 {
 	auto showHexEntry = [&](BattleObjectsByHex::HexData & hex)
 	{
-		showPiecesOfWall(to, hex.walls);
+		if (siegeController)
+			siegeController->showPiecesOfWall(to, hex.walls);
 		showObstacles(to, hex.obstacles);
 		showAliveStacks(to, hex.alive);
 		showBattleEffects(to, hex.effects);
@@ -3436,33 +3194,9 @@ BattleObjectsByHex CBattleInterface::sortObjectsByHex()
 		}
 	}
 	// Sort wall parts
-	if (siegeH)
-	{
-		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 (siegeController)
+		siegeController->sortObjectsByHex(sorted);
 
-		if (siegeH && siegeH->town->hasBuilt(BuildingID::CITADEL))
-		{
-			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(SiegeHelper::BOTTOM_BATTLEMENT);
-			sorted.beforeAll.walls.push_back(SiegeHelper::UPPER_BATTLEMENT);
-		}
-	}
 	return sorted;
 }
 
@@ -3604,41 +3338,3 @@ void CBattleInterface::redrawBackgroundWithHexes(const CStack *activeStack)
 		CSDL_Ext::blit8bppAlphaTo24bpp(cellBorders, nullptr, backgroundWithHexes, nullptr);
 }
 
-void CBattleInterface::showPiecesOfWall(SDL_Surface *to, std::vector<int> pieces)
-{
-	if (!siegeH)
-		return;
-	for (auto piece : pieces)
-	{
-		if (piece < 15) // not a tower - just print
-			siegeH->printPartOfWall(to, piece);
-		else // tower. find if tower is built and not destroyed - stack is present
-		{
-			// PieceID    StackID
-			// 15 = keep,  -2
-			// 16 = lower, -3
-			// 17 = upper, -4
-
-			// tower. check if tower is alive - stack is found
-			int stackPos = 13 - piece;
-
-			const CStack *turret = nullptr;
-
-			for (auto & stack : curInt->cb->battleGetAllStacks(true))
-			{
-				if(stack->initialPosition == stackPos)
-				{
-					turret = stack;
-					break;
-				}
-			}
-
-			if (turret)
-			{
-				std::vector<const CStack *> stackList(1, turret);
-				showStacks(to, stackList);
-				siegeH->printPartOfWall(to, piece);
-			}
-		}
-	}
-}

+ 3 - 44
client/battle/CBattleInterface.h

@@ -59,6 +59,7 @@ class CAnimation;
 class IImage;
 
 class CBattleProjectileController;
+class CBattleSiegeController;
 
 /// Small struct which contains information about the id of the attacked stack, the damage dealt,...
 struct StackAttackedInfo
@@ -188,52 +189,9 @@ private:
 	void sendCommand(BattleAction *& command, const CStack * actor = nullptr);
 
 	bool isTileAttackable(const BattleHex & number) const; //returns true if tile 'number' is neighboring any tile from active stack's range or is one of these tiles
-	bool isCatapultAttackable(BattleHex hex) const; //returns true if given tile can be attacked by catapult
 
 	std::list<BattleEffect> battleEffects; //different animations to display on the screen like spell effects
 
-	/// Class which is responsible for drawing the wall of a siege during battle
-	class SiegeHelper
-	{
-	private:
-		SDL_Surface* walls[18];
-		const CBattleInterface *owner;
-	public:
-		const CGTownInstance *town; //besieged town
-
-		SiegeHelper(const CGTownInstance *siegeTown, const CBattleInterface *_owner);
-		~SiegeHelper();
-
-		std::string getSiegeName(ui16 what) const;
-		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;
-
 	std::shared_ptr<CPlayerInterface> attackerInt, defenderInt; //because LOCPLINT is not enough in hotSeat
 	std::shared_ptr<CPlayerInterface> curInt; //current player interface
 	const CGHeroInstance *getActiveHero(); //returns hero that can currently cast a spell
@@ -252,7 +210,6 @@ private:
 	void showAliveStacks(SDL_Surface *to, std::vector<const CStack *> stacks);
 	void showStacks(SDL_Surface *to, std::vector<const CStack *> stacks);
 	void showObstacles(SDL_Surface *to, std::vector<std::shared_ptr<const CObstacleInstance>> &obstacles);
-	void showPiecesOfWall(SDL_Surface *to, std::vector<int> pieces);
 
 	void showBattleEffects(SDL_Surface *to, const std::vector<const BattleEffect *> &battleEffects);
 
@@ -269,6 +226,7 @@ private:
 	void setHeroAnimation(ui8 side, int phase);
 public:
 	std::unique_ptr<CBattleProjectileController> projectilesController;
+	std::unique_ptr<CBattleSiegeController> siegeController;
 
 	static CondSh<bool> animsAreDisplayed; //for waiting with the end of battle for end of anims
 	static CondSh<BattleAction *> givenCommand; //data != nullptr if we have i.e. moved current unit
@@ -397,4 +355,5 @@ public:
 	friend class CCastAnimation;
 	friend class CClickableHex;
 	friend class CBattleProjectileController;
+	friend class CBattleSiegeController;
 };

+ 23 - 37
client/battle/CBattleInterfaceClasses.cpp

@@ -11,6 +11,7 @@
 #include "CBattleInterfaceClasses.h"
 
 #include "CBattleInterface.h"
+#include "CBattleSiegeController.h"
 
 #include "../CBitmapHandler.h"
 #include "../CGameInfo.h"
@@ -585,48 +586,33 @@ Point CClickableHex::getXYUnitAnim(BattleHex hexNum, const CStack * stack, CBatt
 
 	Point ret(-500, -500); //returned value
 	if(stack && stack->initialPosition < 0) //creatures in turrets
-	{
-		switch(stack->initialPosition)
-		{
-		case -2: //keep
-			ret = cbi->siegeH->town->town->clientInfo.siegePositions[18];
-			break;
-		case -3: //lower turret
-			ret = cbi->siegeH->town->town->clientInfo.siegePositions[19];
-			break;
-		case -4: //upper turret
-			ret = cbi->siegeH->town->town->clientInfo.siegePositions[20];
-			break;
-		}
-	}
-	else
-	{
-		static const Point basePos(-190, -139); // position of creature in topleft corner
-		static const int imageShiftX = 30; // X offset to base pos for facing right stacks, negative for facing left
+		return cbi->siegeController->turretCreaturePosition(stack->initialPosition);
+
+	static const Point basePos(-190, -139); // position of creature in topleft corner
+	static const int imageShiftX = 30; // X offset to base pos for facing right stacks, negative for facing left
 
-		ret.x = basePos.x + 22 * ( (hexNum.getY() + 1)%2 ) + 44 * hexNum.getX();
-		ret.y = basePos.y + 42 * hexNum.getY();
+	ret.x = basePos.x + 22 * ( (hexNum.getY() + 1)%2 ) + 44 * hexNum.getX();
+	ret.y = basePos.y + 42 * hexNum.getY();
+
+	if (stack)
+	{
+		if(cbi->creDir[stack->ID])
+			ret.x += imageShiftX;
+		else
+			ret.x -= imageShiftX;
 
-		if (stack)
+		//shifting position for double - hex creatures
+		if(stack->doubleWide())
 		{
-			if(cbi->creDir[stack->ID])
-				ret.x += imageShiftX;
+			if(stack->side == BattleSide::ATTACKER)
+			{
+				if(cbi->creDir[stack->ID])
+					ret.x -= 44;
+			}
 			else
-				ret.x -= imageShiftX;
-
-			//shifting position for double - hex creatures
-			if(stack->doubleWide())
 			{
-				if(stack->side == BattleSide::ATTACKER)
-				{
-					if(cbi->creDir[stack->ID])
-						ret.x -= 44;
-				}
-				else
-				{
-					if(!cbi->creDir[stack->ID])
-						ret.x += 44;
-				}
+				if(!cbi->creDir[stack->ID])
+					ret.x += 44;
 			}
 		}
 	}

+ 4 - 5
client/battle/CBattleProjectileController.cpp

@@ -15,6 +15,7 @@
 #include "../CGameInfo.h"
 #include "../gui/CAnimation.h"
 #include "CBattleInterface.h"
+#include "CBattleSiegeController.h"
 #include "CCreatureAnimation.h"
 
 CatapultProjectileInfo::CatapultProjectileInfo(const Point &from, const Point &dest)
@@ -56,7 +57,7 @@ void CBattleProjectileController::initStackProjectile(const CStack * stack)
 {
 	const CCreature * creature;//creature whose shots should be loaded
 	if(stack->getCreature()->idNumber == CreatureID::ARROW_TOWERS)
-		creature = CGI->creh->objects[owner->siegeH->town->town->clientInfo.siegeShooter];
+		creature = owner->siegeController->turretCreature();
 	else
 		creature = stack->getCreature();
 
@@ -210,10 +211,8 @@ void CBattleProjectileController::createProjectile(const CStack * shooter, const
 	const CCreature *shooterInfo = shooter->getCreature();
 
 	if(shooterInfo->idNumber == CreatureID::ARROW_TOWERS)
-	{
-		int creID = owner->siegeH->town->town->clientInfo.siegeShooter;
-		shooterInfo = CGI->creh->operator[](creID);
-	}
+		shooterInfo = owner->siegeController->turretCreature();
+
 	if(!shooterInfo->animation.missleFrameAngles.size())
 		logAnim->error("Mod error: Creature '%s' on the Archer's tower is not a shooter. Mod should be fixed. Trying to use archer's data instead..."
 			, shooterInfo->nameSing);

+ 355 - 0
client/battle/CBattleSiegeController.cpp

@@ -10,3 +10,358 @@
 #include "StdInc.h"
 #include "CBattleSiegeController.h"
 
+#include "../CMusicHandler.h"
+#include "../../lib/NetPacks.h"
+#include "../CGameInfo.h"
+#include "../../CCallback.h"
+#include "../CBitmapHandler.h"
+#include "../CPlayerInterface.h"
+#include "../../lib/CStack.h"
+#include "../../lib/mapObjects/CGTownInstance.h"
+#include "CBattleInterface.h"
+#include "CBattleAnimations.h"
+#include "CBattleInterfaceClasses.h"
+
+CBattleSiegeController::~CBattleSiegeController()
+{
+	auto gateState = owner->curInt->cb->battleGetGateState();
+	for (int g = 0; g < ARRAY_COUNT(walls); ++g)
+	{
+		if (g != EWallVisual::GATE || (gateState != EGateState::NONE && gateState != EGateState::CLOSED && gateState != EGateState::BLOCKED))
+			SDL_FreeSurface(walls[g]);
+	}
+}
+
+std::string CBattleSiegeController::getSiegeName(ui16 what) const
+{
+	return getSiegeName(what, EWallState::INTACT);
+}
+
+std::string CBattleSiegeController::getSiegeName(ui16 what, int state) const
+{
+	auto getImageIndex = [&]() -> int
+	{
+		switch (state)
+		{
+		case EWallState::INTACT : return 1;
+		case EWallState::DAMAGED :
+			if(what == 2 || what == 3 || what == 8) // towers don't have separate image here - INTACT and DAMAGED is 1, DESTROYED is 2
+				return 1;
+			else
+				return 2;
+		case EWallState::DESTROYED :
+			if (what == 2 || what == 3 || what == 8)
+				return 2;
+			else
+				return 3;
+		}
+		return 1;
+	};
+
+	const std::string & prefix = town->town->clientInfo.siegePrefix;
+	std::string addit = boost::lexical_cast<std::string>(getImageIndex());
+
+	switch(what)
+	{
+	case EWallVisual::BACKGROUND:
+		return prefix + "BACK.BMP";
+	case EWallVisual::BACKGROUND_WALL:
+		{
+			switch(town->town->faction->index)
+			{
+			case ETownType::RAMPART:
+			case ETownType::NECROPOLIS:
+			case ETownType::DUNGEON:
+			case ETownType::STRONGHOLD:
+				return prefix + "TPW1.BMP";
+			default:
+				return prefix + "TPWL.BMP";
+			}
+		}
+	case EWallVisual::KEEP:
+		return prefix + "MAN" + addit + ".BMP";
+	case EWallVisual::BOTTOM_TOWER:
+		return prefix + "TW1" + addit + ".BMP";
+	case EWallVisual::BOTTOM_WALL:
+		return prefix + "WA1" + addit + ".BMP";
+	case EWallVisual::WALL_BELLOW_GATE:
+		return prefix + "WA3" + addit + ".BMP";
+	case EWallVisual::WALL_OVER_GATE:
+		return prefix + "WA4" + addit + ".BMP";
+	case EWallVisual::UPPER_WALL:
+		return prefix + "WA6" + addit + ".BMP";
+	case EWallVisual::UPPER_TOWER:
+		return prefix + "TW2" + addit + ".BMP";
+	case EWallVisual::GATE:
+		return prefix + "DRW" + addit + ".BMP";
+	case EWallVisual::GATE_ARCH:
+		return prefix + "ARCH.BMP";
+	case EWallVisual::BOTTOM_STATIC_WALL:
+		return prefix + "WA2.BMP";
+	case EWallVisual::UPPER_STATIC_WALL:
+		return prefix + "WA5.BMP";
+	case EWallVisual::MOAT:
+		return prefix + "MOAT.BMP";
+	case EWallVisual::BACKGROUND_MOAT:
+		return prefix + "MLIP.BMP";
+	case EWallVisual::KEEP_BATTLEMENT:
+		return prefix + "MANC.BMP";
+	case EWallVisual::BOTTOM_BATTLEMENT:
+		return prefix + "TW1C.BMP";
+	case EWallVisual::UPPER_BATTLEMENT:
+		return prefix + "TW2C.BMP";
+	default:
+		return "";
+	}
+}
+
+void CBattleSiegeController::printPartOfWall(SDL_Surface *to, int what)
+{
+	Point pos = Point(-1, -1);
+	auto & ci = town->town->clientInfo;
+
+	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 == EWallVisual::MOAT || what == EWallVisual::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 == EWallVisual::GATE)
+		{
+			auto gateState = owner->curInt->cb->battleGetGateState();
+			if (gateState != EGateState::OPENED && gateState != EGateState::DESTROYED)
+				return;
+		}
+
+		blitAt(walls[what], pos.x, pos.y, to);
+	}
+}
+
+
+CBattleSiegeController::CBattleSiegeController(CBattleInterface * owner, const CGTownInstance *siegeTown)
+{
+	owner->background = BitmapHandler::loadBitmap( getSiegeName(0), false );
+	ui8 siegeLevel = owner->curInt->cb->battleGetSiegeLevel();
+	if (siegeLevel >= 2) //citadel or castle
+	{
+		//print moat/mlip
+		SDL_Surface *moat = BitmapHandler::loadBitmap( getSiegeName(13) ),
+			* mlip = BitmapHandler::loadBitmap( getSiegeName(14) );
+
+		auto & info = town->town->clientInfo;
+		Point moatPos(info.siegePositions[13].x, info.siegePositions[13].y);
+		Point mlipPos(info.siegePositions[14].x, info.siegePositions[14].y);
+
+		if (moat) //eg. tower has no moat
+			blitAt(moat, moatPos.x,moatPos.y, owner->background);
+		if (mlip) //eg. tower has no mlip
+			blitAt(mlip, mlipPos.x, mlipPos.y, owner->background);
+
+		SDL_FreeSurface(moat);
+		SDL_FreeSurface(mlip);
+	}
+
+	for (int g = 0; g < ARRAY_COUNT(walls); ++g)
+	{
+		if (g != EWallVisual::GATE)
+			walls[g] = BitmapHandler::loadBitmap(getSiegeName(g));
+	}
+}
+
+const CCreature *CBattleSiegeController::turretCreature()
+{
+	return CGI->creh->objects[town->town->clientInfo.siegeShooter];
+}
+
+Point CBattleSiegeController::turretCreaturePosition( BattleHex position )
+{
+	// Turret positions are read out of the config/wall_pos.txt
+	int posID = 0;
+	switch (position)
+	{
+	case BattleHex::CASTLE_CENTRAL_TOWER: // keep creature
+		posID = 18;
+		break;
+	case BattleHex::CASTLE_BOTTOM_TOWER: // bottom creature
+		posID = 19;
+		break;
+	case BattleHex::CASTLE_UPPER_TOWER: // upper creature
+		posID = 20;
+		break;
+	}
+
+	if (posID != 0)
+	{
+		Point result = owner->pos.topLeft();
+		result.x += town->town->clientInfo.siegePositions[posID].x;
+		result.y += town->town->clientInfo.siegePositions[posID].y;
+		return result;
+	}
+
+	assert(0);
+	return Point(0,0);
+}
+
+
+void CBattleSiegeController::gateStateChanged(const EGateState state)
+{
+	auto oldState = owner->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;
+	}
+
+	if (oldState != EGateState::NONE && oldState != EGateState::CLOSED && oldState != EGateState::BLOCKED)
+		SDL_FreeSurface(walls[EWallVisual::GATE]);
+
+	if (stateId != EWallState::NONE)
+		walls[EWallVisual::GATE] = BitmapHandler::loadBitmap(getSiegeName(EWallVisual::GATE, stateId));
+	if (playSound)
+		CCS->soundh->playSound(soundBase::DRAWBRG);
+}
+
+void CBattleSiegeController::showAbsoluteObstacles(SDL_Surface * to)
+{
+	if (town->hasBuilt(BuildingID::CITADEL))
+		printPartOfWall(to, EWallVisual::BACKGROUND_MOAT);
+}
+
+void CBattleSiegeController::sortObjectsByHex(BattleObjectsByHex & sorted)
+{
+
+	sorted.beforeAll.walls.push_back(EWallVisual::BACKGROUND_WALL);
+	sorted.hex[135].walls.push_back(EWallVisual::KEEP);
+	sorted.afterAll.walls.push_back(EWallVisual::BOTTOM_TOWER);
+	sorted.hex[182].walls.push_back(EWallVisual::BOTTOM_WALL);
+	sorted.hex[130].walls.push_back(EWallVisual::WALL_BELLOW_GATE);
+	sorted.hex[78].walls.push_back(EWallVisual::WALL_OVER_GATE);
+	sorted.hex[12].walls.push_back(EWallVisual::UPPER_WALL);
+	sorted.beforeAll.walls.push_back(EWallVisual::UPPER_TOWER);
+	sorted.hex[94].walls.push_back(EWallVisual::GATE);
+	sorted.hex[112].walls.push_back(EWallVisual::GATE_ARCH);
+	sorted.hex[165].walls.push_back(EWallVisual::BOTTOM_STATIC_WALL);
+	sorted.hex[45].walls.push_back(EWallVisual::UPPER_STATIC_WALL);
+
+	if (town->hasBuilt(BuildingID::CITADEL))
+	{
+		sorted.beforeAll.walls.push_back(EWallVisual::MOAT);
+		//sorted.beforeAll.walls.push_back(EWallVisual::BACKGROUND_MOAT); // blit as absolute obstacle
+		sorted.hex[135].walls.push_back(EWallVisual::KEEP_BATTLEMENT);
+	}
+	if (town->hasBuilt(BuildingID::CASTLE))
+	{
+		sorted.afterAll.walls.push_back(EWallVisual::BOTTOM_BATTLEMENT);
+		sorted.beforeAll.walls.push_back(EWallVisual::UPPER_BATTLEMENT);
+	}
+}
+
+
+void CBattleSiegeController::showPiecesOfWall(SDL_Surface *to, std::vector<int> pieces)
+{
+	for (auto piece : pieces)
+	{
+		if (piece < 15) // not a tower - just print
+			printPartOfWall(to, piece);
+		else // tower. find if tower is built and not destroyed - stack is present
+		{
+			// PieceID    StackID
+			// 15 = keep,  -2
+			// 16 = lower, -3
+			// 17 = upper, -4
+
+			// tower. check if tower is alive - stack is found
+			int stackPos = 13 - piece;
+
+			const CStack *turret = nullptr;
+
+			for (auto & stack : owner->curInt->cb->battleGetAllStacks(true))
+			{
+				if(stack->initialPosition == stackPos)
+				{
+					turret = stack;
+					break;
+				}
+			}
+
+			if (turret)
+			{
+				std::vector<const CStack *> stackList(1, turret);
+				owner->showStacks(to, stackList);
+				printPartOfWall(to, piece);
+			}
+		}
+	}
+}
+
+
+bool CBattleSiegeController::isCatapultAttackable(BattleHex hex) const
+{
+	if (owner->tacticsMode)
+		return false;
+
+	auto wallPart = owner->curInt->cb->battleHexToWallPart(hex);
+	if (!owner->curInt->cb->isWallPartPotentiallyAttackable(wallPart))
+		return false;
+
+	auto state = owner->curInt->cb->battleGetWallState(static_cast<int>(wallPart));
+	return state != EWallState::DESTROYED && state != EWallState::NONE;
+}
+
+void CBattleSiegeController::stackIsCatapulting(const CatapultAttack & ca)
+{
+	if (ca.attacker != -1)
+	{
+		const CStack *stack = owner->curInt->cb->battleGetStackByID(ca.attacker);
+		for (auto attackInfo : ca.attackedParts)
+		{
+			owner->addNewAnim(new CShootingAnimation(owner, stack, attackInfo.destinationTile, nullptr, true, attackInfo.damageDealt));
+		}
+	}
+	else
+	{
+		//no attacker stack, assume spell-related (earthquake) - only hit animation
+		for (auto attackInfo : ca.attackedParts)
+		{
+			Point destPos = CClickableHex::getXYUnitAnim(attackInfo.destinationTile, nullptr, owner) + Point(99, 120);
+
+			owner->addNewAnim(new CEffectAnimation(owner, "SGEXPL.DEF", destPos.x, destPos.y));
+		}
+	}
+
+	owner->waitForAnims();
+
+	for (auto attackInfo : ca.attackedParts)
+	{
+		int wallId = attackInfo.attackedPart + 2;
+		//gate state changing handled separately
+		if (wallId == EWallVisual::GATE)
+			continue;
+
+		SDL_FreeSurface(walls[wallId]);
+		walls[wallId] = BitmapHandler::loadBitmap(
+			getSiegeName(wallId, owner->curInt->cb->battleGetWallState(attackInfo.attackedPart)));
+	}
+}

+ 59 - 0
client/battle/CBattleSiegeController.h

@@ -9,9 +9,68 @@
  */
 #pragma once
 
+#include "../../lib/GameConstants.h"
 
+struct BattleObjectsByHex;
+struct CatapultAttack;
+struct SDL_Surface;
+struct BattleHex;
+struct Point;
+class CGTownInstance;
+class CBattleInterface;
+class CCreature;
+
+namespace EWallVisual
+{
+	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
+	};
+}
 
 class CBattleSiegeController
 {
+	CBattleInterface * owner;
+
+	SDL_Surface* walls[18];
+	const CGTownInstance *town; //besieged town
+
+	std::string getSiegeName(ui16 what) const;
+	std::string getSiegeName(ui16 what, int state) const; // state uses EWallState enum
+
+	void printPartOfWall(SDL_Surface *to, int what);
+
+public:
+	CBattleSiegeController(CBattleInterface * owner, const CGTownInstance *siegeTown);
+	~CBattleSiegeController();
+
+	void showPiecesOfWall(SDL_Surface *to, std::vector<int> pieces);
+
+	const CCreature *turretCreature();
+	Point turretCreaturePosition( BattleHex position );
+
+	void gateStateChanged(const EGateState state);
+
+	void showAbsoluteObstacles(SDL_Surface * to);
+	bool isCatapultAttackable(BattleHex hex) const; //returns true if given tile can be attacked by catapult
+	void stackIsCatapulting(const CatapultAttack & ca); //called when a stack is attacking walls
 
+	void sortObjectsByHex(BattleObjectsByHex & sorted);
 };

+ 4 - 0
lib/battle/BattleHex.h

@@ -34,6 +34,10 @@ typedef boost::optional<ui8> BattleSideOpt;
 // for battle stacks' positions
 struct DLL_LINKAGE BattleHex //TODO: decide if this should be changed to class for better code design
 {
+	static const si16 CASTLE_CENTRAL_TOWER = -2;
+	static const si16 CASTLE_BOTTOM_TOWER = -3;
+	static const si16 CASTLE_UPPER_TOWER = -4;
+
 	si16 hex;
 	static const si16 INVALID = -1;
 	enum EDir

+ 3 - 3
lib/battle/BattleInfo.cpp

@@ -436,14 +436,14 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const
 	if (curB->town && curB->town->fortLevel() >= CGTownInstance::CITADEL)
 	{
 		// keep tower
-		curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), 1, SlotID::ARROW_TOWERS_SLOT, -2);
+		curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), 1, SlotID::ARROW_TOWERS_SLOT, BattleHex::CASTLE_CENTRAL_TOWER);
 
 		if (curB->town->fortLevel() >= CGTownInstance::CASTLE)
 		{
 			// lower tower + upper tower
-			curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), 1, SlotID::ARROW_TOWERS_SLOT, -4);
+			curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), 1, SlotID::ARROW_TOWERS_SLOT, BattleHex::CASTLE_UPPER_TOWER);
 
-			curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), 1, SlotID::ARROW_TOWERS_SLOT, -3);
+			curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(CreatureID::ARROW_TOWERS, 1), 1, SlotID::ARROW_TOWERS_SLOT, BattleHex::CASTLE_BOTTOM_TOWER);
 		}
 
 		//moat