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:
parent
7a6ad671ab
commit
b01737daf2
@ -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
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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;
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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;
|
||||||
|
@ -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
@ -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;
|
||||||
};
|
};
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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,11 +184,7 @@ 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);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
601
client/battle/CBattleStacksController.cpp
Normal file
601
client/battle/CBattleStacksController.cpp
Normal 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);
|
||||||
|
}
|
85
client/battle/CBattleStacksController.h
Normal file
85
client/battle/CBattleStacksController.h
Normal 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
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user