1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

Moved stacks & animations handling into a separate class

This commit is contained in:
Ivan Savenko 2022-11-20 19:11:34 +02:00
parent 7a6ad671ab
commit b01737daf2
16 changed files with 979 additions and 707 deletions

View File

@ -10,6 +10,7 @@ set(client_SRCS
battle/CBattleObstacleController.cpp battle/CBattleObstacleController.cpp
battle/CBattleProjectileController.cpp battle/CBattleProjectileController.cpp
battle/CBattleSiegeController.cpp battle/CBattleSiegeController.cpp
battle/CBattleStacksController.cpp
battle/CCreatureAnimation.cpp battle/CCreatureAnimation.cpp
gui/CAnimation.cpp gui/CAnimation.cpp
@ -88,6 +89,7 @@ set(client_HEADERS
battle/CBattleObstacleController.h battle/CBattleObstacleController.h
battle/CBattleProjectileController.h battle/CBattleProjectileController.h
battle/CBattleSiegeController.h battle/CBattleSiegeController.h
battle/CBattleStacksController.h
battle/CCreatureAnimation.h battle/CCreatureAnimation.h
gui/CAnimation.h gui/CAnimation.h

View File

@ -738,34 +738,14 @@ void CPlayerInterface::battleUnitsChanged(const std::vector<UnitChanges> & units
{ {
case UnitChanges::EOperation::RESET_STATE: case UnitChanges::EOperation::RESET_STATE:
{ {
const battle::Unit * unit = cb->battleGetUnitByID(info.id); const CStack * stack = cb->battleGetStackByID(info.id );
if(!unit) if(!stack)
{ {
logGlobal->error("Invalid unit ID %d", info.id); logGlobal->error("Invalid unit ID %d", info.id);
continue; continue;
} }
battleInt->stackReset(stack);
auto iter = battleInt->creAnims.find(info.id);
if(iter == battleInt->creAnims.end())
{
logGlobal->error("Unit %d have no animation", info.id);
continue;
}
auto animation = iter->second;
if(unit->alive() && animation->isDead())
animation->setType(CCreatureAnim::HOLDING);
if (unit->isClone())
{
std::unique_ptr<ColorShifterDeepBlue> shifter(new ColorShifterDeepBlue());
animation->shiftColor(shifter.get());
}
//TODO: handle more cases
} }
break; break;
case UnitChanges::EOperation::REMOVE: case UnitChanges::EOperation::REMOVE:
@ -779,7 +759,7 @@ void CPlayerInterface::battleUnitsChanged(const std::vector<UnitChanges> & units
logGlobal->error("Invalid unit ID %d", info.id); logGlobal->error("Invalid unit ID %d", info.id);
continue; continue;
} }
battleInt->unitAdded(unit); battleInt->stackAdded(unit);
} }
break; break;
default: default:
@ -808,7 +788,7 @@ void CPlayerInterface::battleObstaclesChanged(const std::vector<ObstacleChanges>
} }
else else
{ {
battleInt->fieldController->redrawBackgroundWithHexes(battleInt->activeStack); battleInt->fieldController->redrawBackgroundWithHexes();
} }
} }
} }

View File

