Bläddra i källkod

BattleInt split is finished, start of refactoring:

- Refactoring of siege controller code
- Replaced some usages of C struct SDL_Surface with proper c++ class
IImage
- Refactoring of rendering of battlefield objects (WIP)
Ivan Savenko 2 år sedan
förälder
incheckning
6b3beb05e5

+ 1 - 1
client/battle/CBattleActionsController.cpp

@@ -370,7 +370,7 @@ void CBattleActionsController::handleHex(BattleHex myNumber, int eventType)
 				}
 				break;
 			case PossiblePlayerBattleAction::CATAPULT:
-				if (owner->siegeController && owner->siegeController->isCatapultAttackable(myNumber))
+				if (owner->siegeController && owner->siegeController->isAttackableByCatapult(myNumber))
 					legalAction = true;
 				break;
 			case PossiblePlayerBattleAction::HEAL:

+ 2 - 2
client/battle/CBattleAnimations.cpp

@@ -762,7 +762,7 @@ bool CShootingAnimation::init()
 	const CCreature *shooterInfo = shooter->getCreature();
 
 	if(shooterInfo->idNumber == CreatureID::ARROW_TOWERS)
-		shooterInfo = owner->siegeController->turretCreature();
+		shooterInfo = owner->siegeController->getTurretCreature();
 
 	Point shooterPos;
 	Point shotPos;
@@ -831,7 +831,7 @@ void CShootingAnimation::nextFrame()
 		const CCreature *shooterInfo = attackingStack->getCreature();
 
 		if(shooterInfo->idNumber == CreatureID::ARROW_TOWERS)
-			shooterInfo = owner->siegeController->turretCreature();
+			shooterInfo = owner->siegeController->getTurretCreature();
 
 		// animation should be paused if there is an active projectile
 		if ( stackAnimation(attackingStack)->getCurrentFrame() >= shooterInfo->animation.attackClimaxFrame )

+ 11 - 18
client/battle/CBattleEffectsController.cpp

@@ -94,7 +94,6 @@ void CBattleEffectsController::battleTriggerEffect(const BattleTriggerEffect & b
 	//waitForAnims(); //fixme: freezes game :?
 }
 
-
 void CBattleEffectsController::startAction(const BattleAction* action)
 {
 	const CStack *stack = owner->curInt->cb->battleGetStackByID(action->stackNumber);
@@ -124,29 +123,23 @@ void CBattleEffectsController::startAction(const BattleAction* action)
 	}
 }
 
-
-void CBattleEffectsController::showBattleEffects(SDL_Surface *to, const std::vector<const BattleEffect *> &battleEffects)
+void CBattleEffectsController::showBattlefieldObjects(SDL_Surface *to, const BattleHex & destTile)
 {
 	for (auto & elem : battleEffects)
 	{
-		int currentFrame = static_cast<int>(floor(elem->currentFrame));
-		currentFrame %= elem->animation->size();
+		if (!elem.position.isValid() && destTile != BattleHex::HEX_AFTER_ALL)
+			continue;
 
-		auto img = elem->animation->getImage(currentFrame);
+		if (elem.position.isValid() && elem.position != destTile)
+			continue;
 
-		SDL_Rect temp_rect = genRect(img->height(), img->width(), elem->x, elem->y);
+		int currentFrame = static_cast<int>(floor(elem.currentFrame));
+		currentFrame %= elem.animation->size();
 
-		img->draw(to, &temp_rect, nullptr);
-	}
-}
+		auto img = elem.animation->getImage(currentFrame);
 
-void CBattleEffectsController::sortObjectsByHex(BattleObjectsByHex & sorted)
-{
-	for (auto & battleEffect : battleEffects)
-	{
-		if (battleEffect.position.isValid())
-			sorted.hex[battleEffect.position].effects.push_back(&battleEffect);
-		else
-			sorted.afterAll.effects.push_back(&battleEffect);
+		SDL_Rect temp_rect = genRect(img->height(), img->width(), elem.x, elem.y);
+
+		img->draw(to, &temp_rect, nullptr);
 	}
 }

+ 5 - 3
client/battle/CBattleEffectsController.h

@@ -54,19 +54,21 @@ class CBattleEffectsController
 {
 	CBattleInterface * owner;
 
-	std::vector<BattleEffect> battleEffects; //different animations to display on the screen like spell effects
+	/// list of current effects that are being displayed on screen (spells & creature abilities)
+	std::vector<BattleEffect> battleEffects;
+
 public:
 	CBattleEffectsController(CBattleInterface * owner);
 
 	void startAction(const BattleAction* action);
-	void sortObjectsByHex(BattleObjectsByHex & sorted);
 
 	void displayCustomEffects(const std::vector<CustomEffectInfo> & customEffects);
 
 	void displayEffect(EBattleEffect::EBattleEffect effect, const BattleHex & destTile); //displays custom effect on the battlefield
 	void displayEffect(EBattleEffect::EBattleEffect effect, uint32_t soundID, const BattleHex & destTile); //displays custom effect on the battlefield
 	void battleTriggerEffect(const BattleTriggerEffect & bte);
-	void showBattleEffects(SDL_Surface *to, const std::vector<const BattleEffect *> &battleEffects);
+
+	void showBattlefieldObjects(SDL_Surface *to, const BattleHex & destTile);
 
 
 	friend class CEffectAnimation; // currently, battleEffects is largely managed by CEffectAnimation, TODO: move this logic into CBattleEffectsController

+ 26 - 30
client/battle/CBattleInterface.cpp

@@ -957,27 +957,36 @@ void CBattleInterface::show(SDL_Surface *to)
 	activateStack();
 }
 
+void CBattleInterface::showBattlefieldObjects(SDL_Surface *to, const BattleHex & location )
+{
+	if (siegeController)
+		siegeController->showBattlefieldObjects(to, location);
+	obstacleController->showBattlefieldObjects(to, location);
+	stacksController->showBattlefieldObjects(to, location);
+	effectsController->showBattlefieldObjects(to, location);
+}
+
 void CBattleInterface::showBattlefieldObjects(SDL_Surface *to)
 {
-	auto showHexEntry = [&](BattleObjectsByHex::HexData & hex)
-	{
-		if (siegeController)
-			siegeController->showPiecesOfWall(to, hex.walls);
-		obstacleController->showObstacles(to, hex.obstacles);
-		stacksController->showAliveStacks(to, hex.alive);
-		effectsController->showBattleEffects(to, hex.effects);
-	};
+	//auto showHexEntry = [&](BattleObjectsByHex::HexData & hex)
+	//{
+	//	if (siegeController)
+	//		siegeController->showPiecesOfWall(to, hex.walls);
+	//	obstacleController->showObstacles(to, hex.obstacles);
+	//	stacksController->showAliveStacks(to, hex.alive);
+	//	effectsController->showBattleEffects(to, hex.effects);
+	//};
 
-	BattleObjectsByHex objects = sortObjectsByHex();
+	//BattleObjectsByHex objects = sortObjectsByHex();
 
 	// dead stacks should be blit first
-	stacksController->showStacks(to, objects.beforeAll.dead);
-	for (auto & data : objects.hex)
-		stacksController->showStacks(to, data.dead);
-	stacksController->showStacks(to, objects.afterAll.dead);
+	//stacksController->showStacks(to, objects.beforeAll.dead);
+	//for (auto & data : objects.hex)
+	//	stacksController->showStacks(to, data.dead);
+	//stacksController->showStacks(to, objects.afterAll.dead);
 
 	// display objects that must be blit before anything else (e.g. topmost walls)
-	showHexEntry(objects.beforeAll);
+	//showHexEntry(objects.beforeAll);
 
 	// show heroes after "beforeAll" - e.g. topmost wall in siege
 	if (attackingHero)
@@ -987,11 +996,11 @@ void CBattleInterface::showBattlefieldObjects(SDL_Surface *to)
 
 	// actual blit of most of objects, hex by hex
 	// NOTE: row-by-row blitting may be a better approach
-	for (auto &data : objects.hex)
-		showHexEntry(data);
+	//for (auto &data : objects.hex)
+	//	showHexEntry(data);
 
 	// objects that must be blit *after* everything else - e.g. bottom tower or some spell effects
-	showHexEntry(objects.afterAll);
+	//showHexEntry(objects.afterAll);
 }
 
 void CBattleInterface::showInterface(SDL_Surface *to)
