diff --git a/client/battle/CBattleEffectsController.cpp b/client/battle/CBattleEffectsController.cpp index 7db1078ca..9441c0f48 100644 --- a/client/battle/CBattleEffectsController.cpp +++ b/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 canvas, const BattleHex & destTile) +void CBattleEffectsController::collectRenderableObjects(CBattleFieldRenderer & renderer) { for (auto & elem : battleEffects) { - if (!elem.position.isValid() && destTile != BattleHex::HEX_AFTER_ALL) - continue; + renderer.insert( EBattleFieldLayer::EFFECTS, elem.position, [&elem](CBattleFieldRenderer::RendererPtr canvas) + { + int currentFrame = static_cast(floor(elem.currentFrame)); + currentFrame %= elem.animation->size(); - if (elem.position.isValid() && elem.position != destTile) - continue; + auto img = elem.animation->getImage(currentFrame); - int currentFrame = static_cast(floor(elem.currentFrame)); - currentFrame %= elem.animation->size(); - - auto img = elem.animation->getImage(currentFrame); - - canvas->draw(img, Point(elem.x, elem.y)); + canvas->draw(img, Point(elem.x, elem.y)); + }); } } diff --git a/client/battle/CBattleEffectsController.h b/client/battle/CBattleEffectsController.h index c97287ddc..8df53f091 100644 --- a/client/battle/CBattleEffectsController.h +++ b/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 canvas, const BattleHex & destTile); + void collectRenderableObjects(CBattleFieldRenderer & renderer); friend class CPointEffectAnimation; }; diff --git a/client/battle/CBattleFieldController.cpp b/client/battle/CBattleFieldController.cpp index c4ab9eae1..72ffc16e4 100644 --- a/client/battle/CBattleFieldController.cpp +++ b/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 canvas) +{ + showBackground(canvas); + + CBattleFieldRenderer renderer(owner); + + renderer.execute(canvas); + + owner->projectilesController->showProjectiles(canvas); +} + void CBattleFieldController::showBackground(std::shared_ptr 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); +} diff --git a/client/battle/CBattleFieldController.h b/client/battle/CBattleFieldController.h index 607f95a3b..2961377d2 100644 --- a/client/battle/CBattleFieldController.h +++ b/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; + using RenderFunctor = std::function; + +private: + CBattleInterface * owner; + + struct RenderableInstance + { + RenderFunctor functor; + EBattleFieldLayer layer; + BattleHex tile; + }; + std::vector 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 getHighlightedHexesStackRange(); std::set getHighlightedHexesSpellRange(); + void showBackground(std::shared_ptr canvas); void showBackgroundImage(std::shared_ptr canvas); void showBackgroundImageWithHexes(std::shared_ptr canvas); void showHighlightedHexes(std::shared_ptr canvas); @@ -61,9 +101,8 @@ class CBattleFieldController : public CIntObject public: CBattleFieldController(CBattleInterface * owner); - void showBackground(std::shared_ptr canvas); - void redrawBackgroundWithHexes(); + void renderBattlefield(std::shared_ptr canvas); Rect hexPositionLocal(BattleHex hex) const; Rect hexPosition(BattleHex hex) const; diff --git a/client/battle/CBattleInterface.cpp b/client/battle/CBattleInterface.cpp index 8bf36f61e..a30165d10 100644 --- a/client/battle/CBattleInterface.cpp +++ b/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 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 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 canvas) diff --git a/client/battle/CBattleInterface.h b/client/battle/CBattleInterface.h index 420f83221..1069941d3 100644 --- a/client/battle/CBattleInterface.h +++ b/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 canvas); - void showBattlefieldObjects(std::shared_ptr canvas); - void showBattlefieldObjects(std::shared_ptr canvas, const BattleHex & location ); - void setHeroAnimation(ui8 side, int phase); public: std::unique_ptr 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); diff --git a/client/battle/CBattleObstacleController.cpp b/client/battle/CBattleObstacleController.cpp index 79dadfc87..90f7f3cc1 100644 --- a/client/battle/CBattleObstacleController.cpp +++ b/client/battle/CBattleObstacleController.cpp @@ -126,9 +126,9 @@ void CBattleObstacleController::showAbsoluteObstacles(std::shared_ptr c } } -void CBattleObstacleController::showBattlefieldObjects(std::shared_ptr 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 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); + } + }); } } diff --git a/client/battle/CBattleObstacleController.h b/client/battle/CBattleObstacleController.h index ffd6da1f3..586a68300 100644 --- a/client/battle/CBattleObstacleController.h +++ b/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> &obstacles); void showAbsoluteObstacles(std::shared_ptr canvas, const Point & offset); - void showBattlefieldObjects(std::shared_ptr canvas, const BattleHex & location ); + void collectRenderableObjects(CBattleFieldRenderer & renderer); }; diff --git a/client/battle/CBattleProjectileController.cpp b/client/battle/CBattleProjectileController.cpp index 826dbf172..511960564 100644 --- a/client/battle/CBattleProjectileController.cpp +++ b/client/battle/CBattleProjectileController.cpp @@ -228,10 +228,6 @@ void CBattleProjectileController::showProjectiles(std::shared_ptr 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); diff --git a/client/battle/CBattleSiegeController.cpp b/client/battle/CBattleSiegeController.cpp index 1733ab140..d95f0b7c4 100644 --- a/client/battle/CBattleSiegeController.cpp +++ b/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 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 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()); + }); + + } } diff --git a/client/battle/CBattleSiegeController.h b/client/battle/CBattleSiegeController.h index c944ba080..01f2e328e 100644 --- a/client/battle/CBattleSiegeController.h +++ b/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 canvas, const Point & offset); - void showBattlefieldObjects(std::shared_ptr canvas, const BattleHex & location ); + void collectRenderableObjects(CBattleFieldRenderer & renderer); /// queries from other battle controllers bool isAttackableByCatapult(BattleHex hex) const; diff --git a/client/battle/CBattleStacksController.cpp b/client/battle/CBattleStacksController.cpp index 2c6f01d5f..155e6a8b8 100644 --- a/client/battle/CBattleStacksController.cpp +++ b/client/battle/CBattleStacksController.cpp @@ -93,66 +93,54 @@ CBattleStacksController::CBattleStacksController(CBattleInterface * owner): } } -void CBattleStacksController::showBattlefieldObjects(std::shared_ptr canvas, const BattleHex & location ) +BattleHex CBattleStacksController::getStackCurrentPosition(const CStack * stack) { - auto getCurrentPosition = [&](const CStack *stack) -> BattleHex - { - 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(anim)) - { - if (move->stack == stack) - return move->currentHex; - } - - } + if ( !stackAnimation[stack->ID]->isMoving()) return stack->getPosition(); - }; + if (stack->hasBonusOfType(Bonus::FLYING)) + return BattleHex::HEX_AFTER_ALL; + + 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(anim)) + { + if (move->stack == stack) + return move->currentHex; + } + } + 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 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() diff --git a/client/battle/CBattleStacksController.h b/client/battle/CBattleStacksController.h index 63b7beec7..01f3482e9 100644 --- a/client/battle/CBattleStacksController.h +++ b/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 canvas, const CStack * stack); + BattleHex getStackCurrentPosition(const CStack * stack); std::shared_ptr getStackAmountBox(const CStack * stack); @@ -87,7 +89,7 @@ public: void showAliveStack(std::shared_ptr canvas, const CStack * stack); void showStack(std::shared_ptr canvas, const CStack * stack); - void showBattlefieldObjects(std::shared_ptr canvas, const BattleHex & location ); + void collectRenderableObjects(CBattleFieldRenderer & renderer); void addNewAnim(CBattleAnimation *anim); //adds new anim to pendingAnims void updateBattleAnimations();