Browse Source

Implemented proper rendering order generation for battlefield objects

Ivan Savenko 2 years ago
parent
commit
1b4c5be4de

+ 9 - 11
client/battle/CBattleEffectsController.cpp

@@ -14,6 +14,7 @@
 #include "CBattleControlPanel.h"
 #include "CBattleInterface.h"
 #include "CBattleInterfaceClasses.h"
+#include "CBattleFieldController.h"
 #include "CBattleStacksController.h"
 
 #include "../CMusicHandler.h"
@@ -121,21 +122,18 @@ void CBattleEffectsController::startAction(const BattleAction* action)
 	}
 }
 
-void CBattleEffectsController::showBattlefieldObjects(std::shared_ptr<CCanvas> canvas, const BattleHex & destTile)
+void CBattleEffectsController::collectRenderableObjects(CBattleFieldRenderer & renderer)
 {
 	for (auto & elem : battleEffects)
 	{
-		if (!elem.position.isValid() && destTile != BattleHex::HEX_AFTER_ALL)
-			continue;
-
-		if (elem.position.isValid() && elem.position != destTile)
-			continue;
-
-		int currentFrame = static_cast<int>(floor(elem.currentFrame));
-		currentFrame %= elem.animation->size();
+		renderer.insert( EBattleFieldLayer::EFFECTS, elem.position, [&elem](CBattleFieldRenderer::RendererPtr canvas)
+		{
+			int currentFrame = static_cast<int>(floor(elem.currentFrame));
+			currentFrame %= elem.animation->size();
 
-		auto img = elem.animation->getImage(currentFrame);
+			auto img = elem.animation->getImage(currentFrame);
 
-		canvas->draw(img, Point(elem.x, elem.y));
+			canvas->draw(img, Point(elem.x, elem.y));
+		});
 	}
 }

+ 2 - 1
client/battle/CBattleEffectsController.h

@@ -23,6 +23,7 @@ struct SDL_Surface;
 class CAnimation;
 class CCanvas;
 class CBattleInterface;
+class CBattleFieldRenderer;
 class CPointEffectAnimation;
 
 namespace EBattleEffect
@@ -77,7 +78,7 @@ public:
 
 	void battleTriggerEffect(const BattleTriggerEffect & bte);
 
-	void showBattlefieldObjects(std::shared_ptr<CCanvas> canvas, const BattleHex & destTile);
+	void collectRenderableObjects(CBattleFieldRenderer & renderer);
 
 	friend class CPointEffectAnimation;
 };

+ 72 - 0
client/battle/CBattleFieldController.cpp

@@ -13,9 +13,11 @@
 #include "CBattleInterface.h"
 #include "CBattleActionsController.h"
 #include "CBattleInterfaceClasses.h"
+#include "CBattleEffectsController.h"
 #include "CBattleSiegeController.h"
 #include "CBattleStacksController.h"
 #include "CBattleObstacleController.h"
+#include "CBattleProjectileController.h"
 
 #include "../CGameInfo.h"
 #include "../CPlayerInterface.h"
@@ -86,6 +88,17 @@ CBattleFieldController::CBattleFieldController(CBattleInterface * owner):
 		stackCountOutsideHexes[i] = (accessibility[i] == EAccessibility::ACCESSIBLE);
 }
 