@@ -1020,19 +1029,6 @@ void CBattleInterface::showInterface(SDL_Surface *to)
 	}
 }
 
-BattleObjectsByHex CBattleInterface::sortObjectsByHex()
-{
-	BattleObjectsByHex sorted;
-
-	stacksController->sortObjectsByHex(sorted);
-	obstacleController->sortObjectsByHex(sorted);
-	effectsController->sortObjectsByHex(sorted);
-	if (siegeController)
-		siegeController->sortObjectsByHex(sorted);
-
-	return sorted;
-}
-
 void CBattleInterface::castThisSpell(SpellID spellID)
 {
 	actionsController->castThisSpell(spellID);

+ 1 - 24
client/battle/CBattleInterface.h

@@ -70,28 +70,6 @@ struct StackAttackedInfo
 	bool cloneKilled;
 };
 
-
-struct BattleObjectsByHex
-{
-	typedef std::vector<int> TWallList;
-	typedef std::vector<const CStack *> TStackList;
-	typedef std::vector<const BattleEffect *> TEffectList;
-	typedef std::vector<std::shared_ptr<const CObstacleInstance>> TObstacleList;
-
-	struct HexData
-	{
-		TWallList walls;
-		TStackList dead;
-		TStackList alive;
-		TEffectList effects;
-		TObstacleList obstacles;
-	};
-
-	HexData beforeAll;
-	HexData afterAll;
-	std::array<HexData, GameConstants::BFIELD_SIZE> hex;
-};
-
 /// Big class which handles the overall battle interface actions and it is also responsible for
 /// drawing everything correctly.
 class CBattleInterface : public WindowBase
@@ -128,8 +106,7 @@ private:
 	void showInterface(SDL_Surface *to);
 
 	void showBattlefieldObjects(SDL_Surface *to);
-
-	BattleObjectsByHex sortObjectsByHex();
+	void showBattlefieldObjects(SDL_Surface *to, const BattleHex & location );
 
 	void setHeroAnimation(ui8 side, int phase);
 public:

+ 1 - 1
client/battle/CBattleInterfaceClasses.cpp

@@ -584,7 +584,7 @@ Point CClickableHex::getXYUnitAnim(BattleHex hexNum, const CStack * stack, CBatt
 
 	Point ret(-500, -500); //returned value
 	if(stack && stack->initialPosition < 0) //creatures in turrets
-		return cbi->siegeController->turretCreaturePosition(stack->initialPosition);
+		return cbi->siegeController->getTurretCreaturePosition(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

+ 5 - 1
client/battle/CBattleInterfaceClasses.h

@@ -145,7 +145,11 @@ public:
 	//CStack * ourStack;
 	bool strictHovered; //for determining if hex is hovered by mouse (this is different problem than hex's graphic hovering)
 	CBattleInterface * myInterface; //interface that owns me
-	static Point getXYUnitAnim(BattleHex hexNum, const CStack * creature, CBattleInterface * cbi); //returns (x, y) of left top corner of animation
+
+	/// returns (x, y) of left top corner of animation
+	/// FIXME: move someplace else?
+	/// FIXME: some usages should be replaced with creAnims->pos?
+	static Point getXYUnitAnim(BattleHex hexNum, const CStack * creature, CBattleInterface * cbi);
 
 	//for user interactions
 	void hover (bool on) override;

+ 11 - 18
client/battle/CBattleObstacleController.cpp

@@ -122,10 +122,19 @@ void CBattleObstacleController::showAbsoluteObstacles(SDL_Surface * to)
 	}
 }
 
