# 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 <EFBFBD> 2024 The FreeType
Project ( www . freetype . org ) . Please see LICENSE_FT . txt for more information .
All rights reserved .
*/
# pragma endregion
# include "AdventuresInLestoria.h"
# include "DEFINES.h"
# include "Monster.h"
# include "MonsterStrategyHelpers.h"
# include "BulletTypes.h"
# include "util.h"
# include "SoundEffect.h"
INCLUDE_game
INCLUDE_BULLET_LIST
using A = Attribute ;
/*
* Every 2 seconds after completing an attack do one of the following attacks :
Casts a Stone Pillar on the location of the Player : radius 350. Hits with 3 seconds delay . Becomes an solid object ( Player colision ) and decays after 5 seconds again .
If the Stone pillar would hit the Stone Elemental it will move out of its range .
Shoot a Stone ( bullet ) in the direction of the target .
The Stone gets created in form of a Cone and tracks the player movement without moving for the first second .
Once it locks it doesnt move for another second . then it starts flying towards the player with 3 x the normal Bullet speed .
Dive under earth and show up 1 second later in a random location 400 - 700 away from player . while emerging shoot a ring of bullets ( 0.5 x Attack )
( maybe emerge with 200 away from anything with colision to avoid the Elemental getting stuck ? Other solutions for that are welcome aswell )
if range to player > 1200 always use 3 rd attack
*/
void Monster : : STRATEGY : : STONE_ELEMENTAL ( Monster & m , float fElapsedTime , std : : string strategy ) {
enum PhaseName {
INITIALIZE ,
WAITING ,
STONE_PILLAR_CAST ,
SHOOT_STONE_CAST ,
DIVE_UNDERGROUND_DIG ,
DIVE_UNDERGROUND_MOVE ,
DIVE_UNDERGROUND_SURFACE ,
} ;
auto ReturnToWaitingPhase = [ & ] ( ) {
m . phase = WAITING ;
m . PerformIdleAnimation ( ) ;
m . F ( A : : ATTACK_COOLDOWN ) = 0.f ;
} ;
switch ( m . phase ) {
case INITIALIZE : {
m . F ( A : : ATTACK_COOLDOWN ) = util : : random ( ConfigFloat ( " Attack Wait Time " ) / 1.5f ) ;
m . phase = WAITING ;
} break ;
case WAITING : {
m . F ( A : : ATTACK_COOLDOWN ) + = fElapsedTime ;
if ( m . F ( A : : ATTACK_COOLDOWN ) > = ConfigFloat ( " Attack Wait Time " ) ) {
int randomAttackChoice = util : : random ( ) % 3 ;
float distToPlayer = util : : distance ( m . GetPos ( ) , game - > GetPlayer ( ) - > GetPos ( ) ) ;
if ( distToPlayer > = ConfigPixels ( " Auto Dive Range " ) ) randomAttackChoice = 2 ; //Force dig attack if too far away.
switch ( randomAttackChoice ) {
case 0 : {
m . PerformAnimation ( " STONE PILLAR CAST " ) ;
m . SIZET ( A : : LOOPING_SOUND_ID ) = SoundEffect : : PlayLoopingSFX ( " Rock Toss Cast " , m . GetPos ( ) ) ;
m . phase = STONE_PILLAR_CAST ;
m . F ( A : : CASTING_TIMER ) = ConfigFloat ( " Stone Pillar Cast Time " ) ;
m . V ( A : : LOCKON_POS ) = game - > GetPlayer ( ) - > GetPos ( ) ;
game - > AddEffect ( std : : make_unique < SpellCircle > ( m . V ( A : : LOCKON_POS ) , ConfigFloat ( " Stone Pillar Cast Time " ) , " range_indicator.png " , " spell_insignia.png " , m . OnUpperLevel ( ) , vf2d { 1.f , 1.f } * ( MONSTER_DATA . at ( " Stone Pillar " ) . GetCollisionRadius ( ) * MONSTER_DATA . at ( " Stone Pillar " ) . GetSizeMult ( ) / 12.f ) * 1.25f , 0.3f , vf2d { } , ConfigPixel ( " Stone Pillar Spell Circle Color " ) , util : : random ( 2 * PI ) , util : : degToRad ( ConfigFloat ( " Stone Pillar Spell Circle Rotation Spd " ) ) , false , vf2d { 1.f , 1.f } * ( MONSTER_DATA . at ( " Stone Pillar " ) . GetCollisionRadius ( ) * MONSTER_DATA . at ( " Stone Pillar " ) . GetSizeMult ( ) / 12.f ) * 0.9f , 0.3f , vf2d { } , ConfigPixel ( " Stone Pillar Spell Insignia Color " ) , util : : random ( 2 * PI ) , util : : degToRad ( ConfigFloat ( " Stone Pillar Spell Insignia Rotation Spd " ) ) ) , false ) ;
} break ;
case 1 : {
m . PerformAnimation ( " ROCK TOSS CAST " ) ;
m . SIZET ( A : : LOOPING_SOUND_ID ) = SoundEffect : : PlayLoopingSFX ( " Rock Toss Cast " , m . GetPos ( ) ) ;
m . phase = SHOOT_STONE_CAST ;
m . B ( A : : PLAYED_FLAG ) = false ;
m . F ( A : : CASTING_TIMER ) = ConfigFloat ( " Rock Toss Track Time " ) + ConfigFloat ( " Rock Toss Wait Time " ) ;
CreateBullet ( LevitatingRock ) ( m , game - > GetPlayer ( ) - > GetPos ( ) , 1.f , 0.f , ConfigPixels ( " Rock Toss Max Spawn Distance " ) , ConfigFloat ( " Rock Toss Track Time " ) , ConfigFloat ( " Rock Toss Wait Time " ) , ConfigFloat ( " Rock Toss Bullet Speed " ) , ConfigFloat ( " Rock Radius " ) , std : : max ( 1 , ConfigInt ( " Rock Toss Damage " ) / 5 ) , m . OnUpperLevel ( ) , false , WHITE , vf2d { 1 , 1 } ) EndBullet ;
const int masterRockInd = BULLET_LIST . size ( ) - 1 ;
CreateBullet ( LevitatingRock ) ( m , game - > GetPlayer ( ) - > GetPos ( ) , 1.f , util : : degToRad ( - 20.f ) , ConfigPixels ( " Rock Toss Max Spawn Distance " ) * 0.75f , ConfigFloat ( " Rock Toss Track Time " ) , ConfigFloat ( " Rock Toss Wait Time " ) , ConfigFloat ( " Rock Toss Bullet Speed " ) , ConfigFloat ( " Rock Radius " ) * 0.6f , std : : max ( 1 , ConfigInt ( " Rock Toss Damage " ) / 5 ) , m . OnUpperLevel ( ) , false , WHITE , vf2d { 0.6f , 0.6f } ) EndBullet ;
CreateBullet ( LevitatingRock ) ( m , game - > GetPlayer ( ) - > GetPos ( ) , 1.f , util : : degToRad ( - 40.f ) , ConfigPixels ( " Rock Toss Max Spawn Distance " ) * 0.5f , ConfigFloat ( " Rock Toss Track Time " ) , ConfigFloat ( " Rock Toss Wait Time " ) , ConfigFloat ( " Rock Toss Bullet Speed " ) , ConfigFloat ( " Rock Radius " ) * 0.6f , std : : max ( 1 , ConfigInt ( " Rock Toss Damage " ) / 5 ) , m . OnUpperLevel ( ) , false , WHITE , vf2d { 0.6f , 0.6f } ) EndBullet ;
CreateBullet ( LevitatingRock ) ( m , game - > GetPlayer ( ) - > GetPos ( ) , 1.f , util : : degToRad ( 20.f ) , ConfigPixels ( " Rock Toss Max Spawn Distance " ) * 0.75f , ConfigFloat ( " Rock Toss Track Time " ) , ConfigFloat ( " Rock Toss Wait Time " ) , ConfigFloat ( " Rock Toss Bullet Speed " ) , ConfigFloat ( " Rock Radius " ) * 0.6f , std : : max ( 1 , ConfigInt ( " Rock Toss Damage " ) / 5 ) , m . OnUpperLevel ( ) , false , WHITE , vf2d { 0.6f , 0.6f } ) EndBullet ;
CreateBullet ( LevitatingRock ) ( m , game - > GetPlayer ( ) - > GetPos ( ) , 1.f , util : : degToRad ( 40.f ) , ConfigPixels ( " Rock Toss Max Spawn Distance " ) * 0.5f , ConfigFloat ( " Rock Toss Track Time " ) , ConfigFloat ( " Rock Toss Wait Time " ) , ConfigFloat ( " Rock Toss Bullet Speed " ) , ConfigFloat ( " Rock Radius " ) * 0.6f , std : : max ( 1 , ConfigInt ( " Rock Toss Damage " ) / 5 ) , m . OnUpperLevel ( ) , false , WHITE , vf2d { 0.6f , 0.6f } ) EndBullet ;
auto bulletListEndIter { BULLET_LIST . end ( ) } ;
for ( int i = 0 ; i < 4 ; i + + ) { //This is going to assign the last four stones we created as slaves to the first rock so that their directions can all be changed together.
bulletListEndIter - - ;
( ( LevitatingRock * ) ( * bulletListEndIter ) . get ( ) ) - > AssignMaster ( ( LevitatingRock * ) BULLET_LIST [ masterRockInd ] . get ( ) ) ;
}
} break ;
case 2 : {
SoundEffect : : PlaySFX ( " Dig " , m . GetPos ( ) ) ;
m . PerformAnimation ( " BURROW UNDERGROUND " ) ;
m . phase = DIVE_UNDERGROUND_DIG ;
m . F ( A : : CASTING_TIMER ) = m . GetCurrentAnimation ( ) . GetTotalAnimationDuration ( ) ;
} break ;
}
}
} break ;
case STONE_PILLAR_CAST : {
m . F ( A : : CASTING_TIMER ) - = fElapsedTime ;
if ( m . F ( A : : CASTING_TIMER ) < = 0.f ) {
SoundEffect : : StopLoopingSFX ( m . SIZET ( A : : LOOPING_SOUND_ID ) ) ;
SoundEffect : : PlaySFX ( " Pillar Rise " , m . V ( A : : LOCKON_POS ) ) ;
game - > SpawnMonster ( m . V ( A : : LOCKON_POS ) , MONSTER_DATA . at ( " Stone Pillar " ) , m . OnUpperLevel ( ) ) ;
ReturnToWaitingPhase ( ) ;
game - > Hurt ( m . V ( A : : LOCKON_POS ) , MONSTER_DATA . at ( " Stone Pillar " ) . GetCollisionRadius ( ) * MONSTER_DATA . at ( " Stone Pillar " ) . GetSizeMult ( ) , m . GetAttack ( ) , m . OnUpperLevel ( ) , 0.f , HurtType : : PLAYER ) ;
}
if ( geom2d : : overlaps ( geom2d : : circle < float > { m . V ( A : : LOCKON_POS ) , MONSTER_DATA . at ( " Stone Pillar " ) . GetCollisionRadius ( ) * MONSTER_DATA . at ( " Stone Pillar " ) . GetSizeMult ( ) } , geom2d : : circle < float > { m . GetPos ( ) , m . GetCollisionRadius ( ) } ) ) {
geom2d : : line < float > stonePillarCastLine { m . V ( A : : LOCKON_POS ) , m . GetPos ( ) } ;
const vf2d targetWalkPos = stonePillarCastLine . rpoint ( stonePillarCastLine . length ( ) + 48.f ) ;
m . target = targetWalkPos ;
RUN_TOWARDS ( m , fElapsedTime , " Run Towards " ) ;
m . PerformAnimation ( " STONE PILLAR CAST " ) ;
m . UpdateFacingDirection ( targetWalkPos ) ;
} else {
m . PerformAnimation ( " STONE PILLAR CAST " ) ;
m . UpdateFacingDirection ( m . V ( A : : LOCKON_POS ) ) ;
}
} break ;
case SHOOT_STONE_CAST : {
m . F ( A : : CASTING_TIMER ) - = fElapsedTime ;
if ( m . F ( A : : CASTING_TIMER ) > = ConfigFloat ( " Rock Toss Wait Time " ) - ConfigFloat ( " Rock Toss Track Time " ) ) {
SoundEffect : : StopLoopingSFX ( m . SIZET ( A : : LOOPING_SOUND_ID ) ) ;
m . PerformAnimation ( " STONE PILLAR CAST " ) ;
m . UpdateFacingDirection ( game - > GetPlayer ( ) - > GetPos ( ) ) ;
}
if ( m . F ( A : : CASTING_TIMER ) > = ConfigFloat ( " Rock Toss Track Time " ) & & ! m . B ( A : : PLAYED_FLAG ) ) {
SoundEffect : : PlaySFX ( " Rock Break " , m . GetPos ( ) ) ;
m . B ( A : : PLAYED_FLAG ) = true ;
}
if ( m . F ( A : : CASTING_TIMER ) < = 0.f ) {
SoundEffect : : StopLoopingSFX ( m . SIZET ( A : : LOOPING_SOUND_ID ) ) ;
ReturnToWaitingPhase ( ) ;
}
} break ;
case DIVE_UNDERGROUND_DIG : {
m . F ( A : : CASTING_TIMER ) - = fElapsedTime ;
if ( m . F ( A : : CASTING_TIMER ) < = 0.f ) {
m . phase = DIVE_UNDERGROUND_MOVE ;
float randomAngle = util : : random ( 2 * PI ) ;
const float minDist = ConfigPixelsArr ( " Burrow Teleport Distance " , 0 ) ;
const float maxDist = ConfigPixelsArr ( " Burrow Teleport Distance " , 1 ) ;
const float distRange = maxDist - minDist ;
float randomDist = util : : random ( distRange ) + minDist ;
m . V ( A : : LOCKON_POS ) = game - > GetPlayer ( ) - > GetPos ( ) + vf2d { randomDist , randomAngle } . cart ( ) ;
m . target = m . V ( A : : LOCKON_POS ) ;
m . targetAcquireTimer = INFINITY ;
m . SetState ( State : : MOVE_TOWARDS ) ;
const float baseMoveSpd = 100.f * m . GetMoveSpdMult ( ) ;
const float distToTarget = geom2d : : line < float > ( m . GetPos ( ) , m . V ( A : : LOCKON_POS ) ) . length ( ) ;
const float targetMoveSpdRatio = std : : max ( 0.f , distToTarget / baseMoveSpd / ConfigFloat ( " Burrow Wait Time " ) - 1 ) ;
m . AddBuff ( BuffType : : SPEEDBOOST , ConfigFloat ( " Burrow Wait Time " ) , targetMoveSpdRatio ) ;
m . F ( A : : CASTING_TIMER ) = ConfigFloat ( " Burrow Wait Time " ) ;
m . B ( A : : IGNORE_DEFAULT_ANIMATIONS ) = true ;
m . ApplyIframes ( ConfigFloat ( " Burrow Wait Time " ) + m . GetAnimation ( " RISE FROM UNDERGROUND " ) . GetTotalAnimationDuration ( ) ) ;
}
} break ;
case DIVE_UNDERGROUND_MOVE : {
m . F ( A : : CASTING_TIMER ) - = fElapsedTime ;
RUN_TOWARDS ( m , fElapsedTime , " Run Towards " ) ;
if ( m . F ( A : : CASTING_TIMER ) < = 0.f ) {
m . B ( A : : IGNORE_DEFAULT_ANIMATIONS ) = false ;
SoundEffect : : PlaySFX ( " Rise " , m . GetPos ( ) ) ;
m . PerformAnimation ( " RISE FROM UNDERGROUND " ) ;
m . phase = DIVE_UNDERGROUND_SURFACE ;
m . targetAcquireTimer = 0 ;
m . F ( A : : CASTING_TIMER ) = m . GetCurrentAnimation ( ) . GetTotalAnimationDuration ( ) ;
const float bulletAngRandomOffset { util : : random ( PI / 2 ) } ;
for ( int i = 0 ; i < ConfigInt ( " Burrow Ring Bullet Count " ) ; i + + ) {
const float bulletAngle = ( ( 2 * PI ) / ConfigInt ( " Burrow Ring Bullet Count " ) ) * i + bulletAngRandomOffset ;
CreateBullet ( Bullet ) ( m . GetPos ( ) + vf2d { 0 , 4 * m . GetSizeMult ( ) } , vf2d { ConfigFloat ( " Burrow Ring Bullet Speed " ) , bulletAngle } . cart ( ) , ConfigFloat ( " Burrow Ring Bullet Size " ) , ConfigInt ( " Burrow Ring Bullet Damage " ) , m . OnUpperLevel ( ) , false , ConfigPixel ( " Burrow Ring Bullet Color " ) , vf2d { ConfigFloat ( " Burrow Ring Bullet Size " ) / 3 , ConfigFloat ( " Burrow Ring Bullet Size " ) / 3 } ) EndBullet ;
BULLET_LIST . back ( ) - > SetIframeTimeOnHit ( 0.15f ) ;
}
}
} break ;
case DIVE_UNDERGROUND_SURFACE : {
m . F ( A : : CASTING_TIMER ) - = fElapsedTime ;
if ( m . F ( A : : CASTING_TIMER ) < = 0.f ) {
ReturnToWaitingPhase ( ) ;
}
} break ;
}
}