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

556 lines
22 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 © 2023 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 CastInfo{
std::string name="???";
float castTimer=0.f;
float castTotalTime=0.f;
vf2d castPos{};
};
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;
const float&GetStat(ItemAttribute stat)const; //Get stats with equipment applied.
const float&GetStat(std::string_view stat)const; //Get stats with equipment applied.
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 SaveFile;
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();
float GetX();
float GetY();
float GetZ();
const float&GetStat(ItemAttribute a)const;
const float&GetStat(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 float&GetMaxHealth()const;
const int GetHealth()const;
const int GetMana()const;
const int GetMaxMana()const;
const int GetAttack();
const float GetDamageReductionFromBuffs()const;
const float GetDamageReductionFromArmor()const;
float GetMoveSpdMult();
float GetSizeMult();
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);
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();
bool CanAct(Ability&ability);
void Knockback(vf2d vel);
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);
//Returns true if the move was valid and successful.
bool SetY(float y);
void SetZ(float z);
//Returns true if the move was valid and successful.
bool SetPos(vf2d pos);
void SetState(State::State newState);
void AddBuff(BuffType type,float duration,float intensity);
void AddBuff(BuffType type,float duration,float intensity,std::set<ItemAttribute>attr);
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;
void RemoveBuff(BuffType type); //Removes the first buff found.
void RemoveAllBuffs(BuffType type); //Removes all buffs of a certain type.
void RemoveAllBuffs(); //Remove every buff.
void RecalculateEquipStats();
bool Hurt(int damage,bool onUpperLevel,float z);
//Return false if healing was not possible.
bool Heal(int damage);
//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);
Animate2D::Frame GetFrame();
Key GetLastReleasedMovementKey();
float GetSwordSwingTimer();
bool OnUpperLevel();
void ResetLastCombatTime();
bool IsOutOfCombat();
float GetEndZoneStandTime();
//Triggers when the player has moved.
void Moved();
virtual ~Player()=default;
virtual Class GetClass()=0;
virtual bool AutoAttack()=0;
virtual void OnUpdate(float fElapsedTime)=0;
virtual 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;
void CheckEndZoneCollision();
CastInfo&GetCastInfo();
void SetAnimationBasedOnTargetingDirection(float targetDirection);
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::set<MenuComponent*>moneyListeners;
static void AddMoneyListener(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);
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 spin_spd=0;
float spin_angle=0;
float lastAnimationFlip=0;
float manaTickTimer=0;
float hpRecoveryTimer=0;
float hp6RecoveryTimer=0;
float hp4RecoveryTimer=0;
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;
Key lastReleasedMovementKey;
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;
Ability useItem1;
Ability useItem2;
Ability useItem3;
uint32_t money="Player.Starting Money"_I;
EntityStats stats;
ItemAttribute&Get(std::string_view attr);
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 SetLastReleasedMovementKey(Key k);
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;
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;
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();
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();
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();
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();
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();
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();
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;