-void CBattleObstacleController::showObstacles(SDL_Surface * to, std::vector<std::shared_ptr<const CObstacleInstance>> & obstacles)
+void CBattleObstacleController::showBattlefieldObjects(SDL_Surface *to, const BattleHex & location )
 {
-	for(auto & obstacle : obstacles)
+	for (auto &obstacle : owner->curInt->cb->battleGetAllObstacles())
 	{
+		if (obstacle->obstacleType == CObstacleInstance::ABSOLUTE_OBSTACLE)
+			continue;
+
+		if (obstacle->obstacleType != CObstacleInstance::MOAT)
+			continue;
+
+		if ( obstacle->pos != location)
+			continue;
+
 		auto img = getObstacleImage(*obstacle);
 		if(img)
 		{
@@ -135,22 +144,6 @@ void CBattleObstacleController::showObstacles(SDL_Surface * to, std::vector<std:
 	}
 }
 
-void CBattleObstacleController::sortObjectsByHex(BattleObjectsByHex & sorted)
-{
-	std::map<BattleHex, std::shared_ptr<const CObstacleInstance>> backgroundObstacles;
-	for (auto &obstacle : owner->curInt->cb->battleGetAllObstacles()) {
-		if (obstacle->obstacleType != CObstacleInstance::ABSOLUTE_OBSTACLE
-			&& obstacle->obstacleType != CObstacleInstance::MOAT) {
-			backgroundObstacles[obstacle->pos] = obstacle;
-		}
-	}
-	for (auto &op : backgroundObstacles)
-	{
-		sorted.beforeAll.obstacles.push_back(op.second);
-	}
-}
-
-
 std::shared_ptr<IImage> CBattleObstacleController::getObstacleImage(const CObstacleInstance & oi)
 {
 	int frameIndex = (owner->animCount+1) *25 / owner->getAnimSpeed();

+ 6 - 5
client/battle/CBattleObstacleController.h

@@ -11,6 +11,7 @@
 
 struct SDL_Surface;
 struct BattleObjectsByHex;
+struct BattleHex;
 class IImage;
 class CAnimation;
 class CBattleInterface;
@@ -24,6 +25,10 @@ class CBattleObstacleController
 	CBattleInterface * owner;
 
 	std::map<si32, std::shared_ptr<CAnimation>> obstacleAnimations;
+
+	std::shared_ptr<IImage> getObstacleImage(const CObstacleInstance & oi);
+	Point getObstaclePosition(std::shared_ptr<IImage> image, const CObstacleInstance & obstacle);
+
 public:
 	CBattleObstacleController(CBattleInterface * owner);
 
@@ -31,11 +36,7 @@ public:
 	void showObstacles(SDL_Surface *to, std::vector<std::shared_ptr<const CObstacleInstance>> &obstacles);
 	void showAbsoluteObstacles(SDL_Surface *to);
 
-	void sortObjectsByHex(BattleObjectsByHex & sorted);
-
-	std::shared_ptr<IImage> getObstacleImage(const CObstacleInstance & oi);
-
-	Point getObstaclePosition(std::shared_ptr<IImage> image, const CObstacleInstance & obstacle);
+	void showBattlefieldObjects(SDL_Surface *to, const BattleHex & location );
 
 	void redrawBackgroundWithHexes(SDL_Surface * to);
 };

+ 2 - 2
client/battle/CBattleProjectileController.cpp

@@ -58,7 +58,7 @@ void CBattleProjectileController::initStackProjectile(const CStack * stack)
 {
 	const CCreature * creature;//creature whose shots should be loaded
 	if(stack->getCreature()->idNumber == CreatureID::ARROW_TOWERS)
-		creature = owner->siegeController->turretCreature();
+		creature = owner->siegeController->getTurretCreature();
 	else
 		creature = stack->getCreature();
 
@@ -208,7 +208,7 @@ void CBattleProjectileController::createProjectile(const CStack * shooter, const
 	const CCreature *shooterInfo = shooter->getCreature();
 
 	if(shooterInfo->idNumber == CreatureID::ARROW_TOWERS)
-		shooterInfo = owner->siegeController->turretCreature();
+		shooterInfo = owner->siegeController->getTurretCreature();
 
 	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..."

+ 135 - 139
client/battle/CBattleSiegeController.cpp

@@ -10,51 +10,37 @@
 #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 "CBattleInterface.h"
 #include "CBattleInterfaceClasses.h"
 #include "CBattleStacksController.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]);
-	}
-
-	SDL_FreeSurface(moatSurface);
-	SDL_FreeSurface(mlipSurface);
-}
+#include "../CMusicHandler.h"
+#include "../CGameInfo.h"
+#include "../CPlayerInterface.h"
+#include "../gui/CAnimation.h"
 
-std::string CBattleSiegeController::getSiegeName(ui16 what) const
-{
-	return getSiegeName(what, EWallState::INTACT);
-}
+#include "../../CCallback.h"
+#include "../../lib/NetPacks.h"
+#include "../../lib/CStack.h"
+#include "../../lib/mapObjects/CGTownInstance.h"
 
-std::string CBattleSiegeController::getSiegeName(ui16 what, int state) const
+std::string CBattleSiegeController::getWallPieceImageName(EWallVisual::EWallVisual what, EWallState::EWallState state) const
 {
 	auto getImageIndex = [&]() -> int
 	{
 		switch (state)
 		{
-		case EWallState::INTACT : return 1;
+		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
+			// towers don't have separate image here - INTACT and DAMAGED is 1, DESTROYED is 2
+			if(what == EWallVisual::KEEP || what == EWallVisual::BOTTOM_TOWER || what == EWallVisual::UPPER_TOWER)
 				return 1;
 			else
 				return 2;
 		case EWallState::DESTROYED :
-			if (what == 2 || what == 3 || what == 8)
+			if (what == EWallVisual::KEEP || what == EWallVisual::BOTTOM_TOWER || what == EWallVisual::UPPER_TOWER)
 				return 2;
 			else
 				return 3;
@@ -67,8 +53,6 @@ std::string CBattleSiegeController::getSiegeName(ui16 what, int state) const
 
 	switch(what)
 	{
-	case EWallVisual::BACKGROUND:
-		return prefix + "BACK.BMP";
 	case EWallVisual::BACKGROUND_WALL:
 		{
 			switch(town->town->faction->index)
@@ -119,38 +103,64 @@ std::string CBattleSiegeController::getSiegeName(ui16 what, int state) const
 	}
 }
 
-void CBattleSiegeController::printPartOfWall(SDL_Surface *to, int what)
+void CBattleSiegeController::showWallPiece(SDL_Surface *to, EWallVisual::EWallVisual what)
 {
-	Point pos = Point(-1, -1);
 	auto & ci = town->town->clientInfo;
+	auto const & pos = ci.siegePositions[what];
 
-	if (vstd::iswithin(what, 1, 17))
-	{
-		pos.x = ci.siegePositions[what].x + owner->pos.x;
-		pos.y = ci.siegePositions[what].y + owner->pos.y;
-	}
+	wallPieceImages[what]->draw(to, pos.x + owner->pos.x, pos.y + owner->pos.y);
+}
+
+std::string CBattleSiegeController::getBattleBackgroundName() const
+{
+	const std::string & prefix = town->town->clientInfo.siegePrefix;
+	return prefix + "BACK.BMP";
+}
 
-	if (town->town->faction->index == ETownType::TOWER
-		&& (what == EWallVisual::MOAT || what == EWallVisual::BACKGROUND_MOAT))
-		return; // no moat in Tower. TODO: remove hardcode somehow?
+bool CBattleSiegeController::getWallPieceExistance(EWallVisual::EWallVisual what) const
+{
+	//FIXME: use this instead of buildings test?
+	//ui8 siegeLevel = owner->curInt->cb->battleGetSiegeLevel();
 
-	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;
-		}
+	bool isMoat = (what == EWallVisual::BACKGROUND_MOAT || what == EWallVisual::MOAT);
+	bool isKeep = what == EWallVisual::KEEP_BATTLEMENT;
+	bool isTower = (what == EWallVisual::UPPER_BATTLEMENT || what == EWallVisual::BOTTOM_BATTLEMENT);
 
-		blitAt(walls[what], pos.x, pos.y, to);
-	}
+	bool hasMoat = town->hasBuilt(BuildingID::CITADEL) && town->town->faction->index != ETownType::TOWER;
+	bool hasKeep = town->hasBuilt(BuildingID::CITADEL);
+	bool hasTower = town->hasBuilt(BuildingID::CASTLE);
+
+	if ( isMoat ) return hasMoat;
+	if ( isKeep ) return hasKeep;
+	if ( isTower ) return hasTower;
+
+	return true;
 }
 
-std::string CBattleSiegeController::getBattleBackgroundName()
+BattleHex CBattleSiegeController::getWallPiecePosition(EWallVisual::EWallVisual what) const
 {
-	return getSiegeName(0);
+	static const std::array<BattleHex, 18> wallsPositions = {
+		BattleHex::INVALID, // background, handled separately
+		BattleHex::HEX_BEFORE_ALL,
+		135,
+		BattleHex::HEX_AFTER_ALL,
+		182,
+		130,
+		78,
+		12,
+		BattleHex::HEX_BEFORE_ALL,
+		94,
+		112,
+		165,
+		45,
+		BattleHex::INVALID, //moat, printed as obstacle // BattleHex::HEX_BEFORE_ALL,
+		BattleHex::INVALID, //moat, printed as obstacle
+		135,
+		BattleHex::HEX_AFTER_ALL,
+		BattleHex::HEX_BEFORE_ALL
+	};
+
+	return wallsPositions[what];
 }
 
 CBattleSiegeController::CBattleSiegeController(CBattleInterface * owner, const CGTownInstance *siegeTown):
@@ -159,35 +169,37 @@ CBattleSiegeController::CBattleSiegeController(CBattleInterface * owner, const C
 {
 	assert(owner->fieldController.get() == nullptr); // must be created after this
 
-	moatSurface = BitmapHandler::loadBitmap( getSiegeName(13) );
-	mlipSurface = BitmapHandler::loadBitmap( getSiegeName(14) );
-
-	for (int g = 0; g < ARRAY_COUNT(walls); ++g)
+	for (int g = 0; g < wallPieceImages.size(); ++g)
 	{
-		if (g != EWallVisual::GATE)
-			walls[g] = BitmapHandler::loadBitmap(getSiegeName(g));
+		if ( g == EWallVisual::GATE ) // gate is initially closed and has no image to display in this state
+			continue;
+
+		if ( !getWallPieceExistance(EWallVisual::EWallVisual(g)) )
+			continue;
+
+		wallPieceImages[g] = IImage::createFromFile(getWallPieceImageName(EWallVisual::EWallVisual(g), EWallState::INTACT));
 	}
 }
 
-const CCreature *CBattleSiegeController::turretCreature()
+const CCreature *CBattleSiegeController::getTurretCreature() const
 {
 	return CGI->creh->objects[town->town->clientInfo.siegeShooter];
 }
 
-Point CBattleSiegeController::turretCreaturePosition( BattleHex position )
+Point CBattleSiegeController::getTurretCreaturePosition( BattleHex position ) const
 {
 	// 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;
+		posID = EWallVisual::CREATURE_KEEP;
 		break;
 	case BattleHex::CASTLE_BOTTOM_TOWER: // bottom creature
-		posID = 19;
+		posID = EWallVisual::CREATURE_BOTTOM_TOWER;
 		break;
 	case BattleHex::CASTLE_UPPER_TOWER: // upper creature
-		posID = 20;
+		posID = EWallVisual::CREATURE_UPPER_TOWER;
 		break;
 	}
 
@@ -203,12 +215,11 @@ Point CBattleSiegeController::turretCreaturePosition( BattleHex position )
 	return Point(0,0);
 }
 
-
 void CBattleSiegeController::gateStateChanged(const EGateState state)
 {
 	auto oldState = owner->curInt->cb->battleGetGateState();
 	bool playSound = false;
-	int stateId = EWallState::NONE;
+	auto stateId = EWallState::NONE;
 	switch(state)
 	{
 	case EGateState::CLOSED:
@@ -229,102 +240,87 @@ void CBattleSiegeController::gateStateChanged(const EGateState state)
 	}
 
 	if (oldState != EGateState::NONE && oldState != EGateState::CLOSED && oldState != EGateState::BLOCKED)
-		SDL_FreeSurface(walls[EWallVisual::GATE]);
+		wallPieceImages[EWallVisual::GATE] = nullptr;
 
 	if (stateId != EWallState::NONE)
-		walls[EWallVisual::GATE] = BitmapHandler::loadBitmap(getSiegeName(EWallVisual::GATE, stateId));
+		wallPieceImages[EWallVisual::GATE] = IImage::createFromFile(getWallPieceImageName(EWallVisual::GATE,  stateId));
+
 	if (playSound)
 		CCS->soundh->playSound(soundBase::DRAWBRG);
 }
 
 void CBattleSiegeController::showAbsoluteObstacles(SDL_Surface * to)
 {
-	ui8 siegeLevel = owner->curInt->cb->battleGetSiegeLevel();
-	if (siegeLevel >= 2) //citadel or castle
-	{
-		//print moat/mlip
-
-		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);
+	auto & info = town->town->clientInfo;
 
-		if (moatSurface) //eg. tower has no moat
-			blitAt(moatSurface, moatPos.x + owner->pos.x, moatPos.y  + owner->pos.y, to);
-		if (mlipSurface) //eg. tower has no mlip
-			blitAt(mlipSurface, mlipPos.x + owner->pos.x, mlipPos.y + owner->pos.y, to);
-
-		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))
+	if (getWallPieceExistance(EWallVisual::MOAT))
 	{
-		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);
+		auto const & pos = info.siegePositions[EWallVisual::MOAT];
+		wallPieceImages[EWallVisual::MOAT]->draw(to, pos.x + owner->pos.x, pos.y + owner->pos.y);
 	}
-	if (town->hasBuilt(BuildingID::CASTLE))
+
+	if (getWallPieceExistance(EWallVisual::BACKGROUND_MOAT))
 	{
-		sorted.afterAll.walls.push_back(EWallVisual::BOTTOM_BATTLEMENT);
-		sorted.beforeAll.walls.push_back(EWallVisual::UPPER_BATTLEMENT);
+		auto const & pos = info.siegePositions[EWallVisual::BACKGROUND_MOAT];
+		wallPieceImages[EWallVisual::BACKGROUND_MOAT]->draw(to, pos.x + owner->pos.x, pos.y + owner->pos.y);
 	}
 }
 