+void CBattleFieldController::renderBattlefield(std::shared_ptr<CCanvas> canvas)
+{
+	showBackground(canvas);
+
+	CBattleFieldRenderer renderer(owner);
+
+	renderer.execute(canvas);
+
+	owner->projectilesController->showProjectiles(canvas);
+}
+
 void CBattleFieldController::showBackground(std::shared_ptr<CCanvas> canvas)
 {
 	if (owner->stacksController->getActiveStack() != nullptr ) //&& creAnims[stacksController->getActiveStack()->ID]->isIdle() //show everything with range
@@ -618,3 +631,62 @@ bool CBattleFieldController::stackCountOutsideHex(const BattleHex & number) cons
 {
 	return stackCountOutsideHexes[number];
 }
+
+void CBattleFieldRenderer::collectObjects()
+{
+	owner->collectRenderableObjects(*this);
+	owner->effectsController->collectRenderableObjects(*this);
+	owner->obstacleController->collectRenderableObjects(*this);
+	owner->siegeController->collectRenderableObjects(*this);
+	owner->stacksController->collectRenderableObjects(*this);
+}
+
+void CBattleFieldRenderer::sortObjects()
+{
+	auto getRow = [](const RenderableInstance & object) -> int
+	{
+		if (object.tile.isValid())
+			return object.tile.getY();
+
+		if ( object.tile == BattleHex::HEX_BEFORE_ALL )
+			return -1;
+
+		if ( object.tile == BattleHex::HEX_AFTER_ALL )
+			return GameConstants::BFIELD_HEIGHT;
+
+		if ( object.tile == BattleHex::INVALID )
+			return GameConstants::BFIELD_HEIGHT;
+
+		assert(0);
+		return GameConstants::BFIELD_HEIGHT;
+	};
+
+	std::stable_sort(objects.begin(), objects.end(), [&](const RenderableInstance & left, const RenderableInstance & right){
+		if ( getRow(left) != getRow(right))
+			return getRow(left) < getRow(right);
+		return left.layer < right.layer;
+	});
+}
+
+void CBattleFieldRenderer::renderObjects(CBattleFieldRenderer::RendererPtr targetCanvas)
+{
+	for (auto const & object : objects)
+		object.functor(targetCanvas);
+}
+
+CBattleFieldRenderer::CBattleFieldRenderer(CBattleInterface * owner):
+	owner(owner)
+{
+}
+
+void CBattleFieldRenderer::insert(EBattleFieldLayer layer, BattleHex tile, CBattleFieldRenderer::RenderFunctor functor)
+{
+	objects.push_back({ functor, layer, tile });
+}
+
+void CBattleFieldRenderer::execute(CBattleFieldRenderer::RendererPtr targetCanvas)
+{
+	collectObjects();
+	sortObjects();
+	renderObjects(targetCanvas);
+}

+ 41 - 2
client/battle/CBattleFieldController.h

@@ -27,6 +27,45 @@ class CCanvas;
 class IImage;
 class CBattleInterface;
 
+enum class EBattleFieldLayer {
+					   // confirmed ordering requirements:
+	OBSTACLES     = 0,
+	CORPSES       = 0,
+	WALLS         = 1,
+	HEROES        = 1,
+	STACKS        = 1, // after corpses, obstacles
+	BATTLEMENTS   = 2, // after stacks
+	STACK_AMOUNTS = 2, // after stacks, obstacles, corpses
+	EFFECTS       = 3, // after obstacles, battlements
+};
+
+class CBattleFieldRenderer
+{
+public:
+	using RendererPtr = std::shared_ptr<CCanvas>;
+	using RenderFunctor = std::function<void(RendererPtr)>;
+
+private:
+	CBattleInterface * owner;
+
+	struct RenderableInstance
+	{
+		RenderFunctor functor;
+		EBattleFieldLayer layer;
+		BattleHex tile;
+	};
+	std::vector<RenderableInstance> objects;
+
+	void collectObjects();
+	void sortObjects();
+	void renderObjects(RendererPtr targetCanvas);
+public:
+	CBattleFieldRenderer(CBattleInterface * owner);
+
+	void insert(EBattleFieldLayer layer, BattleHex tile, RenderFunctor functor);
+	void execute(RendererPtr targetCanvas);
+};
+
 class CBattleFieldController : public CIntObject
 {
 	CBattleInterface * owner;
@@ -54,6 +93,7 @@ class CBattleFieldController : public CIntObject
 	std::set<BattleHex> getHighlightedHexesStackRange();
 	std::set<BattleHex> getHighlightedHexesSpellRange();
 
+	void showBackground(std::shared_ptr<CCanvas> canvas);
 	void showBackgroundImage(std::shared_ptr<CCanvas> canvas);
 	void showBackgroundImageWithHexes(std::shared_ptr<CCanvas> canvas);
 	void showHighlightedHexes(std::shared_ptr<CCanvas> canvas);
@@ -61,9 +101,8 @@ class CBattleFieldController : public CIntObject
 public:
 	CBattleFieldController(CBattleInterface * owner);
 
-	void showBackground(std::shared_ptr<CCanvas> canvas);
-
 	void redrawBackgroundWithHexes();
+	void renderBattlefield(std::shared_ptr<CCanvas> canvas);
 
 	Rect hexPositionLocal(BattleHex hex) const;
 	Rect hexPosition(BattleHex hex) const;

+ 14 - 23
client/battle/CBattleInterface.cpp

@@ -899,9 +899,7 @@ void CBattleInterface::show(SDL_Surface *to)
 
 	++animCount;
 
-	fieldController->showBackground(canvas);
-	showBattlefieldObjects(canvas);
-	projectilesController->showProjectiles(canvas);
+	fieldController->renderBattlefield(canvas);
 
 	if(battleActionsStarted)
 		stacksController->updateBattleAnimations();
@@ -915,29 +913,22 @@ void CBattleInterface::show(SDL_Surface *to)
 	//activateStack();
 }
 
-void CBattleInterface::showBattlefieldObjects(std::shared_ptr<CCanvas> canvas, const BattleHex & location )
+void CBattleInterface::collectRenderableObjects(CBattleFieldRenderer & renderer)
 {
-	if (siegeController)
-		siegeController->showBattlefieldObjects(canvas, location);
-	obstacleController->showBattlefieldObjects(canvas, location);
-	stacksController->showBattlefieldObjects(canvas, location);
-	effectsController->showBattlefieldObjects(canvas, location);
-}
-
-void CBattleInterface::showBattlefieldObjects(std::shared_ptr<CCanvas> canvas)
-{
-	showBattlefieldObjects(canvas, BattleHex::HEX_BEFORE_ALL);
-
-	// show heroes after "beforeAll" - e.g. topmost wall in siege
 	if (attackingHero)
-		attackingHero->show(canvas->getSurface());
+	{
+		renderer.insert(EBattleFieldLayer::HEROES, BattleHex(0),[this](CBattleFieldRenderer::RendererPtr canvas)
+		{
+			attackingHero->show(canvas->getSurface());
+		});
+	}
 	if (defendingHero)
-		defendingHero->show(canvas->getSurface());
-
-	for (int i = 0; i < GameConstants::BFIELD_SIZE; ++i)
-		showBattlefieldObjects(canvas, BattleHex(i));
-
-	showBattlefieldObjects(canvas, BattleHex::HEX_AFTER_ALL);
+	{
+		renderer.insert(EBattleFieldLayer::HEROES, BattleHex(GameConstants::BFIELD_WIDTH-1),[this](CBattleFieldRenderer::RendererPtr canvas)
+		{
+			defendingHero->show(canvas->getSurface());
+		});
+	}
 }
 
 void CBattleInterface::showInterface(std::shared_ptr<CCanvas> canvas)

+ 3 - 3
client/battle/CBattleInterface.h

@@ -47,6 +47,7 @@ class CBattleProjectileController;
 class CBattleSiegeController;
 class CBattleObstacleController;
 class CBattleFieldController;
+class CBattleFieldRenderer;
 class CBattleControlPanel;
 class CBattleStacksController;
 class CBattleActionsController;
@@ -99,9 +100,6 @@ private:
 
 	void showInterface(std::shared_ptr<CCanvas> canvas);
 
-	void showBattlefieldObjects(std::shared_ptr<CCanvas> canvas);
-	void showBattlefieldObjects(std::shared_ptr<CCanvas> canvas, const BattleHex & location );
-
 	void setHeroAnimation(ui8 side, int phase);
 public:
 	std::unique_ptr<CBattleProjectileController> projectilesController;
@@ -145,6 +143,8 @@ public:
 	void show(SDL_Surface *to) override;
 	void showAll(SDL_Surface *to) override;
 
+	void collectRenderableObjects(CBattleFieldRenderer & renderer);
+
 	//call-ins
 	void startAction(const BattleAction* action);
 	void stackReset(const CStack * stack);

+ 10 - 11
client/battle/CBattleObstacleController.cpp

@@ -126,9 +126,9 @@ void CBattleObstacleController::showAbsoluteObstacles(std::shared_ptr<CCanvas> c
 	}
 }
 
