2023-11-20 23:25:36 -06:00
# pragma region License
2023-11-14 18:11:32 -06:00
/*
License ( OLC - 3 )
~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
2024-01-02 00:46:32 -06:00
Copyright 2024 Joshua Sigona < sigonasr2 @ gmail . com >
2023-11-14 18:11:32 -06:00
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 .
2023-11-29 00:50:00 -06:00
2024-01-30 14:48:49 +00:00
Portions of this software are copyright © 2024 The FreeType
2023-11-29 00:50:00 -06:00
Project ( www . freetype . org ) . Please see LICENSE_FT . txt for more information .
All rights reserved .
2023-11-14 18:11:32 -06:00
*/
2023-11-20 23:25:36 -06:00
# pragma endregion
2023-06-11 20:26:41 -05:00
# include "Monster.h"
2023-06-12 00:37:55 -05:00
# include "DamageNumber.h"
2024-01-04 05:21:56 -06:00
# include "AdventuresInLestoria.h"
2023-06-16 01:41:38 -05:00
# include "Bullet.h"
2023-06-30 15:44:41 -07:00
# include "BulletTypes.h"
2023-06-15 04:53:57 -05:00
# include "DEFINES.h"
2023-08-06 19:00:09 -05:00
# include "safemap.h"
2023-09-19 03:38:09 -05:00
# include "MonsterStrategyHelpers.h"
2023-11-13 21:26:34 -06:00
# include "util.h"
2023-09-23 15:28:21 -05:00
# include "MonsterAttribute.h"
2023-11-22 15:49:41 -06:00
# include "ItemDrop.h"
2024-01-08 07:42:44 -06:00
# include "SoundEffect.h"
2024-03-29 06:00:03 -05:00
# include "Unlock.h"
2024-03-29 06:19:10 -05:00
# ifndef __EMSCRIPTEN__
2024-03-29 21:04:55 -05:00
# include "steam/isteamuserstats.h"
2024-03-29 06:19:10 -05:00
# endif
2023-06-11 20:26:41 -05:00
2023-06-15 04:53:57 -05:00
INCLUDE_ANIMATION_DATA
INCLUDE_MONSTER_DATA
INCLUDE_MONSTER_LIST
INCLUDE_DAMAGENUMBER_LIST
2023-06-15 23:44:34 -05:00
INCLUDE_game
2023-06-16 01:41:38 -05:00
INCLUDE_BULLET_LIST
2023-08-06 19:00:09 -05:00
INCLUDE_DATA
2023-09-16 07:00:38 -05:00
INCLUDE_GFX
2023-08-06 19:00:09 -05:00
2023-12-18 15:40:36 -06:00
safemap < std : : string , std : : function < void ( Monster & , float , std : : string ) > > STRATEGY_DATA ;
2023-12-19 00:15:47 -06:00
std : : map < std : : string , Renderable * > MonsterData : : imgs ;
2023-06-11 20:44:51 -05:00
2023-09-26 06:35:21 -05:00
Monster : : Monster ( vf2d pos , MonsterData data , bool upperLevel , bool bossMob ) :
2024-01-19 01:44:50 -06:00
pos ( pos ) , spawnPos ( pos ) , hp ( data . GetHealth ( ) ) , size ( data . GetSizeMult ( ) ) , targetSize ( data . GetSizeMult ( ) ) , strategy ( data . GetAIStrategy ( ) ) , name ( data . GetDisplayName ( ) ) , upperLevel ( upperLevel ) , isBoss ( bossMob ) , facingDirection ( DOWN ) {
2023-06-11 21:19:45 -05:00
bool firstAnimation = true ;
2023-08-06 19:00:09 -05:00
for ( std : : string & anim : data . GetAnimations ( ) ) {
2023-06-11 21:19:45 -05:00
animation . AddState ( anim , ANIMATION_DATA [ anim ] ) ;
if ( firstAnimation ) {
animation . ChangeState ( internal_animState , anim ) ;
firstAnimation = false ;
}
}
2023-12-23 21:15:08 -06:00
stats . A ( " Health " ) = data . GetHealth ( ) ;
stats . A ( " Attack " ) = data . GetAttack ( ) ;
stats . A ( " Move Spd % " ) = data . GetMoveSpdMult ( ) ;
2023-12-31 16:32:30 -06:00
randomFrameOffset = ( util : : random ( ) % 1000 ) / 1000.f ;
2024-01-08 07:42:44 -06:00
monsterWalkSoundTimer = util : : random ( 1.f ) ;
2023-06-11 20:26:41 -05:00
}
2024-02-27 03:16:35 -06:00
const vf2d & Monster : : GetPos ( ) const {
2023-06-11 20:26:41 -05:00
return pos ;
}
2024-01-23 18:48:41 +00:00
const int Monster : : GetHealth ( ) const {
2023-12-24 01:39:52 -06:00
return hp ;
2023-11-19 13:40:01 -06:00
}
2024-01-23 18:48:41 +00:00
const int Monster : : GetMaxHealth ( ) const {
return stats . A_Read ( " Health " ) ;
}
const float Monster : : GetRemainingHPPct ( ) const {
return float ( GetHealth ( ) ) / GetMaxHealth ( ) ;
}
2023-06-11 20:26:41 -05:00
int Monster : : GetAttack ( ) {
2023-12-23 21:15:08 -06:00
float mod_atk = float ( stats . A ( " Attack " ) ) ;
mod_atk + = Get ( " Attack % " ) ;
mod_atk + = Get ( " Attack " ) ;
2023-06-19 03:25:01 -05:00
return int ( mod_atk ) ;
2023-06-11 20:26:41 -05:00
}
float Monster : : GetMoveSpdMult ( ) {
2024-01-14 12:53:40 -06:00
float moveSpdPct = stats . A ( " Move Spd % " ) / 100.f ;
float mod_moveSpd = moveSpdPct ;
2023-06-19 03:25:01 -05:00
for ( Buff & b : GetBuffs ( SLOWDOWN ) ) {
2024-01-14 12:53:40 -06:00
mod_moveSpd - = moveSpdPct * b . intensity ;
}
for ( Buff & b : GetBuffs ( LOCKON_SPEEDBOOST ) ) {
mod_moveSpd + = moveSpdPct * b . intensity ;
}
for ( Buff & b : GetBuffs ( SPEEDBOOST ) ) {
mod_moveSpd + = moveSpdPct * b . intensity ;
2023-06-19 03:25:01 -05:00
}
return mod_moveSpd ;
2023-06-11 20:26:41 -05:00
}
2024-02-27 03:16:35 -06:00
float Monster : : GetSizeMult ( ) const {
2023-06-11 20:26:41 -05:00
return size ;
2023-06-11 21:19:45 -05:00
}
2024-02-27 03:16:35 -06:00
Animate2D : : Frame Monster : : GetFrame ( ) const {
2023-06-11 21:54:51 -05:00
return animation . GetFrame ( internal_animState ) ;
}
2023-06-16 01:10:40 -05:00
void Monster : : PerformJumpAnimation ( ) {
2023-12-19 00:15:47 -06:00
animation . ChangeState ( internal_animState , MONSTER_DATA [ name ] . GetJumpAnimation ( ) ) ;
2023-06-16 01:10:40 -05:00
}
2023-06-16 01:41:38 -05:00
void Monster : : PerformShootAnimation ( ) {
2023-12-19 00:15:47 -06:00
animation . ChangeState ( internal_animState , MONSTER_DATA [ name ] . GetShootAnimation ( ) ) ;
2023-08-13 02:12:19 -05:00
}
void Monster : : PerformIdleAnimation ( ) {
2023-12-19 00:15:47 -06:00
animation . ChangeState ( internal_animState , MONSTER_DATA [ name ] . GetIdleAnimation ( ) ) ;
2023-06-16 01:41:38 -05:00
}
2024-01-29 00:27:16 -06:00
void Monster : : PerformNPCDownAnimation ( ) {
animation . ChangeState ( internal_animState , MONSTER_DATA [ name ] . GetIdleAnimation ( ) ) ;
}
void Monster : : PerformNPCUpAnimation ( ) {
animation . ChangeState ( internal_animState , MONSTER_DATA [ name ] . GetJumpAnimation ( ) ) ;
}
void Monster : : PerformNPCLeftAnimation ( ) {
animation . ChangeState ( internal_animState , MONSTER_DATA [ name ] . GetShootAnimation ( ) ) ;
}
void Monster : : PerformNPCRightAnimation ( ) {
animation . ChangeState ( internal_animState , MONSTER_DATA [ name ] . GetDeathAnimation ( ) ) ;
}
2024-01-15 01:30:06 -06:00
void Monster : : PerformOtherAnimation ( const uint8_t otherInd ) {
animation . ChangeState ( internal_animState , MONSTER_DATA [ name ] . GetAnimations ( ) [ 4 + otherInd ] ) ;
}
2024-01-12 09:56:49 -06:00
bool Monster : : _SetX ( float x , const bool monsterInvoked ) {
2023-06-24 00:00:14 -07:00
vf2d newPos = { x , pos . y } ;
2024-01-08 02:05:09 -06:00
vi2d tilePos = vi2d ( newPos / float ( game - > GetCurrentMapData ( ) . tilewidth ) ) * game - > GetCurrentMapData ( ) . tilewidth ;
2024-01-11 01:26:58 -06:00
geom2d : : rect < float > collisionRect = game - > GetTileCollision ( game - > GetCurrentLevel ( ) , newPos , upperLevel ) ;
2024-02-20 22:56:54 -06:00
if ( collisionRect = = game - > NO_COLLISION ) {
2024-01-08 02:05:09 -06:00
pos . x = std : : clamp ( x , game - > GetCurrentMapData ( ) . tilewidth / 2.f * GetSizeMult ( ) , float ( game - > GetCurrentMapData ( ) . width * game - > GetCurrentMapData ( ) . tilewidth - game - > GetCurrentMapData ( ) . tilewidth / 2.f * GetSizeMult ( ) ) ) ;
2023-07-07 06:42:49 -05:00
Moved ( ) ;
2023-06-24 01:18:21 -07:00
return true ;
2023-06-16 01:10:40 -05:00
} else {
2023-06-24 00:00:14 -07:00
geom2d : : rect < float > collision = { collisionRect . pos , collisionRect . size } ;
2024-02-20 23:43:58 -06:00
bool insideArenaBounds = true ;
# pragma region Calculate Arena Bounds check for Bosses
if ( isBoss ) {
const geom2d : : rect < int > arenaBounds = game - > GetZones ( ) . at ( " BossArena " ) [ 0 ] . zone ;
if ( ! geom2d : : contains ( arenaBounds , newPos ) ) {
insideArenaBounds = false ;
}
}
# pragma endregion
2024-01-12 09:56:49 -06:00
# pragma region lambdas
2024-04-01 17:39:33 -05:00
auto NoEnemyCollisionWithTile = [ & ] ( ) { return ( isBoss & & insideArenaBounds ) | | ( ! isBoss & & ! geom2d : : overlaps ( newPos , collision ) ) ; } ;
2024-01-12 09:56:49 -06:00
# pragma endregion
2023-06-24 00:00:14 -07:00
collision . pos + = tilePos ;
2024-01-12 09:56:49 -06:00
if ( NoEnemyCollisionWithTile ( ) ) {
2024-01-08 02:05:09 -06:00
pos . x = std : : clamp ( x , game - > GetCurrentMapData ( ) . tilewidth / 2.f * GetSizeMult ( ) , float ( game - > GetCurrentMapData ( ) . width * game - > GetCurrentMapData ( ) . tilewidth - game - > GetCurrentMapData ( ) . tilewidth / 2.f * GetSizeMult ( ) ) ) ;
2023-07-07 06:42:49 -05:00
Moved ( ) ;
2023-06-24 01:18:21 -07:00
return true ;
2024-01-12 09:56:49 -06:00
} else
if ( monsterInvoked ) { //If player invoked, we'll try the smart move system.
vf2d pushDir = geom2d : : line < float > ( collision . middle ( ) , pos ) . vector ( ) . norm ( ) ;
newPos = { newPos . x , pos . y + pushDir . y * 12 } ;
if ( NoEnemyCollisionWithTile ( ) ) {
2024-01-12 12:16:19 -06:00
return _SetY ( pos . y + pushDir . y * game - > GetElapsedTime ( ) * 12 , false ) ;
2024-01-12 09:56:49 -06:00
}
2023-06-24 00:00:14 -07:00
}
2023-06-16 01:10:40 -05:00
}
2023-06-24 01:18:21 -07:00
return false ;
2023-06-16 01:10:40 -05:00
}
2024-01-12 09:56:49 -06:00
bool Monster : : _SetY ( float y , const bool monsterInvoked ) {
2023-06-24 00:00:14 -07:00
vf2d newPos = { pos . x , y } ;
2024-01-08 02:05:09 -06:00
vi2d tilePos = vi2d ( newPos / float ( game - > GetCurrentMapData ( ) . tilewidth ) ) * game - > GetCurrentMapData ( ) . tilewidth ;
2024-01-11 01:26:58 -06:00
geom2d : : rect < float > collisionRect = game - > GetTileCollision ( game - > GetCurrentLevel ( ) , newPos , upperLevel ) ;
2024-02-20 22:56:54 -06:00
if ( collisionRect = = game - > NO_COLLISION ) {
2024-01-08 02:05:09 -06:00
pos . y = std : : clamp ( y , game - > GetCurrentMapData ( ) . tilewidth / 2.f * GetSizeMult ( ) , float ( game - > GetCurrentMapData ( ) . height * game - > GetCurrentMapData ( ) . tilewidth - game - > GetCurrentMapData ( ) . tilewidth / 2.f * GetSizeMult ( ) ) ) ;
2023-07-07 06:42:49 -05:00
Moved ( ) ;
2023-06-24 01:18:21 -07:00
return true ;
2023-06-16 01:10:40 -05:00
} else {
2023-06-24 00:00:14 -07:00
geom2d : : rect < float > collision = { collisionRect . pos , collisionRect . size } ;
2024-02-20 23:43:58 -06:00
bool insideArenaBounds = true ;
# pragma region Calculate Arena Bounds check for Bosses
if ( isBoss ) {
const geom2d : : rect < int > arenaBounds = game - > GetZones ( ) . at ( " BossArena " ) [ 0 ] . zone ;
if ( ! geom2d : : contains ( arenaBounds , newPos ) ) {
insideArenaBounds = false ;
}
}
# pragma endregion
2024-01-12 09:56:49 -06:00
# pragma region lambdas
2024-04-01 17:39:33 -05:00
auto NoEnemyCollisionWithTile = [ & ] ( ) { return ( isBoss & & insideArenaBounds ) | | ( ! isBoss & & ! geom2d : : overlaps ( newPos , collision ) ) ; } ;
2024-01-12 09:56:49 -06:00
# pragma endregion
2023-06-24 00:00:14 -07:00
collision . pos + = tilePos ;
2024-01-12 09:56:49 -06:00
if ( NoEnemyCollisionWithTile ( ) ) {
2024-01-08 02:05:09 -06:00
pos . y = std : : clamp ( y , game - > GetCurrentMapData ( ) . tilewidth / 2.f * GetSizeMult ( ) , float ( game - > GetCurrentMapData ( ) . height * game - > GetCurrentMapData ( ) . tilewidth - game - > GetCurrentMapData ( ) . tilewidth / 2.f * GetSizeMult ( ) ) ) ;
2023-07-07 06:42:49 -05:00
Moved ( ) ;
2023-06-24 01:18:21 -07:00
return true ;
2024-01-12 09:56:49 -06:00
} else
if ( monsterInvoked ) { //If player invoked, we'll try the smart move system.{
vf2d pushDir = geom2d : : line < float > ( collision . middle ( ) , pos ) . vector ( ) . norm ( ) ;
newPos = { pos . x + pushDir . x * 12 , newPos . y } ;
if ( NoEnemyCollisionWithTile ( ) ) {
2024-01-12 12:16:19 -06:00
return _SetX ( pos . x + pushDir . x * game - > GetElapsedTime ( ) * 12 , false ) ;
2024-01-12 09:56:49 -06:00
}
2023-06-24 00:00:14 -07:00
}
2023-06-16 01:10:40 -05:00
}
2023-06-24 01:18:21 -07:00
return false ;
2023-06-16 01:10:40 -05:00
}
2024-01-12 09:56:49 -06:00
bool Monster : : SetX ( float x ) {
return _SetX ( x ) ;
}
bool Monster : : SetY ( float y ) {
return _SetY ( y ) ;
}
2023-06-11 22:57:43 -05:00
bool Monster : : Update ( float fElapsedTime ) {
2023-07-25 19:48:24 -05:00
lastHitTimer = std : : max ( 0.f , lastHitTimer - fElapsedTime ) ;
2023-09-07 01:35:23 -05:00
iframe_timer = std : : max ( 0.f , iframe_timer - fElapsedTime ) ;
2024-01-08 07:42:44 -06:00
monsterHurtSoundCooldown = std : : max ( 0.f , monsterHurtSoundCooldown - fElapsedTime ) ;
2024-01-12 09:56:49 -06:00
lastHitPlayer = std : : max ( 0.f , lastHitPlayer - fElapsedTime ) ;
2024-02-28 23:09:55 -06:00
lastPathfindingCooldown = std : : max ( 0.f , lastPathfindingCooldown - fElapsedTime ) ;
2024-01-08 07:42:44 -06:00
2023-09-07 20:20:21 +00:00
if ( size ! = targetSize ) {
if ( size > targetSize ) {
2024-01-04 05:21:56 -06:00
size = std : : max ( targetSize , size - AiL : : SIZE_CHANGE_SPEED * fElapsedTime ) ;
2023-09-07 20:20:21 +00:00
} else {
2024-01-04 05:21:56 -06:00
size = std : : min ( targetSize , size + AiL : : SIZE_CHANGE_SPEED * fElapsedTime ) ;
2023-09-07 20:20:21 +00:00
}
}
2024-01-14 23:13:00 -06:00
# pragma region Handle knockup timers
if ( knockUpTimer > 0.f ) {
knockUpTimer = std : : max ( 0.f , knockUpTimer - fElapsedTime ) ;
if ( knockUpTimer = = 0.f ) {
totalKnockupTime = 0.f ;
knockUpZAmt = 0.f ;
SetZ ( 0.f ) ;
} else {
SetZ ( util : : lerp ( 0.f , 1.f , - ( pow ( ( knockUpTimer - totalKnockupTime / 2 ) / ( totalKnockupTime / 2 ) , 2 ) ) + 1 ) * knockUpZAmt ) ;
}
}
# pragma endregion
2023-06-15 23:44:34 -05:00
if ( IsAlive ( ) ) {
2023-06-19 03:25:01 -05:00
for ( std : : vector < Buff > : : iterator it = buffList . begin ( ) ; it ! = buffList . end ( ) ; + + it ) {
Buff & b = * it ;
b . duration - = fElapsedTime ;
if ( b . duration < = 0 ) {
it = buffList . erase ( it ) ;
if ( it = = buffList . end ( ) ) break ;
}
}
2023-08-13 22:32:04 -05:00
if ( ! HasIframes ( ) ) {
2024-04-09 00:49:04 -05:00
for ( std : : unique_ptr < Monster > & m : MONSTER_LIST ) {
if ( & * m = = this ) continue ;
if ( ! m - > HasIframes ( ) & & OnUpperLevel ( ) = = m - > OnUpperLevel ( ) & & abs ( m - > GetZ ( ) - GetZ ( ) ) < = 1 & & geom2d : : overlaps ( geom2d : : circle ( pos , 12 * size / 2 ) , geom2d : : circle ( m - > GetPos ( ) , 12 * m - > GetSizeMult ( ) / 2 ) ) ) {
m - > Collision ( * this ) ;
geom2d : : line line ( pos , m - > GetPos ( ) ) ;
2023-08-13 22:32:04 -05:00
float dist = line . length ( ) ;
2024-04-09 00:49:04 -05:00
m - > SetPos ( line . rpoint ( dist * 1.1f ) ) ;
if ( m - > IsAlive ( ) ) {
2023-08-13 22:32:04 -05:00
vel = line . vector ( ) . norm ( ) * - 128 ;
}
2023-06-15 23:44:34 -05:00
}
}
2023-09-16 07:00:38 -05:00
if ( ! game - > GetPlayer ( ) - > HasIframes ( ) & & abs ( game - > GetPlayer ( ) - > GetZ ( ) - GetZ ( ) ) < = 1 & & game - > GetPlayer ( ) - > OnUpperLevel ( ) = = OnUpperLevel ( ) & & geom2d : : overlaps ( geom2d : : circle ( pos , 12 * size / 2 ) , geom2d : : circle ( game - > GetPlayer ( ) - > GetPos ( ) , 12 * game - > GetPlayer ( ) - > GetSizeMult ( ) / 2 ) ) ) {
2023-08-13 22:32:04 -05:00
geom2d : : line line ( pos , game - > GetPlayer ( ) - > GetPos ( ) ) ;
float dist = line . length ( ) ;
2023-11-27 02:38:12 -06:00
SetPos ( line . rpoint ( - 0.1f ) ) ;
2023-08-13 22:32:04 -05:00
vel = line . vector ( ) . norm ( ) * - 128 ;
}
2023-06-15 23:44:34 -05:00
}
2023-07-29 10:21:53 -05:00
if ( GetState ( ) = = State : : NORMAL ) {
2023-07-13 20:24:47 +00:00
if ( game - > GetPlayer ( ) - > GetX ( ) > pos . x ) {
2023-06-15 23:44:34 -05:00
facingDirection = RIGHT ;
} else {
facingDirection = LEFT ;
}
}
2023-08-13 02:12:19 -05:00
Monster : : STRATEGY : : RUN_STRATEGY ( * this , fElapsedTime ) ;
2024-03-03 05:08:01 -06:00
}
if ( vel . x > 0 ) {
vel . x = std : : max ( 0.f , vel . x - friction * fElapsedTime ) ;
} else {
vel . x = std : : min ( 0.f , vel . x + friction * fElapsedTime ) ;
}
if ( vel . y > 0 ) {
vel . y = std : : max ( 0.f , vel . y - friction * fElapsedTime ) ;
} else {
vel . y = std : : min ( 0.f , vel . y + friction * fElapsedTime ) ;
}
if ( vel ! = vf2d { 0 , 0 } ) {
SetX ( pos . x + vel . x * fElapsedTime ) ;
SetY ( pos . y + vel . y * fElapsedTime ) ;
2023-06-11 21:19:45 -05:00
}
2023-09-07 01:35:23 -05:00
if ( ! IsAlive ( ) ) {
2023-06-11 22:57:43 -05:00
deathTimer + = fElapsedTime ;
if ( deathTimer > 3 ) {
return false ;
}
}
2023-06-11 21:54:51 -05:00
animation . UpdateState ( internal_animState , randomFrameOffset + fElapsedTime ) ;
randomFrameOffset = 0 ;
2023-06-11 22:57:43 -05:00
return true ;
}
2024-02-27 03:16:35 -06:00
Key Monster : : GetFacingDirection ( ) const {
2023-06-15 23:44:34 -05:00
return facingDirection ;
}
2024-01-14 16:23:31 -06:00
void Monster : : UpdateFacingDirection ( vf2d facingTargetPoint ) {
if ( facingTargetPoint . x > GetPos ( ) . x ) {
facingDirection = RIGHT ;
}
if ( facingTargetPoint . x < GetPos ( ) . x ) {
facingDirection = LEFT ;
}
}
2024-02-27 03:16:35 -06:00
void Monster : : Draw ( ) const {
2023-08-13 23:01:23 -05:00
if ( GetZ ( ) > 0 ) {
vf2d shadowScale = vf2d { 8 * GetSizeMult ( ) / 3.f , 1 } / std : : max ( 1.f , GetZ ( ) / 24 ) ;
2023-09-16 07:00:38 -05:00
game - > view . DrawDecal ( GetPos ( ) - vf2d { 3 , 3 } * shadowScale / 2 + vf2d { 0 , 6 * GetSizeMult ( ) } , GFX [ " circle.png " ] . Decal ( ) , shadowScale , BLACK ) ;
2023-08-13 23:01:23 -05:00
}
2024-01-24 17:13:33 -06:00
Pixel blendCol = GetBuffs ( BuffType : : SLOWDOWN ) . size ( ) > 0 ? Pixel { uint8_t ( 255 * abs ( sin ( 1.4 * GetBuffs ( BuffType : : SLOWDOWN ) [ 0 ] . duration ) ) ) , uint8_t ( 255 * abs ( sin ( 1.4 * GetBuffs ( BuffType : : SLOWDOWN ) [ 0 ] . duration ) ) ) , uint8_t ( 128 + 127 * abs ( sin ( 1.4 * GetBuffs ( BuffType : : SLOWDOWN ) [ 0 ] . duration ) ) ) } : WHITE ;
game - > view . DrawPartialRotatedDecal ( GetPos ( ) - vf2d { 0 , GetZ ( ) } , GetFrame ( ) . GetSourceImage ( ) - > Decal ( ) , spriteRot , GetFrame ( ) . GetSourceRect ( ) . size / 2 , GetFrame ( ) . GetSourceRect ( ) . pos , GetFrame ( ) . GetSourceRect ( ) . size , vf2d ( GetSizeMult ( ) * ( GetFacingDirection ( ) = = RIGHT ? - 1 : 1 ) , GetSizeMult ( ) ) , blendCol ) ;
if ( overlaySprite . length ( ) ! = 0 ) {
game - > view . DrawPartialRotatedDecal ( GetPos ( ) - vf2d { 0 , GetZ ( ) } , GFX [ overlaySprite ] . Decal ( ) , spriteRot , GetFrame ( ) . GetSourceRect ( ) . size / 2 , GetFrame ( ) . GetSourceRect ( ) . pos , GetFrame ( ) . GetSourceRect ( ) . size , vf2d ( GetSizeMult ( ) * ( GetFacingDirection ( ) = = RIGHT ? - 1 : 1 ) , GetSizeMult ( ) ) , { blendCol . r , blendCol . g , blendCol . b , overlaySpriteTransparency } ) ;
}
std : : vector < Buff > shieldBuffs = GetBuffs ( BARRIER_DAMAGE_REDUCTION ) ;
if ( shieldBuffs . size ( ) > 0 ) {
game - > view . DrawRotatedDecal ( GetPos ( ) - vf2d { 0 , GetZ ( ) } , GFX [ " block.png " ] . Decal ( ) , 0.f , GFX [ " block.png " ] . Sprite ( ) - > Size ( ) / 2 , { GetSizeMult ( ) , GetSizeMult ( ) } ) ;
}
2024-01-11 06:12:26 -06:00
2024-01-12 06:59:43 -06:00
# pragma region Debug Pathfinding
# ifdef _DEBUG
if ( " debug_pathfinding " _I ) {
for ( float index = 0.f ; index < path . points . size ( ) ; index + = 0.01f ) {
Pixel col = DARK_GREY ;
if ( index < pathIndex ) {
col = VERY_DARK_GREY ;
}
if ( index > pathIndex + 1 ) {
col = GREY ;
}
game - > view . FillRectDecal ( path . GetSplinePoint ( index ) . pos , { 1 , 1 } , col ) ;
}
2024-01-11 06:12:26 -06:00
2024-02-27 03:16:35 -06:00
for ( size_t counter = 0 ; const Pathfinding : : sPoint2D & point : path . points ) {
2024-01-12 06:59:43 -06:00
Pixel col = CYAN ;
if ( counter < pathIndex ) {
col = RED ;
}
if ( counter > pathIndex + 1 ) {
col = YELLOW ;
}
game - > view . FillRectDecal ( point . pos , { 3 , 3 } , col ) ;
counter + + ;
}
2024-01-10 06:26:30 -06:00
}
2024-01-12 06:59:43 -06:00
# endif
# pragma endregion
2023-09-16 04:38:38 -05:00
}
void Monster : : DrawReflection ( float drawRatioX , float multiplierX ) {
game - > SetDecalMode ( DecalMode : : ADDITIVE ) ;
2024-01-22 04:36:49 -06:00
vf2d defaultPos = GetPos ( ) + vf2d { drawRatioX * GetFrame ( ) . GetSourceRect ( ) . size . x , GetZ ( ) + ( GetFrame ( ) . GetSourceRect ( ) . size . y - 16 ) * GetSizeMult ( ) } ;
vf2d spriteSize = GetFrame ( ) . GetSourceRect ( ) . size / 1.5f * GetSizeMult ( ) ;
float bottomExpansionAmount = abs ( util : : radToDeg ( spriteRot ) ) / 10 ;
//BL is in TR, BR is in TL, TR is in BL and TL is in BR.
std : : array < vf2d , 4 > points = {
vf2d { defaultPos + vf2d { - spriteSize . x / 2 , spriteSize . y } - vf2d { bottomExpansionAmount , 0 } } , //BL
vf2d { defaultPos - spriteSize . x / 2 } , //TL
vf2d { defaultPos + vf2d { spriteSize . x / 2 , - spriteSize . y / 2 } } , //TR
vf2d { defaultPos + spriteSize / 2 + vf2d { bottomExpansionAmount , 0 } } , //BR
} ;
if ( GetFacingDirection ( ) = = RIGHT ) {
points = {
vf2d { defaultPos + spriteSize / 2 + vf2d { bottomExpansionAmount , 0 } } , //BR
vf2d { defaultPos + vf2d { spriteSize . x / 2 , - spriteSize . y / 2 } } , //TR
vf2d { defaultPos - spriteSize . x / 2 } , //TL
vf2d { defaultPos + vf2d { - spriteSize . x / 2 , spriteSize . y } - vf2d { bottomExpansionAmount , 0 } } , //BL
} ;
}
game - > view . DrawPartialWarpedDecal ( GetFrame ( ) . GetSourceImage ( ) - > Decal ( ) , points , GetFrame ( ) . GetSourceRect ( ) . pos , GetFrame ( ) . GetSourceRect ( ) . size ) ;
2023-09-16 04:38:38 -05:00
game - > SetDecalMode ( DecalMode : : NORMAL ) ;
2023-06-15 23:44:34 -05:00
}
2023-07-14 15:44:17 +00:00
void Monster : : Collision ( Player * p ) {
2024-01-24 22:44:18 -06:00
if ( GetCollisionDamage ( ) > 0 & & lastHitPlayer = = 0.0f ) {
if ( p - > Hurt ( GetCollisionDamage ( ) , OnUpperLevel ( ) , GetZ ( ) ) ) {
2024-01-12 09:56:49 -06:00
lastHitPlayer = 1.0f ;
2023-09-11 05:27:36 -05:00
}
2023-06-16 01:10:40 -05:00
}
2024-01-24 22:44:18 -06:00
# pragma region Knockback due to buffs
vf2d knockbackVecNorm = geom2d : : line < float > ( GetPos ( ) , p - > GetPos ( ) ) . vector ( ) . norm ( ) ;
float knockbackStrength = 0.f ;
std : : vector < Buff > knockbackBuffs = GetBuffs ( COLLISION_KNOCKBACK_STRENGTH ) ;
for ( Buff & b : knockbackBuffs ) {
knockbackStrength + = b . intensity ;
}
p - > Knockback ( knockbackVecNorm * knockbackStrength ) ;
# pragma endregion
2024-01-29 18:27:08 +00:00
B ( Attribute : : COLLIDED_WITH_PLAYER ) = true ;
2023-06-15 23:44:34 -05:00
Collision ( ) ;
}
void Monster : : Collision ( Monster & m ) {
2024-01-24 22:44:18 -06:00
# pragma region Knockback due to buffs
vf2d knockbackVecNorm = geom2d : : line < float > ( GetPos ( ) , m . GetPos ( ) ) . vector ( ) . norm ( ) ;
float knockbackStrength = 0.f ;
std : : vector < Buff > knockbackBuffs = GetBuffs ( COLLISION_KNOCKBACK_STRENGTH ) ;
for ( Buff & b : knockbackBuffs ) {
knockbackStrength + = b . intensity ;
}
m . Knockback ( knockbackVecNorm * knockbackStrength ) ;
# pragma endregion
2023-06-15 23:44:34 -05:00
Collision ( ) ;
}
void Monster : : Collision ( ) {
2023-12-18 15:40:36 -06:00
if ( strategy = = " Run Towards " & & GetState ( ) = = State : : MOVE_TOWARDS & & util : : random ( float ( Monster : : STRATEGY : : _GetInt ( * this , " BumpStopChance " , strategy ) ) ) < 1 ) { //The run towards strategy causes state to return to normal upon a collision.
2023-09-09 07:10:31 -05:00
SetState ( State : : NORMAL ) ;
2024-01-11 06:12:26 -06:00
targetAcquireTimer = 0 ;
2023-06-15 23:44:34 -05:00
}
}
void Monster : : SetVelocity ( vf2d vel ) {
this - > vel = vel ;
}
2023-09-09 07:10:31 -05:00
bool Monster : : SetPos ( vf2d pos ) {
2023-07-06 14:24:43 -05:00
bool resultX = SetX ( pos . x ) ;
bool resultY = SetY ( pos . y ) ;
if ( resultY & & ! resultX ) {
resultX = SetX ( pos . x ) ;
}
2023-11-27 02:38:12 -06:00
return resultX | | resultY ;
2023-06-15 23:44:34 -05:00
}
2023-07-07 06:42:49 -05:00
void Monster : : Moved ( ) {
2024-02-25 17:28:07 -06:00
const std : : map < std : : string , std : : vector < ZoneData > > & zoneData = game - > GetZones ( game - > GetCurrentLevel ( ) ) ;
for ( const ZoneData & upperLevelZone : zoneData . at ( " UpperZone " ) ) {
2023-11-16 20:41:59 -06:00
if ( geom2d : : overlaps ( upperLevelZone . zone , pos ) ) {
2023-07-07 06:42:49 -05:00
upperLevel = true ;
}
}
2024-02-25 17:28:07 -06:00
for ( const ZoneData & lowerLevelZone : zoneData . at ( " LowerZone " ) ) {
2023-11-16 20:41:59 -06:00
if ( geom2d : : overlaps ( lowerLevelZone . zone , pos ) ) {
2023-07-07 06:42:49 -05:00
upperLevel = false ;
}
}
2024-01-08 07:42:44 -06:00
monsterWalkSoundTimer + = game - > GetElapsedTime ( ) ;
if ( monsterWalkSoundTimer > 1.f ) {
monsterWalkSoundTimer - = 1.f ;
2024-01-09 05:44:37 -06:00
SoundEffect : : PlaySFX ( GetWalkSound ( ) , GetPos ( ) ) ;
2024-01-08 07:42:44 -06:00
}
2024-01-19 01:44:50 -06:00
if ( ! std : : isfinite ( pos . x ) ) {
ERR ( std : : format ( " WARNING! Player X position is {}...Trying to recover. THIS SHOULD NOT BE HAPPENING! " , pos . x ) ) ;
pos . x = spawnPos . x ;
}
if ( ! std : : isfinite ( pos . y ) ) {
ERR ( std : : format ( " WARNING! Player Y position is {}...Trying to recover. THIS SHOULD NOT BE HAPPENING! " , pos . y ) ) ;
pos . y = spawnPos . y ;
}
2023-07-07 06:42:49 -05:00
}
2023-08-06 19:00:09 -05:00
std : : string Monster : : GetDeathAnimationName ( ) {
2023-12-19 00:15:47 -06:00
return MONSTER_DATA [ name ] . GetDeathAnimation ( ) ;
2023-06-16 09:52:49 -05:00
}
2024-01-15 01:30:06 -06:00
const bool Monster : : AttackAvoided ( const float attackZ ) const {
return HasIframes ( ) | | abs ( GetZ ( ) - attackZ ) > 1 ;
}
2023-09-11 05:27:36 -05:00
bool Monster : : Hurt ( int damage , bool onUpperLevel , float z ) {
2024-01-15 01:30:06 -06:00
if ( ! IsAlive ( ) | | onUpperLevel ! = OnUpperLevel ( ) | | AttackAvoided ( z ) ) return false ;
2023-09-26 06:35:21 -05:00
if ( game - > InBossEncounter ( ) ) {
game - > StartBossEncounter ( ) ;
}
2023-11-19 13:40:01 -06:00
game - > GetPlayer ( ) - > ResetLastCombatTime ( ) ;
2023-11-27 02:38:12 -06:00
float mod_dmg = float ( damage ) ;
2023-12-24 06:04:49 -06:00
# pragma region Handle Crits
bool crit = false ;
if ( util : : random ( 1 ) < game - > GetPlayer ( ) - > GetCritRatePct ( ) ) {
2023-12-26 23:59:21 -06:00
mod_dmg * = 1 + game - > GetPlayer ( ) - > GetCritDmgPct ( ) ;
2023-12-24 06:04:49 -06:00
crit = true ;
}
# pragma endregion
2024-01-24 17:13:33 -06:00
mod_dmg - = mod_dmg * GetDamageReductionFromBuffs ( ) ;
2023-12-23 21:15:08 -06:00
mod_dmg = std : : ceil ( mod_dmg ) ;
2023-12-24 01:39:52 -06:00
hp = std : : max ( 0 , hp - int ( mod_dmg ) ) ;
2023-12-24 06:04:49 -06:00
2023-07-25 19:48:24 -05:00
if ( lastHitTimer > 0 ) {
damageNumberPtr . get ( ) - > damage + = int ( mod_dmg ) ;
2023-11-27 02:38:12 -06:00
damageNumberPtr . get ( ) - > pauseTime = 0.4f ;
2024-01-22 18:49:40 -06:00
damageNumberPtr . get ( ) - > RecalculateSize ( ) ;
2023-07-25 19:48:24 -05:00
} else {
2023-09-25 23:51:56 -05:00
damageNumberPtr = std : : make_shared < DamageNumber > ( pos , int ( mod_dmg ) ) ;
DAMAGENUMBER_LIST . push_back ( damageNumberPtr ) ;
2023-07-25 19:48:24 -05:00
}
2023-12-24 06:04:49 -06:00
# pragma region Change Label to Crit
if ( crit ) {
damageNumberPtr . get ( ) - > type = CRIT ;
}
# pragma endregion
2023-11-27 02:38:12 -06:00
lastHitTimer = 0.05f ;
2023-09-07 01:35:23 -05:00
if ( ! IsAlive ( ) ) {
2023-11-22 15:49:41 -06:00
OnDeath ( ) ;
2024-01-08 07:42:44 -06:00
2024-01-09 05:44:37 -06:00
SoundEffect : : PlaySFX ( GetDeathSound ( ) , GetPos ( ) ) ;
2023-09-23 16:27:10 -05:00
} else {
2023-12-24 01:39:52 -06:00
hp = std : : max ( 1 , hp ) ; //Make sure it stays alive if it's supposed to be alive...
2024-01-08 07:42:44 -06:00
if ( monsterHurtSoundCooldown = = 0.f ) {
monsterHurtSoundCooldown = util : : random ( 0.5f ) + 0.5f ;
2024-01-09 05:44:37 -06:00
SoundEffect : : PlaySFX ( GetHurtSound ( ) , GetPos ( ) ) ;
2024-01-08 07:42:44 -06:00
}
2023-06-11 22:57:43 -05:00
}
2023-09-26 06:35:21 -05:00
if ( game - > InBossEncounter ( ) ) {
game - > BossDamageDealt ( int ( mod_dmg ) ) ;
}
2023-09-23 16:27:10 -05:00
GetInt ( Attribute : : HITS_UNTIL_DEATH ) = std : : max ( 0 , GetInt ( Attribute : : HITS_UNTIL_DEATH ) - 1 ) ;
2023-09-08 16:00:07 +00:00
iframe_timer = GetFloat ( Attribute : : IFRAME_TIME_UPON_HIT ) ;
2024-01-08 07:42:44 -06:00
2023-06-15 00:47:11 -05:00
return true ;
2023-06-11 21:19:45 -05:00
}
2023-06-12 00:37:55 -05:00
bool Monster : : IsAlive ( ) {
2023-12-24 01:39:52 -06:00
return hp > 0 | | ! diesNormally ;
2023-06-12 00:37:55 -05:00
}
2023-06-15 23:44:34 -05:00
vf2d & Monster : : GetTargetPos ( ) {
return target ;
}
2023-06-12 00:37:55 -05:00
2023-06-11 21:19:45 -05:00
MonsterSpawner : : MonsterSpawner ( ) { }
2023-12-19 00:15:47 -06:00
MonsterSpawner : : MonsterSpawner ( vf2d pos , vf2d range , std : : vector < std : : pair < std : : string , vf2d > > monsters , bool upperLevel , std : : string bossNameDisplay )
2023-09-26 05:22:04 -05:00
: pos ( pos ) , range ( range ) , monsters ( monsters ) , upperLevel ( upperLevel ) , bossNameDisplay ( bossNameDisplay ) {
2023-06-11 21:19:45 -05:00
}
bool MonsterSpawner : : SpawnTriggered ( ) {
return triggered ;
}
2023-06-22 17:44:51 -07:00
vf2d MonsterSpawner : : GetRange ( ) {
2023-06-11 21:19:45 -05:00
return range ;
}
vf2d MonsterSpawner : : GetPos ( ) {
return pos ;
}
2023-09-07 20:20:21 +00:00
2023-06-11 21:54:51 -05:00
void MonsterSpawner : : SetTriggered ( bool trigger , bool spawnMonsters ) {
2023-06-11 21:19:45 -05:00
triggered = trigger ;
2023-06-11 21:54:51 -05:00
if ( spawnMonsters ) {
2023-12-19 00:15:47 -06:00
for ( std : : pair < std : : string , vf2d > & monsterInfo : monsters ) {
game - > SpawnMonster ( pos + monsterInfo . second , MONSTER_DATA [ monsterInfo . first ] , DoesUpperLevelSpawning ( ) , bossNameDisplay ! = " " ) ;
2023-06-11 21:54:51 -05:00
}
2023-09-26 05:22:04 -05:00
if ( bossNameDisplay ! = " " ) {
game - > SetBossNameDisplay ( bossNameDisplay ) ;
}
2023-06-11 21:54:51 -05:00
}
2023-06-19 03:25:01 -05:00
}
2023-07-10 19:22:33 -05:00
bool MonsterSpawner : : DoesUpperLevelSpawning ( ) {
return upperLevel ;
}
2023-07-07 04:49:26 -05:00
bool Monster : : OnUpperLevel ( ) {
return upperLevel ;
}
2023-06-19 03:25:01 -05:00
void Monster : : AddBuff ( BuffType type , float duration , float intensity ) {
buffList . push_back ( Buff { type , duration , intensity } ) ;
}
2024-01-14 16:23:31 -06:00
void Monster : : RemoveBuff ( BuffType type ) {
std : : erase_if ( buffList , [ & ] ( const Buff & buff ) { return buff . type = = type ; } ) ;
}
2024-01-12 09:56:49 -06:00
bool Monster : : StartPathfinding ( float pathingTime ) {
2023-09-09 07:10:31 -05:00
SetState ( State : : PATH_AROUND ) ;
2024-02-28 23:09:55 -06:00
if ( lastPathfindingCooldown = = 0.f ) {
path = game - > pathfinder . Solve_WalkPath ( pos , target , 24 , OnUpperLevel ( ) ) ;
lastPathfindingCooldown = 0.25f ;
}
2024-01-10 06:26:30 -06:00
if ( path . points . size ( ) > 0 ) {
pathIndex = 0.f ;
2023-07-10 18:40:51 +00:00
//We gives this mob 5 seconds to figure out a path to the target.
targetAcquireTimer = pathingTime ;
}
2024-01-12 09:56:49 -06:00
return path . points . size ( ) > 0 ;
2023-07-10 18:40:51 +00:00
}
void Monster : : PathAroundBehavior ( float fElapsedTime ) {
2024-01-10 06:26:30 -06:00
if ( path . points . size ( ) > 0 ) {
2023-07-10 18:40:51 +00:00
//Move towards the new path.
2024-01-10 10:52:23 -05:00
geom2d : : line moveTowardsLine = geom2d : : line ( pos , path . GetSplinePoint ( pathIndex ) . pos ) ;
2024-01-24 18:07:18 -06:00
if ( moveTowardsLine . length ( ) > 100 * fElapsedTime * GetMoveSpdMult ( ) ) {
SetPos ( pos + moveTowardsLine . vector ( ) . norm ( ) * 100 * fElapsedTime * GetMoveSpdMult ( ) ) ;
/*if(!SetPos(pos+moveTowardsLine.vector().norm()*100*fElapsedTime*GetMoveSpdMult())){
2024-01-12 09:56:49 -06:00
//We are stuck, so stop pathfinding.
path . points . clear ( ) ;
pathIndex = 0 ;
targetAcquireTimer = 0 ;
2024-01-24 18:07:18 -06:00
} */
2024-01-14 16:23:31 -06:00
UpdateFacingDirection ( moveTowardsLine . end ) ;
2023-07-10 18:40:51 +00:00
} else {
2024-01-12 06:59:43 -06:00
if ( pathIndex > = path . points . size ( ) - 1 ) {
2023-07-10 18:40:51 +00:00
//We have reached the end of the path!
2024-01-11 06:12:26 -06:00
pathIndex = 0 ;
2023-07-10 18:40:51 +00:00
targetAcquireTimer = 0 ;
} else {
2024-01-24 18:07:18 -06:00
while ( moveTowardsLine . length ( ) < 100 * fElapsedTime * GetMoveSpdMult ( ) ) {
pathIndex + = 0.1f ;
moveTowardsLine = geom2d : : line ( pos , path . GetSplinePoint ( pathIndex ) . pos ) ;
if ( pathIndex > = path . points . size ( ) - 1 ) {
//We have reached the end of the path!
pathIndex = 0 ;
targetAcquireTimer = 0 ;
break ;
}
}
2023-07-10 18:40:51 +00:00
}
}
} else {
//We actually can't do anything so just quit.
targetAcquireTimer = 0 ;
}
}
2024-01-24 17:13:33 -06:00
std : : vector < Buff > Monster : : GetBuffs ( BuffType buff ) const {
2023-06-19 03:25:01 -05:00
std : : vector < Buff > filteredBuffs ;
2024-01-24 17:13:33 -06:00
std : : copy_if ( buffList . begin ( ) , buffList . end ( ) , std : : back_inserter ( filteredBuffs ) , [ buff ] ( const Buff & b ) { return b . type = = buff ; } ) ;
2023-06-19 03:25:01 -05:00
return filteredBuffs ;
2023-07-29 10:21:53 -05:00
}
2023-09-09 07:10:31 -05:00
State : : State Monster : : GetState ( ) {
2023-07-29 10:21:53 -05:00
return state ;
}
2023-09-09 07:10:31 -05:00
void Monster : : SetState ( State : : State newState ) {
2023-07-29 10:21:53 -05:00
state = newState ;
2023-08-06 19:00:09 -05:00
}
2024-01-15 01:30:06 -06:00
const bool Monster : : HasIframes ( ) const {
2023-08-13 22:32:04 -05:00
return iframe_timer > 0 ;
2023-08-13 23:01:23 -05:00
}
2024-01-15 01:30:06 -06:00
const float Monster : : GetZ ( ) const {
2023-08-13 23:01:23 -05:00
return z ;
2023-09-07 01:35:23 -05:00
}
2023-12-18 15:40:36 -06:00
const std : : function < void ( Monster & , float , std : : string ) > & Monster : : GetStrategy ( ) const {
return STRATEGY_DATA [ strategy ] ;
2023-09-07 20:20:21 +00:00
}
void Monster : : SetSize ( float newSize , bool immediate ) {
if ( immediate ) {
size = targetSize = newSize ;
} else {
targetSize = newSize ;
}
2023-09-08 14:36:31 +00:00
}
2023-09-09 07:10:31 -05:00
void Monster : : SetZ ( float z ) {
this - > z = z ;
}
2024-01-26 00:48:32 -06:00
void Monster : : SetStrategyDrawFunction ( std : : function < void ( AiL * , Monster & , const std : : string & ) > func ) {
2023-09-09 07:10:31 -05:00
strategyDraw = func ;
2023-11-22 15:49:41 -06:00
}
2024-01-29 19:29:38 -06:00
void Monster : : SetStrategyDrawOverlayFunction ( std : : function < void ( AiL * , Monster & , const std : : string & ) > func ) {
strategyDrawOverlay = func ;
}
2024-01-18 13:08:30 -06:00
std : : map < ItemInfo * , uint16_t > Monster : : SpawnDrops ( ) {
std : : map < ItemInfo * , uint16_t > drops ;
2023-12-19 00:15:47 -06:00
for ( MonsterDropData data : MONSTER_DATA . at ( name ) . GetDropData ( ) ) {
2023-11-22 15:49:41 -06:00
if ( util : : random ( 100 ) < = data . dropChance ) {
//This isn't necessarily fair odds for each quantity dropped.
2023-11-27 02:38:12 -06:00
int dropQuantity = int ( data . minQty + std : : round ( util : : random ( float ( data . maxQty - data . minQty ) ) ) ) ;
2023-11-22 15:49:41 -06:00
for ( int i = 0 ; i < dropQuantity ; i + + ) {
ItemDrop : : SpawnItem ( data . item , GetPos ( ) , OnUpperLevel ( ) ) ;
2024-01-18 13:08:30 -06:00
drops [ data . item ] + + ;
2023-11-22 15:49:41 -06:00
}
}
}
2024-01-18 13:08:30 -06:00
return drops ;
}
void Monster : : OnDeath ( ) {
animation . ChangeState ( internal_animState , GetDeathAnimationName ( ) ) ;
if ( isBoss ) {
game - > ReduceBossEncounterMobCount ( ) ;
2024-01-28 19:39:01 -06:00
if ( game - > BossEncounterMobCount ( ) = = 0 ) {
ZoneData exitRing { geom2d : : rect < int > { vi2d { GetPos ( ) - vf2d { " boss_spawn_ring_radius " _F , " boss_spawn_ring_radius " _F } } , vi2d { " boss_spawn_ring_radius " _I * 2 , " boss_spawn_ring_radius " _I * 2 } } , OnUpperLevel ( ) } ;
const geom2d : : rect < int > arenaBounds = game - > GetZones ( ) . at ( " BossArena " ) [ 0 ] . zone ;
geom2d : : rect < int > clampedArena { vi2d ( arenaBounds . pos + " boss_spawn_ring_radius " _I ) , vi2d ( arenaBounds . size - " boss_spawn_ring_radius " _I * 2 ) } ;
2024-01-29 20:22:12 +00:00
exitRing . zone . pos . x = std : : clamp ( exitRing . zone . pos . x , clampedArena . pos . x - " boss_spawn_ring_radius " _I , clampedArena . pos . x - " boss_spawn_ring_radius " _I + clampedArena . size . x ) ;
exitRing . zone . pos . y = std : : clamp ( exitRing . zone . pos . y , clampedArena . pos . y - " boss_spawn_ring_radius " _I , clampedArena . pos . y - " boss_spawn_ring_radius " _I + clampedArena . size . y ) ;
2024-01-28 19:39:01 -06:00
game - > AddZone ( " EndZone " , exitRing ) ; //Create a 144x144 ring around the dead boss.
}
2024-01-18 13:08:30 -06:00
}
2024-03-29 06:00:03 -05:00
Unlock : : IncreaseKillCount ( ) ;
2024-03-29 21:04:55 -05:00
STEAMUSERSTATS (
2024-03-29 06:19:10 -05:00
for ( auto & [ key , size ] : DATA . GetProperty ( " Achievement.Kill Unlocks " ) ) {
//Monster-specific achievement unlocks.
datafile & unlock = DATA . GetProperty ( std : : format ( " Achievement.Kill Unlocks.{} " , key ) ) ;
if ( unlock . HasProperty ( " Monster Name " ) ) {
if ( unlock [ " Monster Name " ] . GetString ( ) ! = GetName ( ) ) continue ;
if ( unlock . HasProperty ( " Time Limit " ) & & isBoss ) {
if ( game - > GetEncounterDuration ( ) < = unlock [ " Time Limit " ] . GetReal ( ) ) {
SteamUserStats ( ) - > SetAchievement ( unlock [ " API Name " ] . GetString ( ) . c_str ( ) ) ;
SteamUserStats ( ) - > StoreStats ( ) ;
}
} else {
SteamUserStats ( ) - > SetAchievement ( unlock [ " API Name " ] . GetString ( ) . c_str ( ) ) ;
SteamUserStats ( ) - > StoreStats ( ) ;
}
}
}
2024-03-29 21:04:55 -05:00
)
2024-03-29 06:19:10 -05:00
2024-01-26 00:48:32 -06:00
if ( hasStrategyDeathFunction ) {
GameEvent : : AddEvent ( std : : make_unique < MonsterStrategyGameEvent > ( strategyDeathFunc , * this , MONSTER_DATA [ name ] . GetAIStrategy ( ) ) ) ;
}
2024-01-18 13:08:30 -06:00
SpawnDrops ( ) ;
2023-12-24 22:50:25 -06:00
game - > GetPlayer ( ) - > AddAccumulatedXP ( MONSTER_DATA . at ( name ) . GetXP ( ) ) ;
2023-12-23 21:15:08 -06:00
}
const ItemAttributable & Monster : : GetStats ( ) const {
return stats ;
}
ItemAttribute & Monster : : Get ( std : : string_view attr ) {
return ItemAttribute : : Get ( attr , this ) ;
2023-12-24 22:50:25 -06:00
}
const uint32_t MonsterData : : GetXP ( ) const {
return xp ;
2024-01-08 07:42:44 -06:00
}
const EventName & Monster : : GetHurtSound ( ) {
return MONSTER_DATA [ name ] . GetHurtSound ( ) ;
}
const EventName & Monster : : GetDeathSound ( ) {
return MONSTER_DATA [ name ] . GetDeathSound ( ) ;
}
const EventName & Monster : : GetWalkSound ( ) {
return MONSTER_DATA [ name ] . GetWalkSound ( ) ;
2024-01-12 11:51:07 -06:00
}
geom2d : : circle < float > Monster : : Hitbox ( ) {
return { GetPos ( ) , 12 * GetSizeMult ( ) } ;
2024-01-13 17:20:13 -06:00
}
void Monster : : Knockback ( const vf2d & vel ) {
this - > vel + = vel ;
2024-01-14 23:13:00 -06:00
}
void Monster : : Knockup ( float duration ) {
knockUpTimer + = duration ;
totalKnockupTime + = duration ;
knockUpZAmt + = 32 * pow ( duration , 2 ) ;
2024-01-15 01:30:06 -06:00
}
const std : : string & Monster : : GetName ( ) const {
return name ;
}
void Monster : : RotateTowardsPos ( const vf2d & targetPos ) {
float dirToPlayer = util : : angleTo ( GetPos ( ) , targetPos ) ;
# pragma region Face towards lockon direction
if ( abs ( dirToPlayer ) < 0.5f * PI ) { //This sprite is supposed to be facing right (flipped)
facingDirection = RIGHT ;
spriteRot = dirToPlayer ;
} else {
facingDirection = LEFT ;
if ( dirToPlayer > 0 ) {
spriteRot = - PI + dirToPlayer ;
} else {
spriteRot = PI + dirToPlayer ;
}
}
# pragma endregion
2024-01-24 17:13:33 -06:00
}
const float Monster : : GetDamageReductionFromBuffs ( ) const {
float dmgReduction = 0 ;
for ( const Buff & b : GetBuffs ( BuffType : : DAMAGE_REDUCTION ) ) {
dmgReduction + = b . intensity ;
}
for ( const Buff & b : GetBuffs ( BuffType : : BARRIER_DAMAGE_REDUCTION ) ) {
dmgReduction + = b . intensity ;
}
return std : : min ( 1.0f , dmgReduction ) ;
2024-01-24 22:44:18 -06:00
}
const float Monster : : GetCollisionDamage ( ) const {
float collisionDmg = 0.f ;
for ( Buff & b : GetBuffs ( FIXED_COLLISION_DMG ) ) {
collisionDmg + = b . intensity ;
}
if ( collisionDmg > 0 ) return collisionDmg ;
else return MONSTER_DATA [ name ] . GetCollisionDmg ( ) ;
2024-01-26 00:48:32 -06:00
}
void Monster : : SetStrategyDeathFunction ( std : : function < bool ( GameEvent & , Monster & , const std : : string & ) > func ) {
hasStrategyDeathFunction = true ;
strategyDeathFunc = func ;
2024-01-29 18:27:08 +00:00
}
const bool Monster : : IsNPC ( ) const {
return MONSTER_DATA [ name ] . IsNPC ( ) ;
}
const bool MonsterData : : IsNPC ( ) const {
return isNPC ;
2023-06-11 20:26:41 -05:00
}