# include "Monster.h"
# include "DamageNumber.h"
# include "Crawler.h"
# include "Bullet.h"
# include "BulletTypes.h"
# include "DEFINES.h"
# include "safemap.h"
# include "MonsterStrategyHelpers.h"
# include "util.h"
# include "MonsterAttribute.h"
INCLUDE_ANIMATION_DATA
INCLUDE_MONSTER_DATA
INCLUDE_MONSTER_LIST
INCLUDE_DAMAGENUMBER_LIST
INCLUDE_game
INCLUDE_BULLET_LIST
INCLUDE_DATA
INCLUDE_STRATEGY_DATA
INCLUDE_GFX
safemap < std : : string , int > STRATEGY_DATA ;
safemap < int , std : : string > STRATEGY_ID_DATA ;
std : : map < int , Renderable * > MonsterData : : imgs ;
Monster : : Monster ( vf2d pos , MonsterData data , bool upperLevel , bool bossMob ) :
pos ( pos ) , hp ( data . GetHealth ( ) ) , maxhp ( data . GetHealth ( ) ) , atk ( data . GetAttack ( ) ) , moveSpd ( data . GetMoveSpdMult ( ) ) , size ( data . GetSizeMult ( ) ) , targetSize ( data . GetSizeMult ( ) ) , strategy ( data . GetAIStrategy ( ) ) , id ( data . GetID ( ) ) , upperLevel ( upperLevel ) , isBoss ( bossMob ) {
bool firstAnimation = true ;
for ( std : : string & anim : data . GetAnimations ( ) ) {
animation . AddState ( anim , ANIMATION_DATA [ anim ] ) ;
if ( firstAnimation ) {
animation . ChangeState ( internal_animState , anim ) ;
firstAnimation = false ;
}
}
randomFrameOffset = ( rand ( ) % 1000 ) / 1000.f ;
}
vf2d & Monster : : GetPos ( ) {
return pos ;
}
int Monster : : GetHealth ( ) {
return hp ;
}
int Monster : : GetAttack ( ) {
float mod_atk = atk ;
for ( Buff & b : GetBuffs ( ATTACK_UP ) ) {
mod_atk + = atk * b . intensity ;
}
return int ( mod_atk ) ;
}
float Monster : : GetMoveSpdMult ( ) {
float mod_moveSpd = moveSpd ;
for ( Buff & b : GetBuffs ( SLOWDOWN ) ) {
mod_moveSpd - = moveSpd * b . intensity ;
}
return mod_moveSpd ;
}
float Monster : : GetSizeMult ( ) {
return size ;
}
Animate2D : : Frame Monster : : GetFrame ( ) {
return animation . GetFrame ( internal_animState ) ;
}
void Monster : : UpdateAnimation ( std : : string state ) {
animation . ChangeState ( internal_animState , state ) ;
}
void Monster : : PerformJumpAnimation ( ) {
animation . ChangeState ( internal_animState , MONSTER_DATA [ id ] . GetJumpAnimation ( ) ) ;
}
void Monster : : PerformShootAnimation ( ) {
animation . ChangeState ( internal_animState , MONSTER_DATA [ id ] . GetShootAnimation ( ) ) ;
}
void Monster : : PerformIdleAnimation ( ) {
animation . ChangeState ( internal_animState , MONSTER_DATA [ id ] . GetIdleAnimation ( ) ) ;
}
bool Monster : : SetX ( float x ) {
vf2d newPos = { x , pos . y } ;
vi2d tilePos = vi2d ( newPos / game - > GetCurrentMap ( ) . tilewidth ) * game - > GetCurrentMap ( ) . tilewidth ;
geom2d : : rect < int > collisionRect = game - > GetTileCollision ( game - > GetCurrentLevel ( ) , newPos , upperLevel ) ;
if ( collisionRect . pos = = vi2d { 0 , 0 } & & collisionRect . size = = vi2d { 1 , 1 } ) {
pos . x = std : : clamp ( x , game - > GetCurrentMap ( ) . tilewidth / 2 * GetSizeMult ( ) , float ( game - > GetCurrentMap ( ) . width * game - > GetCurrentMap ( ) . tilewidth - game - > GetCurrentMap ( ) . tilewidth / 2 * GetSizeMult ( ) ) ) ;
Moved ( ) ;
return true ;
} else {
geom2d : : rect < float > collision = { collisionRect . pos , collisionRect . size } ;
collision . pos + = tilePos ;
if ( ! geom2d : : overlaps ( geom2d : : circle < float > ( newPos , 12 * GetSizeMult ( ) ) , collision ) ) {
pos . x = std : : clamp ( x , game - > GetCurrentMap ( ) . tilewidth / 2 * GetSizeMult ( ) , float ( game - > GetCurrentMap ( ) . width * game - > GetCurrentMap ( ) . tilewidth - game - > GetCurrentMap ( ) . tilewidth / 2 * GetSizeMult ( ) ) ) ;
Moved ( ) ;
return true ;
}
}
return false ;
}
bool Monster : : SetY ( float y ) {
vf2d newPos = { pos . x , y } ;
vi2d tilePos = vi2d ( newPos / game - > GetCurrentMap ( ) . tilewidth ) * game - > GetCurrentMap ( ) . tilewidth ;
geom2d : : rect < int > collisionRect = game - > GetTileCollision ( game - > GetCurrentLevel ( ) , newPos , upperLevel ) ;
if ( collisionRect . pos = = vi2d { 0 , 0 } & & collisionRect . size = = vi2d { 1 , 1 } ) {
pos . y = std : : clamp ( y , game - > GetCurrentMap ( ) . tilewidth / 2 * GetSizeMult ( ) , float ( game - > GetCurrentMap ( ) . height * game - > GetCurrentMap ( ) . tilewidth - game - > GetCurrentMap ( ) . tilewidth / 2 * GetSizeMult ( ) ) ) ;
Moved ( ) ;
return true ;
} else {
geom2d : : rect < float > collision = { collisionRect . pos , collisionRect . size } ;
collision . pos + = tilePos ;
if ( ! geom2d : : overlaps ( geom2d : : circle < float > ( newPos , game - > GetCurrentMap ( ) . tilewidth / 2 * GetSizeMult ( ) ) , collision ) ) {
pos . y = std : : clamp ( y , game - > GetCurrentMap ( ) . tilewidth / 2 * GetSizeMult ( ) , float ( game - > GetCurrentMap ( ) . height * game - > GetCurrentMap ( ) . tilewidth - game - > GetCurrentMap ( ) . tilewidth / 2 * GetSizeMult ( ) ) ) ;
Moved ( ) ;
return true ;
}
}
return false ;
}
bool Monster : : Update ( float fElapsedTime ) {
lastHitTimer = std : : max ( 0.f , lastHitTimer - fElapsedTime ) ;
iframe_timer = std : : max ( 0.f , iframe_timer - fElapsedTime ) ;
if ( size ! = targetSize ) {
if ( size > targetSize ) {
size = std : : max ( targetSize , size - Crawler : : SIZE_CHANGE_SPEED * fElapsedTime ) ;
} else {
size = std : : min ( targetSize , size + Crawler : : SIZE_CHANGE_SPEED * fElapsedTime ) ;
}
}
if ( IsAlive ( ) ) {
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 ;
}
}
if ( ! HasIframes ( ) ) {
for ( 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 ( ) ) ;
float dist = line . length ( ) ;
m . SetPos ( line . rpoint ( dist * 1.1 ) ) ;
if ( m . IsAlive ( ) ) {
vel = line . vector ( ) . norm ( ) * - 128 ;
}
}
}
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 ) ) ) {
geom2d : : line line ( pos , game - > GetPlayer ( ) - > GetPos ( ) ) ;
float dist = line . length ( ) ;
SetPos ( line . rpoint ( - 0.1 ) ) ;
vel = line . vector ( ) . norm ( ) * - 128 ;
}
}
if ( GetState ( ) = = State : : NORMAL ) {
if ( game - > GetPlayer ( ) - > GetX ( ) > pos . x ) {
facingDirection = RIGHT ;
} else {
facingDirection = LEFT ;
}
}
Monster : : STRATEGY : : RUN_STRATEGY ( * this , fElapsedTime ) ;
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 ) ;
}
}
if ( ! IsAlive ( ) ) {
deathTimer + = fElapsedTime ;
if ( deathTimer > 3 ) {
return false ;
}
}
animation . UpdateState ( internal_animState , randomFrameOffset + fElapsedTime ) ;
randomFrameOffset = 0 ;
return true ;
}
Key Monster : : GetFacingDirection ( ) {
return facingDirection ;
}
void Monster : : Draw ( ) {
if ( GetZ ( ) > 0 ) {
vf2d shadowScale = vf2d { 8 * GetSizeMult ( ) / 3.f , 1 } / std : : max ( 1.f , GetZ ( ) / 24 ) ;
game - > view . DrawDecal ( GetPos ( ) - vf2d { 3 , 3 } * shadowScale / 2 + vf2d { 0 , 6 * GetSizeMult ( ) } , GFX [ " circle.png " ] . Decal ( ) , shadowScale , BLACK ) ;
}
game - > view . DrawPartialRotatedDecal ( GetPos ( ) - vf2d { 0 , GetZ ( ) } , GetFrame ( ) . GetSourceImage ( ) - > Decal ( ) , 0 , GetFrame ( ) . GetSourceRect ( ) . size / 2 , GetFrame ( ) . GetSourceRect ( ) . pos , GetFrame ( ) . GetSourceRect ( ) . size , vf2d ( GetSizeMult ( ) * ( GetFacingDirection ( ) = = RIGHT ? - 1 : 1 ) , GetSizeMult ( ) ) , 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 ) ;
}
void Monster : : DrawReflection ( float drawRatioX , float multiplierX ) {
game - > SetDecalMode ( DecalMode : : ADDITIVE ) ;
game - > view . DrawPartialRotatedDecal ( GetPos ( ) + vf2d { drawRatioX * GetFrame ( ) . GetSourceRect ( ) . size . x , GetZ ( ) + ( GetFrame ( ) . GetSourceRect ( ) . size . y - 16 ) * GetSizeMult ( ) } , GetFrame ( ) . GetSourceImage ( ) - > Decal ( ) , 0 , GetFrame ( ) . GetSourceRect ( ) . size / 2 , GetFrame ( ) . GetSourceRect ( ) . pos , GetFrame ( ) . GetSourceRect ( ) . size , vf2d ( GetSizeMult ( ) * ( GetFacingDirection ( ) = = RIGHT ? - 1 : 1 ) * multiplierX , GetSizeMult ( ) * - 1 ) , 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 - > SetDecalMode ( DecalMode : : NORMAL ) ;
}
void Monster : : Collision ( Player * p ) {
if ( MONSTER_DATA [ id ] . GetCollisionDmg ( ) > 0 & & ! hasHitPlayer ) {
if ( p - > Hurt ( MONSTER_DATA [ id ] . GetCollisionDmg ( ) , OnUpperLevel ( ) , GetZ ( ) ) ) {
hasHitPlayer = true ;
}
}
Collision ( ) ;
}
void Monster : : Collision ( Monster & m ) {
Collision ( ) ;
}
void Monster : : Collision ( ) {
if ( strategy = = 0 & & GetState ( ) = = State : : MOVE_TOWARDS & & util : : random ( Monster : : STRATEGY : : _GetInt ( * this , " BumpStopChance " , strategy ) ) < 1 ) { //The run towards strategy causes state to return to normal upon a collision.
SetState ( State : : NORMAL ) ;
}
}
void Monster : : SetVelocity ( vf2d vel ) {
this - > vel = vel ;
}
bool Monster : : SetPos ( vf2d pos ) {
bool resultX = SetX ( pos . x ) ;
bool resultY = SetY ( pos . y ) ;
if ( resultY & & ! resultX ) {
resultX = SetX ( pos . x ) ;
}
return resultX | resultY ;
}
void Monster : : Moved ( ) {
ZoneData & zoneData = game - > GetZoneData ( game - > GetCurrentLevel ( ) ) ;
for ( geom2d : : rect < int > & upperLevelZone : zoneData [ " UpperZone " ] ) {
if ( geom2d : : overlaps ( upperLevelZone , pos ) ) {
upperLevel = true ;
}
}
for ( geom2d : : rect < int > & lowerLevelZone : zoneData [ " LowerZone " ] ) {
if ( geom2d : : overlaps ( lowerLevelZone , pos ) ) {
upperLevel = false ;
}
}
}
std : : string Monster : : GetDeathAnimationName ( ) {
return MONSTER_DATA [ id ] . GetDeathAnimation ( ) ;
}
bool Monster : : Hurt ( int damage , bool onUpperLevel , float z ) {
if ( ! IsAlive ( ) | | onUpperLevel ! = OnUpperLevel ( ) | | HasIframes ( ) | | abs ( GetZ ( ) - z ) > 1 ) return false ;
if ( game - > InBossEncounter ( ) ) {
game - > StartBossEncounter ( ) ;
}
float mod_dmg = damage ;
for ( Buff & b : GetBuffs ( BuffType : : DAMAGE_REDUCTION ) ) {
mod_dmg - = damage * b . intensity ;
}
hp = std : : max ( 0 , hp - int ( mod_dmg ) ) ;
if ( lastHitTimer > 0 ) {
damageNumberPtr . get ( ) - > damage + = int ( mod_dmg ) ;
damageNumberPtr . get ( ) - > pauseTime = 0.4 ;
} else {
damageNumberPtr = std : : make_shared < DamageNumber > ( pos , int ( mod_dmg ) ) ;
DAMAGENUMBER_LIST . push_back ( damageNumberPtr ) ;
}
lastHitTimer = 0.05 ;
if ( ! IsAlive ( ) ) {
animation . ChangeState ( internal_animState , GetDeathAnimationName ( ) ) ;
if ( isBoss ) {
game - > ReduceBossEncounterMobCount ( ) ;
}
} else {
hp = std : : max ( 1 , hp ) ; //Make sure it stays alive if it's supposed to be alive...
}
if ( game - > InBossEncounter ( ) ) {
game - > BossDamageDealt ( int ( mod_dmg ) ) ;
}
GetInt ( Attribute : : HITS_UNTIL_DEATH ) = std : : max ( 0 , GetInt ( Attribute : : HITS_UNTIL_DEATH ) - 1 ) ;
iframe_timer = GetFloat ( Attribute : : IFRAME_TIME_UPON_HIT ) ;
return true ;
}
bool Monster : : IsAlive ( ) {
return hp > 0 | | ! diesNormally ;
}
vf2d & Monster : : GetTargetPos ( ) {
return target ;
}
MonsterSpawner : : MonsterSpawner ( ) { }
MonsterSpawner : : MonsterSpawner ( vf2d pos , vf2d range , std : : vector < std : : pair < int , vf2d > > monsters , bool upperLevel , std : : string bossNameDisplay )
: pos ( pos ) , range ( range ) , monsters ( monsters ) , upperLevel ( upperLevel ) , bossNameDisplay ( bossNameDisplay ) {
}
bool MonsterSpawner : : SpawnTriggered ( ) {
return triggered ;
}
vf2d MonsterSpawner : : GetRange ( ) {
return range ;
}
vf2d MonsterSpawner : : GetPos ( ) {
return pos ;
}
void MonsterSpawner : : SetTriggered ( bool trigger , bool spawnMonsters ) {
triggered = trigger ;
if ( spawnMonsters ) {
for ( std : : pair < int , vf2d > & monsterInfo : monsters ) {
game - > SpawnMonster ( pos + monsterInfo . second , & MONSTER_DATA [ monsterInfo . first ] , DoesUpperLevelSpawning ( ) , bossNameDisplay ! = " " ) ;
}
if ( bossNameDisplay ! = " " ) {
game - > SetBossNameDisplay ( bossNameDisplay ) ;
}
}
}
bool MonsterSpawner : : DoesUpperLevelSpawning ( ) {
return upperLevel ;
}
bool Monster : : OnUpperLevel ( ) {
return upperLevel ;
}
void Monster : : AddBuff ( BuffType type , float duration , float intensity ) {
buffList . push_back ( Buff { type , duration , intensity } ) ;
}
void Monster : : StartPathfinding ( float pathingTime ) {
SetState ( State : : PATH_AROUND ) ;
path = game - > pathfinder . Solve_AStar ( pos , target , 12 , OnUpperLevel ( ) ) ;
if ( path . size ( ) > 0 ) {
pathIndex = 0 ;
//We gives this mob 5 seconds to figure out a path to the target.
targetAcquireTimer = pathingTime ;
}
}
void Monster : : PathAroundBehavior ( float fElapsedTime ) {
if ( path . size ( ) > 0 ) {
//Move towards the new path.
geom2d : : line moveTowardsLine = geom2d : : line ( pos , path [ pathIndex ] * game - > GetCurrentMap ( ) . tilewidth ) ;
if ( moveTowardsLine . length ( ) > 2 ) {
SetPos ( pos + moveTowardsLine . vector ( ) . norm ( ) * 100 * fElapsedTime * GetMoveSpdMult ( ) ) ;
if ( moveTowardsLine . vector ( ) . x > 0 ) {
facingDirection = RIGHT ;
} else {
facingDirection = LEFT ;
}
} else {
if ( pathIndex + 1 > = path . size ( ) ) {
//We have reached the end of the path!
targetAcquireTimer = 0 ;
} else {
pathIndex + + ;
}
}
} else {
//We actually can't do anything so just quit.
targetAcquireTimer = 0 ;
}
}
std : : vector < Buff > Monster : : GetBuffs ( BuffType buff ) {
std : : vector < Buff > filteredBuffs ;
std : : copy_if ( buffList . begin ( ) , buffList . end ( ) , std : : back_inserter ( filteredBuffs ) , [ buff ] ( Buff & b ) { return b . type = = buff ; } ) ;
return filteredBuffs ;
}
State : : State Monster : : GetState ( ) {
return state ;
}
void Monster : : SetState ( State : : State newState ) {
state = newState ;
}
void Monster : : InitializeStrategies ( ) {
int readCounter = 0 ;
while ( DATA [ " MonsterStrategy " ] . HasProperty ( std : : to_string ( readCounter ) ) ) {
std : : string strategyName = DATA [ " MonsterStrategy " ] [ std : : to_string ( readCounter ) ] [ " Name " ] . GetString ( ) ;
STRATEGY_DATA [ strategyName ] = readCounter ;
STRATEGY_ID_DATA [ readCounter ] = strategyName ;
readCounter + + ;
}
STRATEGY_DATA . SetInitialized ( ) ;
STRATEGY_ID_DATA . SetInitialized ( ) ;
}
bool Monster : : HasIframes ( ) {
return iframe_timer > 0 ;
}
float Monster : : GetZ ( ) {
return z ;
}
std : : string Monster : : GetStrategy ( ) {
return STRATEGY_ID_DATA [ strategy ] ;
}
void Monster : : SetSize ( float newSize , bool immediate ) {
if ( immediate ) {
size = targetSize = newSize ;
} else {
targetSize = newSize ;
}
}
void Monster : : SetZ ( float z ) {
this - > z = z ;
}
void Monster : : SetStrategyDrawFunction ( std : : function < void ( Crawler * ) > func ) {
strategyDraw = func ;
}