2022-11-20 19:11:34 +02:00
/*
2022-12-11 23:16:23 +02:00
* BattleStacksController . cpp , part of VCMI engine
2022-11-20 19:11:34 +02:00
*
* 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"
2022-12-09 13:38:46 +02:00
# include "BattleStacksController.h"
2022-11-28 16:43:38 +02:00
2022-12-09 13:38:46 +02:00
# include "BattleSiegeController.h"
# include "BattleInterfaceClasses.h"
# include "BattleInterface.h"
# include "BattleAnimationClasses.h"
# include "BattleFieldController.h"
# include "BattleEffectsController.h"
# include "BattleProjectileController.h"
# include "BattleControlPanel.h"
# include "BattleRenderer.h"
# include "CreatureAnimation.h"
2022-11-28 16:43:38 +02:00
# include "../CPlayerInterface.h"
# include "../CMusicHandler.h"
# include "../CGameInfo.h"
2022-11-25 00:26:14 +02:00
# include "../gui/CAnimation.h"
2022-11-20 19:11:34 +02:00
# include "../gui/CGuiHandler.h"
2022-12-11 22:09:57 +02:00
# include "../gui/Canvas.h"
2022-11-28 16:43:38 +02:00
# include "../../CCallback.h"
2022-11-20 19:11:34 +02:00
# include "../../lib/battle/BattleHex.h"
# include "../../lib/CGameState.h"
# include "../../lib/CStack.h"
# include "../../lib/CondSh.h"
2022-12-09 13:26:17 +02:00
static void onAnimationFinished ( const CStack * stack , std : : weak_ptr < CreatureAnimation > anim )
2022-11-20 19:11:34 +02:00
{
2022-12-09 13:26:17 +02:00
std : : shared_ptr < CreatureAnimation > animation = anim . lock ( ) ;
2022-11-20 19:11:34 +02:00
if ( ! animation )
return ;
if ( animation - > isIdle ( ) )
{
const CCreature * creature = stack - > getCreature ( ) ;
2022-12-08 19:41:02 +02:00
if ( animation - > framesInGroup ( ECreatureAnimType : : MOUSEON ) > 0 )
2022-11-20 19:11:34 +02:00
{
if ( CRandomGenerator : : getDefault ( ) . nextDouble ( 99.0 ) < creature - > animation . timeBetweenFidgets * 10 )
2022-12-08 19:41:02 +02:00
animation - > playOnce ( ECreatureAnimType : : MOUSEON ) ;
2022-11-20 19:11:34 +02:00
else
2022-12-08 19:41:02 +02:00
animation - > setType ( ECreatureAnimType : : HOLDING ) ;
2022-11-20 19:11:34 +02:00
}
else
{
2022-12-08 19:41:02 +02:00
animation - > setType ( ECreatureAnimType : : HOLDING ) ;
2022-11-20 19:11:34 +02:00
}
}
// always reset callback
animation - > onAnimationReset + = std : : bind ( & onAnimationFinished , stack , anim ) ;
}
2022-12-13 13:58:16 +02:00
BattleStacksController : : BattleStacksController ( BattleInterface & owner ) :
2022-11-27 02:26:02 +02:00
owner ( owner ) ,
activeStack ( nullptr ) ,
mouseHoveredStack ( nullptr ) ,
stackToActivate ( nullptr ) ,
selectedStack ( nullptr ) ,
stackCanCastSpell ( false ) ,
creatureSpellToCast ( uint32_t ( - 1 ) ) ,
animIDhelper ( 0 )
2022-11-20 19:11:34 +02:00
{
//preparing graphics for displaying amounts of creatures
2022-11-25 00:26:14 +02:00
amountNormal = IImage : : createFromFile ( " CMNUMWIN.BMP " ) ;
amountPositive = IImage : : createFromFile ( " CMNUMWIN.BMP " ) ;
amountNegative = IImage : : createFromFile ( " CMNUMWIN.BMP " ) ;
amountEffNeutral = IImage : : createFromFile ( " CMNUMWIN.BMP " ) ;
2022-11-20 19:11:34 +02:00
2022-12-11 23:43:43 +02:00
static const ColorShifterMultiplyAndAddExcept shifterNormal ( { 150 , 50 , 255 , 255 } , { 0 , 0 , 0 , 0 } , { 255 , 231 , 132 , 255 } ) ;
static const ColorShifterMultiplyAndAddExcept shifterPositive ( { 50 , 255 , 50 , 255 } , { 0 , 0 , 0 , 0 } , { 255 , 231 , 132 , 255 } ) ;
static const ColorShifterMultiplyAndAddExcept shifterNegative ( { 255 , 50 , 50 , 255 } , { 0 , 0 , 0 , 0 } , { 255 , 231 , 132 , 255 } ) ;
static const ColorShifterMultiplyAndAddExcept shifterNeutral ( { 255 , 255 , 50 , 255 } , { 0 , 0 , 0 , 0 } , { 255 , 231 , 132 , 255 } ) ;
2022-11-20 19:11:34 +02:00
2022-11-25 00:26:14 +02:00
amountNormal - > adjustPalette ( & shifterNormal ) ;
amountPositive - > adjustPalette ( & shifterPositive ) ;
amountNegative - > adjustPalette ( & shifterNegative ) ;
amountEffNeutral - > adjustPalette ( & shifterNeutral ) ;
2022-11-20 19:11:34 +02:00
2022-12-13 13:58:16 +02:00
std : : vector < const CStack * > stacks = owner . curInt - > cb - > battleGetAllStacks ( true ) ;
2022-11-20 19:11:34 +02:00
for ( const CStack * s : stacks )
{
stackAdded ( s ) ;
}
}
2022-12-13 15:10:31 +02:00
BattleHex BattleStacksController : : getStackCurrentPosition ( const CStack * stack ) const
2022-11-20 19:11:34 +02:00
{
2022-12-13 15:10:31 +02:00
if ( ! stackAnimation . at ( stack - > ID ) - > isMoving ( ) )
2022-12-02 17:49:38 +02:00
return stack - > getPosition ( ) ;
if ( stack - > hasBonusOfType ( Bonus : : FLYING ) )
return BattleHex : : HEX_AFTER_ALL ;
for ( auto & anim : currentAnimations )
2022-11-20 19:11:34 +02:00
{
2022-12-02 17:49:38 +02:00
// certainly ugly workaround but fixes quite annoying bug
// stack position will be updated only *after* movement is finished
// before this - stack is always at its initial position. Thus we need to find
// its current position. Which can be found only in this class
if ( CStackMoveAnimation * move = dynamic_cast < CStackMoveAnimation * > ( anim ) )
2022-11-20 19:11:34 +02:00
{
2022-12-02 17:49:38 +02:00
if ( move - > stack = = stack )
return move - > currentHex ;
2022-11-20 19:11:34 +02:00
}
2022-12-02 17:49:38 +02:00
}
return stack - > getPosition ( ) ;
}
2022-11-20 19:11:34 +02:00
2022-12-09 13:26:17 +02:00
void BattleStacksController : : collectRenderableObjects ( BattleRenderer & renderer )
2022-12-02 17:49:38 +02:00
{
2022-12-13 13:58:16 +02:00
auto stacks = owner . curInt - > cb - > battleGetAllStacks ( false ) ;
2022-11-20 19:11:34 +02:00
2022-12-02 17:49:38 +02:00
for ( auto stack : stacks )
2022-11-20 19:11:34 +02:00
{
2022-11-28 16:02:46 +02:00
if ( stackAnimation . find ( stack - > ID ) = = stackAnimation . end ( ) ) //e.g. for summoned but not yet handled stacks
2022-11-20 19:11:34 +02:00
continue ;
//FIXME: hack to ignore ghost stacks
2022-12-08 19:41:02 +02:00
if ( ( stackAnimation [ stack - > ID ] - > getType ( ) = = ECreatureAnimType : : DEAD | | stackAnimation [ stack - > ID ] - > getType ( ) = = ECreatureAnimType : : HOLDING ) & & stack - > isGhost ( ) )
2022-11-20 19:11:34 +02:00
continue ;
2022-12-02 17:49:38 +02:00
auto layer = stackAnimation [ stack - > ID ] - > isDead ( ) ? EBattleFieldLayer : : CORPSES : EBattleFieldLayer : : STACKS ;
auto location = getStackCurrentPosition ( stack ) ;
2022-11-20 19:11:34 +02:00
2022-12-13 15:10:31 +02:00
renderer . insert ( layer , location , [ this , stack ] ( BattleRenderer : : RendererRef renderer ) {
2022-12-02 17:49:38 +02:00
showStack ( renderer , stack ) ;
} ) ;
2022-11-20 19:11:34 +02:00
2022-12-02 17:49:38 +02:00
if ( stackNeedsAmountBox ( stack ) )
2022-11-25 00:26:14 +02:00
{
2022-12-13 15:10:31 +02:00
renderer . insert ( EBattleFieldLayer : : STACK_AMOUNTS , location , [ this , stack ] ( BattleRenderer : : RendererRef renderer ) {
2022-12-02 17:49:38 +02:00
showStackAmountBox ( renderer , stack ) ;
} ) ;
2022-11-25 00:26:14 +02:00
}
2022-11-20 19:11:34 +02:00
}
}
2022-12-09 13:26:17 +02:00
void BattleStacksController : : stackReset ( const CStack * stack )
2022-11-20 19:11:34 +02:00
{
2022-12-09 13:10:35 +02:00
//FIXME: there should be no more ongoing animations. If not - then some other method created animations but did not wait for them to end
assert ( ! owner . animsAreDisplayed . get ( ) ) ;
owner . waitForAnims ( ) ;
2022-11-28 16:02:46 +02:00
auto iter = stackAnimation . find ( stack - > ID ) ;
2022-11-20 19:11:34 +02:00
2022-11-28 16:02:46 +02:00
if ( iter = = stackAnimation . end ( ) )
2022-11-20 19:11:34 +02:00
{
logGlobal - > error ( " Unit %d have no animation " , stack - > ID ) ;
return ;
}
auto animation = iter - > second ;
2022-11-28 22:35:38 +02:00
if ( stack - > alive ( ) & & animation - > isDeadOrDying ( ) )
2022-12-08 20:43:51 +02:00
{
addNewAnim ( new CResurrectionAnimation ( owner , stack ) ) ;
}
2022-11-20 19:11:34 +02:00
2022-12-11 23:43:43 +02:00
static const ColorShifterMultiplyAndAdd shifterClone ( { 255 , 255 , 0 , 255 } , { 0 , 0 , 255 , 0 } ) ;
2022-11-20 19:11:34 +02:00
if ( stack - > isClone ( ) )
{
2022-12-11 23:43:43 +02:00
animation - > shiftColor ( & shifterClone ) ;
2022-11-20 19:11:34 +02:00
}
2022-12-08 20:43:51 +02:00
owner . waitForAnims ( ) ;
2022-11-20 19:11:34 +02:00
}
2022-12-09 13:26:17 +02:00
void BattleStacksController : : stackAdded ( const CStack * stack )
2022-11-20 19:11:34 +02:00
{
2022-12-13 15:10:31 +02:00
// Tower shooters have only their upper half visible
static const int turretCreatureAnimationHeight = 235 ;
2022-11-28 16:02:46 +02:00
stackFacingRight [ stack - > ID ] = stack - > side = = BattleSide : : ATTACKER ; // must be set before getting stack position
2022-11-20 19:11:34 +02:00
2022-11-27 02:26:02 +02:00
Point coords = getStackPositionAtHex ( stack - > getPosition ( ) , stack ) ;
2022-11-20 19:11:34 +02:00
if ( stack - > initialPosition < 0 ) //turret
{
2022-12-13 13:58:16 +02:00
assert ( owner . siegeController ) ;
2022-11-20 19:11:34 +02:00
2022-12-13 13:58:16 +02:00
const CCreature * turretCreature = owner . siegeController - > getTurretCreature ( ) ;
2022-11-20 19:11:34 +02:00
2022-11-28 16:02:46 +02:00
stackAnimation [ stack - > ID ] = AnimationControls : : getAnimation ( turretCreature ) ;
2022-12-13 15:10:31 +02:00
stackAnimation [ stack - > ID ] - > pos . h = turretCreatureAnimationHeight ;
2022-11-20 19:11:34 +02:00
2022-12-13 13:58:16 +02:00
coords = owner . siegeController - > getTurretCreaturePosition ( stack - > initialPosition ) ;
2022-11-20 19:11:34 +02:00
}
else
{
2022-11-28 16:02:46 +02:00
stackAnimation [ stack - > ID ] = AnimationControls : : getAnimation ( stack - > getCreature ( ) ) ;
stackAnimation [ stack - > ID ] - > onAnimationReset + = std : : bind ( & onAnimationFinished , stack , stackAnimation [ stack - > ID ] ) ;
stackAnimation [ stack - > ID ] - > pos . h = stackAnimation [ stack - > ID ] - > getHeight ( ) ;
2022-11-20 19:11:34 +02:00
}
2022-11-28 16:02:46 +02:00
stackAnimation [ stack - > ID ] - > pos . x = coords . x ;
stackAnimation [ stack - > ID ] - > pos . y = coords . y ;
stackAnimation [ stack - > ID ] - > pos . w = stackAnimation [ stack - > ID ] - > getWidth ( ) ;
2022-12-08 19:41:02 +02:00
stackAnimation [ stack - > ID ] - > setType ( ECreatureAnimType : : HOLDING ) ;
2022-11-20 19:11:34 +02:00
}
2022-12-09 13:26:17 +02:00
void BattleStacksController : : setActiveStack ( const CStack * stack )
2022-11-20 19:11:34 +02:00
{
if ( activeStack ) // update UI
2022-11-28 16:02:46 +02:00
stackAnimation [ activeStack - > ID ] - > setBorderColor ( AnimationControls : : getNoBorder ( ) ) ;
2022-11-20 19:11:34 +02:00
activeStack = stack ;
if ( activeStack ) // update UI
2022-11-28 16:02:46 +02:00
stackAnimation [ activeStack - > ID ] - > setBorderColor ( AnimationControls : : getGoldBorder ( ) ) ;
2022-11-20 19:11:34 +02:00
2022-12-13 13:58:16 +02:00
owner . controlPanel - > blockUI ( activeStack = = nullptr ) ;
2022-11-20 19:11:34 +02:00
}
2022-12-09 13:26:17 +02:00
void BattleStacksController : : setHoveredStack ( const CStack * stack )
2022-11-20 19:11:34 +02:00
{
if ( stack = = mouseHoveredStack )
return ;
if ( mouseHoveredStack )
2022-11-28 16:02:46 +02:00
stackAnimation [ mouseHoveredStack - > ID ] - > setBorderColor ( AnimationControls : : getNoBorder ( ) ) ;
2022-11-20 19:11:34 +02:00
// stack must be alive and not active (which uses gold border instead)
if ( stack & & stack - > alive ( ) & & stack ! = activeStack )
{
mouseHoveredStack = stack ;
if ( mouseHoveredStack )
{
2022-11-28 16:02:46 +02:00
stackAnimation [ mouseHoveredStack - > ID ] - > setBorderColor ( AnimationControls : : getBlueBorder ( ) ) ;
2022-12-08 19:41:02 +02:00
if ( stackAnimation [ mouseHoveredStack - > ID ] - > framesInGroup ( ECreatureAnimType : : MOUSEON ) > 0 )
stackAnimation [ mouseHoveredStack - > ID ] - > playOnce ( ECreatureAnimType : : MOUSEON ) ;
2022-11-20 19:11:34 +02:00
}
}
else
mouseHoveredStack = nullptr ;
}
2022-12-13 15:10:31 +02:00
bool BattleStacksController : : stackNeedsAmountBox ( const CStack * stack ) const
2022-11-20 19:11:34 +02:00
{
BattleHex currentActionTarget ;
2022-12-13 13:58:16 +02:00
if ( owner . curInt - > curAction )
2022-11-20 19:11:34 +02:00
{
2022-12-13 13:58:16 +02:00
auto target = owner . curInt - > curAction - > getTarget ( owner . curInt - > cb . get ( ) ) ;
2022-11-20 19:11:34 +02:00
if ( ! target . empty ( ) )
currentActionTarget = target . at ( 0 ) . hexValue ;
}
2022-11-28 22:47:28 +02:00
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
2022-11-25 00:26:14 +02:00
return false ;
2022-11-20 19:11:34 +02:00
2022-12-13 13:58:16 +02:00
if ( ! owner . battleActionsStarted ) // do not perform any further checks since they are related to actions that will only occur after intro music
2022-11-28 22:47:28 +02:00
return true ;
if ( ! stack - > alive ( ) )
2022-11-25 00:26:14 +02:00
return false ;
if ( stack - > getCount ( ) = = 0 ) //hide box when target is going to die anyway - do not display "0 creatures"
return false ;
2022-11-28 16:02:46 +02:00
for ( auto anim : currentAnimations ) //no matter what other conditions below are, hide box when creature is playing hit animation
2022-11-25 00:26:14 +02:00
{
2022-11-28 16:02:46 +02:00
auto hitAnimation = dynamic_cast < CDefenceAnimation * > ( anim ) ;
2022-11-26 23:12:20 +02:00
if ( hitAnimation & & ( hitAnimation - > stack - > ID = = stack - > ID ) )
2022-11-20 19:11:34 +02:00
return false ;
2022-11-25 00:26:14 +02:00
}
2022-11-20 19:11:34 +02:00
2022-12-13 13:58:16 +02:00
if ( owner . curInt - > curAction )
2022-11-25 00:26:14 +02:00
{
2022-12-13 13:58:16 +02:00
if ( owner . curInt - > curAction - > stackNumber = = stack - > ID ) //stack is currently taking action (is not a target of another creature's action etc)
2022-11-20 19:11:34 +02:00
{
2022-12-13 13:58:16 +02:00
if ( owner . curInt - > curAction - > actionType = = EActionType : : WALK | | owner . curInt - > curAction - > actionType = = EActionType : : SHOOT ) //hide when stack walks or shoots
2022-11-20 19:11:34 +02:00
return false ;
2022-12-13 13:58:16 +02:00
else if ( owner . curInt - > curAction - > actionType = = EActionType : : WALK_AND_ATTACK & & currentActionTarget ! = stack - > getPosition ( ) ) //when attacking, hide until walk phase finished
2022-11-20 19:11:34 +02:00
return false ;
}
2022-12-13 13:58:16 +02:00
if ( owner . curInt - > curAction - > actionType = = EActionType : : SHOOT & & currentActionTarget = = stack - > getPosition ( ) ) //hide if we are ranged attack target
2022-11-25 00:26:14 +02:00
return false ;
}
return true ;
}
2022-11-20 19:11:34 +02:00
2022-12-09 13:26:17 +02:00
std : : shared_ptr < IImage > BattleStacksController : : getStackAmountBox ( const CStack * stack )
2022-11-25 00:26:14 +02:00
{
2022-11-26 23:12:20 +02:00
std : : vector < si32 > activeSpells = stack - > activeSpells ( ) ;
2022-11-20 19:11:34 +02:00
2022-11-26 23:12:20 +02:00
if ( activeSpells . empty ( ) )
return amountNormal ;
int effectsPositivness = 0 ;
for ( auto const & spellID : activeSpells )
effectsPositivness + = CGI - > spellh - > objects . at ( spellID ) - > positiveness ;
if ( effectsPositivness > 0 )
return amountPositive ;
if ( effectsPositivness < 0 )
return amountNegative ;
return amountEffNeutral ;
}
2022-12-11 22:09:57 +02:00
void BattleStacksController : : showStackAmountBox ( Canvas & canvas , const CStack * stack )
2022-11-26 23:12:20 +02:00
{
//blitting amount background box
auto amountBG = getStackAmountBox ( stack ) ;
2022-11-20 19:11:34 +02:00
2022-11-25 00:26:14 +02:00
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 ) ;
2022-12-13 13:58:16 +02:00
const bool moveInside = ! edge & & ! owner . fieldController - > stackCountOutsideHex ( nextPos ) ;
2022-11-26 23:12:20 +02:00
2022-11-25 00:26:14 +02:00
int xAdd = ( stack - > side = = BattleSide : : ATTACKER ? 220 : 202 ) +
( stack - > doubleWide ( ) ? 44 : 0 ) * sideShift +
2022-11-26 23:12:20 +02:00
( moveInside ? amountBG - > width ( ) + 10 : 0 ) * reverseSideShift ;
2022-11-25 00:26:14 +02:00
int yAdd = 260 + ( ( stack - > side = = BattleSide : : ATTACKER | | moveInside ) ? 0 : - 15 ) ;
2022-12-11 22:09:57 +02:00
canvas . draw ( amountBG , stackAnimation [ stack - > ID ] - > pos . topLeft ( ) + Point ( xAdd , yAdd ) ) ;
2022-11-25 00:26:14 +02:00
//blitting amount
2022-11-28 16:02:46 +02:00
Point textPos = stackAnimation [ stack - > ID ] - > pos . topLeft ( ) + amountBG - > dimensions ( ) / 2 + Point ( xAdd , yAdd ) ;
2022-11-20 19:11:34 +02:00
2022-12-11 22:09:57 +02:00
canvas . drawText ( textPos , EFonts : : FONT_TINY , Colors : : WHITE , ETextAlignment : : CENTER , makeNumberShort ( stack - > getCount ( ) ) ) ;
2022-11-20 19:11:34 +02:00
}
2022-12-11 22:09:57 +02:00
void BattleStacksController : : showStack ( Canvas & canvas , const CStack * stack )
2022-11-20 19:11:34 +02:00
{
2022-11-28 16:02:46 +02:00
stackAnimation [ stack - > ID ] - > nextFrame ( canvas , facingRight ( stack ) ) ; // do actual blit
stackAnimation [ stack - > ID ] - > incrementFrame ( float ( GH . mainFPSmng - > getElapsedMilliseconds ( ) ) / 1000 ) ;
2022-11-20 19:11:34 +02:00
}
2022-12-09 13:26:17 +02:00
void BattleStacksController : : updateBattleAnimations ( )
2022-11-20 19:11:34 +02:00
{
2022-11-28 16:02:46 +02:00
for ( auto & elem : currentAnimations )
2022-11-20 19:11:34 +02:00
{
2022-11-28 16:02:46 +02:00
if ( ! elem )
2022-11-20 19:11:34 +02:00
continue ;
2022-11-28 16:02:46 +02:00
if ( elem - > isInitialized ( ) )
elem - > nextFrame ( ) ;
else
elem - > tryInitialize ( ) ;
2022-11-20 19:11:34 +02:00
}
2022-11-28 16:02:46 +02:00
bool hadAnimations = ! currentAnimations . empty ( ) ;
2022-12-13 15:10:31 +02:00
vstd : : erase ( currentAnimations , nullptr ) ;
2022-11-20 19:11:34 +02:00
2022-11-28 16:02:46 +02:00
if ( hadAnimations & & currentAnimations . empty ( ) )
2022-11-20 19:11:34 +02:00
{
//anims ended
2022-12-13 13:58:16 +02:00
owner . controlPanel - > blockUI ( activeStack = = nullptr ) ;
owner . animsAreDisplayed . setn ( false ) ;
2022-11-20 19:11:34 +02:00
}
}
2022-12-09 13:26:17 +02:00
void BattleStacksController : : addNewAnim ( CBattleAnimation * anim )
2022-11-20 19:11:34 +02:00
{
2022-11-28 16:02:46 +02:00
currentAnimations . push_back ( anim ) ;
2022-12-13 13:58:16 +02:00
owner . animsAreDisplayed . setn ( true ) ;
2022-11-20 19:11:34 +02:00
}
2022-12-09 13:26:17 +02:00
void BattleStacksController : : stackActivated ( const CStack * stack ) //TODO: check it all before game state is changed due to abilities
2022-11-20 19:11:34 +02:00
{
stackToActivate = stack ;
2022-12-13 13:58:16 +02:00
owner . waitForAnims ( ) ;
2022-11-20 19:11:34 +02:00
if ( stackToActivate ) //during waiting stack may have gotten activated through show
2022-12-13 13:58:16 +02:00
owner . activateStack ( ) ;
2022-11-20 19:11:34 +02:00
}
2022-12-09 13:26:17 +02:00
void BattleStacksController : : stackRemoved ( uint32_t stackID )
2022-11-20 19:11:34 +02:00
{
2022-12-13 15:10:31 +02:00
if ( getActiveStack ( ) & & getActiveStack ( ) - > ID = = stackID )
2022-11-20 19:11:34 +02:00
{
2022-12-13 15:10:31 +02:00
BattleAction * action = new BattleAction ( ) ;
action - > side = owner . defendingHeroInstance ? ( owner . curInt - > playerID = = owner . defendingHeroInstance - > tempOwner ) : false ;
action - > actionType = EActionType : : CANCEL ;
action - > stackNumber = getActiveStack ( ) - > ID ;
owner . givenCommand . setn ( action ) ;
setActiveStack ( nullptr ) ;
2022-11-20 19:11:34 +02:00
}
}
2022-12-09 13:26:17 +02:00
void BattleStacksController : : stacksAreAttacked ( std : : vector < StackAttackedInfo > attackedInfos )
2022-11-20 19:11:34 +02:00
{
for ( auto & attackedInfo : attackedInfos )
{
2022-12-09 13:10:35 +02:00
if ( ! attackedInfo . attacker )
continue ;
2022-11-20 19:11:34 +02:00
2022-12-09 13:10:35 +02:00
bool needsReverse =
owner . curInt - > cb - > isToReverse (
attackedInfo . defender - > getPosition ( ) ,
attackedInfo . attacker - > getPosition ( ) ,
facingRight ( attackedInfo . defender ) ,
attackedInfo . attacker - > doubleWide ( ) ,
facingRight ( attackedInfo . attacker ) ) ;
if ( needsReverse )
addNewAnim ( new CReverseAnimation ( owner , attackedInfo . defender , attackedInfo . defender - > getPosition ( ) ) ) ;
}
for ( auto & attackedInfo : attackedInfos )
{
addNewAnim ( new CDefenceAnimation ( attackedInfo , owner ) ) ;
if ( attackedInfo . battleEffect ! = EBattleEffect : : INVALID )
owner . effectsController - > displayEffect ( EBattleEffect : : EBattleEffect ( attackedInfo . battleEffect ) , attackedInfo . defender - > getPosition ( ) ) ;
if ( attackedInfo . spellEffect ! = SpellID : : NONE )
owner . displaySpellEffect ( attackedInfo . spellEffect , attackedInfo . defender - > getPosition ( ) ) ;
2022-11-20 19:11:34 +02:00
}
2022-12-13 13:58:16 +02:00
owner . waitForAnims ( ) ;
2022-11-20 19:11:34 +02:00
for ( auto & attackedInfo : attackedInfos )
{
if ( attackedInfo . rebirth )
2022-12-09 13:10:35 +02:00
{
owner . effectsController - > displayEffect ( EBattleEffect : : RESURRECT , soundBase : : RESURECT , attackedInfo . defender - > getPosition ( ) ) ;
2022-12-08 20:43:51 +02:00
addNewAnim ( new CResurrectionAnimation ( owner , attackedInfo . defender ) ) ;
2022-12-09 13:10:35 +02:00
}
2022-11-20 19:11:34 +02:00
if ( attackedInfo . cloneKilled )
stackRemoved ( attackedInfo . defender - > ID ) ;
}
2022-12-08 20:43:51 +02:00
owner . waitForAnims ( ) ;
2022-11-20 19:11:34 +02:00
}
2022-12-09 13:26:17 +02:00
void BattleStacksController : : stackMoved ( const CStack * stack , std : : vector < BattleHex > destHex , int distance )
2022-11-20 19:11:34 +02:00
{
2022-12-09 13:10:35 +02:00
assert ( destHex . size ( ) > 0 ) ;
//FIXME: there should be no more ongoing animations. If not - then some other method created animations but did not wait for them to end
assert ( ! owner . animsAreDisplayed . get ( ) ) ;
if ( shouldRotate ( stack , stack - > getPosition ( ) , destHex [ 0 ] ) )
addNewAnim ( new CReverseAnimation ( owner , stack , destHex [ 0 ] ) ) ;
addNewAnim ( new CMovementStartAnimation ( owner , stack ) ) ;
owner . waitForAnims ( ) ;
2022-11-20 19:11:34 +02:00
addNewAnim ( new CMovementAnimation ( owner , stack , destHex , distance ) ) ;
2022-12-13 13:58:16 +02:00
owner . waitForAnims ( ) ;
2022-12-09 13:10:35 +02:00
addNewAnim ( new CMovementEndAnimation ( owner , stack , destHex . back ( ) ) ) ;
owner . waitForAnims ( ) ;
2022-11-20 19:11:34 +02:00
}
2022-12-09 13:10:35 +02:00
void BattleStacksController : : stackAttacking ( const CStack * attacker , BattleHex dest , const CStack * defender , bool shooting )
2022-11-20 19:11:34 +02:00
{
2022-12-09 13:10:35 +02:00
bool needsReverse =
owner . curInt - > cb - > isToReverse (
attacker - > getPosition ( ) ,
defender - > getPosition ( ) ,
facingRight ( attacker ) ,
attacker - > doubleWide ( ) ,
facingRight ( defender ) ) ;
if ( needsReverse )
addNewAnim ( new CReverseAnimation ( owner , attacker , attacker - > getPosition ( ) ) ) ;
owner . waitForAnims ( ) ;
2022-11-20 19:11:34 +02:00
if ( shooting )
{
2022-12-09 13:10:35 +02:00
addNewAnim ( new CShootingAnimation ( owner , attacker , dest , defender ) ) ;
2022-11-20 19:11:34 +02:00
}
else
{
2022-12-09 13:10:35 +02:00
addNewAnim ( new CMeleeAttackAnimation ( owner , attacker , dest , defender ) ) ;
2022-11-20 19:11:34 +02:00
}
2022-12-09 13:10:35 +02:00
// do not wait - waiting will be done at stacksAreAttacked
// waitForAnims();
2022-11-20 19:11:34 +02:00
}
2022-12-11 23:16:23 +02:00
bool BattleStacksController : : shouldRotate ( const CStack * stack , const BattleHex & oldPos , const BattleHex & nextHex ) const
2022-11-20 19:11:34 +02:00
{
2022-11-27 02:26:02 +02:00
Point begPosition = getStackPositionAtHex ( oldPos , stack ) ;
Point endPosition = getStackPositionAtHex ( nextHex , stack ) ;
2022-11-20 19:11:34 +02:00
if ( ( begPosition . x > endPosition . x ) & & facingRight ( stack ) )
return true ;
else if ( ( begPosition . x < endPosition . x ) & & ! facingRight ( stack ) )
return true ;
return false ;
}
2022-12-09 13:26:17 +02:00
void BattleStacksController : : endAction ( const BattleAction * action )
2022-11-20 19:11:34 +02:00
{
2022-12-09 13:10:35 +02:00
//FIXME: there should be no more ongoing animations. If not - then some other method created animations but did not wait for them to end
assert ( ! owner . animsAreDisplayed . get ( ) ) ;
owner . waitForAnims ( ) ;
2022-11-20 19:11:34 +02:00
//check if we should reverse stacks
2022-12-13 13:58:16 +02:00
TStacks stacks = owner . curInt - > cb - > battleGetStacks ( CBattleCallback : : MINE_AND_ENEMY ) ;
2022-11-20 19:11:34 +02:00
for ( const CStack * s : stacks )
{
bool shouldFaceRight = s & & s - > side = = BattleSide : : ATTACKER ;
2022-11-28 16:02:46 +02:00
if ( s & & facingRight ( s ) ! = shouldFaceRight & & s - > alive ( ) & & stackAnimation [ s - > ID ] - > isIdle ( ) )
2022-11-20 19:11:34 +02:00
{
2022-12-09 13:10:35 +02:00
addNewAnim ( new CReverseAnimation ( owner , s , s - > getPosition ( ) ) ) ;
2022-11-20 19:11:34 +02:00
}
}
2022-12-09 13:10:35 +02:00
owner . waitForAnims ( ) ;
2022-11-20 19:11:34 +02:00
}
2022-12-09 13:26:17 +02:00
void BattleStacksController : : startAction ( const BattleAction * action )
2022-11-20 19:11:34 +02:00
{
setHoveredStack ( nullptr ) ;
}
2022-12-09 13:26:17 +02:00
void BattleStacksController : : activateStack ( )
2022-11-20 19:11:34 +02:00
{
2022-11-28 16:02:46 +02:00
if ( ! currentAnimations . empty ( ) )
2022-11-20 19:11:34 +02:00
return ;
if ( ! stackToActivate )
return ;
2022-12-13 13:58:16 +02:00
owner . trySetActivePlayer ( stackToActivate - > owner ) ;
2022-11-20 19:11:34 +02:00
setActiveStack ( stackToActivate ) ;
stackToActivate = nullptr ;
2022-11-27 02:26:02 +02:00
const CStack * s = getActiveStack ( ) ;
2022-11-20 19:11:34 +02:00
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
2022-12-13 13:58:16 +02:00
creatureSpellToCast = owner . curInt - > cb - > battleGetRandomStackSpell ( CRandomGenerator : : getDefault ( ) , s , CBattleInfoCallback : : RANDOM_AIMED ) ; //faerie dragon can cast only one spell until their next move
2022-11-20 19:11:34 +02:00
//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 ;
}
}
2022-12-09 13:26:17 +02:00
void BattleStacksController : : setSelectedStack ( const CStack * stack )
2022-11-20 19:11:34 +02:00
{
selectedStack = stack ;
}
2022-12-11 23:16:23 +02:00
const CStack * BattleStacksController : : getSelectedStack ( ) const
2022-11-20 19:11:34 +02:00
{
return selectedStack ;
}
2022-12-11 23:16:23 +02:00
const CStack * BattleStacksController : : getActiveStack ( ) const
2022-11-20 19:11:34 +02:00
{
return activeStack ;
}
2022-12-11 23:16:23 +02:00
bool BattleStacksController : : facingRight ( const CStack * stack ) const
2022-11-20 19:11:34 +02:00
{
2022-12-11 23:16:23 +02:00
return stackFacingRight . at ( stack - > ID ) ;
2022-11-20 19:11:34 +02:00
}
2022-12-09 13:26:17 +02:00
bool BattleStacksController : : activeStackSpellcaster ( )
2022-11-20 19:11:34 +02:00
{
return stackCanCastSpell ;
}
2022-12-09 13:26:17 +02:00
SpellID BattleStacksController : : activeStackSpellToCast ( )
2022-11-20 19:11:34 +02:00
{
if ( ! stackCanCastSpell )
return SpellID : : NONE ;
return SpellID ( creatureSpellToCast ) ;
}
2022-11-27 02:26:02 +02:00
2022-12-11 23:16:23 +02:00
Point BattleStacksController : : getStackPositionAtHex ( BattleHex hexNum , const CStack * stack ) const
2022-11-27 02:26:02 +02:00
{
Point ret ( - 500 , - 500 ) ; //returned value
if ( stack & & stack - > initialPosition < 0 ) //creatures in turrets
2022-12-13 13:58:16 +02:00
return owner . siegeController - > getTurretCreaturePosition ( stack - > initialPosition ) ;
2022-11-27 02:26:02 +02:00
static const Point basePos ( - 190 , - 139 ) ; // position of creature in topleft corner
static const int imageShiftX = 30 ; // X offset to base pos for facing right stacks, negative for facing left
ret . x = basePos . x + 22 * ( ( hexNum . getY ( ) + 1 ) % 2 ) + 44 * hexNum . getX ( ) ;
ret . y = basePos . y + 42 * hexNum . getY ( ) ;
if ( stack )
{
if ( facingRight ( stack ) )
ret . x + = imageShiftX ;
else
ret . x - = imageShiftX ;
//shifting position for double - hex creatures
if ( stack - > doubleWide ( ) )
{
if ( stack - > side = = BattleSide : : ATTACKER )
{
if ( facingRight ( stack ) )
ret . x - = 44 ;
}
else
{
if ( ! facingRight ( stack ) )
ret . x + = 44 ;
}
}
}
//returning
2022-12-13 13:58:16 +02:00
return ret + owner . pos . topLeft ( ) ;
2022-11-27 02:26:02 +02:00
}