The open source repository for the action RPG game in development by Sig Productions titled 'Adventures in Lestoria'! https://forums.lestoria.net
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
AdventuresInLestoria/Adventures in Lestoria/Player.h

650 lines
26 KiB

#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
#pragma once
#include "olcUTIL_Animate2D.h"
#include "Animation.h"
#include "Monster.h"
#include "State.h"
#include "Ability.h"
#include "Buff.h"
#include "Pathfinding.h"
#include "config.h"
#include "Key.h"
#include "Class.h"
#include "Item.h"
#include "AttributableStat.h"
#undef GetClassName
struct DamageNumber;
class MenuComponent;
struct Ability;
struct CastInfo{
std::string name="???";
float castTimer=0.f;
float castTotalTime=0.f;
vf2d castPos{};
};
namespace MoveFlag{
enum MoveFlag{
NONE = 0b0,
PREVENT_CAST_CANCELLING = 0b1,
};
};
namespace PlayerTest{
class PlayerTests;
}
class EntityStats{
friend class Inventory;
ItemAttributable equipStats; //The stats after gear calculations are applied.
ItemAttributable baseStats;
public:
void RecalculateEquipStats(); //Called when equipment is updated.
const ItemAttributable&GetStats()const;
const ItemAttributable&GetBaseStats()const;
//Get stats with equipment applied.
const float&GetEquipStat(ItemAttribute stat)const;
//Get stats with equipment applied.
const float&GetEquipStat(std::string_view stat)const;
const float&GetBaseStat(ItemAttribute stat)const;
const float&GetBaseStat(std::string_view stat)const;
void SetBaseStat(ItemAttribute a,float val);
void SetBaseStat(std::string_view a,float val);
void Reset();
};
class Player{
friend class AiL;
friend class sig::Animation;
friend struct Warrior;
friend struct Thief;
friend struct Ranger;
friend struct Trapper;
friend struct Wizard;
friend struct Witch;
friend class State_GameRun;
friend class Inventory;
friend void ItemOverlay::Draw();
friend class PlayerTests::PlayerTest;
friend class ItemTests::ItemTest;
public:
Player();
//So this is rather fascinating and only exists because we have the ability to change classes which means we need to initialize a class
//using a new object type... Because of that we'll take the pointer reference to the old object and copy some of its properties to this new
//one. It's hackish but it means we can reduce the amount of extra boilerplate when class changing...I don't know how to feel about this.
Player(Player*player);
static float GROUND_SLAM_SPIN_TIME;
vf2d&GetPos()const;
float GetX();
float GetY();
float GetZ();
const float&GetEquipStat(ItemAttribute a)const;
const float&GetEquipStat(std::string_view a)const;
const float&GetBaseStat(ItemAttribute a)const;
const float&GetBaseStat(std::string_view a)const;
void SetBaseStat(std::string_view a,float val);
void SetBaseStat(ItemAttribute a,float val);
const int GetMaxHealth()const;
const int GetHealth()const;
const int GetMana()const;
const int GetMaxMana()const;
const int GetAttack()const;
const int GetDefense()const;
const float GetDamageReductionFromBuffs()const;
const float GetDamageReductionFromArmor()const;
float GetMoveSpdMult();
float GetSizeMult()const;
const float GetCooldownReductionPct()const;
const float GetCritRatePct()const;
const float GetCritDmgPct()const;
const float GetHPRecoveryPct()const;
const float GetHP6RecoveryPct()const;
const float GetHP4RecoveryPct()const;
const float GetDamageReductionPct()const;
const float GetAttackRecoveryRateReduction()const;
void SetSizeMult(float size);
float GetAttackRangeMult();
float GetSpinAngle();
State::State GetState();
Key GetFacingDirection();
vf2d GetVelocity();
bool HasIframes();
void Update(float fElapsedTime);
void UpdateWalkingAnimation(Key direction, const float frameMult=1.f);
void UpdateIdleAnimation(Key direction);
//The range is the search range in tiles.
bool CanPathfindTo(vf2d pos,vf2d targetPos,float range=8);
bool CanMove();
bool CanAct();
const bool CanAct(const Ability&ability);
void Knockback(vf2d vel);
void ProximityKnockback(const vf2d centerPoint,const float knockbackFactor);
void ApplyIframes(float duration);
//NOTE: Will directly override the iframe timer for the player, as opposed to _ApplyIframes which actually keeps the longest current iframe time.
void _SetIframes(float duration);
void RestoreMana(int amt,bool suppressDamageNumber=false);
void ConsumeMana(int amt);
//Returns true if the move was valid and successful.
bool SetX(float x,MoveFlag::MoveFlag flags=MoveFlag::NONE);
//Returns true if the move was valid and successful.
bool SetY(float y,MoveFlag::MoveFlag flags=MoveFlag::NONE);
void SetZ(float z);
//Returns true if the move was valid and successful.
bool SetPos(vf2d pos,MoveFlag::MoveFlag flags=MoveFlag::NONE);
//Ignores collision checking and sets the player at the given position.
void ForceSetPos(vf2d pos);
void SetState(State::State newState);
void AddBuff(BuffType type,float duration,float intensity);
//NOTE: If adding a % increase stat, please use the percentage version! 100% = 1!!
void AddBuff(BuffType type,float duration,float intensity,std::set<ItemAttribute>attr);
//NOTE: If adding a % increase stat, please use the percentage version! 100% = 1!!
void AddBuff(BuffType type,float duration,float intensity,std::set<std::string>attr);
void AddBuff(BuffType type,float duration,float intensity,float timeBetweenTicks,std::function<void(AiL*,int)>repeatAction);
const std::vector<Buff>GetBuffs(BuffType buff)const;
const std::vector<Buff>GetStatBuffs(const std::vector<std::string>&attr)const;
//Removes the first buff found.
void RemoveBuff(BuffType type);
//Removes all buffs of a certain type.
void RemoveAllBuffs(BuffType type);
//Remove every buff.
void RemoveAllBuffs();
void UpdateHealthAndMana();
void RecalculateEquipStats();
bool Hurt(int damage,bool onUpperLevel,float z);
//Return false if healing was not possible.
bool Heal(int damage,bool suppressDamageNumber=false);
//specificClass is a bitwise-combination of classes from the Class enum. It makes sure certain animations only play if you are a certain class.=
void UpdateAnimation(std::string animState,int specificClass=ANY,const float frameMult=1.f);
Animate2D::Frame GetFrame();
float GetSwordSwingTimer();
bool OnUpperLevel();
void ResetLastCombatTime();
bool IsOutOfCombat();
float GetEndZoneStandTime();
//Triggers when the player has moved.
void Moved(MoveFlag::MoveFlag flags=MoveFlag::NONE);
virtual ~Player()=default;
virtual Class GetClass()=0;
virtual bool AutoAttack()=0;
virtual void OnUpdate(float fElapsedTime)=0;
virtual const std::string&GetClassName()=0;
virtual Ability&GetRightClickAbility()=0;
virtual Ability&GetAbility1()=0;
virtual Ability&GetAbility2()=0;
virtual Ability&GetAbility3()=0;
virtual Ability&GetAbility4()=0;
virtual std::string&GetWalkNAnimation()=0;
virtual std::string&GetWalkEAnimation()=0;
virtual std::string&GetWalkSAnimation()=0;
virtual std::string&GetWalkWAnimation()=0;
virtual std::string&GetIdleNAnimation()=0;
virtual std::string&GetIdleEAnimation()=0;
virtual std::string&GetIdleSAnimation()=0;
virtual std::string&GetIdleWAnimation()=0;
geom2d::circle<float>Hitbox();
void CheckEndZoneCollision();
CastInfo&GetCastInfo();
//Provide the base animation name and this function does the rest. Ex. If you want WARRIOR_SWINGSWORD_S, use SWINGSWORD
void SetAnimationBasedOnTargetingDirection(const std::string_view animation_name,float targetDirection);
void SetAnimationBasedOnTarget(const std::string_view animation_name,const vf2d&target);
void SetItem1UseFunc(Ability a);
void SetItem2UseFunc(Ability a);
void SetItem3UseFunc(Ability a);
void PerformHPRecovery();
static InputGroup KEY_ABILITY1, KEY_ABILITY2, KEY_ABILITY3, KEY_ABILITY4, KEY_DEFENSIVE, KEY_ITEM1, KEY_ITEM2, KEY_ITEM3;
static std::vector<std::weak_ptr<MenuComponent>>moneyListeners;
static void AddMoneyListener(std::weak_ptr<MenuComponent>component);
uint32_t GetMoney()const;
void SetMoney(uint32_t newMoney);
void AddXP(const uint32_t xpGain);
void OnLevelUp();
const uint8_t LevelCap()const;
const uint8_t Level()const;
const uint32_t CurrentXP()const;
const uint32_t TotalXP()const;
const uint32_t NextLevelXPRequired()const;
void CancelCast();
const ItemAttributable&GetStats()const;
const ItemAttributable&GetBaseStats()const;
void ResetAccumulatedXP();
const uint32_t GetAccumulatedXP()const;
void AddAccumulatedXP(const uint32_t xpGain);
//Knockup the player for duration amount of seconds, and Zamt pixels.
void Knockup(float duration);
static const bool INVERTED,USE_WALK_DIR;
const vf2d GetAimingLocation(bool useWalkDir=false,bool invert=false);
const vf2d GetWorldAimingLocation(bool useWalkDir=false,bool invert=false);
void SetXP(const uint32_t xp);
void SetTotalXPEarned(const uint32_t totalXP);
void SetLevel(uint8_t newLevel);
void SetInvisible(const bool invisibleState);
const bool IsInvisible()const;
void ResetVelocity();
const float GetHealthGrowthRate()const;
const float GetAtkGrowthRate()const;
const float GetIframeTime()const;
const Renderable&GetMinimapImage()const;
void AddVelocity(vf2d vel);
const float GetHealthRatio()const;
const bool IsAlive()const;
//An all-in-one function that applies a stat plus any % modifiers the stat may have as well.
const float GetModdedStatBonuses(std::string_view stat)const;
//An all-in-one function that applies a stat plus any % modifiers the stat may have as well.
const float GetModdedStatBonuses(ItemAttribute stat)const;
//Flag to make the player character render using additive blending / normal blending.
void SetAdditiveBlending(const bool additiveBlending);
const bool IsUsingAdditiveBlending()const;
vf2d MoveUsingPathfinding(vf2d targetPos);
const std::unordered_set<std::string>&GetMyClass()const;
private:
int hp="Warrior.BaseHealth"_I;
int mana="Player.BaseMana"_I;
float hpGrowthRate="Warrior.HealthGrowthRate"_F;
float atkGrowthRate="Warrior.AtkGrowthRate"_F;
vf2d pos;
uint8_t level=1;
uint8_t levelCap=1;
uint32_t totalXPEarned=0;
uint32_t currentLevelXP=0;
uint32_t accumulatedXP=0;
float z=0;
float size=1.0f;
float spin_attack_timer=0;
float rolling_timer{};
float spin_spd=0;
float spin_angle=0;
float lastAnimationFlip=0;
float manaTickTimer=0;
float hpRecoveryTimer=0;
float hp6RecoveryTimer=0;
float hp4RecoveryTimer=0;
float knockUpTimer=0.f;
float totalKnockupTime=0.f;
float knockUpZAmt=0.f;
//angle in polar coords.
vf2d aimingAngle{};
std::pair<std::string,float> notEnoughManaDisplay={"",0.f};
float teleportAttemptWaitTime=0; //If a teleport fails, we wait awhile before trying again, it's expensive.
State::State state=State::NORMAL;
Animate2D::Animation<std::string>animation;
Animate2D::AnimationState internal_animState;
void AddAnimation(std::string state);
std::vector<Buff>buffList;
CastInfo castInfo={"",0};
vf2d movementVelocity={};//This tells us if the player is moving (mostly controlled by user input) since their velocity is not used for regular movement.
float lastHitTimer=0; //When this is greater than zero, if we get hit again it adds to our displayed combo number.
std::shared_ptr<DamageNumber>damageNumberPtr;
void Initialize();
float iframe_time=0;
float lastCombatTime=0;
float hurtRumbleTime=0.0f;
Ability useItem1;
Ability useItem2;
Ability useItem3;
uint32_t money="Player.Starting Money"_I;
EntityStats stats;
//So this is kind of a stupid one...
//If the attr provided is a % modifier, it will take the lhs in a += operation and multiply it by the % modifier from player's actual stats.
//If the attr provided is not a % modifier, it will add in a +=/+ operation...
const ItemAttribute&GetBonusStat(std::string_view attr)const;
bool rendered=false;
bool invisibility=false;
//Returns true if the move was valid and successful.
//If playerInvoked is true, this means the player was the one that instantiated this input, and it's not an extra movement done via collision.
//Set playerInvoked to false when you don't want a movement loop due to collisions.
//Typical usage is playerInvoked is true on first call, and playerInvoked is false on all subsequent chained calls.
bool _SetX(float x,MoveFlag::MoveFlag flags=MoveFlag::NONE,const bool playerInvoked=true);
//Returns true if the move was valid and successful.
//If playerInvoked is true, this means the player was the one that instantiated this input, and it's not an extra movement done via collision.
//Set playerInvoked to false when you don't want a movement loop due to collisions.
//Typical usage is playerInvoked is true on first call, and playerInvoked is false on all subsequent chained calls.
bool _SetY(float y,MoveFlag::MoveFlag flags=MoveFlag::NONE,const bool playerInvoked=true);
const bool UsingAutoAim()const;
void InitializeMinimapImage();
bool lowHealthSoundPlayed=false;
float lowHealthSoundPlayedTimer=0.f;
float rangerShootAnimationTimer=0.f;
Renderable minimapImg; //An image of the character represented on a minimap. Should be 12x12 and generally be a circle.
void CheckAndPerformAbility(Ability&ability,InputGroup key);
float deadlyDashWaitTimer{};
float deadlyDashAfterDashTimer{};
vf2d deadlyDashEndingPos{};
bool renderedSpriteUsesAdditiveBlending{false};
float deadlyDashAdditiveBlendingToggleTimer{};
std::unordered_set<std::string>myClass{};
protected:
const float ATTACK_COOLDOWN="Warrior.Auto Attack.Cooldown"_F;
const float MAGIC_ATTACK_COOLDOWN="Wizard.Auto Attack.Cooldown"_F;
float ARROW_ATTACK_COOLDOWN="Ranger.Auto Attack.Cooldown"_F;
void SetSwordSwingTimer(float val);
void SetFacingDirection(Key direction);
void Spin(float duration,float spinSpd);
float friction="Player.Friction"_F;
float attack_cooldown_timer=0;
float teleportAnimationTimer=0;
vf2d teleportTarget={};
vf2d teleportStartPosition={};
std::pair<std::string,float> notificationDisplay={"",0.f};
bool upperLevel=false;
vf2d vel={0,0};
float attack_range="Warrior.Auto Attack.Range"_F/100.f;
Key facingDirection=DOWN;
float swordSwingTimer=0;
void CastSpell(Ability&ability);
Ability*castPrepAbility;
void PrepareCast(Ability&ability);
vf2d precastLocation={};
void SetVelocity(vf2d vel);
const float RETREAT_DISTANCE=24*"Ranger.Right Click Ability.RetreatDistance"_F/100;
float RETREAT_TIME="Ranger.Right Click Ability.RetreatTime"_F; //How long the Retreat ability takes.
const int RETREAT_GHOST_FRAMES=8;
const float RETREAT_GHOST_FRAME_DELAY=0.025f;
float ghostFrameTimer=0;
float ghostRemoveTimer=0;
float blockTimer=0;
float retreatTimer=0;
std::vector<vf2d>ghostPositions;
float rapidFireTimer=0;
int remainingRapidFireShots=0;
float endZoneStandTime=0;
float lastPathfindingCooldown=0.f;
const float RAPID_FIRE_SHOOT_DELAY="Ranger.Ability 1.ArrowDelay"_F;
const int RAPID_FIRE_SHOOT_AMOUNT="Ranger.Ability 1.ArrowCount"_I;
float footstepTimer=0.f;
float ySquishFactor{1.f};
size_t cooldownSoundInstance=std::numeric_limits<size_t>::max();
};
#pragma region Warrior
struct Warrior:Player{
static std::string name;
static Class cl;
static Ability rightClickAbility,ability1,ability2,ability3,ability4;
static std::string walk_n,walk_e,walk_s,walk_w,idle_n,idle_e,idle_s,idle_w;
static void Initialize();
Warrior();
Warrior(Player*player);
Class GetClass()override;
bool AutoAttack()override;
//Include only WARRIOR-specific implementations!
void OnUpdate(float fElapsedTime)override;
static void InitializeClassAbilities();
const std::string&GetClassName()override;
Ability&GetRightClickAbility()override;
Ability&GetAbility1()override;
Ability&GetAbility2()override;
Ability&GetAbility3()override;
Ability&GetAbility4()override;
std::string&GetWalkNAnimation()override;
std::string&GetWalkEAnimation()override;
std::string&GetWalkSAnimation()override;
std::string&GetWalkWAnimation()override;
std::string&GetIdleNAnimation()override;
std::string&GetIdleEAnimation()override;
std::string&GetIdleSAnimation()override;
std::string&GetIdleWAnimation()override;
};
#pragma endregion
#pragma region Thief
struct Thief:Player{
static std::string name;
static Class cl;
static Ability rightClickAbility,ability1,ability2,ability3,ability4;
static std::string walk_n,walk_e,walk_s,walk_w,idle_n,idle_e,idle_s,idle_w;
static void Initialize();
Thief();
Thief(Player*player);
Class GetClass()override;
bool AutoAttack()override;
//Include only THIEF-specific implementations!
void OnUpdate(float fElapsedTime)override;
static void InitializeClassAbilities();
const std::string&GetClassName()override;
Ability&GetRightClickAbility()override;
Ability&GetAbility1()override;
Ability&GetAbility2()override;
Ability&GetAbility3()override;
Ability&GetAbility4()override;
std::string&GetWalkNAnimation()override;
std::string&GetWalkEAnimation()override;
std::string&GetWalkSAnimation()override;
std::string&GetWalkWAnimation()override;
std::string&GetIdleNAnimation()override;
std::string&GetIdleEAnimation()override;
std::string&GetIdleSAnimation()override;
std::string&GetIdleWAnimation()override;
};
#pragma endregion
#pragma region Ranger
struct Ranger:Player{
static std::string name;
static Class cl;
static Ability rightClickAbility,ability1,ability2,ability3,ability4;
static std::string walk_n,walk_e,walk_s,walk_w,idle_n,idle_e,idle_s,idle_w;
static void Initialize();
Ranger();
Ranger(Player*player);
Class GetClass()override;
bool AutoAttack()override;
//Include only RANGER-specific implementations!
void OnUpdate(float fElapsedTime)override;
static void InitializeClassAbilities();
const std::string&GetClassName()override;
Ability&GetRightClickAbility()override;
Ability&GetAbility1()override;
Ability&GetAbility2()override;
Ability&GetAbility3()override;
Ability&GetAbility4()override;
std::string&GetWalkNAnimation()override;
std::string&GetWalkEAnimation()override;
std::string&GetWalkSAnimation()override;
std::string&GetWalkWAnimation()override;
std::string&GetIdleNAnimation()override;
std::string&GetIdleEAnimation()override;
std::string&GetIdleSAnimation()override;
std::string&GetIdleWAnimation()override;
};
#pragma endregion
#pragma region Trapper
struct Trapper:Player{
static std::string name;
static Class cl;
static Ability rightClickAbility,ability1,ability2,ability3,ability4;
static std::string walk_n,walk_e,walk_s,walk_w,idle_n,idle_e,idle_s,idle_w;
static void Initialize();
Trapper();
Trapper(Player*player);
Class GetClass()override;
bool AutoAttack()override;
//Include only TRAPPER-specific implementations!
void OnUpdate(float fElapsedTime)override;
static void InitializeClassAbilities();
const std::string&GetClassName()override;
Ability&GetRightClickAbility()override;
Ability&GetAbility1()override;
Ability&GetAbility2()override;
Ability&GetAbility3()override;
Ability&GetAbility4()override;
std::string&GetWalkNAnimation()override;
std::string&GetWalkEAnimation()override;
std::string&GetWalkSAnimation()override;
std::string&GetWalkWAnimation()override;
std::string&GetIdleNAnimation()override;
std::string&GetIdleEAnimation()override;
std::string&GetIdleSAnimation()override;
std::string&GetIdleWAnimation()override;
};
#pragma endregion
#pragma region Wizard
struct Wizard:Player{
static std::string name;
static Class cl;
static Ability rightClickAbility,ability1,ability2,ability3,ability4;
static std::string walk_n,walk_e,walk_s,walk_w,idle_n,idle_e,idle_s,idle_w;
static void Initialize();
Wizard();
Wizard(Player*player);
Class GetClass()override;
bool AutoAttack()override;
//Include only WIZARD-specific implementations!
void OnUpdate(float fElapsedTime)override;
static void InitializeClassAbilities();
const std::string&GetClassName()override;
Ability&GetRightClickAbility()override;
Ability&GetAbility1()override;
Ability&GetAbility2()override;
Ability&GetAbility3()override;
Ability&GetAbility4()override;
std::string&GetWalkNAnimation()override;
std::string&GetWalkEAnimation()override;
std::string&GetWalkSAnimation()override;
std::string&GetWalkWAnimation()override;
std::string&GetIdleNAnimation()override;
std::string&GetIdleEAnimation()override;
std::string&GetIdleSAnimation()override;
std::string&GetIdleWAnimation()override;
};
#pragma endregion
#pragma region Witch
struct Witch:Player{
static std::string name;
static Class cl;
static Ability rightClickAbility,ability1,ability2,ability3,ability4;
static std::string walk_n,walk_e,walk_s,walk_w,idle_n,idle_e,idle_s,idle_w;
static void Initialize();
Witch();
Witch(Player*player);
Class GetClass()override;
bool AutoAttack()override;
//Include only WITCHs-specific implementations!
void OnUpdate(float fElapsedTime)override;
static void InitializeClassAbilities();
const std::string&GetClassName()override;
Ability&GetRightClickAbility()override;
Ability&GetAbility1()override;
Ability&GetAbility2()override;
Ability&GetAbility3()override;
Ability&GetAbility4()override;
std::string&GetWalkNAnimation()override;
std::string&GetWalkEAnimation()override;
std::string&GetWalkSAnimation()override;
std::string&GetWalkWAnimation()override;
std::string&GetIdleNAnimation()override;
std::string&GetIdleEAnimation()override;
std::string&GetIdleSAnimation()override;
std::string&GetIdleWAnimation()override;
};
#pragma endregion
#define READFROMCONFIG(class,enum) \
class::name=#class".ClassName"_S; \
class::cl=enum; \
class::rightClickAbility={ \
#class".Right Click Ability.Name"_S, \
#class".Right Click Ability.Short Name"_S, \
#class".Right Click Ability.Description"_s.concat(), \
#class".Right Click Ability.Cooldown"_F, \
#class".Right Click Ability.Mana Cost"_I, \
&KEY_DEFENSIVE, \
"Ability Icons/"+#class".Right Click Ability.Icon"_S, \
{uint8_t(#class".Right Click Ability.Cooldown Bar Color 1"_f[0]),uint8_t(#class".Right Click Ability.Cooldown Bar Color 1"_f[1]),uint8_t(#class".Right Click Ability.Cooldown Bar Color 1"_f[2]),uint8_t(#class".Right Click Ability.Cooldown Bar Color 1"_f[3]==0?255:#class".Right Click Ability.Cooldown Bar Color 1"_f[3])}, \
{uint8_t(#class".Right Click Ability.Cooldown Bar Color 2"_f[0]),uint8_t(#class".Right Click Ability.Cooldown Bar Color 2"_f[1]),uint8_t(#class".Right Click Ability.Cooldown Bar Color 2"_f[2]),uint8_t(#class".Right Click Ability.Cooldown Bar Color 2"_f[3]==0?255:#class".Right Click Ability.Cooldown Bar Color 2"_f[3])}, \
{#class".Right Click Ability.Precast Time"_F,#class".Right Click Ability.Casting Range"_I/100.f*24,#class".Right Click Ability.Casting Size"_I/100.f*24}, \
bool( #class".Right Click Ability.CancelCast"_I ) \
}; \
class::ability1={ \
#class".Ability 1.Name"_S, \
#class".Ability 1.Short Name"_S, \
#class".Ability 1.Description"_s.concat(), \
#class".Ability 1.Cooldown"_F, \
#class".Ability 1.Mana Cost"_I, \
&KEY_ABILITY1, \
"Ability Icons/"+#class".Ability 1.Icon"_S, \
{uint8_t(#class".Ability 1.Cooldown Bar Color 1"_f[0]),uint8_t(#class".Ability 1.Cooldown Bar Color 1"_f[1]),uint8_t(#class".Ability 1.Cooldown Bar Color 1"_f[2]),uint8_t(#class".Ability 1.Cooldown Bar Color 1"_f[3]==0?255:#class".Ability 1.Cooldown Bar Color 1"_f[3])}, \
{uint8_t(#class".Ability 1.Cooldown Bar Color 2"_f[0]),uint8_t(#class".Ability 1.Cooldown Bar Color 2"_f[1]),uint8_t(#class".Ability 1.Cooldown Bar Color 2"_f[2]),uint8_t(#class".Ability 1.Cooldown Bar Color 2"_f[3]==0?255:#class".Ability 1.Cooldown Bar Color 2"_f[3])}, \
{#class".Ability 1.Precast Time"_F,#class".Ability 1.Casting Range"_I/100.f*24,#class".Ability 1.Casting Size"_I/100.f*24}, \
bool(#class".Ability 1.CancelCast"_I) \
}; \
class::ability2={ \
#class".Ability 2.Name"_S, \
#class".Ability 2.Short Name"_S, \
#class".Ability 2.Description"_s.concat(), \
#class".Ability 2.Cooldown"_F, \
#class".Ability 2.Mana Cost"_I, \
&KEY_ABILITY2, \
"Ability Icons/"+#class".Ability 2.Icon"_S, \
{uint8_t(#class".Ability 2.Cooldown Bar Color 1"_f[0]),uint8_t(#class".Ability 2.Cooldown Bar Color 1"_f[1]),uint8_t(#class".Ability 2.Cooldown Bar Color 1"_f[2]),uint8_t(#class".Ability 2.Cooldown Bar Color 1"_f[3]==0?255:#class".Ability 2.Cooldown Bar Color 1"_f[3])}, \
{uint8_t(#class".Ability 2.Cooldown Bar Color 2"_f[0]),uint8_t(#class".Ability 2.Cooldown Bar Color 2"_f[1]),uint8_t(#class".Ability 2.Cooldown Bar Color 2"_f[2]),uint8_t(#class".Ability 2.Cooldown Bar Color 2"_f[3]==0?255:#class".Ability 2.Cooldown Bar Color 2"_f[3])}, \
{#class".Ability 2.Precast Time"_F,#class".Ability 2.Casting Range"_I/100.f*24,#class".Ability 2.Casting Size"_I/100.f*24}, \
bool(#class".Ability 2.CancelCast"_I) \
}; \
class::ability3={ \
#class".Ability 3.Name"_S, \
#class".Ability 3.Short Name"_S, \
#class".Ability 3.Description"_s.concat(), \
#class".Ability 3.Cooldown"_F, \
#class".Ability 3.Mana Cost"_I, \
&KEY_ABILITY3, \
"Ability Icons/"+#class".Ability 3.Icon"_S, \
{uint8_t(#class".Ability 3.Cooldown Bar Color 1"_f[0]),uint8_t(#class".Ability 3.Cooldown Bar Color 1"_f[1]),uint8_t(#class".Ability 3.Cooldown Bar Color 1"_f[2]),uint8_t(#class".Ability 3.Cooldown Bar Color 1"_f[3]==0?255:#class".Ability 3.Cooldown Bar Color 1"_f[3])}, \
{uint8_t(#class".Ability 3.Cooldown Bar Color 2"_f[0]),uint8_t(#class".Ability 3.Cooldown Bar Color 2"_f[1]),uint8_t(#class".Ability 3.Cooldown Bar Color 2"_f[2]),uint8_t(#class".Ability 3.Cooldown Bar Color 2"_f[3]==0?255:#class".Ability 3.Cooldown Bar Color 2"_f[3])}, \
{#class".Ability 3.Precast Time"_F,#class".Ability 3.Casting Range"_I/100.f*24,#class".Ability 3.Casting Size"_I/100.f*24}, \
bool(#class".Ability 3.CancelCast"_I) \
}; \
class::ability4;