-void CBattleObstacleController::showBattlefieldObjects(std::shared_ptr<CCanvas> canvas, const BattleHex & location )
+void CBattleObstacleController::collectRenderableObjects(CBattleFieldRenderer & renderer)
 {
-	for (auto &obstacle : owner->curInt->cb->battleGetAllObstacles())
+	for (auto obstacle : owner->curInt->cb->battleGetAllObstacles())
 	{
 		if (obstacle->obstacleType == CObstacleInstance::ABSOLUTE_OBSTACLE)
 			continue;
@@ -136,15 +136,14 @@ void CBattleObstacleController::showBattlefieldObjects(std::shared_ptr<CCanvas>
 		if (obstacle->obstacleType == CObstacleInstance::MOAT)
 			continue;
 
-		if ( obstacle->pos != location)
-			continue;
-
-		auto img = getObstacleImage(*obstacle);
-		if(img)
-		{
-			Point p = getObstaclePosition(img, *obstacle);
-			canvas->draw(img, p);
-		}
+		renderer.insert(EBattleFieldLayer::OBSTACLES, obstacle->pos, [this, obstacle]( CBattleFieldRenderer::RendererPtr canvas ){
+			auto img = getObstacleImage(*obstacle);
+			if(img)
+			{
+				Point p = getObstaclePosition(img, *obstacle);
+				canvas->draw(img, p);
+			}
+		});
 	}
 }
 

+ 2 - 1
client/battle/CBattleObstacleController.h

@@ -21,6 +21,7 @@ class IImage;
 class CCanvas;
 class CAnimation;
 class CBattleInterface;
+class CBattleFieldRenderer;
 struct Point;
 
 class CBattleObstacleController
@@ -47,5 +48,5 @@ public:
 	void showObstacles(SDL_Surface *to, std::vector<std::shared_ptr<const CObstacleInstance>> &obstacles);
 	void showAbsoluteObstacles(std::shared_ptr<CCanvas> canvas, const Point & offset);
 
-	void showBattlefieldObjects(std::shared_ptr<CCanvas> canvas, const BattleHex & location );
+	void collectRenderableObjects(CBattleFieldRenderer & renderer);
 };

