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"
2022-12-17 00:10:12 +02:00
# include "BattleActionsController.h"
2022-12-09 13:38:46 +02:00
# include "BattleAnimationClasses.h"
# include "BattleFieldController.h"
# include "BattleEffectsController.h"
# include "BattleProjectileController.h"
2022-12-21 17:06:47 +02:00
# include "BattleWindow.h"
2022-12-09 13:38:46 +02:00
# 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"
2023-01-17 22:01:35 +02:00
# include "../gui/SDL_Extensions.h"
2022-12-17 00:10:12 +02:00
# include "../../lib/spells/ISpellMechanics.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 ( ) )
{
const CCreature * creature = stack - > getCreature ( ) ;
2023-01-08 16:25:47 +02:00
if ( stack - > isFrozen ( ) )
animation - > setType ( ECreatureAnimType : : FROZEN ) ;
else
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 ) ,
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
2023-01-15 23:55:24 +02:00
static const auto shifterNormal = ColorFilter : : genRangeShifter ( 0.f , 0.f , 0.f , 0.6f , 0.2f , 1.0f ) ;
static const auto shifterPositive = ColorFilter : : genRangeShifter ( 0.f , 0.f , 0.f , 0.2f , 1.0f , 0.2f ) ;
static const auto shifterNegative = ColorFilter : : genRangeShifter ( 0.f , 0.f , 0.f , 1.0f , 0.2f , 0.2f ) ;
static const auto shifterNeutral = ColorFilter : : genRangeShifter ( 0.f , 0.f , 0.f , 1.0f , 1.0f , 0.2f ) ;
2022-12-15 23:24:03 +02:00
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
2022-12-17 19:37:00 +02:00
static const int turretCreatureAnimationHeight = 225 ;
2022-12-13 15:10:31 +02:00
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-21 23:29:56 +02:00
addNewAnim ( new ColorTransformAnimation ( owner , stack , " summonFadeIn " , nullptr ) ) ;
2022-12-15 23:24:03 +02:00
if ( stack - > isClone ( ) )
2023-01-04 19:36:18 +02:00
addNewAnim ( new ColorTransformAnimation ( owner , stack , " cloning " , 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-21 17:02:53 +02:00
owner . windowObject - > blockUI ( activeStack = = nullptr ) ;
2022-11-20 19:11:34 +02:00
}
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-12-22 11:49:40 +02:00
//do not show box for singular war machines, stacked war machines with box shown are supported as extension feature
if ( stack - > hasBonusOfType ( Bonus : : SIEGE_WEAPON ) & & stack - > getCount ( ) = = 1 )
2022-11-25 00:26:14 +02:00
return false ;
2022-11-20 19:11:34 +02:00
2022-11-28 22:47:28 +02:00
if ( ! stack - > alive ( ) )
2022-11-25 00:26:14 +02:00
return false ;
2022-12-22 11:49:40 +02:00
//hide box when target is going to die anyway - do not display "0 creatures"
if ( stack - > getCount ( ) = = 0 )
2022-11-25 00:26:14 +02:00
return false ;
2022-12-22 11:49:40 +02:00
// if stack has any ongoing animation - hide the box
for ( auto anim : currentAnimations )
2022-11-25 00:26:14 +02:00
{
2022-12-22 11:49:40 +02:00
auto stackAnimation = dynamic_cast < BattleStackAnimation * > ( anim ) ;
if ( stackAnimation & & ( stackAnimation - > 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-11-25 00:26:14 +02:00
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
2023-01-17 22:01:35 +02:00
canvas . drawText ( textPos , EFonts : : FONT_TINY , Colors : : WHITE , ETextAlignment : : CENTER , CSDL_Ext : : 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-17 00:10:12 +02:00
void BattleStacksController : : update ( )
{
updateHoveredStacks ( ) ;
updateBattleAnimations ( ) ;
}
2023-01-04 23:04:48 +02:00
void BattleStacksController : : initializeBattleAnimations ( )
{
auto copiedVector = currentAnimations ;
for ( auto & elem : copiedVector )
if ( elem & & ! elem - > isInitialized ( ) )
elem - > tryInitialize ( ) ;
}
void BattleStacksController : : stepFrameBattleAnimations ( )
2022-11-20 19:11:34 +02:00
{
2022-12-29 14:28:38 +02:00
// operate on copy - to prevent potential iterator invalidation due to push_back's
// FIXME? : remove remaining calls to addNewAnim from BattleAnimation::nextFrame (only Catapult explosion at the time of writing)
auto copiedVector = currentAnimations ;
for ( auto & elem : copiedVector )
2023-01-04 18:15:26 +02:00
if ( elem & & elem - > isInitialized ( ) )
2022-11-28 16:02:46 +02:00
elem - > nextFrame ( ) ;
2023-01-04 23:04:48 +02:00
}
2023-01-04 18:15:26 +02:00
2023-01-04 23:04:48 +02:00
void BattleStacksController : : updateBattleAnimations ( )
{
2022-11-28 16:02:46 +02:00
bool hadAnimations = ! currentAnimations . empty ( ) ;
2023-01-04 23:04:48 +02:00
initializeBattleAnimations ( ) ;
stepFrameBattleAnimations ( ) ;
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-12-10 00:25:11 +02:00
owner . setAnimationCondition ( EAnimationEvents : : ACTION , false ) ;
2023-01-04 23:04:48 +02:00
initializeBattleAnimations ( ) ;
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-18 11:42:02 +02:00
// In H3, attacked stack will not reverse on ranged attack
if ( attackedInfo . indirectAttack )
continue ;
2022-12-21 18:04:54 +02:00
// Another type of indirect attack - dragon breath
if ( ! CStack : : isMeleeAttackPossible ( attackedInfo . attacker , attackedInfo . defender ) )
continue ;
2022-12-18 11:42:02 +02:00
// defender need to face in direction opposited to out attacker
bool needsReverse = shouldAttackFacingRight ( attackedInfo . attacker , attackedInfo . defender ) = = facingRight ( attackedInfo . defender ) ;
2023-01-08 16:25:47 +02:00
// FIXME: this check is better, however not usable since stacksAreAttacked is called after net pack is applyed - petrification is already removed
// if (needsReverse && !attackedInfo.defender->isFrozen())
if ( needsReverse & & stackAnimation [ attackedInfo . defender - > ID ] - > getType ( ) ! = ECreatureAnimType : : FROZEN )
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
2022-12-17 17:35:15 +02:00
if ( attackedInfo . fireShield )
2022-12-25 20:11:22 +02:00
owner . effectsController - > displayEffect ( EBattleEffect : : FIRE_SHIELD , " FIRESHIE " , attackedInfo . attacker - > getPosition ( ) ) ;
2022-12-10 00:25:11 +02:00
if ( attackedInfo . spellEffect ! = SpellID : : NONE )
2023-01-04 21:55:15 +02:00
{
auto spell = attackedInfo . spellEffect . toSpell ( ) ;
if ( ! spell - > getCastSound ( ) . empty ( ) )
CCS - > soundh - > playSound ( spell - > getCastSound ( ) ) ;
owner . displaySpellEffect ( spell , attackedInfo . defender - > 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-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 , [ = ] ( ) {
2022-12-25 20:11:22 +02:00
owner . effectsController - > displayEffect ( EBattleEffect : : RESURRECT , " 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-21 23:29:56 +02:00
addNewAnim ( new ColorTransformAnimation ( owner , attackedInfo . defender , " summonFadeOut " , nullptr ) ) ;
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-18 18:26:43 +02:00
void BattleStacksController : : stackTeleported ( const CStack * stack , std : : vector < BattleHex > destHex , int distance )
{
assert ( destHex . size ( ) > 0 ) ;
assert ( owner . getAnimationCondition ( EAnimationEvents : : ACTION ) = = false ) ;
owner . executeOnAnimationCondition ( EAnimationEvents : : HIT , true , [ = ] ( ) {
2022-12-21 23:29:56 +02:00
addNewAnim ( new ColorTransformAnimation ( owner , stack , " teleportFadeOut " , nullptr ) ) ;
2022-12-18 18:26:43 +02:00
} ) ;
owner . executeOnAnimationCondition ( EAnimationEvents : : AFTER_HIT , true , [ = ] ( ) {
stackAnimation [ stack - > ID ] - > pos . moveTo ( getStackPositionAtHex ( destHex . back ( ) , stack ) ) ;
2022-12-21 23:29:56 +02:00
addNewAnim ( new ColorTransformAnimation ( owner , stack , " teleportFadeIn " , nullptr ) ) ;
2022-12-18 18:26:43 +02:00
} ) ;
// animations will be executed by spell
}
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
2023-01-04 23:04:48 +02:00
bool stackTeleports = stack - > hasBonus ( Selector : : typeSubtype ( Bonus : : FLYING , 1 ) ) ;
owner . setAnimationCondition ( EAnimationEvents : : MOVEMENT , true ) ;
2022-12-10 00:25:11 +02:00
2023-01-04 23:04:48 +02:00
auto enqueMoveEnd = [ & ] ( ) {
addNewAnim ( new MovementEndAnimation ( owner , stack , destHex . back ( ) ) ) ;
owner . executeOnAnimationCondition ( EAnimationEvents : : ACTION , false , [ & ] ( ) {
owner . setAnimationCondition ( EAnimationEvents : : MOVEMENT , false ) ;
} ) ;
} ;
2022-12-09 13:10:35 +02:00
2023-01-04 23:04:48 +02:00
auto enqueMove = [ & ] ( ) {
if ( ! stackTeleports )
{
addNewAnim ( new MovementAnimation ( owner , stack , destHex , distance ) ) ;
owner . executeOnAnimationCondition ( EAnimationEvents : : ACTION , false , enqueMoveEnd ) ;
}
else
enqueMoveEnd ( ) ;
} ;
auto enqueMoveStart = [ & ] ( ) {
addNewAnim ( new MovementStartAnimation ( owner , stack ) ) ;
owner . executeOnAnimationCondition ( EAnimationEvents : : ACTION , false , enqueMove ) ;
} ;
if ( shouldRotate ( stack , stack - > getPosition ( ) , destHex [ 0 ] ) )
2022-12-18 18:26:43 +02:00
{
2023-01-04 23:04:48 +02:00
addNewAnim ( new ReverseAnimation ( owner , stack , stack - > getPosition ( ) ) ) ;
owner . executeOnAnimationCondition ( EAnimationEvents : : ACTION , false , enqueMoveStart ) ;
2022-12-18 18:26:43 +02:00
}
2023-01-04 23:04:48 +02:00
else
enqueMoveStart ( ) ;
2022-12-09 13:10:35 +02:00
2023-01-04 23:04:48 +02:00
owner . waitForAnimationCondition ( EAnimationEvents : : MOVEMENT , false ) ;
2022-11-20 19:11:34 +02:00
}
2022-12-18 11:42:02 +02:00
bool BattleStacksController : : shouldAttackFacingRight ( const CStack * attacker , const CStack * defender )
{
bool mustReverse = owner . curInt - > cb - > isToReverse (
attacker ,
defender ) ;
if ( attacker - > side = = BattleSide : : ATTACKER )
return ! mustReverse ;
else
return mustReverse ;
}
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-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-18 11:42:02 +02:00
bool needsReverse = false ;
if ( info . indirectAttack )
{
needsReverse = shouldRotate ( attacker , attacker - > position , info . tile ) ;
}
else
{
needsReverse = shouldAttackFacingRight ( attacker , defender ) ! = facingRight ( attacker ) ;
}
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 , [ = ] ( ) {
2022-12-21 17:02:53 +02:00
owner . appendBattleLog ( info . attacker - > formatGeneralMessage ( - 45 ) ) ;
2022-12-25 20:11:22 +02:00
owner . effectsController - > displayEffect ( EBattleEffect : : GOOD_LUCK , " GOODLUCK " , attacker - > getPosition ( ) ) ;
2022-12-11 12:29:11 +02:00
} ) ;
}
if ( info . unlucky )
{
owner . executeOnAnimationCondition ( EAnimationEvents : : BEFORE_HIT , true , [ = ] ( ) {
2022-12-21 17:02:53 +02:00
owner . appendBattleLog ( info . attacker - > formatGeneralMessage ( - 44 ) ) ;
2022-12-25 20:11:22 +02:00
owner . effectsController - > displayEffect ( EBattleEffect : : BAD_LUCK , " BADLUCK " , attacker - > getPosition ( ) ) ;
2022-12-11 12:29:11 +02:00
} ) ;
}
if ( info . deathBlow )
{
owner . executeOnAnimationCondition ( EAnimationEvents : : BEFORE_HIT , true , [ = ] ( ) {
2022-12-21 17:02:53 +02:00
owner . appendBattleLog ( info . attacker - > formatGeneralMessage ( 365 ) ) ;
2022-12-25 20:11:22 +02:00
owner . effectsController - > displayEffect ( EBattleEffect : : DEATH_BLOW , " DEATHBLO " , 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-22 00:25:35 +02:00
owner . displaySpellHit ( spellEffect . toSpell ( ) , tile ) ;
2022-12-11 12:29:11 +02:00
} ) ;
}
if ( info . lifeDrain )
{
owner . executeOnAnimationCondition ( EAnimationEvents : : AFTER_HIT , true , [ = ] ( )
{
2022-12-25 20:11:22 +02:00
owner . effectsController - > displayEffect ( EBattleEffect : : DRAIN_LIFE , " 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 ) ;
2022-12-12 21:17:36 +02:00
2022-12-21 17:02:53 +02:00
owner . windowObject - > 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
{
2022-12-15 23:24:03 +02:00
removeExpiredColorFilters ( ) ;
}
2022-12-25 17:43:55 +02:00
void BattleStacksController : : stackActivated ( const CStack * stack )
2022-12-15 23:24:03 +02:00
{
stackToActivate = stack ;
owner . waitForAnimationCondition ( EAnimationEvents : : ACTION , false ) ;
owner . activateStack ( ) ;
2022-11-20 19:11:34 +02:00
}
2022-12-25 17:43:55 +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
2023-01-05 14:16:01 +02:00
return ret ;
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 )
{
2023-01-08 16:25:47 +02:00
if ( ! filter . persistent )
{
if ( filter . source & & ! filter . target - > hasBonus ( Selector : : source ( Bonus : : SPELL_EFFECT , filter . source - > id ) , Selector : : all ) )
return true ;
if ( filter . effect = = ColorFilter : : genEmptyShifter ( ) )
return true ;
}
return false ;
2022-12-15 23:24:03 +02:00
} ) ;
2022-11-27 02:26:02 +02:00
}
2022-12-17 00:10:12 +02:00
void BattleStacksController : : updateHoveredStacks ( )
{
auto newStacks = selectHoveredStacks ( ) ;
for ( auto const * stack : mouseHoveredStacks )
{
if ( vstd : : contains ( newStacks , stack ) )
continue ;
if ( stack = = activeStack )
stackAnimation [ stack - > ID ] - > setBorderColor ( AnimationControls : : getGoldBorder ( ) ) ;
else
stackAnimation [ stack - > ID ] - > setBorderColor ( AnimationControls : : getNoBorder ( ) ) ;
}
for ( auto const * stack : newStacks )
{
if ( vstd : : contains ( mouseHoveredStacks , stack ) )
continue ;
stackAnimation [ stack - > ID ] - > setBorderColor ( AnimationControls : : getBlueBorder ( ) ) ;
2023-01-04 17:23:13 +02:00
if ( stackAnimation [ stack - > ID ] - > framesInGroup ( ECreatureAnimType : : MOUSEON ) > 0 & & stack - > alive ( ) & & ! stack - > isFrozen ( ) )
2022-12-17 00:10:12 +02:00
stackAnimation [ stack - > ID ] - > playOnce ( ECreatureAnimType : : MOUSEON ) ;
}
mouseHoveredStacks = newStacks ;
}
std : : vector < const CStack * > BattleStacksController : : selectHoveredStacks ( )
{
2022-12-19 01:12:26 +02:00
// only allow during our turn - do not try to highlight creatures while they are in the middle of actions
if ( ! activeStack )
return { } ;
2023-01-04 17:45:08 +02:00
if ( owner . getAnimationCondition ( EAnimationEvents : : ACTION ) = = true )
return { } ;
2023-01-22 21:03:11 +02:00
auto hoveredQueueUnitId = owner . windowObject - > getQueueHoveredUnitId ( ) ;
2023-01-23 13:03:07 +02:00
if ( hoveredQueueUnitId . is_initialized ( ) )
2023-01-22 21:03:11 +02:00
{
return { owner . curInt - > cb - > battleGetStackByUnitId ( hoveredQueueUnitId . value ( ) , true ) } ;
}
2022-12-17 00:10:12 +02:00
auto hoveredHex = owner . fieldController - > getHoveredHex ( ) ;
if ( ! hoveredHex . isValid ( ) )
return { } ;
const spells : : Caster * caster = nullptr ;
const CSpell * spell = nullptr ;
spells : : Mode mode = spells : : Mode : : HERO ;
if ( owner . actionsController - > spellcastingModeActive ( ) ) //hero casts spell
{
spell = owner . actionsController - > selectedSpell ( ) . toSpell ( ) ;
caster = owner . getActiveHero ( ) ;
}
else if ( owner . stacksController - > activeStackSpellToCast ( ) ! = SpellID : : NONE ) //stack casts spell
{
spell = SpellID ( owner . stacksController - > activeStackSpellToCast ( ) ) . toSpell ( ) ;
caster = owner . stacksController - > getActiveStack ( ) ;
mode = spells : : Mode : : CREATURE_ACTIVE ;
}
if ( caster & & spell ) //when casting spell
{
spells : : Target target ;
target . emplace_back ( hoveredHex ) ;
spells : : BattleCast event ( owner . curInt - > cb . get ( ) , caster , mode , spell ) ;
auto mechanics = spell - > battleMechanics ( & event ) ;
return mechanics - > getAffectedStacks ( target ) ;
}
if ( hoveredHex . isValid ( ) )
{
const CStack * const stack = owner . curInt - > cb - > battleGetStackByPos ( hoveredHex , true ) ;
if ( stack )
return { stack } ;
}
return { } ;
}
2023-01-22 21:06:10 +02:00
const std : : vector < uint32_t > BattleStacksController : : getHoveredStacksUnitIds ( ) const
{
auto result = std : : vector < uint32_t > ( ) ;
for ( auto const * stack : mouseHoveredStacks )
{
result . push_back ( stack - > unitId ( ) ) ;
}
return result ;
}