# pragma region License
/*
License ( OLC - 3 )
~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
Copyright 2024 Joshua Sigona < sigonasr2 @ gmail . com >
Redistribution and use in source and binary forms , with or without modification ,
are permitted provided that the following conditions are met :
1. Redistributions or derivations of source code must retain the above copyright
notice , this list of conditions and the following disclaimer .
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice . This list of conditions and the following disclaimer must be
reproduced in the documentation and / or other materials provided with the distribution .
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission .
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS " AS IS " AND ANY
EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED . IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT , INDIRECT ,
INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES ( INCLUDING , BUT NOT LIMITED
TO , PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ; LOSS OF USE , DATA , OR PROFITS ; OR
BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY , WHETHER IN
CONTRACT , STRICT LIABILITY , OR TORT ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE .
Portions of this software are copyright © 2024 The FreeType
Project ( www . freetype . org ) . Please see LICENSE_FT . txt for more information .
All rights reserved .
*/
# pragma endregion
# include "Monster.h"
# include "MonsterStrategyHelpers.h"
# include "DEFINES.h"
# include "AdventuresInLestoria.h"
# include "util.h"
# include "safemap.h"
# include "Effect.h"
# include "FallingDebris.h"
# include "MonsterAttribute.h"
# include "SoundEffect.h"
INCLUDE_game
INCLUDE_BULLET_LIST
INCLUDE_ANIMATION_DATA
INCLUDE_MONSTER_DATA
INCLUDE_GFX
using A = Attribute ;
void Monster : : STRATEGY : : SLIMEKING ( Monster & m , float fElapsedTime , std : : string strategy ) {
float bulletSpd = ConfigFloat ( " BulletSpd " ) / 100 * 24 ;
m . F ( A : : SHOOT_TIMER ) = std : : max ( 0.f , m . F ( A : : SHOOT_TIMER ) - fElapsedTime ) ;
m . F ( A : : SHOOT_RING_TIMER ) = std : : max ( 0.f , m . F ( A : : SHOOT_RING_TIMER ) - fElapsedTime ) ;
m . F ( A : : SHOOT_RING_DELAY ) = std : : max ( 0.f , m . F ( A : : SHOOT_RING_DELAY ) - fElapsedTime ) ;
m . F ( A : : JUMP_LANDING_TIMER ) = std : : max ( 0.f , m . F ( A : : JUMP_LANDING_TIMER ) - fElapsedTime ) ;
m . F ( A : : CASTING_TIMER ) = std : : max ( 0.f , m . F ( A : : CASTING_TIMER ) - fElapsedTime ) ;
m . F ( A : : RUN_AWAY_TIMER ) = std : : max ( 0.f , m . F ( A : : RUN_AWAY_TIMER ) - fElapsedTime ) ;
const auto ShootBulletRing = [ & ] ( float angleOffset ) {
int bulletCount = ConfigInt ( " Phase1.RingBulletCount " ) ;
for ( int i = 0 ; i < bulletCount ; i + + ) {
float angle = ( ( 2 * PI ) / bulletCount ) * i + angleOffset ;
BULLET_LIST . emplace_back ( std : : make_unique < Bullet > ( m . GetPos ( ) , vf2d { cos ( angle ) , sin ( angle ) } * bulletSpd , 6 , ConfigInt ( " ProjectileDamage " ) , m . OnUpperLevel ( ) , false , YELLOW , vf2d { 6 , 6 } ) ) ;
}
SoundEffect : : PlaySFX ( " Slime King Shoot " , m . GetPos ( ) ) ;
} ;
const auto Landed = [ & ShootBulletRing , & m ] ( int currentPhase ) {
if ( currentPhase = = 1 ) {
ShootBulletRing ( m . F ( A : : SHOOT_RING_OFFSET ) ) ;
}
SoundEffect : : PlaySFX ( " Slime King Land " , m . GetPos ( ) ) ;
} ;
const auto TransitionPhase = [ & ] ( int newPhase ) {
const int MAX_ATTEMPTS = 100 ; //Maximum number of tries to find a valid location.
Player * player = game - > GetPlayer ( ) ;
m . I ( A : : PATTERN_REPEAT_COUNT ) = 0 ;
const auto PositionInRangeOfPlayer = [ & player ] ( vf2d & pos , float radius ) {
return geom2d : : line < float > ( player - > GetPos ( ) , pos ) . length ( ) < = player - > GetSizeMult ( ) * 12 * 2 + radius ;
} ;
const auto SpawnMonsterFromConfig = [ & ] ( int phase ) {
std : : string spawnMonster = ConfigStringArr ( " Phase " + std : : to_string ( phase ) + " .MonsterSpawnOnChange " , 0 ) ;
int spawnCount = ConfigIntArr ( " Phase " + std : : to_string ( phase ) + " .MonsterSpawnOnChange " , 1 ) ;
for ( int i = 0 ; i < spawnCount ; i + + ) {
float randomAngle = util : : random ( 2 * PI ) ;
vf2d spawnPos = m . pos + vf2d { cos ( randomAngle ) , sin ( randomAngle ) } * m . GetSizeMult ( ) * 12 ;
for ( int attempts = 0 ; attempts < MAX_ATTEMPTS & & PositionInRangeOfPlayer ( spawnPos , MONSTER_DATA [ spawnMonster ] . GetSizeMult ( ) * 12 ) ; attempts + + ) {
randomAngle = util : : random ( 2 * PI ) ;
spawnPos = m . pos + vf2d { cos ( randomAngle ) , sin ( randomAngle ) } * m . GetSizeMult ( ) * 12 ;
}
game - > SpawnMonster ( spawnPos , MONSTER_DATA [ spawnMonster ] ) ;
}
} ;
switch ( newPhase ) {
case 2 : {
SpawnMonsterFromConfig ( 2 ) ;
} break ;
case 3 : {
SpawnMonsterFromConfig ( 3 ) ;
} break ;
case 4 : {
SpawnMonsterFromConfig ( 4 ) ;
} break ;
case 5 : {
} break ;
}
} ;
const auto StartJumpTowardsPlayer = [ & ] ( float jumpDuration , float recoveryTime , float jumpMoveSpd ) {
m . F ( A : : JUMP_ORIGINAL_LANDING_TIMER ) = m . F ( A : : JUMP_LANDING_TIMER ) = jumpDuration ;
m . B ( A : : JUMP_TOWARDS_PLAYER ) = true ;
m . F ( A : : RECOVERY_TIME ) = recoveryTime ;
m . F ( A : : JUMP_MOVE_SPD ) = jumpMoveSpd ;
m . SetState ( State : : JUMP ) ;
} ;
const auto StartJump = [ & ] ( float jumpDuration , vf2d targetPos , float recoveryTime , float jumpMoveSpd ) {
m . F ( A : : JUMP_ORIGINAL_LANDING_TIMER ) = m . F ( A : : JUMP_LANDING_TIMER ) = jumpDuration ;
m . V ( A : : JUMP_TARGET_POS ) = targetPos ;
m . B ( A : : JUMP_TOWARDS_PLAYER ) = false ;
m . F ( A : : RECOVERY_TIME ) = recoveryTime ;
m . F ( A : : JUMP_MOVE_SPD ) = jumpMoveSpd ;
m . SetState ( State : : JUMP ) ;
} ;
const auto Recovered = [ & ] ( ) {
switch ( m . phase ) {
case 2 : {
switch ( m . I ( A : : JUMP_COUNT ) ) {
case 1 :
case 2 : { //After the 1st and 2nd jumps we still have another jump to accomplish.
m . I ( A : : JUMP_COUNT ) + + ;
float jumpTime = ConfigFloatArr ( " Phase2.Jump[ " + std : : to_string ( m . I ( A : : JUMP_COUNT ) ) + " ] " , 0 ) ;
float jumpSpd = ConfigFloatArr ( " Phase2.Jump[ " + std : : to_string ( m . I ( A : : JUMP_COUNT ) ) + " ] " , 1 ) ;
StartJumpTowardsPlayer ( jumpTime , 0.2f , jumpSpd ) ;
} break ;
default : {
m . PerformIdleAnimation ( ) ;
m . SetState ( State : : NORMAL ) ;
}
}
} break ;
}
} ;
if ( m . F ( A : : RUN_AWAY_TIMER ) > 0 ) {
Monster : : STRATEGY : : RUN_AWAY ( m , fElapsedTime , " Run Away " ) ;
return ;
}
if ( m . GetState ( ) = = State : : RECOVERY ) {
m . F ( A : : RECOVERY_TIME ) = std : : max ( 0.f , m . F ( A : : RECOVERY_TIME ) - fElapsedTime ) ;
if ( m . F ( A : : RECOVERY_TIME ) = = 0 ) {
m . SetState ( State : : NORMAL ) ;
Recovered ( ) ;
}
return ;
}
if ( m . GetState ( ) = = State : : JUMP ) {
float jumpLandingTimerRatio = m . F ( A : : JUMP_LANDING_TIMER ) / m . F ( A : : JUMP_ORIGINAL_LANDING_TIMER ) ;
vf2d jumpTargetPos = m . V ( A : : JUMP_TARGET_POS ) ;
if ( m . B ( A : : JUMP_TOWARDS_PLAYER ) ) {
jumpTargetPos = game - > GetPlayer ( ) - > GetPos ( ) ;
}
if ( m . GetPos ( ) . x > jumpTargetPos . x ) {
m . SetX ( std : : max ( jumpTargetPos . x , m . GetPos ( ) . x - m . F ( A : : JUMP_MOVE_SPD ) * game - > GetElapsedTime ( ) ) ) ;
} else
if ( m . GetPos ( ) . x < jumpTargetPos . x ) {
m . SetX ( std : : min ( jumpTargetPos . x , m . GetPos ( ) . x + m . F ( A : : JUMP_MOVE_SPD ) * game - > GetElapsedTime ( ) ) ) ;
}
if ( m . GetPos ( ) . y > jumpTargetPos . y ) {
m . SetY ( std : : max ( jumpTargetPos . y , m . GetPos ( ) . y - m . F ( A : : JUMP_MOVE_SPD ) * game - > GetElapsedTime ( ) ) ) ;
} else
if ( m . GetPos ( ) . y < jumpTargetPos . y ) {
m . SetY ( std : : min ( jumpTargetPos . y , m . GetPos ( ) . y + m . F ( A : : JUMP_MOVE_SPD ) * game - > GetElapsedTime ( ) ) ) ;
}
if ( m . F ( A : : JUMP_LANDING_TIMER ) > = m . F ( A : : JUMP_ORIGINAL_LANDING_TIMER ) / 2 ) {
m . SetZ ( util : : lerp ( 0 , float ( ConfigInt ( " JumpHeight " ) ) , 1 - jumpLandingTimerRatio ) ) ;
} else {
m . SetZ ( util : : lerp ( 0 , float ( ConfigInt ( " JumpHeight " ) ) , jumpLandingTimerRatio * 2 ) ) ;
}
if ( m . F ( A : : JUMP_LANDING_TIMER ) = = 0 ) {
m . state = State : : RECOVERY ;
game - > SetupWorldShake ( 0.6f ) ;
geom2d : : line < float > lineToPlayer ( m . GetPos ( ) , game - > GetPlayer ( ) - > GetPos ( ) ) ;
float dist = lineToPlayer . length ( ) ;
for ( int i = 0 ; i < 200 ; i + + ) {
float randomDir = util : : random ( 2 * PI ) ;
game - > AddEffect ( std : : make_unique < FallingDebris > ( m . GetPos ( ) + vf2d { cos ( randomDir ) , sin ( randomDir ) } * m . GetSizeMult ( ) * 8 , util : : random ( 1 ) , " circle.png " , m . OnUpperLevel ( ) , vf2d { 1 , 1 } , 0.5 , vf2d { cos ( randomDir ) * util : : random ( 5 ) , sin ( randomDir ) * - util : : random ( 15 ) - 5 } * 30 , BLACK ) , true ) ;
}
if ( dist < 12 * m . GetSizeMult ( ) ) {
game - > GetPlayer ( ) - > Hurt ( ConfigInt ( " JumpAttackDamage " ) , m . OnUpperLevel ( ) , m . GetZ ( ) ) ;
if ( dist < 0.001 ) {
float randomDir = util : : random ( 2 * PI ) ;
lineToPlayer = { m . GetPos ( ) , m . GetPos ( ) + vf2d { cos ( randomDir ) , sin ( randomDir ) } * 1 } ;
}
game - > GetPlayer ( ) - > Knockback ( lineToPlayer . vector ( ) . norm ( ) * float ( ConfigInt ( " JumpKnockbackFactor " ) ) ) ;
if ( m . phase ! = 2 ) { //In phase 2, the player can get slammed multiple times. No iframes for messing up.
game - > GetPlayer ( ) - > SetIframes ( 1 ) ;
}
}
m . SetZ ( 0 ) ;
Landed ( m . phase ) ;
m . SetStrategyDrawFunction ( [ ] ( AiL * game , Monster & m , const std : : string & strategy ) { } ) ;
} else
if ( m . F ( A : : JUMP_LANDING_TIMER ) < = ConfigFloat ( " JumpWarningIndicatorTime " ) ) {
m . SetStrategyDrawFunction ( [ ] ( AiL * game , Monster & m , const std : : string & strategy ) {
Decal * dec = GFX [ " range_indicator.png " ] . Decal ( ) ;
game - > view . DrawRotatedDecal ( m . GetPos ( ) , dec , 0 , dec - > sprite - > Size ( ) / 2 , vf2d { m . GetSizeMult ( ) , m . GetSizeMult ( ) } , RED ) ;
} ) ;
}
return ;
}
if ( m . GetState ( ) = = State : : CASTING ) {
m . PerformOtherAnimation ( 0 ) ;
if ( m . F ( A : : CASTING_TIMER ) = = 0 ) {
m . SetState ( State : : NORMAL ) ;
m . I ( A : : JUMP_COUNT ) + + ;
float jumpTime = ConfigFloatArr ( " Phase2.Jump[ " + std : : to_string ( m . I ( A : : JUMP_COUNT ) ) + " ] " , 0 ) ;
float jumpSpd = ConfigFloatArr ( " Phase2.Jump[ " + std : : to_string ( m . I ( A : : JUMP_COUNT ) ) + " ] " , 1 ) ;
StartJumpTowardsPlayer ( jumpTime , 0.2f , jumpSpd ) ;
}
return ;
}
switch ( m . phase ) {
case 0 : {
m . size = ConfigInt ( " Phase1.Size " ) / 100.f ;
m . diesNormally = false ;
m . F ( A : : IFRAME_TIME_UPON_HIT ) = 0 ;
m . iframe_timer = ConfigFloat ( " Phase5.IframeTimePerHit " ) ;
m . phase = ConfigInt ( " StartPhase " ) ;
} break ;
case 1 : {
if ( m . GetRemainingHPPct ( ) < = ConfigFloat ( " Phase2.Change " ) / 100.f ) {
m . phase = 2 ;
m . SetSize ( ConfigFloat ( " Phase2.Size " ) / 100 , false ) ;
TransitionPhase ( m . phase ) ;
return ;
}
if ( m . F ( A : : SHOOT_RING_TIMER ) = = 0 ) {
if ( m . I ( A : : PATTERN_REPEAT_COUNT ) > = ConfigInt ( " Phase1.JumpAfter " ) ) {
StartJumpTowardsPlayer ( ConfigFloat ( " Phase1.AirborneTime " ) , ConfigFloat ( " Phase1.LandingRecoveryTime " ) , ConfigFloat ( " JumpMoveSpd " ) ) ;
m . I ( A : : PATTERN_REPEAT_COUNT ) = 0 ;
return ;
}
m . I ( A : : SHOOT_RING_COUNTER ) = ConfigInt ( " Phase1.ShootRingCount " ) - 1 ;
m . F ( A : : SHOOT_RING_DELAY ) = ConfigFloat ( " Phase1.ShootRingDelay " ) ;
ShootBulletRing ( m . F ( A : : SHOOT_RING_OFFSET ) ) ;
m . F ( A : : SHOOT_RING_TIMER ) = ConfigFloat ( " Phase1.ShootRepeatTime " ) ;
m . B ( A : : SHOOT_RING_RIGHT ) = bool ( util : : random ( ) % 2 ) ;
m . I ( A : : PATTERN_REPEAT_COUNT ) + + ;
}
if ( m . I ( A : : SHOOT_RING_COUNTER ) > 0 ) {
if ( m . F ( A : : SHOOT_RING_DELAY ) = = 0 ) {
m . I ( A : : SHOOT_RING_COUNTER ) - - ;
m . F ( A : : SHOOT_RING_DELAY ) = ConfigFloat ( " Phase1.ShootRingDelay " ) ;
if ( m . B ( A : : SHOOT_RING_RIGHT ) ) {
m . F ( A : : SHOOT_RING_OFFSET ) + = util : : degToRad ( ConfigFloat ( " Phase1.RingOffset " ) ) ;
} else {
m . F ( A : : SHOOT_RING_OFFSET ) - = util : : degToRad ( ConfigFloat ( " Phase1.RingOffset " ) ) ;
}
ShootBulletRing ( m . F ( A : : SHOOT_RING_OFFSET ) ) ;
}
}
} break ;
case 2 : {
if ( m . GetRemainingHPPct ( ) < = ConfigFloat ( " Phase3.Change " ) / 100.f ) {
m . phase = 3 ;
m . SetSize ( ConfigFloat ( " Phase3.Size " ) / 100 , false ) ;
if ( m . I ( A : : PATTERN_REPEAT_COUNT ) = = 0 ) {
m . I ( A : : PATTERN_REPEAT_COUNT ) = 1 ;
}
TransitionPhase ( m . phase ) ;
return ;
}
if ( m . F ( A : : SHOOT_TIMER ) = = 0 ) {
m . F ( A : : SHOOT_TIMER ) = float ( ConfigInt ( " Phase2.ShootRate " ) ) ;
m . I ( A : : PATTERN_REPEAT_COUNT ) + + ;
int bulletCount = ConfigInt ( " Phase2.ShootProjectileCount " ) ;
for ( int i = 0 ; i < bulletCount ; i + + ) {
float initialAngle = util : : angleTo ( m . GetPos ( ) , game - > GetPlayer ( ) - > GetPos ( ) ) ;
float angle = ( i - ( bulletCount / 2 ) ) * util : : degToRad ( ConfigFloat ( " Phase2.ShootAngleSpread " ) ) + initialAngle ;
BULLET_LIST . emplace_back ( std : : make_unique < Bullet > ( m . GetPos ( ) , vf2d { cos ( angle ) , sin ( angle ) } * bulletSpd , 6 , ConfigInt ( " ProjectileDamage " ) , m . OnUpperLevel ( ) , false , YELLOW , vf2d { 6 , 6 } ) ) ;
}
SoundEffect : : PlaySFX ( " Slime King Shoot " , m . GetPos ( ) ) ;
}
if ( m . I ( A : : PATTERN_REPEAT_COUNT ) > ConfigInt ( " Phase2.ShootCount " ) ) {
m . I ( A : : PATTERN_REPEAT_COUNT ) = 0 ;
m . I ( A : : JUMP_COUNT ) = 0 ;
m . F ( A : : CASTING_TIMER ) = 5 ;
m . SetState ( State : : CASTING ) ;
}
} break ;
case 3 : {
if ( m . GetRemainingHPPct ( ) < = ConfigFloat ( " Phase4.Change " ) / 100.f ) {
m . phase = 4 ;
m . SetSize ( ConfigFloat ( " Phase4.Size " ) / 100 , false ) ;
m . AddBuff ( BuffType : : SLOWDOWN , 99999 , ConfigFloat ( " Phase4.MoveSpdModifier " ) / 100 ) ;
TransitionPhase ( m . phase ) ;
return ;
}
if ( m . I ( A : : PATTERN_REPEAT_COUNT ) = = 0 ) {
StartJumpTowardsPlayer ( ConfigFloat ( " Phase3.JumpDelayTime " ) , ConfigFloat ( " Phase3.JumpRecoveryTime " ) , ConfigFloat ( " Phase3.JumpMoveSpd " ) ) ;
m . I ( A : : PATTERN_REPEAT_COUNT ) + + ;
} else
if ( m . I ( A : : PATTERN_REPEAT_COUNT ) < 4 & & m . F ( A : : SHOOT_TIMER ) = = 0 ) {
m . F ( A : : SHOOT_TIMER ) = ConfigFloat ( " Phase3.ShootRate " ) ;
m . I ( A : : PATTERN_REPEAT_COUNT ) + + ;
int bulletCount = ConfigInt ( " Phase3.ShootProjectileCount " ) ;
for ( int i = 0 ; i < bulletCount ; i + + ) {
float initialAngle = util : : angleTo ( m . GetPos ( ) , game - > GetPlayer ( ) - > GetPos ( ) ) ;
float angle = ( i - ( bulletCount / 2 ) ) * util : : degToRad ( ConfigFloat ( " Phase3.ShootAngleSpread " ) ) + initialAngle ;
BULLET_LIST . emplace_back ( std : : make_unique < Bullet > ( m . GetPos ( ) , vf2d { cos ( angle ) , sin ( angle ) } * bulletSpd , 6 , ConfigInt ( " ProjectileDamage " ) , m . OnUpperLevel ( ) , false , YELLOW , vf2d { 6 , 6 } ) ) ;
}
SoundEffect : : PlaySFX ( " Slime King Shoot " , m . GetPos ( ) ) ;
} else
if ( m . I ( A : : PATTERN_REPEAT_COUNT ) > = 4 ) {
m . F ( A : : RECOVERY_TIME ) = ConfigFloat ( " Phase3.PhaseRecoveryTime " ) ;
m . SetState ( State : : RECOVERY ) ;
m . I ( A : : PATTERN_REPEAT_COUNT ) = 0 ;
}
} break ;
case 4 : {
if ( m . hp < = 1 ) { //HP can't reach 0 when the dies normally flag is on.
m . phase = 5 ;
m . F ( A : : IFRAME_TIME_UPON_HIT ) = 1 ;
m . I ( A : : HITS_UNTIL_DEATH ) = int ( m . GetSizeMult ( ) * 100 / ConfigFloat ( " Phase5.SizeLossPerHit " ) ) - 1 ;
TransitionPhase ( m . phase ) ;
return ;
}
if ( m . I ( A : : PHASE_REPEAT_COUNT ) > = 5 ) {
m . I ( A : : PHASE_REPEAT_COUNT ) = 0 ;
float jumpAngle = util : : angleTo ( m . GetPos ( ) , game - > GetCurrentMapData ( ) . MapSize * game - > GetCurrentMapData ( ) . tilewidth / 2 ) ; //We jump towards the center to keep the player from constantly dealing with a stuck boss.
float jumpDistance = ConfigFloat ( " Phase4.JumpDistance " ) / 100 * game - > GetCurrentMapData ( ) . tilewidth ;
float jumpSpd = jumpDistance / ConfigFloat ( " Phase4.JumpDuration " ) ;
StartJump ( ConfigFloat ( " Phase4.JumpDuration " ) , m . GetPos ( ) + vf2d { cos ( jumpAngle ) * jumpDistance , sin ( jumpAngle ) * jumpDistance } , 0 , jumpSpd ) ;
} else
if ( m . I ( A : : PATTERN_REPEAT_COUNT ) < 5 & & m . F ( A : : SHOOT_TIMER ) = = 0 ) {
m . I ( A : : PATTERN_REPEAT_COUNT ) + + ;
m . F ( A : : SHOOT_TIMER ) = ConfigFloat ( " Phase4.ShootRate " ) ;
float bulletAngle = util : : angleTo ( m . GetPos ( ) , game - > GetPlayer ( ) - > GetPos ( ) ) ;
float spreadAngle = util : : degToRad ( ConfigFloat ( " Phase4.RandomOffsetAngle " ) ) ;
bulletAngle + = util : : random ( spreadAngle * 2 ) - spreadAngle ;
BULLET_LIST . emplace_back ( std : : make_unique < Bullet > ( m . GetPos ( ) , vf2d { cos ( bulletAngle ) , sin ( bulletAngle ) } * bulletSpd , 6 , ConfigInt ( " ProjectileDamage " ) , m . OnUpperLevel ( ) , false , YELLOW , vf2d { 6 , 6 } ) ) ;
SoundEffect : : PlaySFX ( " Slime King Shoot " , m . GetPos ( ) ) ;
} else
if ( m . I ( A : : PATTERN_REPEAT_COUNT ) = = 5 ) {
m . I ( A : : PATTERN_REPEAT_COUNT ) + + ;
m . F ( A : : RUN_AWAY_TIMER ) = ConfigFloat ( " Phase4.RunAwayTime " ) ;
} else
if ( m . I ( A : : PATTERN_REPEAT_COUNT ) = = 6 & & m . F ( A : : RUN_AWAY_TIMER ) = = 0 ) {
m . F ( A : : RECOVERY_TIME ) = ConfigFloat ( " Phase4.WaitTime " ) ;
m . SetState ( State : : RECOVERY ) ;
m . I ( A : : PATTERN_REPEAT_COUNT ) = 0 ;
m . I ( A : : PHASE_REPEAT_COUNT ) + + ;
}
} break ;
case 5 : {
float targetSize = ConfigFloat ( " Phase5.SizeLossPerHit " ) / 100 * m . I ( A : : HITS_UNTIL_DEATH ) ;
Monster : : STRATEGY : : RUN_AWAY ( m , fElapsedTime , " Run Away " ) ;
if ( targetSize > 0 ) {
m . SetSize ( targetSize , false ) ;
} else {
m . diesNormally = true ;
}
} break ;
}
}