+ 0 - 4
client/battle/CBattleProjectileController.cpp

@@ -228,10 +228,6 @@ void CBattleProjectileController::showProjectiles(std::shared_ptr<CCanvas> canva
 	for ( auto it = projectiles.begin(); it != projectiles.end();)
 	{
 		auto projectile = *it;
-		// Check if projectile is already visible (shooter animation did the shot)
-		//if (!it->shotDone)
-		//	continue;
-
 		if ( projectile->playing )
 			projectile->show(canvas);
 

+ 14 - 4
client/battle/CBattleSiegeController.cpp

@@ -14,6 +14,7 @@
 #include "CBattleInterface.h"
 #include "CBattleInterfaceClasses.h"
 #include "CBattleStacksController.h"
+#include "CBattleFieldController.h"
 
 #include "../CMusicHandler.h"
 #include "../CGameInfo.h"
@@ -279,7 +280,7 @@ const CStack * CBattleSiegeController::getTurretStack(EWallVisual::EWallVisual w
 	return nullptr;
 }
 
-void CBattleSiegeController::showBattlefieldObjects(std::shared_ptr<CCanvas> canvas, const BattleHex & location )
+void CBattleSiegeController::collectRenderableObjects(CBattleFieldRenderer & renderer)
 {
 	for (int i = EWallVisual::WALL_FIRST; i <= EWallVisual::WALL_LAST; ++i)
 	{
@@ -288,16 +289,25 @@ void CBattleSiegeController::showBattlefieldObjects(std::shared_ptr<CCanvas> can
 		if ( !getWallPieceExistance(wallPiece))
 			continue;
 
-		if ( getWallPiecePosition(wallPiece) != location)
+		if ( getWallPiecePosition(wallPiece) == BattleHex::INVALID)
 			continue;
 
 		if (wallPiece == EWallVisual::KEEP_BATTLEMENT ||
 			wallPiece == EWallVisual::BOTTOM_BATTLEMENT ||
 			wallPiece == EWallVisual::UPPER_BATTLEMENT)
 		{
-			owner->stacksController->showStack(canvas, getTurretStack(wallPiece));
+			renderer.insert( EBattleFieldLayer::STACKS, getWallPiecePosition(wallPiece), [this, wallPiece](CBattleFieldRenderer::RendererPtr canvas){
+				owner->stacksController->showStack(canvas, getTurretStack(wallPiece));
+			});
+			renderer.insert( EBattleFieldLayer::BATTLEMENTS, getWallPiecePosition(wallPiece), [this, wallPiece](CBattleFieldRenderer::RendererPtr canvas){
+				showWallPiece(canvas, wallPiece, owner->pos.topLeft());
+			});
 		}
-		showWallPiece(canvas, wallPiece, owner->pos.topLeft());
+		renderer.insert( EBattleFieldLayer::WALLS, getWallPiecePosition(wallPiece), [this, wallPiece](CBattleFieldRenderer::RendererPtr canvas){
+			showWallPiece(canvas, wallPiece, owner->pos.topLeft());
+		});
+
+
 	}
 }
 

+ 2 - 1
client/battle/CBattleSiegeController.h

@@ -25,6 +25,7 @@ struct Point;
 struct SDL_Surface;
 class CCanvas;
 class CBattleInterface;
+class CBattleFieldRenderer;
 class IImage;
 
 namespace EWallVisual
@@ -98,7 +99,7 @@ public:
 
 	/// call-ins from other battle controllers
 	void showAbsoluteObstacles(std::shared_ptr<CCanvas> canvas, const Point & offset);
-	void showBattlefieldObjects(std::shared_ptr<CCanvas> canvas, const BattleHex & location );
+	void collectRenderableObjects(CBattleFieldRenderer & renderer);
 
 	/// queries from other battle controllers
 	bool isAttackableByCatapult(BattleHex hex) const;

+ 30 - 45
client/battle/CBattleStacksController.cpp

@@ -93,66 +93,54 @@ CBattleStacksController::CBattleStacksController(CBattleInterface * owner):
 	}
 }
 
-void CBattleStacksController::showBattlefieldObjects(std::shared_ptr<CCanvas> canvas, const BattleHex & location )
+BattleHex CBattleStacksController::getStackCurrentPosition(const CStack * stack)
 {
-	auto getCurrentPosition = [&](const CStack *stack) -> BattleHex
+	if ( !stackAnimation[stack->ID]->isMoving())
+		return stack->getPosition();
+
+	if (stack->hasBonusOfType(Bonus::FLYING))
+		return BattleHex::HEX_AFTER_ALL;
+
+	for (auto & anim : currentAnimations)
 	{
-		for (auto & anim : currentAnimations)
+		// certainly ugly workaround but fixes quite annoying bug
+		// stack position will be updated only *after* movement is finished
+		// before this - stack is always at its initial position. Thus we need to find
+		// its current position. Which can be found only in this class
+		if (CStackMoveAnimation *move = dynamic_cast<CStackMoveAnimation*>(anim))
 		{
-			// certainly ugly workaround but fixes quite annoying bug
-			// stack position will be updated only *after* movement is finished
-			// before this - stack is always at its initial position. Thus we need to find
-			// its current position. Which can be found only in this class
-			if (CStackMoveAnimation *move = dynamic_cast<CStackMoveAnimation*>(anim))
-			{
-				if (move->stack == stack)
-					return move->currentHex;
-			}
-
+			if (move->stack == stack)
+				return move->currentHex;
 		}
-		return stack->getPosition();
-	};
+	}
+	return stack->getPosition();
+}
 
