/*
License ( OLC - 3 )
~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
Copyright 2018 - 2022 OneLoneCoder . 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 .
*/
# include "Monster.h"
# include "Player.h"
# include "Crawler.h"
# include "DamageNumber.h"
# include "Bullet.h"
# include "BulletTypes.h"
# include "DEFINES.h"
# include "safemap.h"
# include "util.h"
# include "Key.h"
# include "Menu.h"
# include "GameState.h"
INCLUDE_MONSTER_DATA
INCLUDE_MONSTER_LIST
INCLUDE_ANIMATION_DATA
INCLUDE_SPAWNER_LIST
INCLUDE_BULLET_LIST
INCLUDE_DAMAGENUMBER_LIST
INCLUDE_game
INCLUDE_DATA
float Player : : GROUND_SLAM_SPIN_TIME = 0.6f ;
InputGroup Player : : KEY_ABILITY1 ;
InputGroup Player : : KEY_ABILITY2 ;
InputGroup Player : : KEY_ABILITY3 ;
InputGroup Player : : KEY_ABILITY4 ;
InputGroup Player : : KEY_DEFENSIVE ;
Player : : Player ( )
: lastReleasedMovementKey ( DOWN ) , facingDirection ( DOWN ) , state ( State : : NORMAL ) {
Initialize ( ) ;
}
Player : : Player ( Player * player )
: pos ( player - > GetPos ( ) ) , vel ( player - > GetVelocity ( ) ) , iframe_time ( player - > iframe_time ) , lastReleasedMovementKey ( DOWN ) ,
facingDirection ( DOWN ) , state ( State : : NORMAL ) {
Initialize ( ) ;
}
void Player : : Initialize ( ) {
Player : : GROUND_SLAM_SPIN_TIME = " Warrior.Ability 2.SpinTime " _F ;
}
bool Player : : 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 ) ;
# pragma region lambdas
auto NoTileCollisionExistsHere = [ & ] ( ) { return collisionRect . pos = = game - > NO_COLLISION . pos & & collisionRect . size = = game - > NO_COLLISION . size ; } ;
# pragma endregion
if ( NoTileCollisionExistsHere ( ) ) {
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 } ;
# pragma region lambdas
auto NoPlayerCollisionWithTile = [ & ] ( ) { return ! geom2d : : overlaps ( newPos , collision ) ; } ;
# pragma endregion
collision . pos + = tilePos ;
if ( NoPlayerCollisionWithTile ( ) ) {
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 Player : : 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 ) ;
# pragma region lambdas
auto NoTileCollisionExistsHere = [ & ] ( ) { return collisionRect . pos = = game - > NO_COLLISION . pos & & collisionRect . size = = game - > NO_COLLISION . size ; } ;
# pragma endregion
if ( NoTileCollisionExistsHere ( ) ) {
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 } ;
# pragma region lambdas
auto NoPlayerCollisionWithTile = [ & ] ( ) { return ! geom2d : : overlaps ( newPos , collision ) ; } ;
# pragma endregion
collision . pos + = tilePos ;
if ( NoPlayerCollisionWithTile ( ) ) {
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 ;
}
void Player : : SetZ ( float z ) {
this - > z = z ;
}
bool Player : : SetPos ( vf2d pos ) {
bool resultX = SetX ( pos . x ) ;
bool resultY = SetY ( pos . y ) ;
if ( resultY & & ! resultX ) {
resultX = SetX ( pos . x ) ;
}
return resultX | resultY ;
}
vf2d & Player : : GetPos ( ) {
return pos ;
}
float Player : : GetX ( ) {
return pos . x ;
}
float Player : : GetY ( ) {
return pos . y ;
}
float Player : : GetZ ( ) {
return z ;
}
int Player : : GetHealth ( ) {
return hp ;
}
int Player : : GetMaxHealth ( ) {
return maxhp ;
}
int Player : : GetMana ( ) {
return mana ;
}
int Player : : GetMaxMana ( ) {
return maxmana ;
}
int Player : : GetAttack ( ) {
float mod_atk = atk ;
for ( Buff & b : GetBuffs ( BuffType : : ATTACK_UP ) ) {
mod_atk + = atk * b . intensity ;
}
return int ( mod_atk ) ;
}
float Player : : GetMoveSpdMult ( ) {
float mod_moveSpd = moveSpd ;
for ( Buff & b : GetBuffs ( BuffType : : SLOWDOWN ) ) {
mod_moveSpd - = mod_moveSpd * b . intensity ;
}
for ( Buff & b : GetBuffs ( BuffType : : BLOCK_SLOWDOWN ) ) {
mod_moveSpd - = mod_moveSpd * b . intensity ;
}
return mod_moveSpd ;
}
float Player : : GetSizeMult ( ) {
return size ;
}
float Player : : GetAttackRangeMult ( ) {
return attack_range ;
}
float Player : : GetSpinAngle ( ) {
return spin_angle ;
}
State : : State Player : : GetState ( ) {
return state ;
}
void Player : : Knockback ( vf2d vel ) {
this - > vel + = vel ;
}
void Player : : Update ( float fElapsedTime ) {
Ability & rightClickAbility = GetRightClickAbility ( ) ,
& ability = GetAbility1 ( ) ,
& ability2 = GetAbility2 ( ) ,
& ability3 = GetAbility3 ( ) ,
& ability4 = GetAbility4 ( ) ;
attack_cooldown_timer = std : : max ( 0.f , attack_cooldown_timer - fElapsedTime ) ;
iframe_time = std : : max ( 0.f , iframe_time - fElapsedTime ) ;
notEnoughManaDisplay . second = std : : max ( 0.f , notEnoughManaDisplay . second - fElapsedTime ) ;
notificationDisplay . second = std : : max ( 0.f , notificationDisplay . second - fElapsedTime ) ;
lastHitTimer = std : : max ( 0.f , lastHitTimer - fElapsedTime ) ;
blockTimer = std : : max ( 0.f , blockTimer - fElapsedTime ) ;
manaTickTimer - = fElapsedTime ;
if ( castInfo . castTimer > 0 ) {
castInfo . castTimer - = fElapsedTime ;
if ( castInfo . castTimer < = 0 ) {
if ( castPrepAbility - > action ( this , castInfo . castPos ) ) {
castPrepAbility - > cooldown = castPrepAbility - > COOLDOWN_TIME ;
ConsumeMana ( castPrepAbility - > manaCost ) ;
}
castInfo . castTimer = 0 ;
SetState ( State : : NORMAL ) ;
}
}
while ( manaTickTimer < = 0 ) {
manaTickTimer + = 0.2 ;
RestoreMana ( 1 ) ;
}
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 ;
}
}
//Class-specific update events.
OnUpdate ( fElapsedTime ) ;
switch ( state ) {
case State : : SPIN : {
switch ( facingDirection ) {
case UP : {
if ( lastAnimationFlip = = 0 ) {
lastAnimationFlip = 0.03 ;
facingDirection = DOWN ;
animation . ChangeState ( internal_animState , " WARRIOR_WALK_S " ) ;
}
} break ;
case DOWN : {
if ( lastAnimationFlip = = 0 ) {
lastAnimationFlip = 0.03 ;
facingDirection = UP ;
animation . ChangeState ( internal_animState , " WARRIOR_WALK_N " ) ;
}
} break ;
}
if ( facingDirection = = RIGHT ) {
spin_angle + = spin_spd * fElapsedTime ;
} else {
spin_angle - = spin_spd * fElapsedTime ;
}
if ( spin_attack_timer > 0 ) {
z = float ( " Warrior.Ability 2.SpinMaxHeight " _I ) * sin ( 3.3 * ( GROUND_SLAM_SPIN_TIME - spin_attack_timer ) / GROUND_SLAM_SPIN_TIME ) ;
spin_attack_timer = std : : max ( 0.f , spin_attack_timer - fElapsedTime ) ;
} else {
SetState ( State : : NORMAL ) ;
spin_angle = 0 ;
z = 0 ;
float numb = 4 ;
game - > HurtEnemies ( pos , " Warrior.Ability 2.Range " _F / 100 * 12 , GetAttack ( ) * " Warrior.Ability 2.DamageMult " _F , OnUpperLevel ( ) , 0 ) ;
game - > AddEffect ( std : : make_unique < Effect > ( GetPos ( ) , " Warrior.Ability 2.EffectLifetime " _F , " ground-slam-attack-front.png " , upperLevel , " Warrior.Ability 2.Range " _F / 300 * 1.33f , " Warrior.Ability 2.EffectFadetime " _F ) , std : : make_unique < Effect > ( GetPos ( ) , " Warrior.Ability 2.EffectLifetime " _F , " ground-slam-attack-back.png " , upperLevel , " Warrior.Ability 2.Range " _F / 300 * 1.33f , " Warrior.Ability 2.EffectFadetime " _F ) ) ;
}
if ( lastAnimationFlip > 0 ) {
lastAnimationFlip = std : : max ( 0.f , lastAnimationFlip - fElapsedTime ) ;
}
animation . UpdateState ( internal_animState , fElapsedTime ) ;
} break ;
case State : : BLOCK : {
if ( blockTimer < = 0 ) {
SetState ( State : : NORMAL ) ;
}
} break ;
case State : : SWING_SONIC_SWORD : {
if ( ability3 . COOLDOWN_TIME - ability3 . cooldown > 0.5 ) {
SetState ( State : : NORMAL ) ;
switch ( facingDirection ) {
case DOWN : {
UpdateAnimation ( " WARRIOR_IDLE_S " ) ;
} break ;
case RIGHT : {
UpdateAnimation ( " WARRIOR_IDLE_E " ) ;
} break ;
case LEFT : {
UpdateAnimation ( " WARRIOR_IDLE_W " ) ;
} break ;
case UP : {
UpdateAnimation ( " WARRIOR_IDLE_N " ) ;
} break ;
}
}
animation . UpdateState ( internal_animState , fElapsedTime ) ;
} break ;
case State : : TELEPORT : {
teleportAnimationTimer = std : : max ( 0.f , teleportAnimationTimer - fElapsedTime ) ;
if ( teleportAnimationTimer < = 0 ) {
SetPos ( teleportTarget ) ;
SetState ( State : : NORMAL ) ;
}
animation . UpdateState ( internal_animState , fElapsedTime ) ;
} break ;
default : {
//Update animations normally.
animation . UpdateState ( internal_animState , fElapsedTime ) ;
}
}
rightClickAbility . cooldown - = fElapsedTime ;
ability . cooldown - = fElapsedTime ;
ability2 . cooldown - = fElapsedTime ;
ability3 . cooldown - = fElapsedTime ;
ability4 . cooldown - = fElapsedTime ;
if ( rightClickAbility . cooldown < 0 ) {
rightClickAbility . cooldown = 0 ;
}
if ( ability . cooldown < 0 ) {
ability . cooldown = 0 ;
}
if ( ability2 . cooldown < 0 ) {
ability2 . cooldown = 0 ;
}
if ( ability3 . cooldown < 0 ) {
ability3 . cooldown = 0 ;
}
if ( ability4 . cooldown < 0 ) {
ability4 . cooldown = 0 ;
}
for ( Monster & m : MONSTER_LIST ) {
if ( ! HasIframes ( ) & & abs ( m . GetZ ( ) - GetZ ( ) ) < = 1 & & OnUpperLevel ( ) = = m . OnUpperLevel ( ) & & geom2d : : overlaps ( geom2d : : circle ( pos , 12 * size / 2 ) , geom2d : : circle ( m . GetPos ( ) , 12 * m . GetSizeMult ( ) / 2 ) ) ) {
if ( m . IsAlive ( ) ) {
m . Collision ( this ) ;
}
geom2d : : line line ( pos , m . GetPos ( ) ) ;
float dist = line . length ( ) ;
if ( dist < = 0.001 ) {
m . SetPos ( m . GetPos ( ) + vf2d { util : : random ( 2 ) - 1 , util : : random ( 2 ) - 1 } ) ;
} else {
m . SetPos ( line . rpoint ( dist * 1.1 ) ) ;
}
if ( m . IsAlive ( ) ) {
vel = line . vector ( ) . norm ( ) * - 128 ;
}
}
}
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 } ) {
float newX = pos . x + vel . x * fElapsedTime ;
float newY = pos . y + vel . y * fElapsedTime ;
SetX ( newX ) ;
SetY ( newY ) ;
}
if ( Menu : : stack . empty ( ) ) {
if ( CanAct ( ) & & attack_cooldown_timer = = 0 & & Crawler : : KEY_ATTACK . Held ( ) ) {
AutoAttack ( ) ;
}
auto AllowedToCast = [ & ] ( Ability & ability ) { return ! ability . precastInfo . precastTargetingRequired & & GetState ( ) ! = State : : ANIMATION_LOCK ; } ;
//If pressed is set to false, uses held instead.
auto CheckAndPerformAbility = [ & ] ( Ability & ability , InputGroup key ) {
if ( ability . name ! = " ??? " ) {
if ( CanAct ( ability ) ) {
if ( ability . cooldown = = 0 & & GetMana ( ) > = ability . manaCost ) {
if ( key . Held ( ) | | key . Released ( ) & & & ability = = castPrepAbility & & GetState ( ) = = State : : PREP_CAST ) {
if ( GetState ( ) = = State : : CASTING ) {
SetState ( State : : NORMAL ) ;
castInfo . castTimer = castInfo . castTotalTime = 0 ;
}
if ( AllowedToCast ( ability ) & & ability . action ( this , { } ) ) {
ability . cooldown = ability . COOLDOWN_TIME ;
ConsumeMana ( ability . manaCost ) ;
} else
if ( ability . precastInfo . precastTargetingRequired & & GetState ( ) = = State : : NORMAL ) {
PrepareCast ( ability ) ;
}
}
} else
if ( ability . cooldown = = 0 & & GetMana ( ) < ability . manaCost & & key . Pressed ( ) ) {
notEnoughManaDisplay = { ability . name , 1 } ;
}
}
}
} ;
CheckAndPerformAbility ( GetAbility1 ( ) , Player : : KEY_ABILITY1 ) ;
CheckAndPerformAbility ( GetAbility2 ( ) , Player : : KEY_ABILITY2 ) ;
CheckAndPerformAbility ( GetAbility3 ( ) , Player : : KEY_ABILITY3 ) ;
CheckAndPerformAbility ( GetAbility4 ( ) , Player : : KEY_ABILITY4 ) ;
CheckAndPerformAbility ( GetRightClickAbility ( ) , Player : : KEY_DEFENSIVE ) ;
if ( GetState ( ) = = State : : PREP_CAST ) {
# define CheckAbilityKeyReleasedAndCastSpell(ability,releaseState) \
if ( & ability = = castPrepAbility & & releaseState ) { CastSpell ( ability ) ; }
CheckAbilityKeyReleasedAndCastSpell ( rightClickAbility , Player : : KEY_DEFENSIVE . Released ( ) )
else
CheckAbilityKeyReleasedAndCastSpell ( ability , Player : : KEY_ABILITY1 . Released ( ) )
else
CheckAbilityKeyReleasedAndCastSpell ( ability2 , Player : : KEY_ABILITY2 . Released ( ) )
else
CheckAbilityKeyReleasedAndCastSpell ( ability3 , Player : : KEY_ABILITY3 . Released ( ) )
else
CheckAbilityKeyReleasedAndCastSpell ( ability4 , Player : : KEY_ABILITY4 . Released ( ) )
}
}
# pragma region Warrior
switch ( GetState ( ) ) {
case State : : SWING_SWORD : {
switch ( GetFacingDirection ( ) ) {
case UP : {
UpdateAnimation ( " WARRIOR_SWINGSWORD_N " ) ;
} break ;
case DOWN : {
UpdateAnimation ( " WARRIOR_SWINGSWORD_S " ) ;
} break ;
case LEFT : {
UpdateAnimation ( " WARRIOR_SWINGSWORD_W " ) ;
} break ;
case RIGHT : {
UpdateAnimation ( " WARRIOR_SWINGSWORD_E " ) ;
} break ;
}
SetSwordSwingTimer ( GetSwordSwingTimer ( ) - fElapsedTime ) ;
if ( GetSwordSwingTimer ( ) < = 0 ) {
SetSwordSwingTimer ( 0 ) ;
SetState ( State : : NORMAL ) ;
}
} break ;
}
# pragma endregion
# pragma region Ranger
if ( GetState ( ) = = State : : SHOOT_ARROW ) {
if ( attack_cooldown_timer < = ARROW_ATTACK_COOLDOWN - 0.3 ) {
SetState ( State : : NORMAL ) ;
}
}
if ( retreatTimer > 0 ) {
SetZ ( 6 * sin ( PI / RETREAT_TIME * retreatTimer ) ) ;
retreatTimer - = fElapsedTime ;
if ( retreatTimer < = 0 ) {
SetVelocity ( { } ) ;
SetZ ( 0 ) ;
SetState ( State : : NORMAL ) ;
}
}
if ( ghostRemoveTimer > 0 ) {
ghostRemoveTimer - = fElapsedTime ;
if ( ghostRemoveTimer < = 0 ) {
if ( ghostPositions . size ( ) > 0 ) {
ghostPositions . erase ( ghostPositions . begin ( ) ) ;
if ( ghostPositions . size ( ) > 0 ) {
ghostRemoveTimer = RETREAT_GHOST_FRAME_DELAY ;
}
}
}
}
if ( ghostFrameTimer > 0 ) {
ghostFrameTimer - = fElapsedTime ;
if ( ghostFrameTimer < = 0 & & GetState ( ) = = State : : RETREAT ) {
ghostPositions . push_back ( GetPos ( ) + vf2d { 0 , - GetZ ( ) } ) ;
ghostFrameTimer = RETREAT_GHOST_FRAME_DELAY ;
}
}
if ( rapidFireTimer > 0 ) {
rapidFireTimer - = fElapsedTime ;
if ( rapidFireTimer < = 0 ) {
if ( remainingRapidFireShots > 0 ) {
remainingRapidFireShots - - ;
geom2d : : line pointTowardsCursor ( GetPos ( ) , game - > GetWorldMousePos ( ) ) ;
vf2d extendedLine = pointTowardsCursor . upoint ( 1.1 ) ;
float angleToCursor = atan2 ( extendedLine . y - GetPos ( ) . y , extendedLine . x - GetPos ( ) . x ) ;
attack_cooldown_timer = ARROW_ATTACK_COOLDOWN ;
BULLET_LIST . push_back ( std : : make_unique < Arrow > ( Arrow ( GetPos ( ) , extendedLine , vf2d { cos ( angleToCursor ) * " Ranger.Ability 1.ArrowSpd " _F , float ( sin ( angleToCursor ) * " Ranger.Ability 1.ArrowSpd " _F - PI / 8 * " Ranger.Ability 1.ArrowSpd " _F ) } + movementVelocity , 12 * " Ranger.Ability 1.ArrowRadius " _F / 100 , GetAttack ( ) * " Ranger.Ability 1.DamageMult " _F , OnUpperLevel ( ) , true ) ) ) ;
SetAnimationBasedOnTargetingDirection ( angleToCursor ) ;
rapidFireTimer = RAPID_FIRE_SHOOT_DELAY ;
} else {
SetState ( State : : NORMAL ) ;
}
}
}
# pragma endregion
}
float Player : : GetSwordSwingTimer ( ) {
return swordSwingTimer ;
}
void Player : : SetSwordSwingTimer ( float val ) {
swordSwingTimer = val ;
}
void Player : : SetState ( State : : State newState ) {
if ( GetState ( ) = = State : : BLOCK ) {
RemoveAllBuffs ( BuffType : : BLOCK_SLOWDOWN ) ;
}
state = newState ;
}
vf2d Player : : GetVelocity ( ) {
return vel ;
}
bool Player : : CanMove ( ) {
return state ! = State : : CASTING & & state ! = State : : ANIMATION_LOCK ;
}
bool Player : : CanAct ( ) {
Ability dummyAbility ;
return CanAct ( dummyAbility ) ;
}
bool Player : : CanAct ( Ability & ability ) {
return ( ability . canCancelCast | | state ! = State : : CASTING ) & & state ! = State : : ANIMATION_LOCK & & GameState : : STATE = = GameState : : states [ States : : GAME_RUN ] ;
}
bool Player : : HasIframes ( ) {
return iframe_time > 0 ;
}
bool Player : : Hurt ( int damage , bool onUpperLevel , float z ) {
if ( hp < = 0 | | HasIframes ( ) | | OnUpperLevel ( ) ! = onUpperLevel | | abs ( GetZ ( ) - z ) > 1 ) return false ;
if ( GetState ( ) = = State : : BLOCK ) damage * = 1 - " Warrior.Right Click Ability.DamageReduction " _F ;
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 ) , true ) ;
DAMAGENUMBER_LIST . push_back ( damageNumberPtr ) ;
}
lastHitTimer = 0.05 ;
return true ;
}
void Player : : AddAnimation ( std : : string state ) {
animation . AddState ( state , ANIMATION_DATA . at ( state ) ) ;
}
void Player : : UpdateAnimation ( std : : string animState , int specificClass ) {
if ( specificClass = = ANY | | specificClass & GetClass ( ) ) {
animation . ChangeState ( internal_animState , animState ) ;
}
}
Animate2D : : Frame Player : : GetFrame ( ) {
return animation . GetFrame ( internal_animState ) ;
}
void Player : : SetLastReleasedMovementKey ( Key k ) {
lastReleasedMovementKey = k ;
}
Key Player : : GetLastReleasedMovementKey ( ) {
return lastReleasedMovementKey ;
}
void Player : : SetFacingDirection ( Key direction ) {
facingDirection = direction ;
}
Key Player : : GetFacingDirection ( ) {
return facingDirection ;
}
void Player : : Moved ( ) {
for ( MonsterSpawner & spawner : SPAWNER_LIST ) {
if ( ! spawner . SpawnTriggered ( ) & & spawner . DoesUpperLevelSpawning ( ) = = OnUpperLevel ( ) & & geom2d : : contains ( geom2d : : rect < float > { spawner . GetPos ( ) , spawner . GetRange ( ) } , pos ) ) {
spawner . SetTriggered ( true ) ;
}
}
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 ;
}
}
}
void Player : : Spin ( float duration , float spinSpd ) {
SetState ( State : : SPIN ) ;
spin_attack_timer = duration ;
spin_spd = spinSpd ;
spin_angle = 0 ;
}
void Player : : UpdateWalkingAnimation ( Key direction ) {
std : : string anim ;
switch ( direction ) {
case UP : anim = GetWalkNAnimation ( ) ; break ;
case RIGHT : anim = GetWalkEAnimation ( ) ; break ;
case DOWN : anim = GetWalkSAnimation ( ) ; break ;
case LEFT : anim = GetWalkWAnimation ( ) ; break ;
}
UpdateAnimation ( anim ) ;
}
void Player : : UpdateIdleAnimation ( Key direction ) {
std : : string anim ;
switch ( direction ) {
case UP : anim = GetIdleNAnimation ( ) ; break ;
case RIGHT : anim = GetIdleEAnimation ( ) ; break ;
case DOWN : anim = GetIdleSAnimation ( ) ; break ;
case LEFT : anim = GetIdleWAnimation ( ) ; break ;
}
UpdateAnimation ( anim ) ;
}
void Player : : AddBuff ( BuffType type , float duration , float intensity ) {
buffList . push_back ( Buff { type , duration , intensity } ) ;
}
bool Player : : OnUpperLevel ( ) {
return upperLevel ;
}
std : : vector < Buff > Player : : 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 ;
}
void Player : : RemoveBuff ( BuffType buff ) {
for ( auto it = buffList . begin ( ) ; it ! = buffList . end ( ) ; + + it ) {
Buff & b = * it ;
if ( b . type = = buff ) {
buffList . erase ( it ) ;
return ;
}
}
}
void Player : : RemoveAllBuffs ( BuffType buff ) {
std : : erase_if ( buffList , [ & ] ( Buff & b ) { return b . type = = buff ; } ) ;
}
void Player : : RemoveAllBuffs ( ) {
buffList . clear ( ) ;
}
void Player : : CastSpell ( Ability & ability ) {
vf2d castPosition = game - > GetWorldMousePos ( ) ;
float distance = sqrt ( pow ( GetX ( ) - game - > GetWorldMousePos ( ) . x , 2 ) + pow ( GetY ( ) - game - > GetWorldMousePos ( ) . y , 2 ) ) ;
if ( distance > ability . precastInfo . range ) { //Clamp the distance.
vf2d pointToCursor = { game - > GetWorldMousePos ( ) . x - GetX ( ) , game - > GetWorldMousePos ( ) . y - GetY ( ) } ;
pointToCursor = pointToCursor . norm ( ) * ability . precastInfo . range ;
castPosition = GetPos ( ) + pointToCursor ;
}
castInfo = { ability . name , ability . precastInfo . castTime , ability . precastInfo . castTime , castPosition } ;
SetState ( State : : CASTING ) ;
}
CastInfo & Player : : GetCastInfo ( ) {
return castInfo ;
}
bool Player : : CanPathfindTo ( vf2d pos , vf2d targetPos , float range ) {
if ( targetPos . x < 0 | | targetPos . y < 0 | | targetPos . x > game - > GetCurrentMap ( ) . width * game - > GetCurrentMap ( ) . tilewidth | | targetPos . y > game - > GetCurrentMap ( ) . height * game - > GetCurrentMap ( ) . tileheight ) return false ;
std : : vector < vf2d > pathing = game - > pathfinder . Solve_AStar ( pos , targetPos , range , upperLevel ) ;
return pathing . size ( ) > 0 & & pathing . size ( ) < range ; //We'll say 7 tiles or less is close enough to 650 range. Have a little bit of wiggle room.
}
void Player : : PrepareCast ( Ability & ability ) {
castPrepAbility = & ability ;
if ( ability . precastInfo . range > 0 ) {
SetState ( State : : PREP_CAST ) ;
} else {
CastSpell ( ability ) ;
}
}
void Player : : SetVelocity ( vf2d vel ) {
this - > vel = vel ;
}
void Player : : SetAnimationBasedOnTargetingDirection ( float targetDirection ) {
if ( GetClass ( ) = = Class : : RANGER ) {
if ( targetDirection < = PI / 4 & & targetDirection > - PI / 4 ) {
UpdateAnimation ( " RANGER_SHOOT_E " ) ;
} else
if ( targetDirection > = 3 * PI / 4 | | targetDirection < - 3 * PI / 4 ) {
UpdateAnimation ( " RANGER_SHOOT_W " ) ;
} else
if ( targetDirection < = 3 * PI / 4 & & targetDirection > PI / 4 ) {
UpdateAnimation ( " RANGER_SHOOT_S " ) ;
} else
if ( targetDirection > = - 3 * PI / 4 & & targetDirection < - PI / 4 ) {
UpdateAnimation ( " RANGER_SHOOT_N " ) ;
}
}
}
void Player : : SetIframes ( float duration ) {
iframe_time = duration ;
}
bool Player : : Heal ( int damage ) {
hp = std : : clamp ( hp + damage , 0 , maxhp ) ;
return true ;
}
void Player : : RestoreMana ( int amt ) {
mana = std : : clamp ( mana + amt , 0 , maxmana ) ;
}
void Player : : ConsumeMana ( int amt ) {
mana = std : : clamp ( mana - amt , 0 , maxmana ) ;
}
void Player : : SetSizeMult ( float size ) {
this - > size = size ;
}
Item & Player : : GetLoadoutItem ( int slot ) {
if ( slot < 0 | | slot > loadout . size ( ) - 1 ) ERR ( " Invalid inventory slot " + std : : to_string ( slot ) + " , please choose a slot in range (0- " + std : : to_string ( loadout . size ( ) - 1 ) + " ). " ) ;
return loadout [ slot ] ;
}
void Player : : SetLoadoutItem ( int slot , std : : string itemName ) {
if ( slot < 0 | | slot > loadout . size ( ) - 1 ) ERR ( " Invalid inventory slot " + std : : to_string ( slot ) + " , please choose a slot in range (0- " + std : : to_string ( loadout . size ( ) - 1 ) + " ). " ) ;
if ( Inventory : : GetItemCount ( itemName ) > 0 ) {
GetLoadoutItem ( slot ) = Inventory : : GetItem ( itemName ) ;
GetLoadoutItem ( slot ) . amt = std : : min ( ( uint32_t ) " Player.Item Loadout Limit " _I , GetLoadoutItem ( slot ) . Amt ( ) ) ; //We are only allowed to have up to 10 maximum of an item on a journey.
} else {
ERR ( " Trying to set item " + itemName + " in Loadout slot " + std : : to_string ( slot ) + " when said item does not exist in our inventory! " ) ;
}
}
void Player : : UseLoadoutItem ( int slot ) {
if ( slot < 0 | | slot > loadout . size ( ) - 1 ) ERR ( " Invalid inventory slot " + std : : to_string ( slot ) + " , please choose a slot in range (0- " + std : : to_string ( loadout . size ( ) - 1 ) + " ). " ) ;
if ( GetLoadoutItem ( slot ) . Amt ( ) > 0 ) {
Inventory : : UseItem ( loadout [ slot ] . Name ( ) ) ;
GetLoadoutItem ( slot ) . OnUseAction ( ) ;
GetLoadoutItem ( slot ) . amt - - ;
}
}
void Player : : ClearLoadoutItem ( int slot ) {
if ( slot < 0 | | slot > loadout . size ( ) - 1 ) ERR ( " Invalid inventory slot " + std : : to_string ( slot ) + " , please choose a slot in range (0- " + std : : to_string ( loadout . size ( ) - 1 ) + " ). " ) ;
loadout [ slot ] = { } ;
}