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-07-14 15:44:17 +00:00
# include "Class.h"
# include "DEFINES.h"
# include "Player.h"
# include "Effect.h"
2024-01-04 05:21:56 -06:00
# include "AdventuresInLestoria.h"
2023-07-26 20:22:33 -05:00
# include "config.h"
2024-07-26 03:09:55 -05:00
# include "SoundEffect.h"
# include "BulletTypes.h"
2024-07-27 09:10:40 -05:00
# include "util.h"
2024-07-28 04:18:52 -05:00
# include <ranges>
2023-07-14 15:44:17 +00:00
INCLUDE_MONSTER_LIST
INCLUDE_BULLET_LIST
INCLUDE_game
2023-07-26 21:08:17 +00:00
void Witch : : Initialize ( ) {
2024-07-28 07:06:11 -05:00
READFROMCONFIG ( Witch , WITCH ) ;
Witch : : idle_n = " WITCH_IDLE_N " ;
Witch : : idle_e = " WITCH_IDLE_E " ;
Witch : : idle_s = " WITCH_IDLE_S " ;
Witch : : idle_w = " WITCH_IDLE_W " ;
Witch : : walk_n = " WITCH_WALK_N " ;
Witch : : walk_e = " WITCH_WALK_E " ;
Witch : : walk_s = " WITCH_WALK_S " ;
Witch : : walk_w = " WITCH_WALK_W " ;
2024-08-09 18:13:08 -05:00
Witch : : ability4 = { } ;
2023-07-26 21:08:17 +00:00
}
2023-07-14 15:44:17 +00:00
SETUP_CLASS ( Witch )
void Witch : : OnUpdate ( float fElapsedTime ) {
2024-07-28 07:06:11 -05:00
if ( attack_cooldown_timer > 0 ) {
2024-07-27 09:10:40 -05:00
idle_n = " WITCH_IDLE_ATTACK_N " ;
idle_e = " WITCH_IDLE_ATTACK_E " ;
idle_s = " WITCH_IDLE_ATTACK_S " ;
idle_w = " WITCH_IDLE_ATTACK_W " ;
walk_n = " WITCH_ATTACK_N " ;
walk_e = " WITCH_ATTACK_E " ;
walk_s = " WITCH_ATTACK_S " ;
walk_w = " WITCH_ATTACK_W " ;
} else {
idle_n = " WITCH_IDLE_N " ;
idle_e = " WITCH_IDLE_E " ;
idle_s = " WITCH_IDLE_S " ;
idle_w = " WITCH_IDLE_W " ;
walk_n = " WITCH_WALK_N " ;
walk_e = " WITCH_WALK_E " ;
walk_s = " WITCH_WALK_S " ;
walk_w = " WITCH_WALK_W " ;
}
if ( GetState ( ) = = State : : CASTING ) {
switch ( GetFacingDirection ( ) ) {
case UP : {
UpdateAnimation ( " WITCH_CAST_N " , WIZARD | WITCH ) ;
} break ;
case DOWN : {
UpdateAnimation ( " WITCH_CAST_S " , WIZARD | WITCH ) ;
} break ;
case LEFT : {
UpdateAnimation ( " WITCH_CAST_W " , WIZARD | WITCH ) ;
} break ;
case RIGHT : {
UpdateAnimation ( " WITCH_CAST_E " , WIZARD | WITCH ) ;
} break ;
}
}
2023-07-14 15:44:17 +00:00
}
bool Witch : : AutoAttack ( ) {
2024-07-26 03:09:55 -05:00
attack_cooldown_timer = MAGIC_ATTACK_COOLDOWN - GetAttackRecoveryRateReduction ( ) ;
float angleToCursor = atan2 ( GetWorldAimingLocation ( ) . y - GetPos ( ) . y , GetWorldAimingLocation ( ) . x - GetPos ( ) . x ) ;
2024-08-29 15:03:05 -05:00
int damage { int ( GetAttack ( ) * " Witch.Auto Attack.DamageMult " _F ) } ;
if ( HasEnchant ( " Bouncing Orb " ) ) damage * = " Bouncing Orb " _ENC [ " ORB DAMAGE " ] / 100.f ;
2024-08-29 18:17:18 -05:00
CreateBullet ( PurpleEnergyBall ) ( GetPos ( ) , " Witch.Auto Attack.Radius " _F / 100 * 12 , " Witch.Auto Attack.Homing Range " _F / 100 * 24 , damage , upperLevel , { cos ( angleToCursor ) * " Witch.Auto Attack.Speed " _F , sin ( angleToCursor ) * " Witch.Auto Attack.Speed " _F } , INFINITE , true ) EndBullet ;
2024-07-28 07:06:11 -05:00
BULLET_LIST . back ( ) - > SetIsPlayerAutoAttackProjectile ( ) ;
2024-07-28 20:21:52 -05:00
SoundEffect : : PlaySFX ( " Witch Attack " , SoundEffect : : CENTERED ) ;
2024-07-26 03:09:55 -05:00
return true ;
2023-07-14 15:44:17 +00:00
}
void Witch : : InitializeClassAbilities ( ) {
2024-07-28 07:06:11 -05:00
# pragma region Witch Right-click Ability (Transform)
Witch : : rightClickAbility . action =
[ ] ( Player * p , vf2d pos = { } ) {
p - > SetupAfterImage ( ) ;
p - > afterImagePos = p - > leapStartingPos = p - > GetPos ( ) ;
geom2d : : line < float > targetLine { p - > GetPos ( ) , p - > GetWorldAimingLocation ( Player : : USE_WALK_DIR , Player : : INVERTED ) } ;
const float LeapMaxRange { " Witch.Right Click Ability.Leap Velocity " _F * " Witch.Right Click Ability.Leap Max Range Time " _F } ;
p - > leapTimer = p - > totalLeapTime = std : : min ( " Witch.Right Click Ability.Leap Max Range Time " _F , util : : lerp ( 0.f , " Witch.Right Click Ability.Leap Max Range Time " _F , targetLine . length ( ) / ( LeapMaxRange / 100.f * 24 ) ) ) ;
p - > transformTargetDir = targetLine . vector ( ) . polar ( ) . y ;
p - > SetAnimationBasedOnTargetingDirection ( " TRANSFORM " , p - > transformTargetDir ) ;
p - > ApplyIframes ( " Witch.Right Click Ability.Leap Max Range Time " _F + 0.1f ) ;
p - > SetState ( State : : LEAP ) ;
2024-08-29 18:17:18 -05:00
if ( p - > HasEnchant ( " Nine Lives " ) ) {
if ( p - > CatFormActive ( ) ) p - > AddTimer ( PlayerTimerType : : CAT_FORM_ALREADY_ACTIVE , Timer { " Cat Form Active Already Notification " , 1.f , [ ] ( ) { } } ) ;
p - > ActivateCatForm ( ) ;
}
2024-07-28 07:06:11 -05:00
SoundEffect : : PlaySFX ( " Meow " , p - > GetPos ( ) ) ;
return true ;
} ;
# pragma endregion
# pragma region Witch Ability 1 (Curse of Pain)
Witch : : ability1 . action =
[ ] ( Player * p , vf2d pos = { } ) {
std : : optional < std : : weak_ptr < Monster > > curseTarget { Monster : : GetNearestMonster ( pos , " Witch.Ability 1.Casting Range " _F / 100.f * 24 , p - > OnUpperLevel ( ) , p - > GetZ ( ) ) } ;
if ( curseTarget . has_value ( ) & & ! curseTarget . value ( ) . expired ( ) ) {
2024-08-30 22:25:49 -05:00
//NOTE: If we have to change/modify Curse of Pain, we must also modify it in Monster::OnDeath (Monster.cpp)
2024-07-28 07:06:11 -05:00
const float buffTimeBetweenTicks { " Witch.Ability 1.Curse Debuff " _f [ 1 ] } ;
const float buffDamageMult { " Witch.Ability 1.Curse Debuff " _f [ 0 ] } ;
const float buffDuration { " Witch.Ability 1.Curse Debuff " _f [ 2 ] } ;
2024-08-30 22:25:49 -05:00
curseTarget . value ( ) . lock ( ) - > ApplyDot ( buffDuration , p - > GetAttack ( ) * buffDamageMult , buffTimeBetweenTicks , BuffType : : CURSE_OF_PAIN ,
2024-07-28 07:06:11 -05:00
[ ] ( std : : weak_ptr < Monster > m , Buff & b ) {
expireCallbackFunc :
2024-07-29 19:06:40 -05:00
if ( ! m . expired ( ) ) m . lock ( ) - > Hurt ( game - > GetPlayer ( ) - > GetAttack ( ) * " Witch.Ability 1.Final Tick Damage " _F , m . lock ( ) - > OnUpperLevel ( ) , m . lock ( ) - > GetZ ( ) , HurtFlag : : DOT ) ;
2024-07-28 07:06:11 -05:00
}
) ;
curseTarget . value ( ) . lock ( ) - > AddBuff ( BuffType : : GLOW_PURPLE , buffDuration , 1.f ) ;
const vf2d targetPos { curseTarget . value ( ) . lock ( ) - > GetPos ( ) } ;
for ( int i : std : : ranges : : iota_view ( 0 , int ( util : : distance ( p - > GetPos ( ) , targetPos ) / 8 ) ) ) {
float drawDist { i * 8.f } ;
float fadeInTime { i * 0.05f } ;
float fadeOutTime { 0.5f + i * 0.05f } ;
float effectSize { util : : random ( 0.2f ) } ;
game - > AddEffect ( std : : make_unique < Effect > ( geom2d : : line < float > ( p - > GetPos ( ) , targetPos ) . rpoint ( drawDist ) , 0.f , " mark_trail.png " , p - > OnUpperLevel ( ) , fadeInTime , fadeOutTime , vf2d { effectSize , effectSize } , vf2d { } , Pixel { 100 , 0 , 155 , uint8_t ( util : : random_range ( 0 , 120 ) ) } , 0.f , 0.f ) , true ) ;
}
2024-07-28 20:21:52 -05:00
SoundEffect : : PlaySFX ( " Curse of Pain " , curseTarget . value ( ) . lock ( ) - > GetPos ( ) ) ;
2024-07-28 07:06:11 -05:00
return true ;
} else return false ;
} ;
# pragma endregion
# pragma region Witch Ability 2 (Throw Poison)
Witch : : ability2 . action =
[ ] ( Player * p , vf2d pos = { } ) {
2024-09-01 01:17:07 -05:00
int additionalBounceCount { 0 } ;
if ( p - > HasEnchant ( " Poison Bounce " ) ) additionalBounceCount + = " Poison Bounce " _ENC [ " BOUNCE COUNT " ] ;
const float totalFallTime { util : : lerp ( 1 / 30.f * ( additionalBounceCount + 1 ) , 0.3f , util : : distance ( p - > GetPos ( ) , pos ) / ( " Witch.Ability 2.Casting Range " _F / 100.f * 24 ) ) } ;
2024-10-18 00:16:55 -05:00
CreateBullet ( PoisonBottle ) ( p - > GetPos ( ) , pos , " poison_bottle.png " , " Witch.Ability 2.Casting Size " _F / 100.f * 24 , " Poison Bounce " _ENC [ " BOUNCE EXPLODE RADIUS " ] / 100.f * 24 , 12.f , totalFallTime , " Witch.Ability 2.Toss Max Z " _F , p - > GetAttack ( ) * " Witch.Ability 2.Damage Mult " _F , additionalBounceCount , p - > OnUpperLevel ( ) , false , INFINITE , true , WHITE , vf2d { 1.f , 1.f } , util : : random ( 2 * PI ) ) EndBullet ;
2024-07-28 07:06:11 -05:00
return true ;
} ;
# pragma endregion
2024-07-28 20:48:09 -05:00
# pragma region Witch Ability 3 (Curse of Death)
2024-07-28 07:06:11 -05:00
Witch : : ability3 . action =
[ ] ( Player * p , vf2d pos = { } ) {
2024-07-28 20:48:09 -05:00
std : : optional < std : : weak_ptr < Monster > > curseTarget { Monster : : GetNearestMonster ( pos , " Witch.Ability 3.Casting Range " _F / 100.f * 24 , p - > OnUpperLevel ( ) , p - > GetZ ( ) ) } ;
if ( curseTarget . has_value ( ) & & ! curseTarget . value ( ) . expired ( ) ) {
const float curseDuration { " Witch.Ability 3.Curse Duration " _F } ;
2024-09-01 01:26:16 -05:00
float damageAmpMult { " Witch.Ability 3.Damage Amplification " _F / 100.f } ;
if ( p - > HasEnchant ( " Expunge " ) ) damageAmpMult + = " Expunge " _ENC [ " BONUS DAMAGE " ] / 100.f ;
2024-09-01 02:15:12 -05:00
curseTarget . value ( ) . lock ( ) - > ResetCurseOfDeathDamage ( ) ;
curseTarget . value ( ) . lock ( ) - > AddBuff ( BuffType : : CURSE_OF_DEATH , curseDuration , damageAmpMult ) ;
2024-07-28 20:48:09 -05:00
const vf2d targetPos { curseTarget . value ( ) . lock ( ) - > GetPos ( ) } ;
for ( int i : std : : ranges : : iota_view ( 0 , int ( util : : distance ( p - > GetPos ( ) , targetPos ) / 8 ) ) ) {
float drawDist { i * 8.f } ;
float fadeInTime { i * 0.05f } ;
float fadeOutTime { 0.5f + i * 0.05f } ;
float effectSize { util : : random_range ( 0.6f , 1.2f ) } ;
game - > AddEffect ( std : : make_unique < Effect > ( geom2d : : line < float > ( p - > GetPos ( ) , targetPos ) . rpoint ( drawDist ) , 0.f , " mark_trail.png " , p - > OnUpperLevel ( ) , fadeInTime , fadeOutTime , vf2d { effectSize , effectSize } , vf2d { } , Pixel { 162 , 0 , 0 , uint8_t ( util : : random_range ( 150 , 255 ) ) } , 0.f , 0.f ) , true ) ;
}
SoundEffect : : PlaySFX ( " Curse of Death " , curseTarget . value ( ) . lock ( ) - > GetPos ( ) ) ;
return true ;
} else return false ;
2024-07-28 07:06:11 -05:00
} ;
# pragma endregion
2023-07-14 15:44:17 +00:00
}