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 ;
2022-12-16 18:34:35 +02:00
if ( ! stack - > isFrozen ( ) & & animation - > getType ( ) = = ECreatureAnimType : : FROZEN )
animation - > setType ( ECreatureAnimType : : HOLDING ) ;
2022-11-20 19:11:34 +02:00
if ( animation - > isIdle ( ) )
{
2022-12-16 18:34:35 +02:00
if ( stack - > isFrozen ( ) )
animation - > setType ( ECreatureAnimType : : FROZEN ) ;
2022-11-20 19:11:34 +02:00
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-15 23:24:03 +02:00
static const auto shifterNormal = ColorFilter : : genRangeShifter ( 0 , 0 , 0 , 0.6 , 0.2 , 1.0 ) ;
static const auto shifterPositive = ColorFilter : : genRangeShifter ( 0 , 0 , 0 , 0.2 , 1.0 , 0.2 ) ;
static const auto shifterNegative = ColorFilter : : genRangeShifter ( 0 , 0 , 0 , 1.0 , 0.2 , 0.2 ) ;
static const auto shifterNeutral = ColorFilter : : genRangeShifter ( 0 , 0 , 0 , 1.0 , 1.0 , 0.2 ) ;
amountNormal - > adjustPalette ( shifterNormal ) ;
amountPositive - > adjustPalette ( shifterPositive ) ;
amountNegative - > adjustPalette ( shifterNegative ) ;
amountEffNeutral - > adjustPalette ( shifterNeutral ) ;
//Restore border color {255, 231, 132, 255} to its original state
amountNormal - > resetPalette ( 26 ) ;
amountPositive - > resetPalette ( 26 ) ;
amountNegative - > resetPalette ( 26 ) ;
amountEffNeutral - > resetPalette ( 26 ) ;
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 )
{
2022-12-14 12:04:37 +02:00
stackAdded ( s , true ) ;
2022-11-20 19:11:34 +02:00
}
}
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 ( ) ;
2022-12-15 23:24:03 +02:00
if ( stack - > hasBonusOfType ( Bonus : : FLYING ) & & stackAnimation . at ( stack - > ID ) - > getType ( ) = = ECreatureAnimType : : MOVING )
2022-12-02 17:49:38 +02:00
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
2022-12-12 22:04:25 +02:00
if ( StackMoveAnimation * move = dynamic_cast < StackMoveAnimation * > ( anim ) )
2022-11-20 19:11:34 +02:00
{
2022-12-02 17:49:38 +02:00
if ( move - > stack = = stack )
2022-12-16 22:07:46 +02:00
return std : : max ( move - > prevHex , move - > nextHex ) ;
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-15 23:24:03 +02:00
assert ( owner . getAnimationCondition ( EAnimationEvents : : ACTION ) = = false ) ;
2022-12-11 12:29:11 +02:00
//reset orientation?
//stackFacingRight[stack->ID] = stack->side == BattleSide::ATTACKER;
2022-12-09 13:10:35 +02:00
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
{
2022-12-11 12:29:11 +02:00
owner . executeOnAnimationCondition ( EAnimationEvents : : HIT , true , [ = ] ( )
{
2022-12-12 22:04:25 +02:00
addNewAnim ( new ResurrectionAnimation ( owner , stack ) ) ;
2022-12-11 12:29:11 +02:00
} ) ;
2022-12-08 20:43:51 +02:00
}
2022-11-20 19:11:34 +02:00
}
2022-12-14 12:04:37 +02:00
void BattleStacksController : : stackAdded ( const CStack * stack , bool instant )
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-12-14 12:04:37 +02:00
if ( ! instant )
{
2022-12-15 23:24:03 +02:00
// immediately make stack transparent, giving correct shifter time to start
auto shifterFade = ColorFilter : : genAlphaShifter ( 0 ) ;
setStackColorFilter ( shifterFade , stack , nullptr , true ) ;
2022-12-14 12:04:37 +02:00
owner . executeOnAnimationCondition ( EAnimationEvents : : HIT , true , [ = ] ( )
{
2022-12-15 23:24:03 +02:00
addNewAnim ( ColorTransformAnimation : : fadeInAnimation ( owner , stack ) ) ;
if ( stack - > isClone ( ) )
addNewAnim ( ColorTransformAnimation : : cloneAnimation ( owner , stack , SpellID ( SpellID : : CLONE ) . toSpell ( ) ) ) ;
2022-12-14 12:04:37 +02:00
} ) ;
}
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 ;
2022-12-16 18:34:35 +02:00
if ( mouseHoveredStack & & ! mouseHoveredStack - > isFrozen ( ) )
2022-11-20 19:11:34 +02:00
{
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-12-12 22:04:25 +02:00
auto hitAnimation = dynamic_cast < DefenceAnimation * > ( 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-12-15 23:24:03 +02:00
ColorFilter fullFilter = ColorFilter : : genEmptyShifter ( ) ;
for ( auto const & filter : stackFilterEffects )
{
if ( filter . target = = stack )
fullFilter = ColorFilter : : genCombined ( fullFilter , filter . effect ) ;
}
bool stackHasProjectile = owner . projectilesController - > hasActiveProjectile ( stack , true ) ;
if ( stackHasProjectile )
stackAnimation [ stack - > ID ] - > pause ( ) ;
else
stackAnimation [ stack - > ID ] - > play ( ) ;
stackAnimation [ stack - > ID ] - > nextFrame ( canvas , fullFilter , facingRight ( stack ) ) ; // do actual blit
2022-11-28 16:02:46 +02:00
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-12-10 00:25:11 +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-10 00:25:11 +02:00
owner . setAnimationCondition ( EAnimationEvents : : ACTION , false ) ;
2022-11-20 19:11:34 +02:00
}
}
2022-12-12 22:04:25 +02:00
void BattleStacksController : : addNewAnim ( BattleAnimation * anim )
2022-11-20 19:11:34 +02:00
{
2022-11-28 16:02:46 +02:00
currentAnimations . push_back ( anim ) ;
2022-12-10 00:25:11 +02:00
owner . setAnimationCondition ( EAnimationEvents : : ACTION , true ) ;
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
{
2022-12-15 23:24:03 +02:00
owner . executeOnAnimationCondition ( EAnimationEvents : : HIT , true , [ = ] ( ) {
// remove any potentially erased petrification effect
removeExpiredColorFilters ( ) ;
} ) ;
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 )
2022-12-11 12:29:11 +02:00
{
owner . executeOnAnimationCondition ( EAnimationEvents : : MOVEMENT , true , [ = ] ( )
{
2022-12-12 22:04:25 +02:00
addNewAnim ( new ReverseAnimation ( owner , attackedInfo . defender , attackedInfo . defender - > getPosition ( ) ) ) ;
2022-12-11 12:29:11 +02:00
} ) ;
}
2022-12-09 13:10:35 +02:00
}
for ( auto & attackedInfo : attackedInfos )
{
2022-12-12 22:04:25 +02:00
bool useDeathAnim = attackedInfo . killed ;
2022-12-10 00:25:11 +02:00
bool useDefenceAnim = attackedInfo . defender - > defendingAnim & & ! attackedInfo . indirectAttack & & ! attackedInfo . killed ;
2022-12-09 13:10:35 +02:00
2022-12-10 00:25:11 +02:00
EAnimationEvents usedEvent = useDefenceAnim ? EAnimationEvents : : ATTACK : EAnimationEvents : : HIT ;
2022-12-09 13:10:35 +02:00
2022-12-10 00:25:11 +02:00
owner . executeOnAnimationCondition ( usedEvent , true , [ = ] ( )
{
2022-12-12 22:04:25 +02:00
if ( useDeathAnim )
addNewAnim ( new DeathAnimation ( owner , attackedInfo . defender , attackedInfo . indirectAttack ) ) ;
else if ( useDefenceAnim )
addNewAnim ( new DefenceAnimation ( owner , attackedInfo . defender ) ) ;
else
addNewAnim ( new HittedAnimation ( owner , attackedInfo . defender ) ) ;
2022-12-10 00:25:11 +02:00
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-10 00:25:11 +02:00
2022-11-20 19:11:34 +02:00
for ( auto & attackedInfo : attackedInfos )
{
if ( attackedInfo . rebirth )
2022-12-09 13:10:35 +02:00
{
2022-12-11 12:29:11 +02:00
owner . executeOnAnimationCondition ( EAnimationEvents : : AFTER_HIT , true , [ = ] ( ) {
owner . effectsController - > displayEffect ( EBattleEffect : : RESURRECT , soundBase : : RESURECT , attackedInfo . defender - > getPosition ( ) ) ;
2022-12-12 22:04:25 +02:00
addNewAnim ( new ResurrectionAnimation ( owner , attackedInfo . defender ) ) ;
2022-12-11 12:29:11 +02:00
} ) ;
2022-12-09 13:10:35 +02:00
}
2022-12-15 23:24:03 +02:00
if ( attackedInfo . killed & & attackedInfo . defender - > summoned )
2022-12-11 12:29:11 +02:00
{
owner . executeOnAnimationCondition ( EAnimationEvents : : AFTER_HIT , true , [ = ] ( ) {
2022-12-15 23:24:03 +02:00
addNewAnim ( ColorTransformAnimation : : fadeOutAnimation ( owner , attackedInfo . defender ) ) ;
2022-12-11 12:29:11 +02:00
stackRemoved ( attackedInfo . defender - > ID ) ;
} ) ;
}
2022-11-20 19:11:34 +02:00
}
2022-12-11 12:29:11 +02:00
executeAttackAnimations ( ) ;
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 ) ;
2022-12-10 00:25:11 +02:00
assert ( owner . getAnimationCondition ( EAnimationEvents : : ACTION ) = = false ) ;
2022-12-09 13:10:35 +02:00
if ( shouldRotate ( stack , stack - > getPosition ( ) , destHex [ 0 ] ) )
2022-12-12 22:04:25 +02:00
addNewAnim ( new ReverseAnimation ( owner , stack , destHex [ 0 ] ) ) ;
2022-12-10 00:25:11 +02:00
2022-12-12 22:04:25 +02:00
addNewAnim ( new MovementStartAnimation ( owner , stack ) ) ;
2022-12-10 00:25:11 +02:00
owner . waitForAnimationCondition ( EAnimationEvents : : ACTION , false ) ;
2022-12-09 13:10:35 +02:00
2022-12-12 22:04:25 +02:00
addNewAnim ( new MovementAnimation ( owner , stack , destHex , distance ) ) ;
2022-12-10 00:25:11 +02:00
owner . waitForAnimationCondition ( EAnimationEvents : : ACTION , false ) ;
2022-12-09 13:10:35 +02:00
2022-12-12 22:04:25 +02:00
addNewAnim ( new MovementEndAnimation ( owner , stack , destHex . back ( ) ) ) ;
2022-12-10 00:25:11 +02:00
owner . waitForAnimationCondition ( EAnimationEvents : : ACTION , false ) ;
2022-11-20 19:11:34 +02:00
}
2022-12-11 12:29:11 +02:00
void BattleStacksController : : stackAttacking ( const StackAttackInfo & info )
2022-11-20 19:11:34 +02:00
{
2022-12-10 00:25:11 +02:00
assert ( owner . getAnimationCondition ( EAnimationEvents : : ACTION ) = = false ) ;
2022-12-09 13:10:35 +02:00
bool needsReverse =
owner . curInt - > cb - > isToReverse (
2022-12-11 12:29:11 +02:00
info . attacker - > getPosition ( ) ,
info . defender - > getPosition ( ) ,
facingRight ( info . attacker ) ,
info . attacker - > doubleWide ( ) ,
facingRight ( info . defender ) ) ;
2022-12-09 13:10:35 +02:00
2022-12-12 19:52:56 +02:00
auto attacker = info . attacker ;
auto defender = info . defender ;
auto tile = info . tile ;
auto spellEffect = info . spellEffect ;
2022-12-14 14:21:58 +02:00
auto multiAttack = ! info . secondaryDefender . empty ( ) ;
2022-12-12 19:52:56 +02:00
2022-12-09 13:10:35 +02:00
if ( needsReverse )
2022-11-20 19:11:34 +02:00
{
2022-12-10 00:25:11 +02:00
owner . executeOnAnimationCondition ( EAnimationEvents : : MOVEMENT , true , [ = ] ( )
{
2022-12-12 22:04:25 +02:00
addNewAnim ( new ReverseAnimation ( owner , attacker , attacker - > getPosition ( ) ) ) ;
2022-12-10 00:25:11 +02:00
} ) ;
2022-11-20 19:11:34 +02:00
}
2022-12-10 00:25:11 +02:00
2022-12-11 12:29:11 +02:00
if ( info . lucky )
{
owner . executeOnAnimationCondition ( EAnimationEvents : : BEFORE_HIT , true , [ = ] ( ) {
owner . controlPanel - > console - > addText ( info . attacker - > formatGeneralMessage ( - 45 ) ) ;
2022-12-12 19:52:56 +02:00
owner . effectsController - > displayEffect ( EBattleEffect : : GOOD_LUCK , soundBase : : GOODLUCK , attacker - > getPosition ( ) ) ;
2022-12-11 12:29:11 +02:00
} ) ;
}
if ( info . unlucky )
{
owner . executeOnAnimationCondition ( EAnimationEvents : : BEFORE_HIT , true , [ = ] ( ) {
owner . controlPanel - > console - > addText ( info . attacker - > formatGeneralMessage ( - 44 ) ) ;
2022-12-12 19:52:56 +02:00
owner . effectsController - > displayEffect ( EBattleEffect : : BAD_LUCK , soundBase : : BADLUCK , attacker - > getPosition ( ) ) ;
2022-12-11 12:29:11 +02:00
} ) ;
}
if ( info . deathBlow )
{
owner . executeOnAnimationCondition ( EAnimationEvents : : BEFORE_HIT , true , [ = ] ( ) {
owner . controlPanel - > console - > addText ( info . attacker - > formatGeneralMessage ( 365 ) ) ;
2022-12-12 19:52:56 +02:00
owner . effectsController - > displayEffect ( EBattleEffect : : DEATH_BLOW , soundBase : : deathBlow , defender - > getPosition ( ) ) ;
2022-12-11 12:29:11 +02:00
} ) ;
2022-12-12 19:52:56 +02:00
for ( auto elem : info . secondaryDefender )
2022-12-11 12:29:11 +02:00
{
owner . executeOnAnimationCondition ( EAnimationEvents : : BEFORE_HIT , true , [ = ] ( ) {
owner . effectsController - > displayEffect ( EBattleEffect : : DEATH_BLOW , elem - > getPosition ( ) ) ;
} ) ;
}
}
2022-12-10 00:25:11 +02:00
owner . executeOnAnimationCondition ( EAnimationEvents : : ATTACK , true , [ = ] ( )
2022-11-20 19:11:34 +02:00
{
2022-12-11 12:29:11 +02:00
if ( info . indirectAttack )
2022-12-10 00:25:11 +02:00
{
2022-12-12 22:04:25 +02:00
addNewAnim ( new ShootingAnimation ( owner , attacker , tile , defender ) ) ;
2022-12-10 00:25:11 +02:00
}
else
{
2022-12-14 14:21:58 +02:00
addNewAnim ( new MeleeAttackAnimation ( owner , attacker , tile , defender , multiAttack ) ) ;
2022-12-10 00:25:11 +02:00
}
} ) ;
2022-12-09 13:10:35 +02:00
2022-12-15 23:24:03 +02:00
if ( info . spellEffect ! = SpellID : : NONE )
2022-12-11 12:29:11 +02:00
{
owner . executeOnAnimationCondition ( EAnimationEvents : : HIT , true , [ = ] ( )
{
2022-12-12 19:52:56 +02:00
owner . displaySpellHit ( spellEffect , tile ) ;
2022-12-11 12:29:11 +02:00
} ) ;
}
if ( info . lifeDrain )
{
owner . executeOnAnimationCondition ( EAnimationEvents : : AFTER_HIT , true , [ = ] ( )
{
2022-12-12 19:52:56 +02:00
owner . effectsController - > displayEffect ( EBattleEffect : : DRAIN_LIFE , soundBase : : DRAINLIF , attacker - > getPosition ( ) ) ;
2022-12-11 12:29:11 +02:00
} ) ;
}
//return, animation playback will be handled by stacksAreAttacked
}
void BattleStacksController : : executeAttackAnimations ( )
{
owner . setAnimationCondition ( EAnimationEvents : : MOVEMENT , true ) ;
owner . waitForAnimationCondition ( EAnimationEvents : : ACTION , false ) ;
owner . setAnimationCondition ( EAnimationEvents : : MOVEMENT , false ) ;
owner . setAnimationCondition ( EAnimationEvents : : BEFORE_HIT , true ) ;
owner . waitForAnimationCondition ( EAnimationEvents : : ACTION , false ) ;
owner . setAnimationCondition ( EAnimationEvents : : BEFORE_HIT , false ) ;
owner . setAnimationCondition ( EAnimationEvents : : ATTACK , true ) ;
owner . waitForAnimationCondition ( EAnimationEvents : : ACTION , false ) ;
owner . setAnimationCondition ( EAnimationEvents : : ATTACK , false ) ;
// Note that HIT event can also be emitted by attack animation
owner . setAnimationCondition ( EAnimationEvents : : HIT , true ) ;
owner . waitForAnimationCondition ( EAnimationEvents : : ACTION , false ) ;
owner . setAnimationCondition ( EAnimationEvents : : HIT , false ) ;
owner . setAnimationCondition ( EAnimationEvents : : AFTER_HIT , true ) ;
owner . waitForAnimationCondition ( EAnimationEvents : : ACTION , false ) ;
owner . setAnimationCondition ( EAnimationEvents : : AFTER_HIT , false ) ;
owner . waitForAnimationCondition ( EAnimationEvents : : ACTION , false ) ;
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-10 00:25:11 +02:00
assert ( owner . getAnimationCondition ( EAnimationEvents : : ACTION ) = = false ) ;
2022-12-09 13:10:35 +02:00
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-12 22:04:25 +02:00
addNewAnim ( new ReverseAnimation ( owner , s , s - > getPosition ( ) ) ) ;
2022-11-20 19:11:34 +02:00
}
}
2022-12-10 00:25:11 +02:00
owner . waitForAnimationCondition ( EAnimationEvents : : ACTION , false ) ;
2022-12-15 23:24:03 +02:00
//Ensure that all animation flags were reset
2022-12-10 00:25:11 +02:00
assert ( owner . getAnimationCondition ( EAnimationEvents : : OPENING ) = = false ) ;
assert ( owner . getAnimationCondition ( EAnimationEvents : : ACTION ) = = false ) ;
assert ( owner . getAnimationCondition ( EAnimationEvents : : MOVEMENT ) = = false ) ;
assert ( owner . getAnimationCondition ( EAnimationEvents : : ATTACK ) = = false ) ;
assert ( owner . getAnimationCondition ( EAnimationEvents : : HIT ) = = false ) ;
assert ( owner . getAnimationCondition ( EAnimationEvents : : PROJECTILES ) = = false ) ;
2022-12-12 21:17:36 +02:00
owner . controlPanel - > blockUI ( activeStack = = nullptr ) ;
2022-12-15 23:24:03 +02:00
removeExpiredColorFilters ( ) ;
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-15 23:24:03 +02:00
removeExpiredColorFilters ( ) ;
}
void BattleStacksController : : stackActivated ( const CStack * stack ) //TODO: check it all before game state is changed due to abilities
{
stackToActivate = stack ;
owner . waitForAnimationCondition ( EAnimationEvents : : ACTION , false ) ;
owner . activateStack ( ) ;
2022-11-20 19:11:34 +02:00
}
2022-12-15 23:24:03 +02:00
void BattleStacksController : : activateStack ( ) //TODO: check it all before game state is changed due to abilities
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-12-15 23:24:03 +02:00
}
void BattleStacksController : : setStackColorFilter ( const ColorFilter & effect , const CStack * target , const CSpell * source , bool persistent )
{
for ( auto & filter : stackFilterEffects )
{
if ( filter . target = = target & & filter . source = = source )
{
filter . effect = effect ;
filter . persistent = persistent ;
return ;
}
}
stackFilterEffects . push_back ( { effect , target , source , persistent } ) ;
}
2022-11-27 02:26:02 +02:00
2022-12-15 23:24:03 +02:00
void BattleStacksController : : removeExpiredColorFilters ( )
{
vstd : : erase_if ( stackFilterEffects , [ & ] ( const BattleStackFilterEffect & filter )
{
if ( filter . persistent )
return false ;
if ( filter . effect = = ColorFilter : : genEmptyShifter ( ) )
return false ;
if ( filter . target - > hasBonus ( Selector : : source ( Bonus : : SPELL_EFFECT , filter . source - > id ) ) )
return false ;
return true ;
} ) ;
2022-11-27 02:26:02 +02:00
}