# 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 © 2024 The FreeType
Project ( www . freetype . org ) . Please see LICENSE_FT . txt for more information .
All rights reserved .
*/
# pragma endregion
# include "Class.h"
# include "DEFINES.h"
# include "Player.h"
# include "Effect.h"
# include "AdventuresInLestoria.h"
# include "config.h"
# include "SoundEffect.h"
# include "BulletTypes.h"
# include "util.h"
# include <ranges>
INCLUDE_MONSTER_LIST
INCLUDE_BULLET_LIST
INCLUDE_game
void Witch : : Initialize ( ) {
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 " ;
Witch : : ability4 = { } ;
}
SETUP_CLASS ( Witch )
void Witch : : OnUpdate ( float fElapsedTime ) {
if ( attack_cooldown_timer > 0 ) {
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 ;
}
}
}
bool Witch : : AutoAttack ( ) {
attack_cooldown_timer = MAGIC_ATTACK_COOLDOWN - GetAttackRecoveryRateReduction ( ) ;
float angleToCursor = atan2 ( GetWorldAimingLocation ( ) . y - GetPos ( ) . y , GetWorldAimingLocation ( ) . x - GetPos ( ) . x ) ;
int damage { int ( GetAttack ( ) * " Witch.Auto Attack.DamageMult " _F ) } ;
if ( HasEnchant ( " Bouncing Orb " ) ) damage * = " Bouncing Orb " _ENC [ " ORB DAMAGE " ] / 100.f ;
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 ;
BULLET_LIST . back ( ) - > SetIsPlayerAutoAttackProjectile ( ) ;
SoundEffect : : PlaySFX ( " Witch Attack " , SoundEffect : : CENTERED ) ;
return true ;
}
void Witch : : InitializeClassAbilities ( ) {
# 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 ) ;
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 ( ) ;
}
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 ( ) ) {
//NOTE: If we have to change/modify Curse of Pain, we must also modify it in Monster::OnDeath (Monster.cpp)
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 ] } ;
curseTarget . value ( ) . lock ( ) - > ApplyDot ( buffDuration , p - > GetAttack ( ) * buffDamageMult , buffTimeBetweenTicks , BuffType : : CURSE_OF_PAIN ,
[ ] ( std : : weak_ptr < Monster > m , Buff & b ) {
expireCallbackFunc :
if ( ! m . expired ( ) ) m . lock ( ) - > Hurt ( game - > GetPlayer ( ) - > GetAttack ( ) * " Witch.Ability 1.Final Tick Damage " _F , m . lock ( ) - > OnUpperLevel ( ) , m . lock ( ) - > GetZ ( ) , HurtFlag : : DOT ) ;
}
) ;
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 ) ;
}
SoundEffect : : PlaySFX ( " Curse of Pain " , curseTarget . value ( ) . lock ( ) - > GetPos ( ) ) ;
return true ;
} else return false ;
} ;
# pragma endregion
# pragma region Witch Ability 2 (Throw Poison)
Witch : : ability2 . action =
[ ] ( Player * p , vf2d pos = { } ) {
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 ) ) } ;
CreateBullet ( PoisonBottle ) ( p - > GetPos ( ) , pos , " 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 ;
return true ;
} ;
# pragma endregion
# pragma region Witch Ability 3 (Curse of Death)
Witch : : ability3 . action =
[ ] ( Player * p , vf2d pos = { } ) {
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 } ;
float damageAmpMult { " Witch.Ability 3.Damage Amplification " _F / 100.f } ;
if ( p - > HasEnchant ( " Expunge " ) ) damageAmpMult + = " Expunge " _ENC [ " BONUS DAMAGE " ] / 100.f ;
curseTarget . value ( ) . lock ( ) - > ResetCurseOfDeathDamage ( ) ;
curseTarget . value ( ) . lock ( ) - > AddBuff ( BuffType : : CURSE_OF_DEATH , curseDuration , damageAmpMult ) ;
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 ;
} ;
# pragma endregion
}