+void CBattleStacksController::collectRenderableObjects(CBattleFieldRenderer & renderer)
+{
 	auto stacks = owner->curInt->cb->battleGetAllStacks(false);
 
-	for (auto & stack : stacks)
+	for (auto stack : stacks)
 	{
 		if (stackAnimation.find(stack->ID) == stackAnimation.end()) //e.g. for summoned but not yet handled stacks
 			continue;
 
 		//FIXME: hack to ignore ghost stacks
 		if ((stackAnimation[stack->ID]->getType() == CCreatureAnim::DEAD || stackAnimation[stack->ID]->getType() == CCreatureAnim::HOLDING) && stack->isGhost())
-			continue;//ignore
-
-		if (stackAnimation[stack->ID]->isDead())
-		{
-			//if ( location == stack->getPosition() )
-			if ( location == BattleHex::HEX_BEFORE_ALL ) //FIXME: any cases when using BEFORE_ALL won't work?
-				showStack(canvas, stack);
 			continue;
-		}
 
-		// standing - blit at current position
-		if (!stackAnimation[stack->ID]->isMoving())
-		{
-			if ( location == stack->getPosition() )
-				showStack(canvas, stack);
-			continue;
-		}
+		auto layer = stackAnimation[stack->ID]->isDead() ? EBattleFieldLayer::CORPSES : EBattleFieldLayer::STACKS;
+		auto location = getStackCurrentPosition(stack);
 
-		// flying creature - just blit them over everyone else
-		if (stack->hasBonusOfType(Bonus::FLYING))
-		{
-			if ( location == BattleHex::HEX_AFTER_ALL)
-				showStack(canvas, stack);
-			continue;
-		}
+		renderer.insert(layer, location, [this, stack]( CBattleFieldRenderer::RendererPtr renderer ){
+			showStack(renderer, stack);
+		});
 
-		// else - unit moving on ground
+		if (stackNeedsAmountBox(stack))
 		{
-			if ( location == getCurrentPosition(stack) )
-				showStack(canvas, stack);
-			continue;
+			renderer.insert(EBattleFieldLayer::STACK_AMOUNTS, location, [this, stack]( CBattleFieldRenderer::RendererPtr renderer ){
+				showStackAmountBox(renderer, stack);
+			});
 		}
 	}
 }