-
-void CBattleSiegeController::showPiecesOfWall(SDL_Surface *to, std::vector<int> pieces)
+void CBattleSiegeController::showBattlefieldObjects(SDL_Surface *to, const BattleHex & location )
 {
-	for (auto piece : pieces)
+	for (int i = EWallVisual::WALL_FIRST; i <= EWallVisual::WALL_LAST; ++i)
 	{
-		if (piece < 15) // not a tower - just print
-			printPartOfWall(to, piece);
-		else // tower. find if tower is built and not destroyed - stack is present
+		auto wallPiece = EWallVisual::EWallVisual(i);
+
+		if ( !getWallPieceExistance(wallPiece))
+			continue;
+
+		if ( getWallPiecePosition(wallPiece) != location)
+			continue;
+
+		if (wallPiece != EWallVisual::KEEP_BATTLEMENT &&
+			wallPiece != EWallVisual::BOTTOM_BATTLEMENT &&
+			wallPiece != EWallVisual::UPPER_BATTLEMENT)
 		{
-			// PieceID    StackID
-			// 15 = keep,  -2
-			// 16 = lower, -3
-			// 17 = upper, -4
+			showWallPiece(to, wallPiece);
+			continue;
+		}
 
-			// tower. check if tower is alive - stack is found
-			int stackPos = 13 - piece;
+		// tower. check if tower is alive - stack is found
+		BattleHex stackPos;
+		switch(wallPiece)
+		{
+		case EWallVisual::KEEP_BATTLEMENT:
+			stackPos = BattleHex::CASTLE_CENTRAL_TOWER;
+			break;
+		case EWallVisual::BOTTOM_BATTLEMENT:
+			stackPos = BattleHex::CASTLE_BOTTOM_TOWER;
+			break;
+		case EWallVisual::UPPER_BATTLEMENT:
+			stackPos = BattleHex::CASTLE_UPPER_TOWER;
+			break;
+		}
 
-			const CStack *turret = nullptr;
+		const CStack *turret = nullptr;
 
-			for (auto & stack : owner->curInt->cb->battleGetAllStacks(true))
+		for (auto & stack : owner->curInt->cb->battleGetAllStacks(true))
+		{
+			if(stack->initialPosition == stackPos)
 			{
-				if(stack->initialPosition == stackPos)
-				{
-					turret = stack;
-					break;
-				}
+				turret = stack;
+				break;
 			}
+		}
 
-			if (turret)
-			{
-				std::vector<const CStack *> stackList(1, turret);
-				owner->stacksController->showStacks(to, stackList);
-				printPartOfWall(to, piece);
-			}
+		if (turret)
+		{
+			owner->stacksController->showStack(to, turret);
+			showWallPiece(to, wallPiece);
 		}
 	}
 }
 
