|
|
|
#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 "BulletTypes.h"
|
|
|
|
#include "config.h"
|
|
|
|
#include "util.h"
|
|
|
|
#include "SoundEffect.h"
|
|
|
|
#include "BlackHole.h"
|
|
|
|
|
|
|
|
INCLUDE_MONSTER_LIST
|
|
|
|
INCLUDE_BULLET_LIST
|
|
|
|
INCLUDE_game
|
|
|
|
|
|
|
|
void Wizard::Initialize(){
|
|
|
|
READFROMCONFIG(Wizard,WIZARD);
|
|
|
|
Wizard::idle_n="WIZARD_IDLE_N";
|
|
|
|
Wizard::idle_e="WIZARD_IDLE_E";
|
|
|
|
Wizard::idle_s="WIZARD_IDLE_S";
|
|
|
|
Wizard::idle_w="WIZARD_IDLE_W";
|
|
|
|
Wizard::walk_n="WIZARD_WALK_N";
|
|
|
|
Wizard::walk_e="WIZARD_WALK_E";
|
|
|
|
Wizard::walk_s="WIZARD_WALK_S";
|
|
|
|
Wizard::walk_w="WIZARD_WALK_W";
|
|
|
|
Wizard::ability4={};
|
|
|
|
}
|
|
|
|
|
|
|
|
SETUP_CLASS(Wizard)
|
|
|
|
|
|
|
|
void Wizard::OnUpdate(float fElapsedTime){
|
|
|
|
if(attack_cooldown_timer>0){
|
|
|
|
idle_n="WIZARD_IDLE_ATTACK_N";
|
|
|
|
idle_e="WIZARD_IDLE_ATTACK_E";
|
|
|
|
idle_s="WIZARD_IDLE_ATTACK_S";
|
|
|
|
idle_w="WIZARD_IDLE_ATTACK_W";
|
|
|
|
walk_n="WIZARD_ATTACK_N";
|
|
|
|
walk_e="WIZARD_ATTACK_E";
|
|
|
|
walk_s="WIZARD_ATTACK_S";
|
|
|
|
walk_w="WIZARD_ATTACK_W";
|
|
|
|
} else {
|
|
|
|
idle_n="WIZARD_IDLE_N";
|
|
|
|
idle_e="WIZARD_IDLE_E";
|
|
|
|
idle_s="WIZARD_IDLE_S";
|
|
|
|
idle_w="WIZARD_IDLE_W";
|
|
|
|
walk_n="WIZARD_WALK_N";
|
|
|
|
walk_e="WIZARD_WALK_E";
|
|
|
|
walk_s="WIZARD_WALK_S";
|
|
|
|
walk_w="WIZARD_WALK_W";
|
|
|
|
}
|
|
|
|
if(GetState()==State::CASTING){
|
|
|
|
switch(GetFacingDirection()){
|
|
|
|
case UP:{
|
|
|
|
UpdateAnimation("WIZARD_CAST_N",WIZARD|WITCH);
|
|
|
|
}break;
|
|
|
|
case DOWN:{
|
|
|
|
UpdateAnimation("WIZARD_CAST_S",WIZARD|WITCH);
|
|
|
|
}break;
|
|
|
|
case LEFT:{
|
|
|
|
UpdateAnimation("WIZARD_CAST_W",WIZARD|WITCH);
|
|
|
|
}break;
|
|
|
|
case RIGHT:{
|
|
|
|
UpdateAnimation("WIZARD_CAST_E",WIZARD|WITCH);
|
|
|
|
}break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Wizard::AutoAttack(){
|
|
|
|
attack_cooldown_timer=MAGIC_ATTACK_COOLDOWN-GetAttackRecoveryRateReduction();
|
|
|
|
float angleToCursor=atan2(GetWorldAimingLocation().y-GetPos().y,GetWorldAimingLocation().x-GetPos().x);
|
|
|
|
CreateBullet(EnergyBolt)(GetPos(),{cos(angleToCursor)*"Wizard.Auto Attack.Speed"_F,sin(angleToCursor)*"Wizard.Auto Attack.Speed"_F},"Wizard.Auto Attack.Radius"_F/100*12,int(GetAttack()*"Wizard.Auto Attack.DamageMult"_F),upperLevel,true,WHITE)EndBullet;
|
|
|
|
BULLET_LIST.back()->SetIsPlayerAutoAttackProjectile();
|
|
|
|
SoundEffect::PlaySFX("Wizard Auto Attack",SoundEffect::CENTERED);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
void Wizard::InitializeClassAbilities(){
|
|
|
|
#pragma region Wizard Right-click Ability (Teleport)
|
|
|
|
Wizard::rightClickAbility.action=
|
|
|
|
[](Player*p,vf2d pos={}){
|
|
|
|
float pointMouseDirection=atan2(p->GetWorldAimingLocation(Player::USE_WALK_DIR,Player::INVERTED).y-p->GetPos().y,p->GetWorldAimingLocation(Player::USE_WALK_DIR,Player::INVERTED).x-p->GetPos().x);
|
|
|
|
vf2d pointTowardsMouse={cos(pointMouseDirection),sin(pointMouseDirection)};
|
|
|
|
float dist=std::clamp(geom2d::line<float>{p->GetPos(),p->GetWorldAimingLocation(Player::USE_WALK_DIR,Player::INVERTED)}.length(),0.f,"Wizard.Right Click Ability.TeleportRange"_F/100*24);
|
|
|
|
if(dist<"Wizard.Right Click Ability.TilesMin"_I*12)return false;
|
|
|
|
vf2d teleportPoint=p->GetPos()+pointTowardsMouse*dist;
|
|
|
|
while(dist>0&&game->HasTileCollision(game->GetCurrentLevel(),teleportPoint)&&p->CanPathfindTo(p->GetPos(),teleportPoint,float("Wizard.Right Click Ability.TilesMax"_I))){
|
|
|
|
dist-=4;
|
|
|
|
teleportPoint=p->GetPos()+pointTowardsMouse*dist;
|
|
|
|
}
|
|
|
|
vi2d tilePos=vi2d(teleportPoint/float(game->GetCurrentMapData().tilewidth))*game->GetCurrentMapData().tilewidth;
|
|
|
|
geom2d::rect<float>collisionRect=game->GetTileCollision(game->GetCurrentLevel(),teleportPoint,p->OnUpperLevel());
|
|
|
|
#pragma region lambdas
|
|
|
|
auto NoTileCollisionExistsHere=[&](){return collisionRect==game->NO_COLLISION;};
|
|
|
|
#pragma endregion
|
|
|
|
collisionRect.pos+=tilePos;
|
|
|
|
#pragma region lambdas
|
|
|
|
auto NoPlayerCollisionWithTile=[&](){return !geom2d::overlaps(geom2d::circle<float>(teleportPoint,4),collisionRect);};
|
|
|
|
#pragma endregion
|
|
|
|
|
|
|
|
const auto TeleportTo=[&p](const vf2d pos){
|
|
|
|
p->SetState(State::TELEPORT);
|
|
|
|
p->teleportAnimationTimer="Wizard.Right Click Ability.AnimationTime"_F;
|
|
|
|
p->teleportTarget=pos;
|
|
|
|
p->teleportStartPosition=p->GetPos();
|
|
|
|
p->ApplyIframes("Wizard.Right Click Ability.IframeTime"_F);
|
|
|
|
for(int i=0;i<"Wizard.Right Click Ability.ParticleCount"_I;i++){
|
|
|
|
game->AddEffect(std::make_unique<Effect>(p->GetPos()+vf2d{(util::random("Wizard.Right Click Ability.ParticleRange"_F/100*2)-"Wizard.Right Click Ability.ParticleRange"_F/100)*12,(util::random("Wizard.Right Click Ability.ParticleRange"_F/100*2)-"Wizard.Right Click Ability.ParticleRange"_F/100)*12},util::random("Wizard.Right Click Ability.ParticleLifetimeMax"_F)+"Wizard.Right Click Ability.ParticleLifetimeMin"_F,"circle.png",p->upperLevel,"Wizard.Right Click Ability.ParticleSize"_F,"Wizard.Right Click Ability.ParticleFadetime"_F,vf2d{util::random("Wizard.Right Click Ability.ParticleSpeedMax"_F*2)+"Wizard.Right Click Ability.ParticleSpeedMin"_F,util::random("Wizard.Right Click Ability.ParticleSpeedMax"_F*2)+"Wizard.Right Click Ability.ParticleSpeedMin"_F},"Wizard.Right Click Ability.ParticleColor"_Pixel));
|
|
|
|
}
|
|
|
|
SoundEffect::PlaySFX("Wizard Teleport",SoundEffect::CENTERED);
|
|
|
|
};
|
|
|
|
|
|
|
|
std::vector<Effect*>portals{game->GetEffect(EffectType::BLINK_PORTAL)};
|
|
|
|
if(portals.size()>0){
|
|
|
|
TeleportTo(portals[0]->pos);
|
|
|
|
portals[0]->lifetime=0.f;
|
|
|
|
portals[0]->fadeout=0.25f;
|
|
|
|
return true;
|
|
|
|
}else if(p->lastPathfindingCooldown==0.f){
|
|
|
|
if(game->TestingModeEnabled()||dist>0&&p->CanPathfindTo(p->GetPos(),teleportPoint,float("Wizard.Right Click Ability.TilesMax"_I))
|
|
|
|
&&(NoTileCollisionExistsHere()||NoPlayerCollisionWithTile())){
|
|
|
|
if(p->HasEnchant("Blink Portal")){
|
|
|
|
Effect&eff{game->AddEffect(std::make_unique<FadeInOutEffect>(Oscillator<vf2d>{p->GetPos(),p->GetPos()+vf2d{0,-6.f},0.5f},"portal.png","Blink Portal"_ENC["REACTIVATION TIME"],p->OnUpperLevel(),Oscillator<vf2d>{{0.9f,0.9f},{1.1f,1.1f},0.5f},vf2d{},Oscillator<Pixel>{WHITE,Pixel(0x50196f),0.5f},0.f,0.f),true)};
|
|
|
|
eff.SetType(EffectType::BLINK_PORTAL);
|
|
|
|
}
|
|
|
|
TeleportTo(teleportPoint);
|
|
|
|
p->lastPathfindingCooldown=0.1f;
|
|
|
|
|
|
|
|
if(p->HasEnchant("Black Hole")){
|
|
|
|
Effect&blackHoleEff{game->AddEffect(std::make_unique<BlackHole>(Oscillator<vf2d>{p->GetPos(),p->GetPos()+vf2d{0,-6.f},0.75f},"blackhole.png","Black Hole"_ENC["BLACK HOLE DURATION"],p->OnUpperLevel(),Oscillator<vf2d>{{3.4f,3.4f},{3.7f,3.7f},0.75f},vf2d{},Oscillator<Pixel>{WHITE,WHITE,0.75f},util::random(2*PI),PI/3,false,0.03f,[](const Effect&self){
|
|
|
|
vf2d particlePos{self.pos+vf2d{"Black Hole"_ENC["PULL IN RADIUS"]/100.f*24,util::random(2*PI)}.cart()};
|
|
|
|
return Effect{particlePos,util::random_range(0.3f,0.5f),"pixel.png",self.OnUpperLevel(),util::random(2.f),0.1f,util::pointTo(particlePos,self.pos)*util::random_range(700,1000),PixelLerp(BLACK,Pixel(0x9859de),util::random(1.f)),0.f,0.f,false};
|
|
|
|
}),true)};
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
p->NotificationDisplay("Cannot Teleport to that location!",0.5f);
|
|
|
|
p->lastPathfindingCooldown=0.1f;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
#pragma endregion
|
|
|
|
#pragma region Wizard Ability 1 (Fire Bolt)
|
|
|
|
Wizard::ability1.action=
|
|
|
|
[](Player*p,vf2d pos={}){
|
|
|
|
float angleToCursor=atan2(p->GetWorldAimingLocation().y-p->GetPos().y,p->GetWorldAimingLocation().x-p->GetPos().x);
|
|
|
|
CreateBullet(FireBolt)(p->GetPos(),{cos(angleToCursor)*"Wizard.Ability 1.BulletSpeed"_F,sin(angleToCursor)*"Wizard.Ability 1.BulletSpeed"_F},"Wizard.Ability 1.Radius"_F/100*12,int(p->GetAttack()*"Wizard.Ability 1.InitialDamageMult"_F),p->upperLevel,true,"Wizard.Ability 1.BulletColor"_Pixel)EndBullet;
|
|
|
|
SoundEffect::PlaySFX("Wizard Fire Bolt Shoot",SoundEffect::CENTERED);
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
#pragma endregion
|
|
|
|
#pragma region Wizard Ability 2 (Lightning Bolt)
|
|
|
|
Wizard::ability2.action=
|
|
|
|
[](Player*p,vf2d pos={}){
|
|
|
|
float angleToCursor=atan2(p->GetWorldAimingLocation().y-p->GetPos().y,p->GetWorldAimingLocation().x-p->GetPos().x);
|
|
|
|
CreateBullet(LightningBolt)(p->GetPos(),{cos(angleToCursor)*"Wizard.Ability 2.BulletSpeed"_F,sin(angleToCursor)*"Wizard.Ability 2.BulletSpeed"_F},"Wizard.Ability 2.Radius"_F/100*12,int(p->GetAttack()*"Wizard.Ability 2.DamageMult"_F),p->upperLevel,true,"Wizard.Ability 2.BulletColor"_Pixel)EndBullet;
|
|
|
|
SoundEffect::PlaySFX("Wizard Lightning Bolt Shoot",SoundEffect::CENTERED);
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
#pragma endregion
|
|
|
|
#pragma region Wizard Ability 3 (Meteor)
|
|
|
|
Wizard::ability3.action=
|
|
|
|
[](Player*p,vf2d pos={}){
|
|
|
|
Meteor::MeteorSetting meteorType{Meteor::METEOR};
|
|
|
|
if(p->HasEnchant("Summon Comet"))meteorType=Meteor::COMET;
|
|
|
|
else if(p->HasEnchant("Solar Flare"))meteorType=Meteor::SOLAR_FLARE;
|
|
|
|
game->AddEffect(std::make_unique<Meteor>(pos,3,p->OnUpperLevel(),meteorType,"Wizard.Ability 3.MeteorFadeoutTime"_F));
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
#pragma endregion
|
|
|
|
}
|