@@ -342,9 +330,6 @@ void CBattleStacksController::showStack(std::shared_ptr<CCanvas> canvas, const C
 {
 	stackAnimation[stack->ID]->nextFrame(canvas, facingRight(stack)); // do actual blit
 	stackAnimation[stack->ID]->incrementFrame(float(GH.mainFPSmng->getElapsedMilliseconds()) / 1000);
-
-	if (stackNeedsAmountBox(stack))
-		showStackAmountBox(canvas, stack);
 }
 
 void CBattleStacksController::updateBattleAnimations()

+ 3 - 1
client/battle/CBattleStacksController.h

@@ -28,6 +28,7 @@ class CBattleInterface;
 class CBattleAnimation;
 class CCreatureAnimation;
 class CBattleAnimation;
+class CBattleFieldRenderer;
 class IImage;
 
 class CBattleStacksController
@@ -55,6 +56,7 @@ class CBattleStacksController
 
 	bool stackNeedsAmountBox(const CStack * stack);
 	void showStackAmountBox(std::shared_ptr<CCanvas> canvas, const CStack * stack);
+	BattleHex getStackCurrentPosition(const CStack * stack);
 
 	std::shared_ptr<IImage> getStackAmountBox(const CStack * stack);
 
@@ -87,7 +89,7 @@ public:
 	void showAliveStack(std::shared_ptr<CCanvas> canvas, const CStack * stack);
 	void showStack(std::shared_ptr<CCanvas> canvas, const CStack * stack);
 
-	void showBattlefieldObjects(std::shared_ptr<CCanvas> canvas, const BattleHex & location );
+	void collectRenderableObjects(CBattleFieldRenderer & renderer);
 
 	void addNewAnim(CBattleAnimation *anim); //adds new anim to pendingAnims
 	void updateBattleAnimations();