-
-bool CBattleSiegeController::isCatapultAttackable(BattleHex hex) const
+bool CBattleSiegeController::isAttackableByCatapult(BattleHex hex) const
 {
 	if (owner->tacticsMode)
 		return false;
@@ -362,13 +358,13 @@ void CBattleSiegeController::stackIsCatapulting(const CatapultAttack & ca)
 
 	for (auto attackInfo : ca.attackedParts)
 	{
-		int wallId = attackInfo.attackedPart + 2;
+		int wallId = attackInfo.attackedPart + EWallVisual::DESTRUCTIBLE_FIRST;
 		//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)));
+		auto wallState = EWallState::EWallState(owner->curInt->cb->battleGetWallState(attackInfo.attackedPart));
+
+		wallPieceImages[wallId] = IImage::createFromFile(getWallPieceImageName(EWallVisual::EWallVisual(wallId), wallState));
 	}
 }

+ 43 - 23
client/battle/CBattleSiegeController.h

@@ -10,22 +10,23 @@
 #pragma once
 
 #include "../../lib/GameConstants.h"
+#include "../../lib/battle/BattleHex.h"
 
-struct BattleObjectsByHex;
 struct CatapultAttack;
-struct SDL_Surface;
-struct BattleHex;
 struct Point;
+struct SDL_Surface;
 class CGTownInstance;
 class CBattleInterface;
 class CCreature;
+class IImage;
 
 namespace EWallVisual
 {
 	enum EWallVisual
 	{
-		BACKGROUND = 0,
-		BACKGROUND_WALL = 1,
+		BACKGROUND,
+		BACKGROUND_WALL,
+
 		KEEP,
 		BOTTOM_TOWER,
 		BOTTOM_WALL,
@@ -34,6 +35,7 @@ namespace EWallVisual
 		UPPER_WALL,
 		UPPER_TOWER,
 		GATE,
+
 		GATE_ARCH,
 		BOTTOM_STATIC_WALL,
 		UPPER_STATIC_WALL,
@@ -41,7 +43,18 @@ namespace EWallVisual
 		BACKGROUND_MOAT,
 		KEEP_BATTLEMENT,
 		BOTTOM_BATTLEMENT,
-		UPPER_BATTLEMENT
+		UPPER_BATTLEMENT,
+
+		CREATURE_KEEP,
+		CREATURE_BOTTOM_TOWER,
+		CREATURE_UPPER_TOWER,
+
+		WALL_FIRST = BACKGROUND_WALL,
+		WALL_LAST  = UPPER_BATTLEMENT,
+
+		// these entries are mapped to EWallPart enum
+		DESTRUCTIBLE_FIRST = KEEP,
+		DESTRUCTIBLE_LAST = GATE,
 	};
 }
 
@@ -49,32 +62,39 @@ class CBattleSiegeController
 {
 	CBattleInterface * owner;
 
-	SDL_Surface *moatSurface;
-	SDL_Surface *mlipSurface;
+	/// besieged town
+	const CGTownInstance *town;
 
-	SDL_Surface* walls[18];
-	const CGTownInstance *town; //besieged town
+	/// sections of castle walls, in their currently visible state
+	std::array<std::shared_ptr<IImage>, EWallVisual::WALL_LAST + 1> wallPieceImages;
 
-	std::string getSiegeName(ui16 what) const;
-	std::string getSiegeName(ui16 what, int state) const; // state uses EWallState enum
+	/// return URI for image for a wall piece
+	std::string getWallPieceImageName(EWallVisual::EWallVisual what, EWallState::EWallState state) const;
 
-	void printPartOfWall(SDL_Surface *to, int what);
+	/// returns BattleHex to which chosen wall piece is bound
+	BattleHex getWallPiecePosition(EWallVisual::EWallVisual what) const;
 
-public:
-	CBattleSiegeController(CBattleInterface * owner, const CGTownInstance *siegeTown);
-	~CBattleSiegeController();
+	/// returns true if chosen wall piece should be present in current battle
+	bool getWallPieceExistance(EWallVisual::EWallVisual what) const;
 
-	void showPiecesOfWall(SDL_Surface *to, std::vector<int> pieces);
+	void showWallPiece(SDL_Surface *to, EWallVisual::EWallVisual what);
 
-	std::string getBattleBackgroundName();
-	const CCreature *turretCreature();
-	Point turretCreaturePosition( BattleHex position );
+public:
+	CBattleSiegeController(CBattleInterface * owner, const CGTownInstance *siegeTown);
 
+	/// call-ins from server
 	void gateStateChanged(const EGateState state);
+	void stackIsCatapulting(const CatapultAttack & ca);
 
+	/// call-ins from other battle controllers
 	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 showBattlefieldObjects(SDL_Surface *to, const BattleHex & location );
+
+	/// queries from other battle controllers
+	bool isAttackableByCatapult(BattleHex hex) const;
+	std::string getBattleBackgroundName() const;
+	const CCreature *getTurretCreature() const;
+	Point getTurretCreaturePosition( BattleHex position ) const;
+
 
-	void sortObjectsByHex(BattleObjectsByHex & sorted);
 };

+ 87 - 116
client/battle/CBattleStacksController.cpp

@@ -17,7 +17,7 @@
 #include "CBattleEffectsController.h"
 #include "CBattleProjectileController.h"
 #include "CBattleControlPanel.h"
-#include "../CBitmapHandler.h"
+#include "../gui/CAnimation.h"
 #include "../gui/SDL_Extensions.h"
 #include "../gui/CGuiHandler.h"
 #include "../../lib/battle/BattleHex.h"
@@ -56,42 +56,24 @@ static void onAnimationFinished(const CStack *stack, std::weak_ptr<CCreatureAnim
 	animation->onAnimationReset += std::bind(&onAnimationFinished, stack, anim);
 }
 
-static void transformPalette(SDL_Surface *surf, double rCor, double gCor, double bCor)
-{
-	SDL_Color *colorsToChange = surf->format->palette->colors;
-	for (int g=0; g<surf->format->palette->ncolors; ++g)
-	{
-		SDL_Color *color = &colorsToChange[g];
-		if (color->b != 132 &&
-			color->g != 231 &&
-			color->r != 255) //it's not yellow border
-		{
-			color->r = static_cast<Uint8>(color->r * rCor);
-			color->g = static_cast<Uint8>(color->g * gCor);
-			color->b = static_cast<Uint8>(color->b * bCor);
-		}
-	}
-}
-
 CBattleStacksController::CBattleStacksController(CBattleInterface * owner):
 	owner(owner)
 {
 	//preparing graphics for displaying amounts of creatures
-	amountNormal = BitmapHandler::loadBitmap("CMNUMWIN.BMP");
-	CSDL_Ext::alphaTransform(amountNormal);
-	transformPalette(amountNormal, 0.59, 0.19, 0.93);
-
-	amountPositive = BitmapHandler::loadBitmap("CMNUMWIN.BMP");
-	CSDL_Ext::alphaTransform(amountPositive);
-	transformPalette(amountPositive, 0.18, 1.00, 0.18);
+	amountNormal     = IImage::createFromFile("CMNUMWIN.BMP");
+	amountPositive   = IImage::createFromFile("CMNUMWIN.BMP");
+	amountNegative   = IImage::createFromFile("CMNUMWIN.BMP");
+	amountEffNeutral = IImage::createFromFile("CMNUMWIN.BMP");
 
-	amountNegative = BitmapHandler::loadBitmap("CMNUMWIN.BMP");
-	CSDL_Ext::alphaTransform(amountNegative);
-	transformPalette(amountNegative, 1.00, 0.18, 0.18);
+	ColorShifterAddMulExcept shifterNormal  ({0,0,0,0}, {150,  48, 237, 255}, {132, 231, 255, 255});
+	ColorShifterAddMulExcept shifterPositive({0,0,0,0}, { 45, 255,  45, 255}, {132, 231, 255, 255});
+	ColorShifterAddMulExcept shifterNegative({0,0,0,0}, {255,  45,  45, 255}, {132, 231, 255, 255});
+	ColorShifterAddMulExcept shifterNeutral ({0,0,0,0}, {255, 255,  45, 255}, {132, 231, 255, 255});
 
-	amountEffNeutral = BitmapHandler::loadBitmap("CMNUMWIN.BMP");
-	CSDL_Ext::alphaTransform(amountEffNeutral);
-	transformPalette(amountEffNeutral, 1.00, 1.00, 0.18);
+	amountNormal->adjustPalette(&shifterNormal);
+	amountPositive->adjustPalette(&shifterPositive);
+	amountNegative->adjustPalette(&shifterNegative);
+	amountEffNeutral->adjustPalette(&shifterNeutral);
 
 	std::vector<const CStack*> stacks = owner->curInt->cb->battleGetAllStacks(true);
 	for(const CStack * s : stacks)
@@ -100,15 +82,7 @@ CBattleStacksController::CBattleStacksController(CBattleInterface * owner):
 	}
 }
 