@ -17,6 +17,7 @@
#include "CBattleProjectileController.h" #include "CBattleProjectileController.h"
#include "CBattleSiegeController.h" #include "CBattleSiegeController.h"
#include "CBattleFieldController.h" #include "CBattleFieldController.h"
#include "CBattleStacksController.h"
#include "CCreatureAnimation.h" #include "CCreatureAnimation.h"
#include "../CGameInfo.h" #include "../CGameInfo.h"
@ -34,7 +35,7 @@
#include "../../lib/mapObjects/CGTownInstance.h" #include "../../lib/mapObjects/CGTownInstance.h"
CBattleAnimation::CBattleAnimation(CBattleInterface * _owner) CBattleAnimation::CBattleAnimation(CBattleInterface * _owner)
: owner(_owner), ID(_owner->animIDhelper++) : owner(_owner), ID(_owner->stacksController->animIDhelper++)
{ {
logAnim->trace("Animation #%d created", ID); logAnim->trace("Animation #%d created", ID);
} }
@ -44,10 +45,35 @@ CBattleAnimation::~CBattleAnimation()
logAnim->trace("Animation #%d deleted", ID); logAnim->trace("Animation #%d deleted", ID);
} }
std::list<std::pair<CBattleAnimation *, bool>> & CBattleAnimation::pendingAnimations()
{
return owner->stacksController->pendingAnims;
}
std::shared_ptr<CCreatureAnimation> CBattleAnimation::stackAnimation(const CStack * stack)
{
return owner->stacksController->creAnims[stack->ID];
}
bool CBattleAnimation::stackFacingRight(const CStack * stack)
{
return owner->stacksController->creDir[stack->ID];
}
ui32 CBattleAnimation::maxAnimationID()
{
return owner->stacksController->animIDhelper;
}
void CBattleAnimation::setStackFacingRight(const CStack * stack, bool facingRight)
{
owner->stacksController->creDir[stack->ID] = facingRight;
}
void CBattleAnimation::endAnim() void CBattleAnimation::endAnim()
{ {
logAnim->trace("Animation #%d ended, type is %s", ID, typeid(this).name()); logAnim->trace("Animation #%d ended, type is %s", ID, typeid(this).name());
for(auto & elem : owner->pendingAnims) for(auto & elem : pendingAnimations())
{ {
if(elem.first == this) if(elem.first == this)
{ {
@ -58,13 +84,12 @@ void CBattleAnimation::endAnim()
bool CBattleAnimation::isEarliest(bool perStackConcurrency) bool CBattleAnimation::isEarliest(bool perStackConcurrency)
{ {
int lowestMoveID = owner->animIDhelper + 5; int lowestMoveID = maxAnimationID() + 5;//FIXME: why 5?
CBattleStackAnimation * thAnim = dynamic_cast<CBattleStackAnimation *>(this); CBattleStackAnimation * thAnim = dynamic_cast<CBattleStackAnimation *>(this);
CEffectAnimation * thSen = dynamic_cast<CEffectAnimation *>(this); CEffectAnimation * thSen = dynamic_cast<CEffectAnimation *>(this);
for(auto & elem : owner->pendingAnims) for(auto & elem : pendingAnimations())
{ {
CBattleStackAnimation * stAnim = dynamic_cast<CBattleStackAnimation *>(elem.first); CBattleStackAnimation * stAnim = dynamic_cast<CBattleStackAnimation *>(elem.first);
CEffectAnimation * sen = dynamic_cast<CEffectAnimation *>(elem.first); CEffectAnimation * sen = dynamic_cast<CEffectAnimation *>(elem.first);
if(perStackConcurrency && stAnim && thAnim && stAnim->stack->ID != thAnim->stack->ID) if(perStackConcurrency && stAnim && thAnim && stAnim->stack->ID != thAnim->stack->ID)
@ -81,12 +106,12 @@ bool CBattleAnimation::isEarliest(bool perStackConcurrency)
if(elem.first) if(elem.first)
vstd::amin(lowestMoveID, elem.first->ID); vstd::amin(lowestMoveID, elem.first->ID);
} }
return (ID == lowestMoveID) || (lowestMoveID == (owner->animIDhelper + 5)); return (ID == lowestMoveID) || (lowestMoveID == (maxAnimationID() + 5));
} }
CBattleStackAnimation::CBattleStackAnimation(CBattleInterface * owner, const CStack * stack) CBattleStackAnimation::CBattleStackAnimation(CBattleInterface * owner, const CStack * stack)
: CBattleAnimation(owner), : CBattleAnimation(owner),
myAnim(owner->creAnims[stack->ID]), myAnim(stackAnimation(stack)),
stack(stack) stack(stack)
{ {
assert(myAnim); assert(myAnim);
@ -125,7 +150,7 @@ void CAttackAnimation::endAnim()
bool CAttackAnimation::checkInitialConditions() bool CAttackAnimation::checkInitialConditions()
{ {
for(auto & elem : owner->pendingAnims) for(auto & elem : pendingAnimations())
{ {
CBattleStackAnimation * stAnim = dynamic_cast<CBattleStackAnimation *>(elem.first); CBattleStackAnimation * stAnim = dynamic_cast<CBattleStackAnimation *>(elem.first);
CReverseAnimation * revAnim = dynamic_cast<CReverseAnimation *>(stAnim); CReverseAnimation * revAnim = dynamic_cast<CReverseAnimation *>(stAnim);
@ -162,8 +187,8 @@ bool CDefenceAnimation::init()
if(attacker == nullptr && owner->battleEffects.size() > 0) if(attacker == nullptr && owner->battleEffects.size() > 0)
return false; return false;
ui32 lowestMoveID = owner->animIDhelper + 5; ui32 lowestMoveID = maxAnimationID() + 5;
for(auto & elem : owner->pendingAnims) for(auto & elem : pendingAnimations())
{ {
CDefenceAnimation * defAnim = dynamic_cast<CDefenceAnimation *>(elem.first); CDefenceAnimation * defAnim = dynamic_cast<CDefenceAnimation *>(elem.first);
@ -192,9 +217,9 @@ bool CDefenceAnimation::init()
//reverse unit if necessary //reverse unit if necessary
if(attacker && owner->getCurrentPlayerInterface()->cb->isToReverse(stack->getPosition(), attacker->getPosition(), owner->creDir[stack->ID], attacker->doubleWide(), owner->creDir[attacker->ID])) if(attacker && owner->getCurrentPlayerInterface()->cb->isToReverse(stack->getPosition(), attacker->getPosition(), stackFacingRight(stack), attacker->doubleWide(), stackFacingRight(attacker)))
{ {
owner->addNewAnim(new CReverseAnimation(owner, stack, stack->getPosition(), true)); owner->stacksController->addNewAnim(new CReverseAnimation(owner, stack, stack->getPosition(), true));
return false; return false;
} }
//unit reversed //unit reversed
@ -209,7 +234,7 @@ bool CDefenceAnimation::init()
if (!rangedAttack && getMyAnimType() != CCreatureAnim::DEFENCE) if (!rangedAttack && getMyAnimType() != CCreatureAnim::DEFENCE)
{ {
float frameLength = AnimationControls::getCreatureAnimationSpeed( float frameLength = AnimationControls::getCreatureAnimationSpeed(
stack->getCreature(), owner->creAnims[stack->ID].get(), getMyAnimType()); stack->getCreature(), stackAnimation(stack).get(), getMyAnimType());
timeToWait = myAnim->framesInGroup(getMyAnimType()) * frameLength / 2; timeToWait = myAnim->framesInGroup(getMyAnimType()) * frameLength / 2;
@ -325,17 +350,17 @@ bool CMeleeAttackAnimation::init()
return false; return false;
} }
bool toReverse = owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStackPosBeforeReturn, attackedStack->getPosition(), owner->creDir[stack->ID], attackedStack->doubleWide(), owner->creDir[attackedStack->ID]); bool toReverse = owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStackPosBeforeReturn, attackedStack->getPosition(), stackFacingRight(stack), attackedStack->doubleWide(), stackFacingRight(attackedStack));
if(toReverse) if(toReverse)
{ {
owner->addNewAnim(new CReverseAnimation(owner, stack, attackingStackPosBeforeReturn, true)); owner->stacksController->addNewAnim(new CReverseAnimation(owner, stack, attackingStackPosBeforeReturn, true));
return false; return false;
} }
// opponent must face attacker ( = different directions) before he can be attacked // opponent must face attacker ( = different directions) before he can be attacked
if(attackingStack && attackedStack && if(attackingStack && attackedStack &&
owner->creDir[attackingStack->ID] == owner->creDir[attackedStack->ID]) stackFacingRight(attackingStack) == stackFacingRight(attackedStack))
return false; return false;
//reversed //reversed
@ -428,7 +453,7 @@ bool CMovementAnimation::init()
return false; return false;
} }
if(owner->creAnims[stack->ID]->framesInGroup(CCreatureAnim::MOVING) == 0 || if(stackAnimation(stack)->framesInGroup(CCreatureAnim::MOVING) == 0 ||
stack->hasBonus(Selector::typeSubtype(Bonus::FLYING, 1))) stack->hasBonus(Selector::typeSubtype(Bonus::FLYING, 1)))
{ {
//no movement or teleport, end immediately //no movement or teleport, end immediately
@ -437,18 +462,18 @@ bool CMovementAnimation::init()
} }
//reverse unit if necessary //reverse unit if necessary
if(owner->shouldRotate(stack, oldPos, nextHex)) if(owner->stacksController->shouldRotate(stack, oldPos, nextHex))
{ {
// it seems that H3 does NOT plays full rotation animation here in most situations // it seems that H3 does NOT plays full rotation animation here in most situations
// Logical since it takes quite a lot of time // Logical since it takes quite a lot of time
if (curentMoveIndex == 0) // full rotation only for moving towards first tile. if (curentMoveIndex == 0) // full rotation only for moving towards first tile.
{ {
owner->addNewAnim(new CReverseAnimation(owner, stack, oldPos, true)); owner->stacksController->addNewAnim(new CReverseAnimation(owner, stack, oldPos, true));
return false; return false;
} }
else else
{ {
CReverseAnimation::rotateStack(owner, stack, oldPos); rotateStack(oldPos);
} }
} }
@ -508,7 +533,7 @@ void CMovementAnimation::nextFrame()
nextHex = destTiles[curentMoveIndex]; nextHex = destTiles[curentMoveIndex];
// re-init animation // re-init animation
for(auto & elem : owner->pendingAnims) for(auto & elem : pendingAnimations())
{ {
if (elem.first == this) if (elem.first == this)
{ {
@ -529,7 +554,7 @@ void CMovementAnimation::endAnim()
myAnim->pos = CClickableHex::getXYUnitAnim(nextHex, stack, owner); myAnim->pos = CClickableHex::getXYUnitAnim(nextHex, stack, owner);
CBattleAnimation::endAnim(); CBattleAnimation::endAnim();
owner->addNewAnim(new CMovementEndAnimation(owner, stack, nextHex)); owner->stacksController->addNewAnim(new CMovementEndAnimation(owner, stack, nextHex));
if(owner->moveSoundHander != -1) if(owner->moveSoundHander != -1)
{ {
@ -662,11 +687,11 @@ void CReverseAnimation::endAnim()
delete this; delete this;
} }
void CReverseAnimation::rotateStack(CBattleInterface * owner, const CStack * stack, BattleHex hex) void CBattleStackAnimation::rotateStack(BattleHex hex)
{ {
owner->creDir[stack->ID] = !owner->creDir[stack->ID]; setStackFacingRight(stack, !stackFacingRight(stack));
owner->creAnims[stack->ID]->pos = CClickableHex::getXYUnitAnim(hex, stack, owner); stackAnimation(stack)->pos = CClickableHex::getXYUnitAnim(hex, stack, owner);
} }
void CReverseAnimation::setupSecondPart() void CReverseAnimation::setupSecondPart()
@ -677,7 +702,7 @@ void CReverseAnimation::setupSecondPart()
return; return;
} }
rotateStack(owner, stack, hex); rotateStack(hex);
if(myAnim->framesInGroup(CCreatureAnim::TURN_R)) if(myAnim->framesInGroup(CCreatureAnim::TURN_R))
{ {
@ -716,9 +741,9 @@ bool CShootingAnimation::init()
} }
//reverse unit if necessary //reverse unit if necessary
if (attackingStack && attackedStack && owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->getPosition(), attackedStack->getPosition(), owner->creDir[attackingStack->ID], attackingStack->doubleWide(), owner->creDir[attackedStack->ID])) if (attackingStack && attackedStack && owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->getPosition(), attackedStack->getPosition(), stackFacingRight(attackingStack), attackingStack->doubleWide(), stackFacingRight(attackedStack)))
{ {
owner->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->getPosition(), true)); owner->stacksController->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->getPosition(), true));
return false; return false;
} }
@ -743,7 +768,7 @@ bool CShootingAnimation::init()
Point destPos; Point destPos;
// NOTE: two lines below return different positions (very notable with 2-hex creatures). Obtaining via creanims seems to be more precise // NOTE: two lines below return different positions (very notable with 2-hex creatures). Obtaining via creanims seems to be more precise
shooterPos = owner->creAnims[shooter->ID]->pos.topLeft(); shooterPos = stackAnimation(shooter)->pos.topLeft();
//xycoord = CClickableHex::getXYUnitAnim(shooter->position, true, shooter, owner); //xycoord = CClickableHex::getXYUnitAnim(shooter->position, true, shooter, owner);
destPos = CClickableHex::getXYUnitAnim(dest, attackedStack, owner) + Point(225, 225); destPos = CClickableHex::getXYUnitAnim(dest, attackedStack, owner) + Point(225, 225);
@ -751,7 +776,7 @@ bool CShootingAnimation::init()
// to properly translate coordinates when shooter is rotated // to properly translate coordinates when shooter is rotated
int multiplier = 0; int multiplier = 0;
if (shooter) if (shooter)
multiplier = owner->creDir[shooter->ID] ? 1 : -1; multiplier = stackFacingRight(shooter) ? 1 : -1;
else else
{ {
assert(false); // unreachable? assert(false); // unreachable?
@ -800,7 +825,22 @@ bool CShootingAnimation::init()
void CShootingAnimation::nextFrame() void CShootingAnimation::nextFrame()
{ {
for(auto & it : owner->pendingAnims) if (owner->projectilesController->hasActiveProjectile(attackingStack))
{
const CCreature *shooterInfo = attackingStack->getCreature();
if(shooterInfo->idNumber == CreatureID::ARROW_TOWERS)
shooterInfo = owner->siegeController->turretCreature();
// animation should be paused if there is an active projectile
if ( stackAnimation(attackingStack)->getCurrentFrame() >= shooterInfo->animation.attackClimaxFrame )
{
owner->projectilesController->fireStackProjectile(attackingStack);//FIXME: should only be called once
return;
}
}
for(auto & it : pendingAnimations())
{ {
CMovementStartAnimation * anim = dynamic_cast<CMovementStartAnimation *>(it.first); CMovementStartAnimation * anim = dynamic_cast<CMovementStartAnimation *>(it.first);
CReverseAnimation * anim2 = dynamic_cast<CReverseAnimation *>(it.first); CReverseAnimation * anim2 = dynamic_cast<CReverseAnimation *>(it.first);
@ -813,6 +853,9 @@ void CShootingAnimation::nextFrame()
void CShootingAnimation::endAnim() void CShootingAnimation::endAnim()
{ {
// FIXME: is this possible? Animation is over but we're yet to fire projectile?
owner->projectilesController->fireStackProjectile(attackingStack);
// play wall hit/miss sound for catapult attack // play wall hit/miss sound for catapult attack
if(!attackedStack) if(!attackedStack)
{ {
@ -851,17 +894,17 @@ bool CCastAnimation::init()
//reverse unit if necessary //reverse unit if necessary
if(attackedStack) if(attackedStack)
{ {
if(owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->getPosition(), attackedStack->getPosition(), owner->creDir[attackingStack->ID], attackingStack->doubleWide(), owner->creDir[attackedStack->ID])) if(owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->getPosition(), attackedStack->getPosition(), stackFacingRight(attackingStack), attackingStack->doubleWide(), stackFacingRight(attackedStack)))
{ {
owner->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->getPosition(), true)); owner->stacksController->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->getPosition(), true));
return false; return false;
} }
} }
else else
{ {
if(dest.isValid() && owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->getPosition(), dest, owner->creDir[attackingStack->ID], false, false)) if(dest.isValid() && owner->getCurrentPlayerInterface()->cb->isToReverse(attackingStack->getPosition(), dest, stackFacingRight(attackingStack), false, false))
{ {
owner->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->getPosition(), true)); owner->stacksController->addNewAnim(new CReverseAnimation(owner, attackingStack, attackingStack->getPosition(), true));
return false; return false;
} }
} }
@ -875,7 +918,7 @@ bool CCastAnimation::init()
Point destPos; Point destPos;
// NOTE: two lines below return different positions (very notable with 2-hex creatures). Obtaining via creanims seems to be more precise // NOTE: two lines below return different positions (very notable with 2-hex creatures). Obtaining via creanims seems to be more precise
fromPos = owner->creAnims[attackingStack->ID]->pos.topLeft(); fromPos = stackAnimation(attackingStack)->pos.topLeft();
//xycoord = CClickableHex::getXYUnitAnim(shooter->getPosition(), true, shooter, owner); //xycoord = CClickableHex::getXYUnitAnim(shooter->getPosition(), true, shooter, owner);
destPos = CClickableHex::getXYUnitAnim(dest, attackedStack, owner); destPos = CClickableHex::getXYUnitAnim(dest, attackedStack, owner);
@ -932,7 +975,7 @@ bool CCastAnimation::init()
void CCastAnimation::nextFrame() void CCastAnimation::nextFrame()
{ {
for(auto & it : owner->pendingAnims) for(auto & it : pendingAnimations())
{ {
CReverseAnimation * anim = dynamic_cast<CReverseAnimation *>(it.first); CReverseAnimation * anim = dynamic_cast<CReverseAnimation *>(it.first);
if(anim && anim->stack->ID == stack->ID && anim->priority) if(anim && anim->stack->ID == stack->ID && anim->priority)

View File

@ -20,6 +20,7 @@ VCMI_LIB_NAMESPACE_END
class CBattleInterface; class CBattleInterface;
class CCreatureAnimation; class CCreatureAnimation;
class CBattleAnimation;
struct CatapultProjectileInfo; struct CatapultProjectileInfo;
struct StackAttackedInfo; struct StackAttackedInfo;
@ -28,6 +29,13 @@ class CBattleAnimation
{ {
protected: protected:
CBattleInterface * owner; CBattleInterface * owner;
std::list<std::pair<CBattleAnimation *, bool>> & pendingAnimations();
std::shared_ptr<CCreatureAnimation> stackAnimation(const CStack * stack);
bool stackFacingRight(const CStack * stack);
void setStackFacingRight(const CStack * stack, bool facingRight);
ui32 maxAnimationID();
public: public:
virtual bool init() = 0; //to be called - if returned false, call again until returns true virtual bool init() = 0; //to be called - if returned false, call again until returns true
virtual void nextFrame() {} //call every new frame virtual void nextFrame() {} //call every new frame
@ -50,6 +58,7 @@ public:
CBattleStackAnimation(CBattleInterface * _owner, const CStack * _stack); CBattleStackAnimation(CBattleInterface * _owner, const CStack * _stack);
void shiftColor(const ColorShifter * shifter); void shiftColor(const ColorShifter * shifter);
void rotateStack(BattleHex hex);
}; };
/// This class is responsible for managing the battle attack animation /// This class is responsible for managing the battle attack animation
@ -177,7 +186,7 @@ public:
bool priority; //true - high, false - low bool priority; //true - high, false - low
bool init() override; bool init() override;
static void rotateStack(CBattleInterface * owner, const CStack * stack, BattleHex hex);
void setupSecondPart(); void setupSecondPart();
void endAnim() override; void endAnim() override;

View File

@ -11,6 +11,7 @@
#include "CBattleControlPanel.h" #include "CBattleControlPanel.h"
#include "CBattleInterface.h" #include "CBattleInterface.h"
#include "CBattleInterfaceClasses.h" #include "CBattleInterfaceClasses.h"
#include "CBattleStacksController.h"
#include "../widgets/Buttons.h" #include "../widgets/Buttons.h"
#include "../CGameInfo.h" #include "../CGameInfo.h"
#include "../CBitmapHandler.h" #include "../CBitmapHandler.h"
@ -224,7 +225,7 @@ void CBattleControlPanel::bWaitf()
if (owner->spellDestSelectMode) //we are casting a spell if (owner->spellDestSelectMode) //we are casting a spell
return; return;
if (owner->activeStack != nullptr) if (owner->stacksController->getActiveStack() != nullptr)
owner->giveCommand(EActionType::WAIT); owner->giveCommand(EActionType::WAIT);
} }
@ -233,7 +234,7 @@ void CBattleControlPanel::bDefencef()
if (owner->spellDestSelectMode) //we are casting a spell if (owner->spellDestSelectMode) //we are casting a spell
return; return;
if (owner->activeStack != nullptr) if (owner->stacksController->getActiveStack() != nullptr)
owner->giveCommand(EActionType::DEFEND); owner->giveCommand(EActionType::DEFEND);
} }
@ -276,7 +277,7 @@ void CBattleControlPanel::blockUI(bool on)
canCastSpells = spellcastingProblem == ESpellCastProblem::OK || spellcastingProblem == ESpellCastProblem::MAGIC_IS_BLOCKED; canCastSpells = spellcastingProblem == ESpellCastProblem::OK || spellcastingProblem == ESpellCastProblem::MAGIC_IS_BLOCKED;
} }
bool canWait = owner->activeStack ? !owner->activeStack->waitedThisTurn : false; bool canWait = owner->stacksController->getActiveStack() ? !owner->stacksController->getActiveStack()->waitedThisTurn : false;
bOptions->block(on); bOptions->block(on);
bFlee->block(on || !owner->curInt->cb->battleCanFlee()); bFlee->block(on || !owner->curInt->cb->battleCanFlee());
@ -284,7 +285,7 @@ void CBattleControlPanel::blockUI(bool on)
// block only if during enemy turn and auto-fight is off // block only if during enemy turn and auto-fight is off
// otherwise - crash on accessing non-exisiting active stack // otherwise - crash on accessing non-exisiting active stack
bAutofight->block(!owner->curInt->isAutoFightOn && !owner->activeStack); bAutofight->block(!owner->curInt->isAutoFightOn && !owner->stacksController->getActiveStack());
if (owner->tacticsMode && btactEnd && btactNext) if (owner->tacticsMode && btactEnd && btactNext)
{ {

View File

@ -12,6 +12,7 @@
#include "CBattleInterface.h" #include "CBattleInterface.h"
#include "CBattleInterfaceClasses.h" #include "CBattleInterfaceClasses.h"
#include "CBattleSiegeController.h" #include "CBattleSiegeController.h"
#include "CBattleStacksController.h"
#include "CBattleObstacleController.h" #include "CBattleObstacleController.h"
#include "../CBitmapHandler.h" #include "../CBitmapHandler.h"
#include "../CGameInfo.h" #include "../CGameInfo.h"
@ -125,8 +126,9 @@ void CBattleFieldController::showBackgroundImageWithHexes(SDL_Surface *to)
blitAt(backgroundWithHexes, owner->pos.x, owner->pos.y, to); blitAt(backgroundWithHexes, owner->pos.x, owner->pos.y, to);
} }
void CBattleFieldController::redrawBackgroundWithHexes(const CStack *activeStack) void CBattleFieldController::redrawBackgroundWithHexes()
{ {
const CStack *activeStack = owner->stacksController->getActiveStack();
attackableHexes.clear(); attackableHexes.clear();
if (activeStack) if (activeStack)
occupyableHexes = owner->curInt->cb->battleGetAvailableHexes(activeStack, true, &attackableHexes); occupyableHexes = owner->curInt->cb->battleGetAvailableHexes(activeStack, true, &attackableHexes);
@ -173,16 +175,16 @@ void CBattleFieldController::showHighlightedHex(SDL_Surface *to, BattleHex hex,
void CBattleFieldController::showHighlightedHexes(SDL_Surface *to) void CBattleFieldController::showHighlightedHexes(SDL_Surface *to)
{ {
bool delayedBlit = false; //workaround for blitting enemy stack hex without mouse shadow with stack range on bool delayedBlit = false; //workaround for blitting enemy stack hex without mouse shadow with stack range on
if(owner->activeStack && settings["battle"]["stackRange"].Bool()) if(owner->stacksController->getActiveStack() && settings["battle"]["stackRange"].Bool())
{ {
std::set<BattleHex> set = owner->curInt->cb->battleGetAttackedHexes(owner->activeStack, currentlyHoveredHex, attackingHex); std::set<BattleHex> set = owner->curInt->cb->battleGetAttackedHexes(owner->stacksController->getActiveStack(), currentlyHoveredHex, attackingHex);
for(BattleHex hex : set) for(BattleHex hex : set)
if(hex != currentlyHoveredHex) if(hex != currentlyHoveredHex)
showHighlightedHex(to, hex, false); showHighlightedHex(to, hex, false);
// display the movement shadow of the stack at b (i.e. stack under mouse) // display the movement shadow of the stack at b (i.e. stack under mouse)
const CStack * const shere = owner->curInt->cb->battleGetStackByPos(currentlyHoveredHex, false); const CStack * const shere = owner->curInt->cb->battleGetStackByPos(currentlyHoveredHex, false);
if(shere && shere != owner->activeStack && shere->alive()) if(shere && shere != owner->stacksController->getActiveStack() && shere->alive())
{ {
std::vector<BattleHex> v = owner->curInt->cb->battleGetAvailableHexes(shere, true, nullptr); std::vector<BattleHex> v = owner->curInt->cb->battleGetAvailableHexes(shere, true, nullptr);
for(BattleHex hex : v) for(BattleHex hex : v)
@ -223,10 +225,10 @@ void CBattleFieldController::showHighlightedHexes(SDL_Surface *to)
spell = SpellID(owner->spellToCast->actionSubtype).toSpell(); spell = SpellID(owner->spellToCast->actionSubtype).toSpell();
caster = owner->getActiveHero(); caster = owner->getActiveHero();
} }
else if(owner->creatureSpellToCast >= 0 && owner->stackCanCastSpell && owner->creatureCasting)//stack casts spell else if(owner->stacksController->activeStackSpellToCast() != SpellID::NONE && owner->creatureCasting)//stack casts spell
{ {
spell = SpellID(owner->creatureSpellToCast).toSpell(); spell = SpellID(owner->stacksController->activeStackSpellToCast()).toSpell();
caster = owner->activeStack; caster = owner->stacksController->getActiveStack();
mode = spells::Mode::CREATURE_ACTIVE; mode = spells::Mode::CREATURE_ACTIVE;
} }
@ -297,7 +299,7 @@ void CBattleFieldController::setBattleCursor(BattleHex myNumber)
sectorCursor.push_back(12); sectorCursor.push_back(12);
sectorCursor.push_back(7); sectorCursor.push_back(7);
const bool doubleWide = owner->activeStack->doubleWide(); const bool doubleWide = owner->stacksController->getActiveStack()->doubleWide();
bool aboveAttackable = true, belowAttackable = true; bool aboveAttackable = true, belowAttackable = true;
// Exclude directions which cannot be attacked from. // Exclude directions which cannot be attacked from.
@ -458,12 +460,12 @@ BattleHex CBattleFieldController::fromWhichHexAttack(BattleHex myNumber)
{ {
case 12: //from bottom right case 12: //from bottom right
{ {
bool doubleWide = owner->activeStack->doubleWide(); bool doubleWide = owner->stacksController->getActiveStack()->doubleWide();
destHex = myNumber + ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH+1 ) + destHex = myNumber + ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH+1 ) +
(owner->activeStack->side == BattleSide::ATTACKER && doubleWide ? 1 : 0); (owner->stacksController->getActiveStack()->side == BattleSide::ATTACKER && doubleWide ? 1 : 0);
if(vstd::contains(occupyableHexes, destHex)) if(vstd::contains(occupyableHexes, destHex))
return destHex; return destHex;
else if(owner->activeStack->side == BattleSide::ATTACKER) else if(owner->stacksController->getActiveStack()->side == BattleSide::ATTACKER)
{ {
if (vstd::contains(occupyableHexes, destHex+1)) if (vstd::contains(occupyableHexes, destHex+1))
return destHex+1; return destHex+1;
@ -480,7 +482,7 @@ BattleHex CBattleFieldController::fromWhichHexAttack(BattleHex myNumber)
destHex = myNumber + ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH-1 : GameConstants::BFIELD_WIDTH ); destHex = myNumber + ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH-1 : GameConstants::BFIELD_WIDTH );
if (vstd::contains(occupyableHexes, destHex)) if (vstd::contains(occupyableHexes, destHex))
return destHex; return destHex;
else if(owner->activeStack->side == BattleSide::ATTACKER) else if(owner->stacksController->getActiveStack()->side == BattleSide::ATTACKER)
{ {
if(vstd::contains(occupyableHexes, destHex+1)) if(vstd::contains(occupyableHexes, destHex+1))
return destHex+1; return destHex+1;
@ -494,9 +496,9 @@ BattleHex CBattleFieldController::fromWhichHexAttack(BattleHex myNumber)
} }
case 8: //from left case 8: //from left
{ {
if(owner->activeStack->doubleWide() && owner->activeStack->side == BattleSide::DEFENDER) if(owner->stacksController->getActiveStack()->doubleWide() && owner->stacksController->getActiveStack()->side == BattleSide::DEFENDER)
{ {
std::vector<BattleHex> acc = owner->curInt->cb->battleGetAvailableHexes(owner->activeStack); std::vector<BattleHex> acc = owner->curInt->cb->battleGetAvailableHexes(owner->stacksController->getActiveStack());
if (vstd::contains(acc, myNumber)) if (vstd::contains(acc, myNumber))
return myNumber - 1; return myNumber - 1;
else else
@ -513,7 +515,7 @@ BattleHex CBattleFieldController::fromWhichHexAttack(BattleHex myNumber)
destHex = myNumber - ((myNumber/GameConstants::BFIELD_WIDTH) % 2 ? GameConstants::BFIELD_WIDTH + 1 : GameConstants::BFIELD_WIDTH); destHex = myNumber - ((myNumber/GameConstants::BFIELD_WIDTH) % 2 ? GameConstants::BFIELD_WIDTH + 1 : GameConstants::BFIELD_WIDTH);
if(vstd::contains(occupyableHexes, destHex)) if(vstd::contains(occupyableHexes, destHex))
return destHex; return destHex;
else if(owner->activeStack->side == BattleSide::ATTACKER) else if(owner->stacksController->getActiveStack()->side == BattleSide::ATTACKER)
{ {
if(vstd::contains(occupyableHexes, destHex+1)) if(vstd::contains(occupyableHexes, destHex+1))
return destHex+1; return destHex+1;
@ -527,12 +529,12 @@ BattleHex CBattleFieldController::fromWhichHexAttack(BattleHex myNumber)
} }
case 10: //from top right case 10: //from top right
{ {
bool doubleWide = owner->activeStack->doubleWide(); bool doubleWide = owner->stacksController->getActiveStack()->doubleWide();
destHex = myNumber - ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH-1 ) + destHex = myNumber - ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH-1 ) +
(owner->activeStack->side == BattleSide::ATTACKER && doubleWide ? 1 : 0); (owner->stacksController->getActiveStack()->side == BattleSide::ATTACKER && doubleWide ? 1 : 0);
if(vstd::contains(occupyableHexes, destHex)) if(vstd::contains(occupyableHexes, destHex))
return destHex; return destHex;
else if(owner->activeStack->side == BattleSide::ATTACKER) else if(owner->stacksController->getActiveStack()->side == BattleSide::ATTACKER)
{ {
if(vstd::contains(occupyableHexes, destHex+1)) if(vstd::contains(occupyableHexes, destHex+1))
return destHex+1; return destHex+1;
@ -546,9 +548,9 @@ BattleHex CBattleFieldController::fromWhichHexAttack(BattleHex myNumber)
} }
case 11: //from right case 11: //from right
{ {
if(owner->activeStack->doubleWide() && owner->activeStack->side == BattleSide::ATTACKER) if(owner->stacksController->getActiveStack()->doubleWide() && owner->stacksController->getActiveStack()->side == BattleSide::ATTACKER)
{ {
std::vector<BattleHex> acc = owner->curInt->cb->battleGetAvailableHexes(owner->activeStack); std::vector<BattleHex> acc = owner->curInt->cb->battleGetAvailableHexes(owner->stacksController->getActiveStack());
if(vstd::contains(acc, myNumber)) if(vstd::contains(acc, myNumber))
return myNumber + 1; return myNumber + 1;
else else
@ -565,7 +567,7 @@ BattleHex CBattleFieldController::fromWhichHexAttack(BattleHex myNumber)
destHex = myNumber + ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH+1 ); destHex = myNumber + ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH+1 );
if(vstd::contains(occupyableHexes, destHex)) if(vstd::contains(occupyableHexes, destHex))
return destHex; return destHex;
else if(owner->activeStack->side == BattleSide::ATTACKER) else if(owner->stacksController->getActiveStack()->side == BattleSide::ATTACKER)
{ {
if(vstd::contains(occupyableHexes, destHex+1)) if(vstd::contains(occupyableHexes, destHex+1))
return destHex+1; return destHex+1;
@ -582,7 +584,7 @@ BattleHex CBattleFieldController::fromWhichHexAttack(BattleHex myNumber)
destHex = myNumber - ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH-1 ); destHex = myNumber - ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH-1 );
if (vstd::contains(occupyableHexes, destHex)) if (vstd::contains(occupyableHexes, destHex))
return destHex; return destHex;
else if(owner->activeStack->side == BattleSide::ATTACKER) else if(owner->stacksController->getActiveStack()->side == BattleSide::ATTACKER)
{ {
if(vstd::contains(occupyableHexes, destHex+1)) if(vstd::contains(occupyableHexes, destHex+1))
return destHex+1; return destHex+1;

View File

@ -47,7 +47,7 @@ public:
void showBackgroundImage(SDL_Surface *to); void showBackgroundImage(SDL_Surface *to);
void showBackgroundImageWithHexes(SDL_Surface *to); void showBackgroundImageWithHexes(SDL_Surface *to);
void redrawBackgroundWithHexes(const CStack *activeStack); void redrawBackgroundWithHexes();
void showHighlightedHexes(SDL_Surface *to); void showHighlightedHexes(SDL_Surface *to);
void showHighlightedHex(SDL_Surface *to, BattleHex hex, bool darkBorder); void showHighlightedHex(SDL_Surface *to, BattleHex hex, bool darkBorder);

File diff suppressed because it is too large Load Diff

View File

@ -63,6 +63,7 @@ class CBattleSiegeController;
class CBattleObstacleController; class CBattleObstacleController;
class CBattleFieldController; class CBattleFieldController;
class CBattleControlPanel; class CBattleControlPanel;
class CBattleStacksController;
/// Small struct which contains information about the id of the attacked stack, the damage dealt,... /// Small struct which contains information about the id of the attacked stack, the damage dealt,...
struct StackAttackedInfo struct StackAttackedInfo
@ -119,33 +120,25 @@ enum class MouseHoveredHexContext
class CBattleInterface : public WindowBase class CBattleInterface : public WindowBase
{ {
private: private:
SDL_Surface *amountNormal, *amountNegative, *amountPositive, *amountEffNeutral;
std::shared_ptr<CBattleHero> attackingHero; std::shared_ptr<CBattleHero> attackingHero;
std::shared_ptr<CBattleHero> defendingHero; std::shared_ptr<CBattleHero> defendingHero;
std::shared_ptr<CStackQueue> queue; std::shared_ptr<CStackQueue> queue;
std::shared_ptr<CBattleControlPanel> controlPanel; std::shared_ptr<CBattleControlPanel> controlPanel;
std::shared_ptr<CPlayerInterface> tacticianInterface; //used during tactics mode, points to the interface of player with higher tactics (can be either attacker or defender in hot-seat), valid onloy for human players
std::shared_ptr<CPlayerInterface> attackerInt, defenderInt; //because LOCPLINT is not enough in hotSeat
std::shared_ptr<CPlayerInterface> curInt; //current player interface
const CCreatureSet *army1, *army2; //copy of initial armies (for result window) const CCreatureSet *army1, *army2; //copy of initial armies (for result window)
const CGHeroInstance *attackingHeroInstance, *defendingHeroInstance; const CGHeroInstance *attackingHeroInstance, *defendingHeroInstance;
std::map<int32_t, std::shared_ptr<CCreatureAnimation>> creAnims; //animations of creatures from fighting armies (order by BattleInfo's stacks' ID)
std::map<int, bool> creDir; // <creatureID, if false reverse creature's animation> //TODO: move it to battle callback
ui8 animCount; ui8 animCount;
const CStack *activeStack; //number of active stack; nullptr - no one
const CStack *mouseHoveredStack; // stack below mouse pointer, used for border animation
const CStack *stackToActivate; //when animation is playing, we should wait till the end to make the next stack active; nullptr of none
const CStack *selectedStack; //for Teleport / Sacrifice
void activateStack(); //sets activeStack to stackToActivate etc. //FIXME: No, it's not clear at all
std::shared_ptr<CPlayerInterface> tacticianInterface; //used during tactics mode, points to the interface of player with higher tactics (can be either attacker or defender in hot-seat), valid onloy for human players
bool tacticsMode; bool tacticsMode;
bool stackCanCastSpell; //if true, active stack could possibly cast some target spell
bool creatureCasting; //if true, stack currently aims to cats a spell bool creatureCasting; //if true, stack currently aims to cats a spell
bool spellDestSelectMode; //if true, player is choosing destination for his spell - only for GUI / console bool spellDestSelectMode; //if true, player is choosing destination for his spell - only for GUI / console
std::shared_ptr<BattleAction> spellToCast; //spell for which player is choosing destination std::shared_ptr<BattleAction> spellToCast; //spell for which player is choosing destination
const CSpell *sp; //spell pointer for convenience const CSpell *sp; //spell pointer for convenience
si32 creatureSpellToCast;
std::vector<PossiblePlayerBattleAction> possibleActions; //all actions possible to call at the moment by player std::vector<PossiblePlayerBattleAction> possibleActions; //all actions possible to call at the moment by player
std::vector<PossiblePlayerBattleAction> localActions; //actions possible to take on hovered hex std::vector<PossiblePlayerBattleAction> localActions; //actions possible to take on hovered hex
std::vector<PossiblePlayerBattleAction> illegalActions; //these actions display message in case of illegal target std::vector<PossiblePlayerBattleAction> illegalActions; //these actions display message in case of illegal target
@ -155,9 +148,10 @@ private:
bool battleActionsStarted; //used for delaying battle actions until intro sound stops bool battleActionsStarted; //used for delaying battle actions until intro sound stops
int battleIntroSoundChannel; //required as variable for disabling it via ESC key int battleIntroSoundChannel; //required as variable for disabling it via ESC key
void setActiveStack(const CStack *stack); std::list<BattleEffect> battleEffects; //different animations to display on the screen like spell effects
void setHoveredStack(const CStack *stack);
void trySetActivePlayer( PlayerColor player ); // if in hotseat, will activate interface of chosen player
void activateStack(); //sets activeStack to stackToActivate etc. //FIXME: No, it's not clear at all
void requestAutofightingAIToTakeAction(); void requestAutofightingAIToTakeAction();
std::vector<PossiblePlayerBattleAction> getPossibleActionsForStack (const CStack *stack); //called when stack gets its turn std::vector<PossiblePlayerBattleAction> getPossibleActionsForStack (const CStack *stack); //called when stack gets its turn
@ -170,26 +164,15 @@ private:
void giveCommand(EActionType action, BattleHex tile = BattleHex(), si32 additional = -1); void giveCommand(EActionType action, BattleHex tile = BattleHex(), si32 additional = -1);
void sendCommand(BattleAction *& command, const CStack * actor = nullptr); void sendCommand(BattleAction *& command, const CStack * actor = nullptr);
std::list<BattleEffect> battleEffects; //different animations to display on the screen like spell effects
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 const CGHeroInstance *getActiveHero(); //returns hero that can currently cast a spell
/** Methods for displaying battle screen */
void showInterface(SDL_Surface *to); void showInterface(SDL_Surface *to);
void showBattlefieldObjects(SDL_Surface *to); void showBattlefieldObjects(SDL_Surface *to);
void showAliveStacks(SDL_Surface *to, std::vector<const CStack *> stacks);
void showStacks(SDL_Surface *to, std::vector<const CStack *> stacks);
void showBattleEffects(SDL_Surface *to, const std::vector<const BattleEffect *> &battleEffects); void showBattleEffects(SDL_Surface *to, const std::vector<const BattleEffect *> &battleEffects);
BattleObjectsByHex sortObjectsByHex(); BattleObjectsByHex sortObjectsByHex();
void updateBattleAnimations();
/** End of battle screen blitting methods */
void setHeroAnimation(ui8 side, int phase); void setHeroAnimation(ui8 side, int phase);
public: public:
@ -197,14 +180,17 @@ public:
std::unique_ptr<CBattleSiegeController> siegeController; std::unique_ptr<CBattleSiegeController> siegeController;
std::unique_ptr<CBattleObstacleController> obstacleController; std::unique_ptr<CBattleObstacleController> obstacleController;
std::unique_ptr<CBattleFieldController> fieldController; std::unique_ptr<CBattleFieldController> fieldController;
std::unique_ptr<CBattleStacksController> stacksController;
static CondSh<bool> animsAreDisplayed; //for waiting with the end of battle for end of anims 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 static CondSh<BattleAction *> givenCommand; //data != nullptr if we have i.e. moved current unit
std::list<std::pair<CBattleAnimation *, bool>> pendingAnims; //currently displayed animations <anim, initialized> bool myTurn; //if true, interface is active (commands can be ordered)
void addNewAnim(CBattleAnimation *anim); //adds new anim to pendingAnims
ui32 animIDhelper; //for giving IDs for animations
bool moveStarted; //if true, the creature that is already moving is going to make its first step
int moveSoundHander; // sound handler used when moving a unit
const BattleResult *bresult; //result of a battle; if non-zero then display when all animations end
CBattleInterface(const CCreatureSet *army1, const CCreatureSet *army2, const CGHeroInstance *hero1, const CGHeroInstance *hero2, const SDL_Rect & myRect, std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen, std::shared_ptr<CPlayerInterface> spectatorInt = nullptr); CBattleInterface(const CCreatureSet *army1, const CCreatureSet *army2, const CGHeroInstance *hero1, const CGHeroInstance *hero2, const SDL_Rect & myRect, std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen, std::shared_ptr<CPlayerInterface> spectatorInt = nullptr);
virtual ~CBattleInterface(); virtual ~CBattleInterface();
@ -216,17 +202,10 @@ public:
void setAnimSpeed(int set); //speed of animation; range 1..100 void setAnimSpeed(int set); //speed of animation; range 1..100
int getAnimSpeed() const; //speed of animation; range 1..100 int getAnimSpeed() const; //speed of animation; range 1..100
CPlayerInterface *getCurrentPlayerInterface() const; CPlayerInterface *getCurrentPlayerInterface() const;
bool shouldRotate(const CStack * stack, const BattleHex & oldPos, const BattleHex & nextHex);
bool myTurn; //if true, interface is active (commands can be ordered)
bool moveStarted; //if true, the creature that is already moving is going to make its first step
int moveSoundHander; // sound handler used when moving a unit
const BattleResult *bresult; //result of a battle; if non-zero then display when all animations end
void tacticNextStack(const CStack *current); void tacticNextStack(const CStack *current);
void tacticPhaseEnd(); void tacticPhaseEnd();
void waitForAnims();
//napisz tu klase odpowiadajaca za wyswietlanie bitwy i obsluge uzytkownika, polecenia ma przekazywac callbackiem //napisz tu klase odpowiadajaca za wyswietlanie bitwy i obsluge uzytkownika, polecenia ma przekazywac callbackiem
void activate() override; void activate() override;
@ -240,11 +219,11 @@ public:
//call-ins //call-ins
void startAction(const BattleAction* action); void startAction(const BattleAction* action);
void unitAdded(const CStack * stack); //new stack appeared on battlefield void stackReset(const CStack * stack);
void stackAdded(const CStack * stack); //new stack appeared on battlefield
void stackRemoved(uint32_t stackID); //stack disappeared from batlefiled void stackRemoved(uint32_t stackID); //stack disappeared from batlefiled
void stackActivated(const CStack *stack); //active stack has been changed void stackActivated(const CStack *stack); //active stack has been changed
void stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance); //stack with id number moved to destHex void stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance); //stack with id number moved to destHex
void waitForAnims();
void stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos); //called when a certain amount of stacks has been attacked void stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos); //called when a certain amount of stacks has been attacked
void stackAttacking(const CStack *attacker, BattleHex dest, const CStack *attacked, bool shooting); //called when stack with id ID is attacking something on hex dest void stackAttacking(const CStack *attacker, BattleHex dest, const CStack *attacked, bool shooting); //called when stack with id ID is attacking something on hex dest
void newRoundFirst( int round ); void newRoundFirst( int round );
@ -306,4 +285,5 @@ public:
friend class CBattleObstacleController; friend class CBattleObstacleController;
friend class CBattleFieldController; friend class CBattleFieldController;
friend class CBattleControlPanel; friend class CBattleControlPanel;
friend class CBattleStacksController;
}; };

View File

@ -13,6 +13,7 @@
#include "CBattleInterface.h" #include "CBattleInterface.h"
#include "CBattleSiegeController.h" #include "CBattleSiegeController.h"
#include "CBattleFieldController.h" #include "CBattleFieldController.h"
#include "CBattleStacksController.h"
#include "CBattleControlPanel.h" #include "CBattleControlPanel.h"
#include "../CBitmapHandler.h" #include "../CBitmapHandler.h"
@ -591,7 +592,7 @@ Point CClickableHex::getXYUnitAnim(BattleHex hexNum, const CStack * stack, CBatt
if (stack) if (stack)
{ {
if(cbi->creDir[stack->ID]) if(cbi->stacksController->facingRight(stack))
ret.x += imageShiftX; ret.x += imageShiftX;
else else
ret.x -= imageShiftX; ret.x -= imageShiftX;
@ -601,12 +602,12 @@ Point CClickableHex::getXYUnitAnim(BattleHex hexNum, const CStack * stack, CBatt
{ {
if(stack->side == BattleSide::ATTACKER) if(stack->side == BattleSide::ATTACKER)
{ {
if(cbi->creDir[stack->ID]) if(cbi->stacksController->facingRight(stack))
ret.x -= 44; ret.x -= 44;
} }
else else
{ {
if(!cbi->creDir[stack->ID]) if(!cbi->stacksController->facingRight(stack))
ret.x += 44; ret.x += 44;
} }
} }

View File

@ -11,6 +11,7 @@
#include "CBattleObstacleController.h" #include "CBattleObstacleController.h"
#include "CBattleInterface.h" #include "CBattleInterface.h"
#include "CBattleFieldController.h" #include "CBattleFieldController.h"
#include "CBattleStacksController.h"
#include "../CPlayerInterface.h" #include "../CPlayerInterface.h"
#include "../../CCallback.h" #include "../../CCallback.h"
#include "../../lib/battle/CObstacleInstance.h" #include "../../lib/battle/CObstacleInstance.h"
@ -100,7 +101,7 @@ void CBattleObstacleController::obstaclePlaced(const CObstacleInstance & oi)
//we assume here that effect graphics have the same size as the usual obstacle image //we assume here that effect graphics have the same size as the usual obstacle image
// -> if we know how to blit obstacle, let's blit the effect in the same place // -> if we know how to blit obstacle, let's blit the effect in the same place
Point whereTo = getObstaclePosition(first, oi); Point whereTo = getObstaclePosition(first, oi);
owner->addNewAnim(new CEffectAnimation(owner, animation, whereTo.x, whereTo.y)); owner->stacksController->addNewAnim(new CEffectAnimation(owner, animation, whereTo.x, whereTo.y));
//TODO we need to wait after playing sound till it's finished, otherwise it overlaps and sounds really bad //TODO we need to wait after playing sound till it's finished, otherwise it overlaps and sounds really bad
//CCS->soundh->playSound(sound); //CCS->soundh->playSound(sound);

View File

@ -16,6 +16,7 @@
#include "../gui/CAnimation.h" #include "../gui/CAnimation.h"
#include "CBattleInterface.h" #include "CBattleInterface.h"
#include "CBattleSiegeController.h" #include "CBattleSiegeController.h"
#include "CBattleStacksController.h"
#include "CCreatureAnimation.h" #include "CCreatureAnimation.h"
CatapultProjectileInfo::CatapultProjectileInfo(const Point &from, const Point &dest) CatapultProjectileInfo::CatapultProjectileInfo(const Point &from, const Point &dest)
@ -79,6 +80,17 @@ void CBattleProjectileController::initStackProjectile(const CStack * stack)
} }
} }
void CBattleProjectileController::fireStackProjectile(const CStack * stack)
{
for (auto it = projectiles.begin(); it!=projectiles.end(); ++it)
{
if ( !it->shotDone && it->stackID == stack->ID)
{
it->shotDone = true;
return;
}
}
}
void CBattleProjectileController::showProjectiles(SDL_Surface *to) void CBattleProjectileController::showProjectiles(SDL_Surface *to)
{ {
@ -89,18 +101,7 @@ void CBattleProjectileController::showProjectiles(SDL_Surface *to)
{ {
// Check if projectile is already visible (shooter animation did the shot) // Check if projectile is already visible (shooter animation did the shot)
if (!it->shotDone) if (!it->shotDone)
{ continue;
// frame we're waiting for is reached OR animation has already finished
if (owner->creAnims[it->stackID]->getCurrentFrame() >= it->animStartDelay ||
owner->creAnims[it->stackID]->isShooting() == false)
{
//at this point projectile should become visible
owner->creAnims[it->stackID]->pause(); // pause animation
it->shotDone = true;
}
else
continue; // wait...
}
if (idToProjectile.count(it->creID)) if (idToProjectile.count(it->creID))
{ {
@ -183,12 +184,8 @@ void CBattleProjectileController::showProjectiles(SDL_Surface *to)
} }
for (auto & elem : toBeDeleted) for (auto & elem : toBeDeleted)
{
// resume animation
owner->creAnims[elem->stackID]->play();
projectiles.erase(elem); projectiles.erase(elem);
} }
}
bool CBattleProjectileController::hasActiveProjectile(const CStack * stack) bool CBattleProjectileController::hasActiveProjectile(const CStack * stack)
{ {
@ -231,7 +228,7 @@ void CBattleProjectileController::createProjectile(const CStack * shooter, const
spi.creID = shooter->getCreature()->idNumber; spi.creID = shooter->getCreature()->idNumber;
spi.stackID = shooter->ID; spi.stackID = shooter->ID;
// reverse if creature is facing right OR this is non-existing stack that is not tower (war machines) // reverse if creature is facing right OR this is non-existing stack that is not tower (war machines)
spi.reverse = shooter ? !owner->creDir[shooter->ID] : shooter->getCreature()->idNumber != CreatureID::ARROW_TOWERS; spi.reverse = shooter ? !owner->stacksController->facingRight(shooter) : shooter->getCreature()->idNumber != CreatureID::ARROW_TOWERS;
spi.step = 0; spi.step = 0;
spi.frameNum = 0; spi.frameNum = 0;
@ -314,6 +311,5 @@ void CBattleProjectileController::createProjectile(const CStack * shooter, const
} }
// Set projectile animation start delay which is specified in frames // Set projectile animation start delay which is specified in frames
spi.animStartDelay = shooterInfo->animation.attackClimaxFrame;
projectiles.push_back(spi); projectiles.push_back(spi);
} }

View File

@ -38,7 +38,6 @@ struct ProjectileInfo
int stackID; //ID of stack int stackID; //ID of stack
int frameNum; //frame to display form projectile animation int frameNum; //frame to display form projectile animation
//bool spin; //if true, frameNum will be increased //bool spin; //if true, frameNum will be increased
int animStartDelay; //frame of shooter animation when projectile should appear
bool shotDone; // actual shot already done, projectile is flying bool shotDone; // actual shot already done, projectile is flying
bool reverse; //if true, projectile will be flipped by vertical asix bool reverse; //if true, projectile will be flipped by vertical asix
std::shared_ptr<CatapultProjectileInfo> catapultInfo; // holds info about the parabolic trajectory of the cannon std::shared_ptr<CatapultProjectileInfo> catapultInfo; // holds info about the parabolic trajectory of the cannon
@ -58,6 +57,7 @@ public:
void showProjectiles(SDL_Surface *to); void showProjectiles(SDL_Surface *to);
void initStackProjectile(const CStack * stack); void initStackProjectile(const CStack * stack);
void fireStackProjectile(const CStack * stack);
bool hasActiveProjectile(const CStack * stack); bool hasActiveProjectile(const CStack * stack);

View File

@ -21,6 +21,7 @@
#include "CBattleInterface.h" #include "CBattleInterface.h"
#include "CBattleAnimations.h" #include "CBattleAnimations.h"
#include "CBattleInterfaceClasses.h" #include "CBattleInterfaceClasses.h"
#include "CBattleStacksController.h"
CBattleSiegeController::~CBattleSiegeController() CBattleSiegeController::~CBattleSiegeController()
{ {
@ -315,7 +316,7 @@ void CBattleSiegeController::showPiecesOfWall(SDL_Surface *to, std::vector<int>
if (turret) if (turret)
{ {
std::vector<const CStack *> stackList(1, turret); std::vector<const CStack *> stackList(1, turret);
owner->showStacks(to, stackList); owner->stacksController->showStacks(to, stackList);
printPartOfWall(to, piece); printPartOfWall(to, piece);
} }
} }
@ -343,7 +344,7 @@ void CBattleSiegeController::stackIsCatapulting(const CatapultAttack & ca)
const CStack *stack = owner->curInt->cb->battleGetStackByID(ca.attacker); const CStack *stack = owner->curInt->cb->battleGetStackByID(ca.attacker);
for (auto attackInfo : ca.attackedParts) for (auto attackInfo : ca.attackedParts)
{ {
owner->addNewAnim(new CShootingAnimation(owner, stack, attackInfo.destinationTile, nullptr, true, attackInfo.damageDealt)); owner->stacksController->addNewAnim(new CShootingAnimation(owner, stack, attackInfo.destinationTile, nullptr, true, attackInfo.damageDealt));
} }
} }
else else
@ -353,7 +354,7 @@ void CBattleSiegeController::stackIsCatapulting(const CatapultAttack & ca)
{ {
Point destPos = CClickableHex::getXYUnitAnim(attackInfo.destinationTile, nullptr, owner) + Point(99, 120); Point destPos = CClickableHex::getXYUnitAnim(attackInfo.destinationTile, nullptr, owner) + Point(99, 120);
owner->addNewAnim(new CEffectAnimation(owner, "SGEXPL.DEF", destPos.x, destPos.y)); owner->stacksController->addNewAnim(new CEffectAnimation(owner, "SGEXPL.DEF", destPos.x, destPos.y));
} }
} }

View File

@ -0,0 +1,601 @@
/*
* CBattleStacksController.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "CBattleStacksController.h"
#include "CBattleSiegeController.h"
#include "CBattleInterfaceClasses.h"
#include "CBattleInterface.h"
#include "CBattleFieldController.h"
#include "CBattleProjectileController.h"
#include "CBattleControlPanel.h"
#include "../CBitmapHandler.h"
#include "../gui/SDL_Extensions.h"
#include "../gui/CGuiHandler.h"
#include "../../lib/battle/BattleHex.h"
#include "../CPlayerInterface.h"
#include "CCreatureAnimation.h"
#include "../../lib/CGameState.h"
#include "../../CCallback.h"
#include "../../lib/CStack.h"
#include "../../lib/CondSh.h"
#include "../CMusicHandler.h"
#include "../CGameInfo.h"
static void onAnimationFinished(const CStack *stack, std::weak_ptr<CCreatureAnimation> anim)
{
std::shared_ptr<CCreatureAnimation> animation = anim.lock();
if(!animation)
return;
if (animation->isIdle())
{
const CCreature *creature = stack->getCreature();
if (animation->framesInGroup(CCreatureAnim::MOUSEON) > 0)
{
if (CRandomGenerator::getDefault().nextDouble(99.0) < creature->animation.timeBetweenFidgets *10)
animation->playOnce(CCreatureAnim::MOUSEON);
else
animation->setType(CCreatureAnim::HOLDING);
}
else
{
animation->setType(CCreatureAnim::HOLDING);
}
}
// always reset callback
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);
amountNegative = BitmapHandler::loadBitmap("CMNUMWIN.BMP");
CSDL_Ext::alphaTransform(amountNegative);
transformPalette(amountNegative, 1.00, 0.18, 0.18);
amountEffNeutral = BitmapHandler::loadBitmap("CMNUMWIN.BMP");
CSDL_Ext::alphaTransform(amountEffNeutral);
transformPalette(amountEffNeutral, 1.00, 1.00, 0.18);
std::vector<const CStack*> stacks = owner->curInt->cb->battleGetAllStacks(true);
for(const CStack * s : stacks)
{
stackAdded(s);
}
}
CBattleStacksController::~CBattleStacksController()
{
SDL_FreeSurface(amountNormal);
SDL_FreeSurface(amountNegative);
SDL_FreeSurface(amountPositive);
SDL_FreeSurface(amountEffNeutral);
}
void CBattleStacksController::sortObjectsByHex(BattleObjectsByHex & sorted)
{
auto getCurrentPosition = [&](const CStack *stack) -> BattleHex
{
for (auto & anim : pendingAnims)
{
// 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 (CMovementAnimation *move = dynamic_cast<CMovementAnimation*>(anim.first))
{
if (move->stack == stack)
return move->nextHex;
}
}
return stack->getPosition();
};
auto stacks = owner->curInt->cb->battleGetStacksIf([](const CStack *s)
{
return !s->isTurret();
});
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;
//FIXME: hack to ignore ghost stacks
if ((creAnims[stack->ID]->getType() == CCreatureAnim::DEAD || creAnims[stack->ID]->getType() == CCreatureAnim::HOLDING) && stack->isGhost())
continue;//ignore
if (creAnims[stack->ID]->isDead())
{
sorted.hex[stack->getPosition()].dead.push_back(stack);
continue;
}
if (!creAnims[stack->ID]->isMoving())
{
sorted.hex[stack->getPosition()].alive.push_back(stack);
continue;
}
// flying creature - just blit them over everyone else
if (stack->hasBonusOfType(Bonus::FLYING))
{
sorted.afterAll.alive.push_back(stack);
continue;
}
sorted.hex[getCurrentPosition(stack)].alive.push_back(stack);
}
}
void CBattleStacksController::stackReset(const CStack * stack)
{
auto iter = creAnims.find(stack->ID);
if(iter == creAnims.end())
{
logGlobal->error("Unit %d have no animation", stack->ID);
return;
}
auto animation = iter->second;
if(stack->alive() && animation->isDead())
animation->setType(CCreatureAnim::HOLDING);
if (stack->isClone())
{
ColorShifterDeepBlue shifter;
animation->shiftColor(&shifter);
}
//TODO: handle more cases
}
void CBattleStacksController::stackAdded(const CStack * stack)
{
creDir[stack->ID] = stack->side == BattleSide::ATTACKER; // must be set before getting stack position
Point coords = CClickableHex::getXYUnitAnim(stack->getPosition(), stack, owner);
if(stack->initialPosition < 0) //turret
{
assert(owner->siegeController);
const CCreature *turretCreature = owner->siegeController->turretCreature();
creAnims[stack->ID] = AnimationControls::getAnimation(turretCreature);
creAnims[stack->ID]->pos.h = 225;
coords = owner->siegeController->turretCreaturePosition(stack->initialPosition);
}
else
{
creAnims[stack->ID] = AnimationControls::getAnimation(stack->getCreature());
creAnims[stack->ID]->onAnimationReset += std::bind(&onAnimationFinished, stack, creAnims[stack->ID]);
creAnims[stack->ID]->pos.h = creAnims[stack->ID]->getHeight();
}
creAnims[stack->ID]->pos.x = coords.x;
creAnims[stack->ID]->pos.y = coords.y;
creAnims[stack->ID]->pos.w = creAnims[stack->ID]->getWidth();
creAnims[stack->ID]->setType(CCreatureAnim::HOLDING);
//loading projectiles for units
if(stack->isShooter())
{
owner->projectilesController->initStackProjectile(stack);
}
}
void CBattleStacksController::setActiveStack(const CStack *stack)
{
if (activeStack) // update UI
creAnims[activeStack->ID]->setBorderColor(AnimationControls::getNoBorder());
activeStack = stack;
if (activeStack) // update UI
creAnims[activeStack->ID]->setBorderColor(AnimationControls::getGoldBorder());
owner->controlPanel->blockUI(activeStack == nullptr);
}
void CBattleStacksController::setHoveredStack(const CStack *stack)
{
if ( stack == mouseHoveredStack )
return;
if (mouseHoveredStack)
creAnims[mouseHoveredStack->ID]->setBorderColor(AnimationControls::getNoBorder());
// stack must be alive and not active (which uses gold border instead)
if (stack && stack->alive() && stack != activeStack)
{
mouseHoveredStack = stack;
if (mouseHoveredStack)
{
creAnims[mouseHoveredStack->ID]->setBorderColor(AnimationControls::getBlueBorder());
if (creAnims[mouseHoveredStack->ID]->framesInGroup(CCreatureAnim::MOUSEON) > 0)
creAnims[mouseHoveredStack->ID]->playOnce(CCreatureAnim::MOUSEON);
}
}
else
mouseHoveredStack = nullptr;
}
void CBattleStacksController::showAliveStacks(SDL_Surface *to, std::vector<const CStack *> stacks)
{
BattleHex currentActionTarget;
if(owner->curInt->curAction)
{
auto target = owner->curInt->curAction->getTarget(owner->curInt->cb.get());
if(!target.empty())
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->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;
}
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
return false;
}
return true;
};
auto getEffectsPositivness = [&](const std::vector<si32> & activeSpells) -> int
{
int pos = 0;
for (const auto & spellId : activeSpells)
{
pos += CGI->spellh->objects.at(spellId)->positiveness;
}
return pos;
};
auto getAmountBoxBackground = [&](int positivness) -> SDL_Surface *
{
if (positivness > 0)
return amountPositive;
if (positivness < 0)
return amountNegative;
return amountEffNeutral;
};
showStacks(to, stacks); // Actual display of all stacks
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)
{
for (const CStack *stack : stacks)
{
creAnims[stack->ID]->nextFrame(to, facingRight(stack)); // do actual blit
creAnims[stack->ID]->incrementFrame(float(GH.mainFPSmng->getElapsedMilliseconds()) / 1000);
}
}
void CBattleStacksController::updateBattleAnimations()
{
//handle animations
for (auto & elem : pendingAnims)
{
if (!elem.first) //this animation should be deleted
continue;
if (!elem.second)
{
elem.second = elem.first->init();
}
if (elem.second && elem.first)
elem.first->nextFrame();
}
//delete anims
int preSize = static_cast<int>(pendingAnims.size());
for (auto it = pendingAnims.begin(); it != pendingAnims.end(); ++it)
{
if (it->first == nullptr)
{
pendingAnims.erase(it);
it = pendingAnims.begin();
break;
}
}
if (preSize > 0 && pendingAnims.empty())
{
//anims ended
owner->controlPanel->blockUI(activeStack == nullptr);
owner->animsAreDisplayed.setn(false);
}
}
void CBattleStacksController::addNewAnim(CBattleAnimation *anim)
{
pendingAnims.push_back( std::make_pair(anim, false) );
owner->animsAreDisplayed.setn(true);
}
void CBattleStacksController::stackActivated(const CStack *stack) //TODO: check it all before game state is changed due to abilities
{
stackToActivate = stack;
owner->waitForAnims();
if (stackToActivate) //during waiting stack may have gotten activated through show
activateStack();
}
void CBattleStacksController::stackRemoved(uint32_t stackID)
{
if (getActiveStack() != nullptr)
{
if (getActiveStack()->ID == stackID)
{
BattleAction *action = new BattleAction();
action->side = owner->defendingHeroInstance ? (owner->curInt->playerID == owner->defendingHeroInstance->tempOwner) : false;
action->actionType = EActionType::CANCEL;
action->stackNumber = owner->stacksController->getActiveStack()->ID;
owner->givenCommand.setn(action);
setActiveStack(nullptr);
}
}
//todo: ensure that ghost stack animation has fadeout effect
}
void CBattleStacksController::stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos)
{
for(auto & attackedInfo : attackedInfos)
{
//if (!attackedInfo.cloneKilled) //FIXME: play dead animation for cloned creature before it vanishes
addNewAnim(new CDefenceAnimation(attackedInfo, owner));
if(attackedInfo.rebirth)
{
owner->displayEffect(50, attackedInfo.defender->getPosition()); //TODO: play reverse death animation
CCS->soundh->playSound(soundBase::RESURECT);
}
}
owner->waitForAnims();
for (auto & attackedInfo : attackedInfos)
{
if (attackedInfo.rebirth)
creAnims[attackedInfo.defender->ID]->setType(CCreatureAnim::HOLDING);
if (attackedInfo.cloneKilled)
stackRemoved(attackedInfo.defender->ID);
}
}
void CBattleStacksController::stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance)
{
addNewAnim(new CMovementAnimation(owner, stack, destHex, distance));
owner->waitForAnims();
}
void CBattleStacksController::stackAttacking( const CStack *attacker, BattleHex dest, const CStack *attacked, bool shooting )
{
if (shooting)
{
addNewAnim(new CShootingAnimation(owner, attacker, dest, attacked));
}
else
{
addNewAnim(new CMeleeAttackAnimation(owner, attacker, dest, attacked));
}
//waitForAnims();
}
bool CBattleStacksController::shouldRotate(const CStack * stack, const BattleHex & oldPos, const BattleHex & nextHex)
{
Point begPosition = CClickableHex::getXYUnitAnim(oldPos,stack, owner);
Point endPosition = CClickableHex::getXYUnitAnim(nextHex, stack, owner);
if((begPosition.x > endPosition.x) && facingRight(stack))
return true;
else if((begPosition.x < endPosition.x) && !facingRight(stack))
return true;
return false;
}
void CBattleStacksController::endAction(const BattleAction* action)
{
//check if we should reverse stacks
//for some strange reason, it's not enough
TStacks stacks = owner->curInt->cb->battleGetStacks(CBattleCallback::MINE_AND_ENEMY);
for (const CStack *s : stacks)
{
bool shouldFaceRight = s && s->side == BattleSide::ATTACKER;
if (s && facingRight(s) != shouldFaceRight && s->alive() && creAnims[s->ID]->isIdle())
{
addNewAnim(new CReverseAnimation(owner, s, s->getPosition(), false));
}
}
}
void CBattleStacksController::startAction(const BattleAction* action)
{
const CStack *stack = owner->curInt->cb->battleGetStackByID(action->stackNumber);
setHoveredStack(nullptr);
auto actionTarget = action->getTarget(owner->curInt->cb.get());
if(action->actionType == EActionType::WALK
|| (action->actionType == EActionType::WALK_AND_ATTACK && actionTarget.at(0).hexValue != stack->getPosition()))
{
assert(stack);
owner->moveStarted = true;
if (creAnims[action->stackNumber]->framesInGroup(CCreatureAnim::MOVE_START))
{
pendingAnims.push_back(std::make_pair(new CMovementStartAnimation(owner, stack), false));
}
if(shouldRotate(stack, stack->getPosition(), actionTarget.at(0).hexValue))
pendingAnims.push_back(std::make_pair(new CReverseAnimation(owner, stack, stack->getPosition(), true), false));
}
}
void CBattleStacksController::activateStack()
{
if ( !pendingAnims.empty())
return;
if ( !stackToActivate)
return;
owner->trySetActivePlayer(stackToActivate->owner);
setActiveStack(stackToActivate);
stackToActivate = nullptr;
const CStack * s = owner->stacksController->getActiveStack();
if(!s)
return;
//set casting flag to true if creature can use it to not check it every time
const auto spellcaster = s->getBonusLocalFirst(Selector::type()(Bonus::SPELLCASTER));
const auto randomSpellcaster = s->getBonusLocalFirst(Selector::type()(Bonus::RANDOM_SPELLCASTER));
if(s->canCast() && (spellcaster || randomSpellcaster))
{
stackCanCastSpell = true;
if(randomSpellcaster)
creatureSpellToCast = -1; //spell will be set later on cast
else
creatureSpellToCast = owner->curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), s, CBattleInfoCallback::RANDOM_AIMED); //faerie dragon can cast only one spell until their next move
//TODO: what if creature can cast BOTH random genie spell and aimed spell?
//TODO: faerie dragon type spell should be selected by server
}
else
{
stackCanCastSpell = false;
creatureSpellToCast = -1;
}
}
void CBattleStacksController::setSelectedStack(const CStack *stack)
{
selectedStack = stack;
}
const CStack* CBattleStacksController::getSelectedStack()
{
return selectedStack;
}
const CStack* CBattleStacksController::getActiveStack()
{
return activeStack;
}
bool CBattleStacksController::facingRight(const CStack * stack)
{
return creDir[stack->ID];
}
bool CBattleStacksController::activeStackSpellcaster()
{
return stackCanCastSpell;
}
SpellID CBattleStacksController::activeStackSpellToCast()
{
if (!stackCanCastSpell)
return SpellID::NONE;
return SpellID(creatureSpellToCast);
}

View File

@ -0,0 +1,85 @@
/*
* CBattleStacksController.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
struct BattleObjectsByHex;
struct SDL_Surface;
struct BattleHex;
struct StackAttackedInfo;
struct BattleAction;
class SpellID;
class CBattleInterface;
class CBattleAnimation;
class CCreatureAnimation;
class CStack;
class CBattleAnimation;
class CBattleStacksController
{
CBattleInterface * owner;
SDL_Surface *amountNormal;
SDL_Surface *amountNegative;
SDL_Surface *amountPositive;
SDL_Surface *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)
std::map<int, bool> creDir; // <creatureID, if false reverse creature's animation> //TODO: move it to battle callback
const CStack *activeStack; //number of active stack; nullptr - no one
const CStack *mouseHoveredStack; // stack below mouse pointer, used for border animation
const CStack *stackToActivate; //when animation is playing, we should wait till the end to make the next stack active; nullptr of none
const CStack *selectedStack; //for Teleport / Sacrifice
bool stackCanCastSpell; //if true, active stack could possibly cast some target spell
si32 creatureSpellToCast;
ui32 animIDhelper; //for giving IDs for animations
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);
void stackReset(const CStack * stack);
void stackAdded(const CStack * stack); //new stack appeared on battlefield
void stackRemoved(uint32_t stackID); //stack disappeared from batlefiled
void stackActivated(const CStack *stack); //active stack has been changed
void stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance); //stack with id number moved to destHex
void stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos); //called when a certain amount of stacks has been attacked
void stackAttacking(const CStack *attacker, BattleHex dest, const CStack *attacked, bool shooting); //called when stack with id ID is attacking something on hex dest
void startAction(const BattleAction* action);
void endAction(const BattleAction* action);
bool activeStackSpellcaster();
SpellID activeStackSpellToCast();
void activateStack(); //sets activeStack to stackToActivate etc. //FIXME: No, it's not clear at all
void setActiveStack(const CStack *stack);
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 addNewAnim(CBattleAnimation *anim); //adds new anim to pendingAnims
void updateBattleAnimations();
const CStack* getActiveStack();
const CStack* getSelectedStack();
friend class CBattleAnimation; // for exposing pendingAnims/creAnims/creDir to animations
};