# 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 © 2023 The FreeType
Project ( www . freetype . org ) . Please see LICENSE_FT . txt for more information .
All rights reserved .
*/
# pragma endregion
# include "Monster.h"
# include "Player.h"
# include "AdventuresInLestoria.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 "MenuComponent.h"
# include "config.h"
# include <string_view>
# include "SoundEffect.h"
# include "olcPGEX_Gamepad.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 ;
InputGroup Player : : KEY_ITEM1 ;
InputGroup Player : : KEY_ITEM2 ;
InputGroup Player : : KEY_ITEM3 ;
std : : vector < std : : weak_ptr < MenuComponent > > Player : : moneyListeners ;
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 ;
SetBaseStat ( " Health " , hp ) ;
SetBaseStat ( " Mana " , mana ) ;
SetBaseStat ( " Defense " , 0 ) ;
SetBaseStat ( " Attack " , " Warrior.BaseAtk " _I ) ;
SetBaseStat ( " Move Spd % " , 100 ) ;
SetBaseStat ( " CDR " , 0 ) ;
SetBaseStat ( " Crit Rate " , " Player.Crit Rate " _F ) ;
SetBaseStat ( " Crit Dmg " , " Player.Crit Dmg " _F ) ;
SetBaseStat ( " Health % " , 0 ) ;
SetBaseStat ( " HP6 Recovery % " , 0 ) ;
cooldownSoundInstance = Audio : : Engine ( ) . LoadSound ( " spell_cast.ogg " _SFX ) ;
}
void Player : : ForceSetPos ( vf2d pos ) {
this - > pos = pos ;
Moved ( ) ;
}
bool Player : : _SetX ( float x , const bool playerInvoked ) {
vf2d newPos = { x , pos . y } ;
vi2d tilePos = vi2d ( newPos / float ( game - > GetCurrentMapData ( ) . tilewidth ) ) * game - > GetCurrentMapData ( ) . tilewidth ;
geom2d : : rect < float > collisionRect = game - > GetTileCollision ( game - > GetCurrentLevel ( ) , newPos , upperLevel ) ;
# pragma region lambdas
auto NoTileCollisionExistsHere = [ & ] ( ) { return collisionRect = = game - > NO_COLLISION ; } ;
# pragma endregion
if ( NoTileCollisionExistsHere ( ) ) {
pos . x = std : : clamp ( x , game - > GetCurrentMapData ( ) . tilewidth / 2.f * GetSizeMult ( ) , float ( game - > GetCurrentMapData ( ) . width * game - > GetCurrentMapData ( ) . tilewidth - game - > GetCurrentMapData ( ) . tilewidth / 2.f * GetSizeMult ( ) ) ) ;
Moved ( ) ;
return true ;
} else {
geom2d : : rect < float > collision = { collisionRect . pos , collisionRect . size } ;
# pragma region lambdas
auto NoPlayerCollisionWithTile = [ & ] ( ) { return ! geom2d : : overlaps ( geom2d : : circle < float > ( newPos , 4 ) , collision ) ; } ;
# pragma endregion
collision . pos + = tilePos ;
if ( NoPlayerCollisionWithTile ( ) ) {
pos . x = std : : clamp ( x , game - > GetCurrentMapData ( ) . tilewidth / 2.f * GetSizeMult ( ) , float ( game - > GetCurrentMapData ( ) . width * game - > GetCurrentMapData ( ) . tilewidth - game - > GetCurrentMapData ( ) . tilewidth / 2.f * GetSizeMult ( ) ) ) ;
Moved ( ) ;
return true ;
} else
if ( playerInvoked ) { //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 ( NoPlayerCollisionWithTile ( ) ) {
return _SetY ( pos . y + pushDir . y * game - > GetElapsedTime ( ) * 12 , false ) ;
}
}
}
return false ;
} ;
bool Player : : _SetY ( float y , const bool playerInvoked ) {
vf2d newPos = { pos . x , y } ;
vi2d tilePos = vi2d ( newPos / float ( game - > GetCurrentMapData ( ) . tilewidth ) ) * game - > GetCurrentMapData ( ) . tilewidth ;
geom2d : : rect < float > collisionRect = game - > GetTileCollision ( game - > GetCurrentLevel ( ) , newPos , upperLevel ) ;
# pragma region lambdas
auto NoTileCollisionExistsHere = [ & ] ( ) { return collisionRect = = game - > NO_COLLISION ; } ;
# pragma endregion
if ( NoTileCollisionExistsHere ( ) ) {
pos . y = std : : clamp ( y , game - > GetCurrentMapData ( ) . tilewidth / 2.f * GetSizeMult ( ) , float ( game - > GetCurrentMapData ( ) . height * game - > GetCurrentMapData ( ) . tilewidth - game - > GetCurrentMapData ( ) . tilewidth / 2.f * GetSizeMult ( ) ) ) ;
Moved ( ) ;
return true ;
} else {
geom2d : : rect < float > collision = { collisionRect . pos , collisionRect . size } ;
# pragma region lambdas
auto NoPlayerCollisionWithTile = [ & ] ( ) { return ! geom2d : : overlaps ( geom2d : : circle < float > ( newPos , 4 ) , collision ) ; } ;
# pragma endregion
collision . pos + = tilePos ;
if ( NoPlayerCollisionWithTile ( ) ) {
pos . y = std : : clamp ( y , game - > GetCurrentMapData ( ) . tilewidth / 2.f * GetSizeMult ( ) , float ( game - > GetCurrentMapData ( ) . height * game - > GetCurrentMapData ( ) . tilewidth - game - > GetCurrentMapData ( ) . tilewidth / 2.f * GetSizeMult ( ) ) ) ;
Moved ( ) ;
return true ;
} else
if ( playerInvoked ) { //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 ( NoPlayerCollisionWithTile ( ) ) {
return _SetX ( pos . x + pushDir . x * game - > GetElapsedTime ( ) * 12 , false ) ;
}
}
}
return false ;
}
bool Player : : SetX ( float x ) {
return _SetX ( x ) ;
}
bool Player : : SetY ( float y ) {
return _SetY ( y ) ;
}
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 ;
}
const int Player : : GetHealth ( ) const {
return hp ;
}
const float & Player : : GetMaxHealth ( ) const {
return GetStat ( " Health " ) ;
}
const int Player : : GetMana ( ) const {
return mana ;
}
const int Player : : GetMaxMana ( ) const {
return GetStat ( " Mana " ) ;
}
const int Player : : GetAttack ( ) {
float mod_atk = float ( GetStat ( " Attack " ) ) ;
mod_atk + = Get ( " Attack " ) ;
mod_atk + = Get ( " Attack % " ) ;
return int ( mod_atk ) ;
}
float Player : : GetMoveSpdMult ( ) {
float moveSpdPct = GetStat ( " Move Spd % " ) / 100.f ;
float mod_moveSpd = moveSpdPct ;
mod_moveSpd + = ItemAttribute : : Get ( " Move Spd % " , this ) ;
for ( const Buff & b : GetBuffs ( BuffType : : SLOWDOWN ) ) {
mod_moveSpd - = moveSpdPct * b . intensity ;
}
for ( const Buff & b : GetBuffs ( BuffType : : BLOCK_SLOWDOWN ) ) {
mod_moveSpd - = moveSpdPct * b . intensity ;
}
for ( const Buff & b : GetBuffs ( LOCKON_SPEEDBOOST ) ) {
mod_moveSpd + = moveSpdPct * b . intensity ;
}
for ( const Buff & b : GetBuffs ( SPEEDBOOST ) ) {
mod_moveSpd + = moveSpdPct * 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 ( ) ,
& item1 = useItem1 ,
& item2 = useItem2 ,
& item3 = useItem3 ;
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 ) ;
if ( hurtRumbleTime > 0.f ) {
hurtRumbleTime = std : : max ( 0.f , hurtRumbleTime - fElapsedTime ) ;
if ( hurtRumbleTime = = 0.f ) {
Input : : StopVibration ( ) ;
}
}
blockTimer = std : : max ( 0.f , blockTimer - fElapsedTime ) ;
lastCombatTime = lastCombatTime + fElapsedTime ;
manaTickTimer - = fElapsedTime ;
hpRecoveryTimer = std : : max ( 0.f , hpRecoveryTimer - fElapsedTime ) ;
hp6RecoveryTimer = std : : max ( 0.f , hp6RecoveryTimer - fElapsedTime ) ;
hp4RecoveryTimer = std : : max ( 0.f , hp4RecoveryTimer - fElapsedTime ) ;
PerformHPRecovery ( ) ;
CheckEndZoneCollision ( ) ;
if ( castInfo . castTimer > 0 ) {
castInfo . castTimer - = fElapsedTime ;
if ( castInfo . castTimer < = 0 ) {
bool allowed = castPrepAbility - > actionPerformedDuringCast ;
if ( ! allowed & & castPrepAbility - > action ( this , castInfo . castPos ) ) allowed = true ;
if ( allowed ) {
castPrepAbility - > cooldown = castPrepAbility - > GetCooldownTime ( ) ;
ConsumeMana ( castPrepAbility - > manaCost ) ;
}
castInfo . castTimer = 0 ;
SetState ( State : : NORMAL ) ;
}
}
if ( state = = State : : CASTING ) {
if ( ! Audio : : Engine ( ) . IsPlaying ( cooldownSoundInstance ) ) {
Audio : : Engine ( ) . SetVolume ( cooldownSoundInstance , " Audio.Casting Sound Volume " _F / 100.f ) ;
Audio : : Engine ( ) . Play ( cooldownSoundInstance , true ) ;
}
} else {
Audio : : Engine ( ) . Stop ( cooldownSoundInstance ) ;
}
while ( manaTickTimer < = 0 ) {
manaTickTimer + = 0.2f ;
RestoreMana ( 1 , true ) ;
}
for ( Buff & b : buffList ) {
b . duration - = fElapsedTime ;
if ( b . nextTick > 0 & & b . duration < b . nextTick ) {
b . repeatAction ( game , b . intensity ) ;
b . nextTick - = b . timeBetweenTicks ;
}
}
std : : erase_if ( buffList , [ ] ( Buff & b ) { return b . duration < = 0 ; } ) ;
//Class-specific update events.
OnUpdate ( fElapsedTime ) ;
switch ( state ) {
case State : : SPIN : {
switch ( facingDirection ) {
case UP : {
if ( lastAnimationFlip = = 0 ) {
lastAnimationFlip = 0.03f ;
facingDirection = DOWN ;
animation . ChangeState ( internal_animState , " WARRIOR_WALK_S " ) ;
}
} break ;
case DOWN : {
if ( lastAnimationFlip = = 0 ) {
lastAnimationFlip = 0.03f ;
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.3f * ( 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 , int ( 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 ) ) ;
SoundEffect : : PlaySFX ( " Warrior Ground Slam " , SoundEffect : : CENTERED ) ;
}
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 . GetCooldownTime ( ) - 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 ) {
ForceSetPos ( teleportTarget ) ;
SetState ( State : : NORMAL ) ;
}
animation . UpdateState ( internal_animState , fElapsedTime ) ;
} break ;
default : {
//Update animations normally.
animation . UpdateState ( internal_animState , fElapsedTime ) ;
}
}
float cooldownMultiplier ;
if ( game - > GetPlayer ( ) - > GetCooldownReductionPct ( ) > = 1.0f ) {
cooldownMultiplier = 999999 ;
} else {
cooldownMultiplier = 1 / ( 1 - game - > GetPlayer ( ) - > GetCooldownReductionPct ( ) ) ;
}
rightClickAbility . cooldown - = fElapsedTime * cooldownMultiplier ;
ability . cooldown - = fElapsedTime * cooldownMultiplier ;
ability2 . cooldown - = fElapsedTime * cooldownMultiplier ;
ability3 . cooldown - = fElapsedTime * cooldownMultiplier ;
ability4 . cooldown - = fElapsedTime * cooldownMultiplier ;
item1 . cooldown - = fElapsedTime ;
item2 . cooldown - = fElapsedTime ;
item3 . 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 ;
}
if ( item1 . cooldown < 0 ) {
item1 . cooldown = 0 ;
}
if ( item2 . cooldown < 0 ) {
item2 . cooldown = 0 ;
}
if ( item3 . cooldown < 0 ) {
item3 . 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.1f ) ) ;
}
if ( m . IsAlive ( ) & & ! m . IsNPC ( ) ) { //Don't set the knockback if this monster is actually an NPC. Let's just push them around.
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 & & AiL : : 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 ( AllowedToCast ( ability ) & & ability . action ( this , { } ) ) {
bool allowed = ability . actionPerformedDuringCast ;
ability . cooldown = ability . GetCooldownTime ( ) ;
CancelCast ( ) ;
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.f } ;
}
} else
if ( key . Released ( ) | | ! key . Held ( ) ) ability . waitForRelease = false ;
}
} ;
CheckAndPerformAbility ( rightClickAbility , Player : : KEY_DEFENSIVE ) ;
CheckAndPerformAbility ( ability , Player : : KEY_ABILITY1 ) ;
CheckAndPerformAbility ( ability2 , Player : : KEY_ABILITY2 ) ;
CheckAndPerformAbility ( ability3 , Player : : KEY_ABILITY3 ) ;
CheckAndPerformAbility ( ability4 , Player : : KEY_ABILITY4 ) ;
CheckAndPerformAbility ( item1 , Player : : KEY_ITEM1 ) ;
CheckAndPerformAbility ( item2 , Player : : KEY_ITEM2 ) ;
CheckAndPerformAbility ( item3 , Player : : KEY_ITEM3 ) ;
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 ( ) )
else
CheckAbilityKeyReleasedAndCastSpell ( item1 , Player : : KEY_ITEM1 . Released ( ) )
else
CheckAbilityKeyReleasedAndCastSpell ( item2 , Player : : KEY_ITEM2 . Released ( ) )
else
CheckAbilityKeyReleasedAndCastSpell ( item3 , Player : : KEY_ITEM3 . 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 ( ) , GetWorldAimingLocation ( ) ) ;
vf2d extendedLine = pointTowardsCursor . upoint ( 1.1f ) ;
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 / 1.5f , 12 * " Ranger.Ability 1.ArrowRadius " _F / 100 , int ( GetAttack ( ) * " Ranger.Ability 1.DamageMult " _F ) , OnUpperLevel ( ) , true ) ) ) ;
SetAnimationBasedOnTargetingDirection ( angleToCursor ) ;
rapidFireTimer = RAPID_FIRE_SHOOT_DELAY ;
} else {
SetState ( State : : NORMAL ) ;
}
}
}
# pragma endregion
# 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
}
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 knockUpTimer = = 0.f & & state ! = State : : ANIMATION_LOCK & & ( state ! = State : : CASTING | | ( castInfo . castTotalTime - castInfo . castTimer > 0.2f ) ) ;
}
bool Player : : CanAct ( ) {
Ability dummyAbility ;
return CanAct ( dummyAbility ) ;
}
bool Player : : CanAct ( Ability & ability ) {
return knockUpTimer = = 0 & & ! ability . waitForRelease & & ( ability . canCancelCast | | state ! = State : : CASTING ) & & state ! = State : : ANIMATION_LOCK & &
( GameState : : STATE = = GameState : : states [ States : : GAME_RUN ]
| | GameState : : STATE = = GameState : : states [ States : : GAME_HUB ] & & ! ability . itemAbility ) ;
}
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 ;
float mod_dmg = float ( damage ) ;
if ( GetState ( ) = = State : : BLOCK ) {
mod_dmg = 0 ;
SoundEffect : : PlaySFX ( " Warrior Block Hit " , SoundEffect : : CENTERED ) ;
} else {
float otherDmgTaken = 1 - GetDamageReductionFromBuffs ( ) ;
float armorDmgTaken = 1 - GetDamageReductionFromArmor ( ) ;
lastCombatTime = 0 ;
float finalPctDmgTaken = armorDmgTaken * otherDmgTaken ;
if ( finalPctDmgTaken < = 6. _Pct ) {
std : : cout < < " WARNING! Damage Reduction has somehow ended up below 6%, which is over the cap! " < < std : : endl ;
}
finalPctDmgTaken = std : : max ( 6.25 _Pct , finalPctDmgTaken ) ; //Apply Damage Cap.
float minPctDmgReduction = 0.05 _Pct * GetStat ( " Defense " ) ;
float finalPctDmgReduction = 1 - finalPctDmgTaken ;
float pctDmgReductionDiff = finalPctDmgReduction - minPctDmgReduction ;
float dmgRoll = minPctDmgReduction + util : : random ( pctDmgReductionDiff ) ;
mod_dmg * = 1 - dmgRoll ;
mod_dmg = std : : ceil ( mod_dmg ) ;
SoundEffect : : PlaySFX ( " Player Hit " , SoundEffect : : CENTERED ) ;
}
hp = std : : max ( 0 , hp - int ( mod_dmg ) ) ;
hurtRumbleTime = " Player.Hurt Rumble Time " _F ;
Input : : StartVibration ( ) ;
if ( lastHitTimer > 0 ) {
damageNumberPtr . get ( ) - > damage + = int ( mod_dmg ) ;
damageNumberPtr . get ( ) - > pauseTime = 0.4f ;
damageNumberPtr . get ( ) - > RecalculateSize ( ) ;
} else {
damageNumberPtr = std : : make_shared < DamageNumber > ( pos , int ( mod_dmg ) , true ) ;
DAMAGENUMBER_LIST . push_back ( damageNumberPtr ) ;
}
lastHitTimer = 0.05f ;
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 : : CancelCast ( ) {
bool wasCasting = castInfo . castTimer > 0 ;
castInfo = { " " , 0 } ;
std : : erase_if ( buffList , [ ] ( Buff & b ) { return b . type = = RESTORATION_DURING_CAST ; } ) ; //Remove all buffs that would be applied during a cast, as we got interrupted.
if ( wasCasting ) {
DAMAGENUMBER_LIST . push_back ( std : : make_shared < DamageNumber > ( GetPos ( ) , 0 , true , INTERRUPT ) ) ;
}
}
void Player : : Moved ( ) {
if ( state = = State : : CASTING ) {
state = State : : NORMAL ;
castPrepAbility - > waitForRelease = true ;
CancelCast ( ) ;
}
for ( MonsterSpawner & spawner : SPAWNER_LIST ) {
if ( ! spawner . SpawnTriggered ( ) & & spawner . DoesUpperLevelSpawning ( ) = = OnUpperLevel ( ) & & geom2d : : contains ( geom2d : : rect < float > { spawner . GetPos ( ) , spawner . GetRange ( ) } , pos ) ) {
spawner . SetTriggered ( true ) ;
}
}
std : : map < std : : string , std : : vector < ZoneData > > & zoneData = game - > GetZoneData ( game - > GetCurrentLevel ( ) ) ;
for ( ZoneData & upperLevelZone : zoneData [ " UpperZone " ] ) {
if ( geom2d : : overlaps ( upperLevelZone . zone , pos ) ) {
upperLevel = true ;
}
}
for ( ZoneData & lowerLevelZone : zoneData [ " LowerZone " ] ) {
if ( geom2d : : overlaps ( lowerLevelZone . zone , pos ) ) {
upperLevel = false ;
}
}
EnvironmentalAudio : : UpdateEnvironmentalAudio ( ) ;
if ( ! std : : isfinite ( pos . x ) ) {
ERR ( std : : format ( " WARNING! Player X position is {}...Trying to recover. THIS SHOULD NOT BE HAPPENING! " , pos . x ) ) ;
ForceSetPos ( { float ( game - > GetCurrentMapData ( ) . playerSpawnLocation . x ) , pos . y } ) ;
}
if ( ! std : : isfinite ( pos . y ) ) {
ERR ( std : : format ( " WARNING! Player Y position is {}...Trying to recover. THIS SHOULD NOT BE HAPPENING! " , pos . y ) ) ;
ForceSetPos ( { pos . x , float ( game - > GetCurrentMapData ( ) . playerSpawnLocation . y ) } ) ;
}
}
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 } ) ;
}
void Player : : AddBuff ( BuffType type , float duration , float intensity , std : : set < ItemAttribute > attr ) {
buffList . push_back ( Buff { type , duration , intensity , attr } ) ;
}
void Player : : AddBuff ( BuffType type , float duration , float intensity , std : : set < std : : string > attr ) {
buffList . push_back ( Buff { type , duration , intensity , attr } ) ;
}
void Player : : AddBuff ( BuffType type , float duration , float intensity , float timeBetweenTicks , std : : function < void ( AiL * , int ) > repeatAction ) {
buffList . push_back ( Buff { type , duration , intensity , timeBetweenTicks , repeatAction } ) ;
}
bool Player : : OnUpperLevel ( ) {
return upperLevel ;
}
const std : : vector < Buff > Player : : GetBuffs ( BuffType buff ) const {
std : : vector < Buff > filteredBuffs ;
std : : copy_if ( buffList . begin ( ) , buffList . end ( ) , std : : back_inserter ( filteredBuffs ) , [ buff ] ( const 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 = GetWorldAimingLocation ( ) ;
float distance = float ( sqrt ( pow ( GetX ( ) - GetWorldAimingLocation ( ) . x , 2 ) + pow ( GetY ( ) - GetWorldAimingLocation ( ) . y , 2 ) ) ) ;
if ( distance > ability . precastInfo . range ) { //Clamp the distance.
vf2d pointToCursor = { GetWorldAimingLocation ( ) . x - GetX ( ) , GetWorldAimingLocation ( ) . y - GetY ( ) } ;
pointToCursor = pointToCursor . norm ( ) * ability . precastInfo . range ;
castPosition = GetPos ( ) + pointToCursor ;
}
castInfo = { ability . name , ability . precastInfo . castTime , ability . precastInfo . castTime , castPosition } ;
if ( ability . actionPerformedDuringCast ) { ability . action ( this , castPosition ) ; }
SetState ( State : : CASTING ) ;
}
CastInfo & Player : : GetCastInfo ( ) {
return castInfo ;
}
bool Player : : CanPathfindTo ( vf2d pos , vf2d targetPos , float range ) {
range * = ( 24 / game - > pathfinder . gridSpacing . x ) ;
if ( targetPos . x < 0 | | targetPos . y < 0 | | targetPos . x > game - > GetCurrentMapData ( ) . width * game - > GetCurrentMapData ( ) . tilewidth | | targetPos . y > game - > GetCurrentMapData ( ) . height * game - > GetCurrentMapData ( ) . 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 , int ( GetStat ( " Health " ) ) ) ;
if ( damage > 0 ) {
DAMAGENUMBER_LIST . push_back ( std : : make_shared < DamageNumber > ( GetPos ( ) , damage , true , HEALTH_GAIN ) ) ;
}
return true ;
}
void Player : : RestoreMana ( int amt , bool suppressDamageNumber ) {
mana = std : : clamp ( mana + amt , 0 , GetMaxMana ( ) ) ;
if ( amt > 0 & & ! suppressDamageNumber ) {
DAMAGENUMBER_LIST . push_back ( std : : make_shared < DamageNumber > ( GetPos ( ) , amt , true , MANA_GAIN ) ) ;
}
}
void Player : : ConsumeMana ( int amt ) {
mana = std : : clamp ( mana - amt , 0 , GetMaxMana ( ) ) ;
}
void Player : : SetSizeMult ( float size ) {
this - > size = size ;
}
void Player : : ResetLastCombatTime ( ) {
lastCombatTime = 0 ;
}
bool Player : : IsOutOfCombat ( ) {
return lastCombatTime > = " Player.Out of Combat Time " _F ;
}
float Player : : GetEndZoneStandTime ( ) {
return endZoneStandTime ;
}
void Player : : CheckEndZoneCollision ( ) {
auto HasZoneData = [ & ] ( ) {
return game - > GetZones ( ) . count ( " EndZone " ) ;
} ;
if ( IsOutOfCombat ( ) & & HasZoneData ( ) ) {
for ( const ZoneData & zone : game - > GetZones ( ) . at ( " EndZone " ) ) {
if ( zone . isUpper = = upperLevel & & geom2d : : overlaps ( GetPos ( ) , zone . zone ) ) {
endZoneStandTime + = game - > GetElapsedTime ( ) ;
if ( endZoneStandTime > = " Player.End Zone Wait Time " _F ) {
GameState : : ChangeState ( States : : LEVEL_COMPLETE ) ;
}
return ;
}
}
}
endZoneStandTime = 0 ;
}
void Player : : SetItem1UseFunc ( Ability a ) {
useItem1 = a ;
} ;
void Player : : SetItem2UseFunc ( Ability a ) {
useItem2 = a ;
} ;
void Player : : SetItem3UseFunc ( Ability a ) {
useItem3 = a ;
} ;
const float & Player : : GetStat ( std : : string_view a ) const {
return GetStat ( ItemAttribute : : Get ( a ) ) ;
}
const float & Player : : GetStat ( ItemAttribute a ) const {
return stats . GetStat ( a ) ;
}
const float & Player : : GetBaseStat ( std : : string_view a ) const {
return GetBaseStat ( ItemAttribute : : Get ( a ) ) ;
}
const float & Player : : GetBaseStat ( ItemAttribute a ) const {
return stats . GetBaseStat ( a ) ;
}
void EntityStats : : RecalculateEquipStats ( ) {
baseStats . copyTo ( equipStats ) ;
for ( int i = int ( EquipSlot : : HELMET ) ; i < = int ( EquipSlot : : RING2 ) ; i < < = 1 ) {
EquipSlot slot = EquipSlot ( i ) ;
std : : weak_ptr < Item > equip = Inventory : : GetEquip ( slot ) ;
if ( ISBLANK ( equip ) ) continue ;
for ( auto & [ key , size ] : ItemAttribute : : attributes ) {
equipStats . A ( key ) + = equip . lock ( ) - > GetStats ( ) . A_Read ( key ) ;
equipStats . A ( key ) + = equip . lock ( ) - > RandomStats ( ) . A_Read ( key ) ;
}
}
const std : : map < ItemSet , int > & setCounts = Inventory : : GetEquippedItemSets ( ) ;
for ( const auto & [ set , equipCount ] : setCounts ) {
const Stats & setStats = set [ equipCount ] ;
for ( auto & [ key , size ] : ItemAttribute : : attributes ) {
equipStats . A ( key ) + = setStats . A_Read ( key ) ;
}
}
for ( std : : weak_ptr < MenuComponent > component : Menu : : equipStatListeners ) {
component . lock ( ) - > OnEquipStatsUpdate ( ) ;
}
}
const float & EntityStats : : GetStat ( ItemAttribute stat ) const {
return equipStats . A_Read ( stat ) ;
}
const float & EntityStats : : GetStat ( std : : string_view stat ) const {
return equipStats . A_Read ( stat ) ;
}
const float & EntityStats : : GetBaseStat ( ItemAttribute stat ) const {
return baseStats . A_Read ( stat ) ;
}
const float & EntityStats : : GetBaseStat ( std : : string_view stat ) const {
return baseStats . A_Read ( stat ) ;
}
void EntityStats : : SetBaseStat ( ItemAttribute stat , float val ) {
baseStats . A ( stat ) = val ;
RecalculateEquipStats ( ) ;
}
void EntityStats : : SetBaseStat ( std : : string_view stat , float val ) {
SetBaseStat ( ItemAttribute : : Get ( stat ) , val ) ;
}
void Player : : SetBaseStat ( std : : string_view a , float val ) {
stats . SetBaseStat ( ItemAttribute : : Get ( a ) , val ) ;
}
void Player : : SetBaseStat ( ItemAttribute a , float val ) {
stats . SetBaseStat ( a , val ) ;
}
const std : : string & ItemSet : : GetSetName ( ) const {
return name ;
}
uint32_t Player : : GetMoney ( ) const {
return money ;
}
void Player : : SetMoney ( uint32_t newMoney ) {
money = newMoney ;
for ( auto & component : moneyListeners ) {
component . lock ( ) - > OnPlayerMoneyUpdate ( newMoney ) ;
}
}
void Player : : AddMoneyListener ( std : : weak_ptr < MenuComponent > component ) {
if ( std : : find_if ( moneyListeners . begin ( ) , moneyListeners . end ( ) , [ & ] ( auto & ptr ) { return & * ptr . lock ( ) = = & * component . lock ( ) ; } ) ! = moneyListeners . end ( ) ) {
ERR ( " WARNING! Component " < < component . lock ( ) - > GetName ( ) < < " has already been added to the Chapter listener list! There should not be any duplicates!! " )
}
moneyListeners . push_back ( component ) ;
}
const float Player : : GetDamageReductionFromBuffs ( ) const {
float dmgReduction = 0 ;
for ( const Buff & b : GetBuffs ( BuffType : : DAMAGE_REDUCTION ) ) {
dmgReduction + = b . intensity ;
}
dmgReduction + = GetDamageReductionPct ( ) ;
return std : : min ( 0.75f , dmgReduction ) ;
} ;
const float Player : : GetDamageReductionFromArmor ( ) const {
float dmgReduction = 0 ;
dmgReduction + = Stats : : GetDamageReductionPct ( GetStat ( " Defense " ) ) ;
return std : : min ( 0.75f , dmgReduction ) ;
} ;
void Player : : RecalculateEquipStats ( ) {
stats . RecalculateEquipStats ( ) ;
}
const std : : vector < Buff > Player : : GetStatBuffs ( const std : : vector < std : : string > & attr ) const {
std : : vector < Buff > statUpBuffs ;
std : : copy_if ( buffList . begin ( ) , buffList . end ( ) , std : : back_inserter ( statUpBuffs ) , [ & ] ( const Buff b ) {
return b . type = = STAT_UP & & std : : find_if ( attr . begin ( ) , attr . end ( ) , [ & ] ( const std : : string & attr ) { return b . attr . count ( ItemAttribute : : Get ( attr ) ) ; } ) ! = attr . end ( ) ;
} ) ;
return statUpBuffs ;
}
const ItemAttributable & EntityStats : : GetStats ( ) const {
return equipStats ;
}
const ItemAttributable & Player : : GetStats ( ) const {
return stats . GetStats ( ) ;
} ;
const ItemAttributable & EntityStats : : GetBaseStats ( ) const {
return baseStats ;
} ;
const ItemAttributable & Player : : GetBaseStats ( ) const {
return stats . GetBaseStats ( ) ;
} ;
ItemAttribute & Player : : Get ( std : : string_view attr ) {
return ItemAttribute : : Get ( attr , this ) ;
}
const float Player : : GetCooldownReductionPct ( ) const {
float modCDRPct = 0 ;
const std : : vector < Buff > & buffs = GetStatBuffs ( { " CDR " } ) ;
for ( const Buff & b : buffs ) {
modCDRPct + = b . intensity ;
}
modCDRPct + = GetStat ( " CDR " ) / 100 ;
return modCDRPct ;
}
const float Player : : GetCritRatePct ( ) const {
float modCritRatePct = 0 ;
modCritRatePct + = GetStat ( " Crit Rate " ) / 100 ;
return modCritRatePct ;
}
const float Player : : GetCritDmgPct ( ) const {
float modCritDmgPct = 0 ;
modCritDmgPct + = GetStat ( " Crit Dmg " ) / 100 ;
return modCritDmgPct ;
}
const float Player : : GetHPRecoveryPct ( ) const {
float modHPRecoveryPct = 0 ;
modHPRecoveryPct + = GetStat ( " HP Recovery % " ) / 100 ;
return modHPRecoveryPct ;
}
const float Player : : GetHP6RecoveryPct ( ) const {
float modHPRecoveryPct = 0 ;
modHPRecoveryPct + = GetStat ( " HP6 Recovery % " ) / 100 ;
return modHPRecoveryPct ;
}
const float Player : : GetHP4RecoveryPct ( ) const {
float modHPRecoveryPct = 0 ;
modHPRecoveryPct + = GetStat ( " HP4 Recovery % " ) / 100 ;
return modHPRecoveryPct ;
}
void Player : : PerformHPRecovery ( ) {
int hpRecoveryAmt = 0 ;
if ( hpRecoveryTimer = = 0 ) {
if ( float ( GetHPRecoveryPct ( ) * GetMaxHealth ( ) ) > 0.1 _Pct ) {
hpRecoveryAmt + = std : : ceil ( GetHPRecoveryPct ( ) * GetMaxHealth ( ) ) ;
}
hpRecoveryTimer = 1 ;
}
if ( hp4RecoveryTimer = = 0 ) {
if ( float ( GetHP4RecoveryPct ( ) * GetMaxHealth ( ) ) > 0.1 _Pct ) {
hpRecoveryAmt + = std : : ceil ( GetHP4RecoveryPct ( ) * GetMaxHealth ( ) ) ;
}
hp4RecoveryTimer = 4 ;
}
if ( hp6RecoveryTimer = = 0 ) {
if ( float ( GetHP6RecoveryPct ( ) * GetMaxHealth ( ) ) > 0.1 _Pct ) {
hpRecoveryAmt + = std : : ceil ( GetHP6RecoveryPct ( ) * GetMaxHealth ( ) ) ;
}
hp6RecoveryTimer = 6 ;
}
if ( GetHealth ( ) < GetMaxHealth ( ) ) {
Heal ( hpRecoveryAmt ) ;
}
}
const float Player : : GetDamageReductionPct ( ) const {
float modDmgReductionPct = 0 ;
modDmgReductionPct + = GetStat ( " Damage Reduction " ) / 100 ;
return modDmgReductionPct ;
}
void Player : : AddXP ( const uint32_t xpGain ) {
currentLevelXP + = xpGain ;
totalXPEarned + = xpGain ;
if ( Level ( ) < LevelCap ( ) ) {
uint32_t nextLevelXP = NextLevelXPRequired ( ) ;
while ( currentLevelXP > nextLevelXP ) {
currentLevelXP - = nextLevelXP ;
level + + ;
OnLevelUp ( ) ;
}
}
}
void Player : : OnLevelUp ( ) {
stats . SetBaseStat ( " Health " , GetBaseStat ( " Health " ) + hpGrowthRate ) ;
stats . SetBaseStat ( " Attack " , GetBaseStat ( " Attack " ) + atkGrowthRate ) ;
Heal ( GetBaseStat ( " Health " ) ) ;
}
const uint8_t Player : : LevelCap ( ) const {
return levelCap ;
}
const uint8_t Player : : Level ( ) const {
return level ;
}
const uint32_t Player : : CurrentXP ( ) const {
return currentLevelXP ;
}
const uint32_t Player : : TotalXP ( ) const {
return totalXPEarned ;
}
const uint32_t Player : : NextLevelXPRequired ( ) const {
if ( Level ( ) < LevelCap ( ) ) {
return DATA . GetProperty ( std : : format ( " PlayerXP.LEVEL[{}] " , Level ( ) + 1 ) ) . GetInt ( ) ;
}
return 0 ;
}
void Player : : ResetAccumulatedXP ( ) {
accumulatedXP = 0 ;
}
void Player : : AddAccumulatedXP ( const uint32_t xpGain ) {
accumulatedXP + = xpGain ;
}
const uint32_t Player : : GetAccumulatedXP ( ) const {
return accumulatedXP ;
}
const float Player : : GetAttackRecoveryRateReduction ( ) const {
return GetStat ( " Attack Spd " ) ;
}
void EntityStats : : Reset ( ) {
equipStats . clear ( ) ;
baseStats . clear ( ) ;
}
geom2d : : circle < float > Player : : Hitbox ( ) {
return { GetPos ( ) , 12 * GetSizeMult ( ) / 2 } ;
}
void Player : : Knockup ( float duration ) {
knockUpTimer + = duration ;
totalKnockupTime + = duration ;
knockUpZAmt + = 32 * pow ( duration , 2 ) ;
}
const vf2d Player : : GetWorldAimingLocation ( ) {
return game - > view . ScreenToWorld ( GetAimingLocation ( ) ) ;
}
const vf2d Player : : GetAimingLocation ( ) {
if ( Input : : UsingGamepad ( ) ) {
float xAxis = 0.f , yAxis = 0.f ;
# pragma region Manual Aiming
for ( GamePad * gamepad : GamePad : : getGamepads ( ) ) {
if ( gamepad - > stillConnected ) {
if ( fabs ( gamepad - > getAxis ( GPAxes : : RX ) ) > 0.f ) {
xAxis = gamepad - > getAxis ( GPAxes : : RX ) ;
} else
if ( fabs ( gamepad - > getAxis ( GPAxes : : LX ) ) > 0.f ) {
xAxis = gamepad - > getAxis ( GPAxes : : LX ) ;
}
if ( fabs ( gamepad - > getAxis ( GPAxes : : RY ) ) > 0.f ) {
yAxis = gamepad - > getAxis ( GPAxes : : RY ) ;
} else
if ( fabs ( gamepad - > getAxis ( GPAxes : : LY ) ) > 0.f ) {
yAxis = gamepad - > getAxis ( GPAxes : : LY ) ;
}
if ( xAxis ! = 0.f | | yAxis ! = 0.f ) break ; //Found a controller, so we're good to break.
}
}
# pragma endregion
if ( xAxis ! = 0.f | | yAxis ! = 0.f ) {
return { ( game - > ScreenWidth ( ) * xAxis ) / 2 + game - > ScreenWidth ( ) / 2 , ( game - > ScreenHeight ( ) * yAxis ) / 2 + game - > ScreenHeight ( ) / 2 } ;
} else {
//Find the closest monster target.
vf2d closestPoint = { std : : numeric_limits < float > : : max ( ) , std : : numeric_limits < float > : : max ( ) } ;
for ( Monster & m : MONSTER_LIST ) {
if ( m . IsAlive ( ) ) {
float distToMonster = geom2d : : line < float > ( GetPos ( ) , m . GetPos ( ) ) . length ( ) ;
geom2d : : line < float > aimingLine = geom2d : : line < float > ( GetPos ( ) , m . GetPos ( ) ) ;
vf2d aimingPoint = aimingLine . rpoint ( operator " " _Pixels ( " Player.Aiming Cursor Max Distance " _F ) ) ;
float distToClosestPoint = geom2d : : line < float > ( GetPos ( ) , closestPoint ) . length ( ) ;
if ( distToClosestPoint > distToMonster & & distToMonster < = operator " " _Pixels ( " Player.Auto Aim Detection Distance " _F ) ) {
closestPoint = aimingPoint ;
}
}
}
if ( closestPoint ! = vf2d { std : : numeric_limits < float > : : max ( ) , std : : numeric_limits < float > : : max ( ) } ) {
return game - > GetScreenSize ( ) / 2 + closestPoint - GetPos ( ) ;
} else
return game - > GetScreenSize ( ) / 2 + vf2d { float ( operator " " _Pixels ( " Player.Aiming Cursor Max Distance " _F ) ) , aimingAngle . y } . cart ( ) ;
}
} else {
return game - > GetMousePos ( ) ;
}
}