-CBattleStacksController::~CBattleStacksController()
-{
-	SDL_FreeSurface(amountNormal);
-	SDL_FreeSurface(amountNegative);
-	SDL_FreeSurface(amountPositive);
-	SDL_FreeSurface(amountEffNeutral);
-}
-
-void CBattleStacksController::sortObjectsByHex(BattleObjectsByHex & sorted)
+void CBattleStacksController::showBattlefieldObjects(SDL_Surface *to, const BattleHex & location )
 {
 	auto getCurrentPosition = [&](const CStack *stack) -> BattleHex
 	{
@@ -127,18 +101,15 @@ void CBattleStacksController::sortObjectsByHex(BattleObjectsByHex & sorted)
 		return stack->getPosition();
 	};
 
-	auto stacks = owner->curInt->cb->battleGetStacksIf([](const CStack *s)
-	{
-		return !s->isTurret();
-	});
+	auto stacks = owner->curInt->cb->battleGetAllStacks(true);
 
 	for (auto & stack : stacks)
 	{
 		if (creAnims.find(stack->ID) == creAnims.end()) //e.g. for summoned but not yet handled stacks
 			continue;
 
-		if (stack->initialPosition < 0) // turret shooters are handled separately
-			continue;
+		//if (stack->initialPosition < 0) // turret shooters are handled separately
+		//	continue;
 
 		//FIXME: hack to ignore ghost stacks
 		if ((creAnims[stack->ID]->getType() == CCreatureAnim::DEAD || creAnims[stack->ID]->getType() == CCreatureAnim::HOLDING) && stack->isGhost())
@@ -146,24 +117,34 @@ void CBattleStacksController::sortObjectsByHex(BattleObjectsByHex & sorted)
 
 		if (creAnims[stack->ID]->isDead())
 		{
-			sorted.hex[stack->getPosition()].dead.push_back(stack);
+			//if ( location == stack->getPosition() )
+			if ( location == BattleHex::HEX_BEFORE_ALL ) //FIXME: any cases when this won't work?
+			{};
 			continue;
 		}
 
+		// standing - blit at current position
 		if (!creAnims[stack->ID]->isMoving())
 		{
-			sorted.hex[stack->getPosition()].alive.push_back(stack);
+			if ( location == stack->getPosition() )
+			{};
 			continue;
 		}
 
 		// flying creature - just blit them over everyone else
 		if (stack->hasBonusOfType(Bonus::FLYING))
 		{
-			sorted.afterAll.alive.push_back(stack);
+			if ( location == BattleHex::HEX_AFTER_ALL)
+			{};
 			continue;
 		}
 
-		sorted.hex[getCurrentPosition(stack)].alive.push_back(stack);
+		// else - unit moving on ground
+		{
+			if ( location == getCurrentPosition(stack) )
+			{};
+			continue;
+		}
 	}
 }
 
@@ -184,7 +165,7 @@ void CBattleStacksController::stackReset(const CStack * stack)
 
 	if (stack->isClone())
 	{
-		ColorShifterDeepBlue shifter;
+		auto shifter = ColorShifterAddMul::deepBlue();
 		animation->shiftColor(&shifter);
 	}
 
@@ -201,12 +182,12 @@ void CBattleStacksController::stackAdded(const CStack * stack)
 	{
 		assert(owner->siegeController);
 
-		const CCreature *turretCreature = owner->siegeController->turretCreature();
+		const CCreature *turretCreature = owner->siegeController->getTurretCreature();
 
 		creAnims[stack->ID] = AnimationControls::getAnimation(turretCreature);
 		creAnims[stack->ID]->pos.h = 225;
 
-		coords = owner->siegeController->turretCreaturePosition(stack->initialPosition);
+		coords = owner->siegeController->getTurretCreaturePosition(stack->initialPosition);
 	}
 	else
 	{
@@ -263,7 +244,7 @@ void CBattleStacksController::setHoveredStack(const CStack *stack)
 		mouseHoveredStack = nullptr;
 }
 
-void CBattleStacksController::showAliveStacks(SDL_Surface *to, std::vector<const CStack *> stacks)
+bool CBattleStacksController::stackNeedsAmountBox(const CStack * stack)
 {
 	BattleHex currentActionTarget;
 	if(owner->curInt->curAction)
@@ -273,39 +254,41 @@ void CBattleStacksController::showAliveStacks(SDL_Surface *to, std::vector<const
 			currentActionTarget = target.at(0).hexValue;
 	}
 
-	auto isAmountBoxVisible = [&](const CStack *stack) -> bool
-	{
-		if(stack->hasBonusOfType(Bonus::SIEGE_WEAPON) && stack->getCount() == 1) //do not show box for singular war machines, stacked war machines with box shown are supported as extension feature
-			return false;
+	if(!stack->alive())
+		return false;
 
-		if(stack->getCount() == 0) //hide box when target is going to die anyway - do not display "0 creatures"
+	if(stack->hasBonusOfType(Bonus::SIEGE_WEAPON) && stack->getCount() == 1) //do not show box for singular war machines, stacked war machines with box shown are supported as extension feature
+		return false;
+
+	if(stack->getCount() == 0) //hide box when target is going to die anyway - do not display "0 creatures"
+		return false;
+
+	for(auto anim : pendingAnims) //no matter what other conditions below are, hide box when creature is playing hit animation
+	{
+		auto hitAnimation = dynamic_cast<CDefenceAnimation*>(anim.first);
+		if(hitAnimation && (hitAnimation->stack->ID == stack->ID)) //we process only "current creature" as other creatures will be processed reliably on their own iteration
 			return false;
+	}
 
-		for(auto anim : pendingAnims) //no matter what other conditions below are, hide box when creature is playing hit animation
+	if(owner->curInt->curAction)
+	{
+		if(owner->curInt->curAction->stackNumber == stack->ID) //stack is currently taking action (is not a target of another creature's action etc)
 		{
-			auto hitAnimation = dynamic_cast<CDefenceAnimation*>(anim.first);
-			if(hitAnimation && (hitAnimation->stack->ID == stack->ID)) //we process only "current creature" as other creatures will be processed reliably on their own iteration
+			if(owner->curInt->curAction->actionType == EActionType::WALK || owner->curInt->curAction->actionType == EActionType::SHOOT) //hide when stack walks or shoots
 				return false;
-		}
-
-		if(owner->curInt->curAction)
-		{
-			if(owner->curInt->curAction->stackNumber == stack->ID) //stack is currently taking action (is not a target of another creature's action etc)
-			{
-				if(owner->curInt->curAction->actionType == EActionType::WALK || owner->curInt->curAction->actionType == EActionType::SHOOT) //hide when stack walks or shoots
-					return false;
-
-				else if(owner->curInt->curAction->actionType == EActionType::WALK_AND_ATTACK && currentActionTarget != stack->getPosition()) //when attacking, hide until walk phase finished
-					return false;
-			}
 
-			if(owner->curInt->curAction->actionType == EActionType::SHOOT && currentActionTarget == stack->getPosition()) //hide if we are ranged attack target
+			else if(owner->curInt->curAction->actionType == EActionType::WALK_AND_ATTACK && currentActionTarget != stack->getPosition()) //when attacking, hide until walk phase finished
 				return false;
 		}
 
-		return true;
-	};
+		if(owner->curInt->curAction->actionType == EActionType::SHOOT && currentActionTarget == stack->getPosition()) //hide if we are ranged attack target
+			return false;
+	}
+	return true;
+}
 
+void CBattleStacksController::showStackAmountBox(SDL_Surface *to, const CStack * stack)
+{
 	auto getEffectsPositivness = [&](const std::vector<si32> & activeSpells) -> int
 	{
 		int pos = 0;
@@ -316,7 +299,7 @@ void CBattleStacksController::showAliveStacks(SDL_Surface *to, std::vector<const
 		return pos;
 	};
 
-	auto getAmountBoxBackground = [&](int positivness) -> SDL_Surface *
+	auto getAmountBoxBackground = [&](int positivness) -> auto
 	{
 		if (positivness > 0)
 			return amountPositive;
@@ -325,48 +308,36 @@ void CBattleStacksController::showAliveStacks(SDL_Surface *to, std::vector<const
 		return amountEffNeutral;
 	};
 
-	showStacks(to, stacks); // Actual display of all stacks
+	const int sideShift = stack->side == BattleSide::ATTACKER ? 1 : -1;
+	const int reverseSideShift = stack->side == BattleSide::ATTACKER ? -1 : 1;
+	const BattleHex nextPos = stack->getPosition() + sideShift;
+	const bool edge = stack->getPosition() % GameConstants::BFIELD_WIDTH == (stack->side == BattleSide::ATTACKER ? GameConstants::BFIELD_WIDTH - 2 : 1);
+	const bool moveInside = !edge && !owner->fieldController->stackCountOutsideHex(nextPos);
+	int xAdd = (stack->side == BattleSide::ATTACKER ? 220 : 202) +
+			(stack->doubleWide() ? 44 : 0) * sideShift +
+			(moveInside ? amountNormal->width() + 10 : 0) * reverseSideShift;
+	int yAdd = 260 + ((stack->side == BattleSide::ATTACKER || moveInside) ? 0 : -15);
+
+	//blitting amount background box
+	std::vector<si32> activeSpells = stack->activeSpells();
+
+	auto amountBG = activeSpells.empty() ? amountNormal : getAmountBoxBackground(getEffectsPositivness(activeSpells));
+	amountBG->draw(to, creAnims[stack->ID]->pos.x + xAdd, creAnims[stack->ID]->pos.y + yAdd);
+
+	//blitting amount
+	Point textPos(creAnims[stack->ID]->pos.x + xAdd + amountNormal->width()/2,
+			creAnims[stack->ID]->pos.y + yAdd + amountNormal->height()/2);
+	graphics->fonts[FONT_TINY]->renderTextCenter(to, makeNumberShort(stack->getCount()), Colors::WHITE, textPos);
 
-	for (auto & stack : stacks)
-	{
-		assert(stack);
-		//printing amount
-		if (isAmountBoxVisible(stack))
-		{
-			const int sideShift = stack->side == BattleSide::ATTACKER ? 1 : -1;
-			const int reverseSideShift = stack->side == BattleSide::ATTACKER ? -1 : 1;
-			const BattleHex nextPos = stack->getPosition() + sideShift;
-			const bool edge = stack->getPosition() % GameConstants::BFIELD_WIDTH == (stack->side == BattleSide::ATTACKER ? GameConstants::BFIELD_WIDTH - 2 : 1);
-			const bool moveInside = !edge && !owner->fieldController->stackCountOutsideHex(nextPos);
-			int xAdd = (stack->side == BattleSide::ATTACKER ? 220 : 202) +
-					   (stack->doubleWide() ? 44 : 0) * sideShift +
-					   (moveInside ? amountNormal->w + 10 : 0) * reverseSideShift;
-			int yAdd = 260 + ((stack->side == BattleSide::ATTACKER || moveInside) ? 0 : -15);
-
-			//blitting amount background box
-			SDL_Surface *amountBG = amountNormal;
-			std::vector<si32> activeSpells = stack->activeSpells();
-			if (!activeSpells.empty())
-				amountBG = getAmountBoxBackground(getEffectsPositivness(activeSpells));
-
-			SDL_Rect temp_rect = genRect(amountBG->h, amountBG->w, creAnims[stack->ID]->pos.x + xAdd, creAnims[stack->ID]->pos.y + yAdd);
-			SDL_BlitSurface(amountBG, nullptr, to, &temp_rect);
-
-			//blitting amount
-			Point textPos(creAnims[stack->ID]->pos.x + xAdd + amountNormal->w/2,
-						  creAnims[stack->ID]->pos.y + yAdd + amountNormal->h/2);
-			graphics->fonts[FONT_TINY]->renderTextCenter(to, makeNumberShort(stack->getCount()), Colors::WHITE, textPos);
-		}
-	}
 }
 
-void CBattleStacksController::showStacks(SDL_Surface *to, std::vector<const CStack *> stacks)
+void CBattleStacksController::showStack(SDL_Surface *to, const CStack * stack)
 {
-	for (const CStack *stack : stacks)
-	{
-		creAnims[stack->ID]->nextFrame(to, facingRight(stack)); // do actual blit
-		creAnims[stack->ID]->incrementFrame(float(GH.mainFPSmng->getElapsedMilliseconds()) / 1000);
-	}
+	creAnims[stack->ID]->nextFrame(to, facingRight(stack)); // do actual blit
+	creAnims[stack->ID]->incrementFrame(float(GH.mainFPSmng->getElapsedMilliseconds()) / 1000);
+
+	if (stackNeedsAmountBox(stack))
+		showStackAmountBox(to, stack);
 }
 
 void CBattleStacksController::updateBattleAnimations()

+ 13 - 8
client/battle/CBattleStacksController.h

@@ -21,15 +21,16 @@ class CBattleAnimation;
 class CCreatureAnimation;
 class CStack;
 class CBattleAnimation;
+class IImage;
 
 class CBattleStacksController
 {
 	CBattleInterface * owner;
 
-	SDL_Surface *amountNormal;
-	SDL_Surface *amountNegative;
-	SDL_Surface *amountPositive;
-	SDL_Surface *amountEffNeutral;
+	std::shared_ptr<IImage> amountNormal;
+	std::shared_ptr<IImage> amountNegative;
+	std::shared_ptr<IImage> amountPositive;
+	std::shared_ptr<IImage> amountEffNeutral;
 
 	std::list<std::pair<CBattleAnimation *, bool>> pendingAnims; //currently displayed animations <anim, initialized>
 	std::map<int32_t, std::shared_ptr<CCreatureAnimation>> creAnims; //animations of creatures from fighting armies (order by BattleInfo's stacks' ID)
@@ -44,11 +45,13 @@ class CBattleStacksController
 	si32 creatureSpellToCast;
 
 	ui32 animIDhelper; //for giving IDs for animations
+
+	bool stackNeedsAmountBox(const CStack * stack);
+	void showStackAmountBox(SDL_Surface *to, const CStack * stack);
+
 public:
 	CBattleStacksController(CBattleInterface * owner);
-	~CBattleStacksController();
 
-	void sortObjectsByHex(BattleObjectsByHex & sorted);
 	bool shouldRotate(const CStack * stack, const BattleHex & oldPos, const BattleHex & nextHex);
 	bool facingRight(const CStack * stack);
 
@@ -72,8 +75,10 @@ public:
 	void setHoveredStack(const CStack *stack);
 	void setSelectedStack(const CStack *stack);
 
-	void showAliveStacks(SDL_Surface *to, std::vector<const CStack *> stacks);
-	void showStacks(SDL_Surface *to, std::vector<const CStack *> stacks);
+	void showAliveStack(SDL_Surface *to, const CStack * stack);
+	void showStack(SDL_Surface *to, const CStack * stack);
+
+	void showBattlefieldObjects(SDL_Surface *to, const BattleHex & location );
 
 	void addNewAnim(CBattleAnimation *anim); //adds new anim to pendingAnims
 	void updateBattleAnimations();

+ 5 - 0
client/gui/CAnimation.cpp

@@ -134,6 +134,11 @@ public:
 	~SDLImageLoader();
 };
 
+std::shared_ptr<IImage> IImage::createFromFile( const std::string & path )
+{
+	return std::shared_ptr<IImage>(new SDLImage(path));
+}
+
 // Extremely simple file cache. TODO: smarter, more general solution
 class CFileCache
 {

+ 3 - 0
client/gui/CAnimation.h

@@ -69,6 +69,9 @@ public:
 
 	IImage();
 	virtual ~IImage();
+
+	/// loads image from specified file. Returns 0-sized images on failure
+	static std::shared_ptr<IImage> createFromFile( const std::string & path );
 };
 
 /// Class for handling animation

+ 30 - 17
client/gui/SDL_Extensions.h

@@ -155,37 +155,50 @@ typedef void (*BlitterWithRotationVal)(SDL_Surface *src,SDL_Rect srcRect, SDL_Su
 class ColorShifter
 {
 public:
-	virtual ~ColorShifter() = default;
-	virtual SDL_Color shiftColor(SDL_Color clr) const = 0;
+	~ColorShifter() = default;
+	virtual SDL_Color shiftColor(SDL_Color input) const = 0;
 };
 
-class ColorShifterLightBlue : public ColorShifter
+class ColorShifterAddMul : public ColorShifter
 {
+	SDL_Color add;
+	SDL_Color mul;
 public:
-	SDL_Color shiftColor(SDL_Color clr) const override
+
+	static ColorShifterAddMul deepBlue()
 	{
-		clr.b = clr.b + (255 - clr.b) / 2;
-		return clr;
+		return ColorShifterAddMul({0, 0, 255, 0}, {255, 255, 0, 255});
 	}
-};
 
-class ColorShifterDeepBlue : public ColorShifter
-{
-public:
-	SDL_Color shiftColor(SDL_Color clr) const override
+	ColorShifterAddMul(SDL_Color add, SDL_Color mul) :
+		add(add),
+		mul(mul)
+	{}
+
+	SDL_Color shiftColor(SDL_Color input) const override
 	{
-		clr.b = 255;
-		return clr;
+		return {
+			uint8_t(std::min(255.f, std::round(input.r * float(mul.r) / 255 + add.r))),
+			uint8_t(std::min(255.f, std::round(input.g * float(mul.g) / 255 + add.g))),
+			uint8_t(std::min(255.f, std::round(input.b * float(mul.b) / 255 + add.b))),
+			uint8_t(std::min(255.f, std::round(input.a * float(mul.a) / 255 + add.a))),
+		};
 	}
 };
 
-class ColorShifterDeepRed : public ColorShifter
+class ColorShifterAddMulExcept : public ColorShifterAddMul
 {
+	SDL_Color ignored;
 public:
-	SDL_Color shiftColor(SDL_Color clr) const override
+	ColorShifterAddMulExcept(SDL_Color add, SDL_Color mul, SDL_Color ignored) :
+		ColorShifterAddMul(add, mul)
+	{}
+
+	SDL_Color shiftColor(SDL_Color input) const override
 	{
-		clr.r = 255;
-		return clr;
+		if ( input.r == ignored.r && input.g == ignored.g && input.b == ignored.b && input.a == ignored.a)
+			return input;
+		return ColorShifterAddMul::shiftColor(input);
 	}
 };
 

+ 3 - 3
client/lobby/OptionsTab.cpp

@@ -468,18 +468,18 @@ OptionsTab::PlayerOptionsEntry::PlayerOptionsEntry(const PlayerSettings & S, con
 	else
 		whoCanPlay = HUMAN;
 
-	static const char * flags[] =
+	static std::string flags[] =
 	{
 		"AOFLGBR.DEF", "AOFLGBB.DEF", "AOFLGBY.DEF", "AOFLGBG.DEF",
 		"AOFLGBO.DEF", "AOFLGBP.DEF", "AOFLGBT.DEF", "AOFLGBS.DEF"
 	};
-	static const char * bgs[] =
+	static std::string bgs[] =
 	{
 		"ADOPRPNL.bmp", "ADOPBPNL.bmp", "ADOPYPNL.bmp", "ADOPGPNL.bmp",
 		"ADOPOPNL.bmp", "ADOPPPNL.bmp", "ADOPTPNL.bmp", "ADOPSPNL.bmp"
 	};
 
-	background = std::make_shared<CPicture>(BitmapHandler::loadBitmap(bgs[s.color.getNum()]), 0, 0, true);
+	background = std::make_shared<CPicture>(bgs[s.color.getNum()], 0, 0);
 	labelPlayerName = std::make_shared<CLabel>(55, 10, EFonts::FONT_SMALL, EAlignment::CENTER, Colors::WHITE, s.name);
 	labelWhoCanPlay = std::make_shared<CMultiLineLabel>(Rect(6, 23, 45, (int)graphics->fonts[EFonts::FONT_TINY]->getLineHeight()*2), EFonts::FONT_TINY, EAlignment::CENTER, Colors::WHITE, CGI->generaltexth->arraytxt[206 + whoCanPlay]);
 

+ 5 - 0
lib/battle/BattleHex.h

@@ -34,10 +34,15 @@ 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
 {
+	// helpers for siege
 	static const si16 CASTLE_CENTRAL_TOWER = -2;
 	static const si16 CASTLE_BOTTOM_TOWER = -3;
 	static const si16 CASTLE_UPPER_TOWER = -4;
 
+	// helpers for rendering
+	static const si16 HEX_BEFORE_ALL = std::numeric_limits<si16>::min();
+	static const si16 HEX_AFTER_ALL = std::numeric_limits<si16>::max();
+
 	si16 hex;
 	static const si16 INVALID = -1;
 	enum EDir