Item Attribute systems reworked from being an enum class to being a class. Added support for the Buff Item Script to modify items. Implemented Damage Reduction proposal.

pull/28/head
sigonasr2 1 year ago
parent 51cbf81204
commit 668a5ca1b1
  1. 121
      Crawler/AttributableStat.cpp
  2. 98
      Crawler/AttributableStat.h
  3. 15
      Crawler/Buff.h
  4. 46
      Crawler/CharacterMenuWindow.cpp
  5. 21
      Crawler/Crawler.cpp
  6. 3
      Crawler/Crawler.vcxproj
  7. 8
      Crawler/Crawler.vcxproj.filters
  8. 58
      Crawler/Item.cpp
  9. 13
      Crawler/Item.h
  10. 13
      Crawler/ItemStats.txt
  11. 2
      Crawler/Menu.h
  12. 33
      Crawler/Monster.cpp
  13. 77
      Crawler/Monster.h
  14. 171
      Crawler/Player.cpp
  15. 189
      Crawler/Player.h
  16. 8
      Crawler/ShootAfar.cpp
  17. 8
      Crawler/SlimeKing.cpp
  18. 37
      Crawler/StatCalculations.txt
  19. 5
      Crawler/StatLabel.h
  20. 2
      Crawler/TODO.txt
  21. 2
      Crawler/Version.h
  22. 2
      Crawler/Warrior.cpp
  23. 3
      Crawler/assets/config/classes/Warrior.txt
  24. 3
      Crawler/assets/config/configuration.txt
  25. 16
      Crawler/assets/config/items/ItemScript.txt
  26. 13
      Crawler/assets/config/items/ItemSets.txt
  27. 62
      Crawler/assets/config/items/ItemStats.txt
  28. 6
      Crawler/olcUTIL_DataFile.h

@ -35,18 +35,131 @@ Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "AttributableStat.h"
#include "olcUTIL_DataFile.h"
#include "DEFINES.h"
#include "Item.h"
#include "Player.h"
#include "Monster.h"
INCLUDE_DATA
ItemAttributable&ItemAttributable::operator+=(Stats&rhs){
for(ItemAttribute a=ItemAttribute(int(ItemAttribute::ENUM_START)+1);a<ItemAttribute::ENUM_END;a=ItemAttribute(int(a)+1)){
A(a)+=rhs.get_readOnly(a);
for(auto&[key,value]:ItemAttribute::attributes){
A(value)+=rhs.get_readOnly(value);
}
return *this;
};
ItemAttributable&ItemAttributable::operator+=(ItemAttributable&rhs){
for(ItemAttribute a=ItemAttribute(int(ItemAttribute::ENUM_START)+1);a<ItemAttribute::ENUM_END;a=ItemAttribute(int(a)+1)){
A(a)+=rhs.get_readOnly(a);
for(auto&[key,value]:ItemAttribute::attributes){
A(value)+=rhs.get_readOnly(value);
}
return *this;
};
ItemAttribute::ItemAttribute(std::string_view name,bool isPct,std::string_view modifies)
:name(name),isPct(isPct),modifies(modifies){}
void ItemAttribute::Initialize(){
for(auto&[key,size]:DATA["Stats"]){
std::string modifies="";
if(DATA["Stats"][key].HasProperty("Modifies")){
modifies=DATA["Stats"][key]["Modifies"].GetString();
}
attributes.insert(key,ItemAttribute(key,DATA["Stats"][key]["Percentage"].GetBool(),modifies));
}
attributes.SetInitialized();
}
ItemAttribute&ItemAttribute::Get(const std::string_view name,const std::optional<std::variant<Player*,Monster*>>target){
attributes.at(std::string(name)).target=target;
return attributes.at(std::string(name));
}
const std::string_view ItemAttribute::Name()const{
return name;
}
const bool ItemAttribute::DisplayAsPercent()const{
return isPct;
}
//WARNING! Implemented for map sorting!!!
const bool ItemAttribute::operator<(const ItemAttribute&rhs)const{
return name<rhs.name;
};
const std::string_view ItemAttribute::Modifies()const{
return modifies;
};
float&operator+=(float&lhs,const ItemAttribute&rhs){
if(rhs.target){
#pragma region Acquire Stats based on Object Type
float statModifierTotal=0;
std::vector<Buff>statUpBuffs;
const ItemAttributable*stats=nullptr;
if(std::holds_alternative<Player*>(rhs.target.value())){
statUpBuffs=std::get<Player*>(rhs.target.value())->GetBuffs(STAT_UP);
stats=&std::get<Player*>(rhs.target.value())->GetStats();
}else{
statUpBuffs=std::get<Monster*>(rhs.target.value())->GetBuffs(STAT_UP);
stats=&std::get<Monster*>(rhs.target.value())->GetStats();
}
for(const Buff&b:statUpBuffs){
if(b.attr.count(rhs)){
statModifierTotal+=b.intensity;
}
}
#pragma endregion
if(rhs.DisplayAsPercent()){ //This is a percentage-based stat modifier.
if(rhs.Modifies().length()>0){
lhs+=stats->A_Read(rhs.Modifies())*statModifierTotal;
}else{
lhs+=stats->A_Read(rhs)*statModifierTotal;
}
}else{ //This is a flat stat modifier.
lhs+=statModifierTotal;
}
}else{
ERR("WARNING! Did not specify a target using the Get() ItemAttribute parameter!");
}
return lhs;
}
float&operator-=(float&lhs,const ItemAttribute&rhs){
if(rhs.target){
#pragma region Acquire Stats based on Object Type
float statModifierTotal=0;
std::vector<Buff>statUpBuffs;
const ItemAttributable*stats=nullptr;
if(std::holds_alternative<Player*>(rhs.target.value())){
statUpBuffs=std::get<Player*>(rhs.target.value())->GetBuffs(STAT_UP);
stats=&std::get<Player*>(rhs.target.value())->GetStats();
}else{
statUpBuffs=std::get<Monster*>(rhs.target.value())->GetBuffs(STAT_UP);
stats=&std::get<Monster*>(rhs.target.value())->GetStats();
}
for(const Buff&b:statUpBuffs){
if(b.attr.count(rhs)){
statModifierTotal+=b.intensity;
}
}
#pragma endregion
if(rhs.DisplayAsPercent()){ //This is a percentage-based stat modifier.
if(rhs.Modifies().length()>0){
lhs-=stats->A_Read(rhs.Modifies())*statModifierTotal;
}else{
lhs-=stats->A_Read(rhs)*statModifierTotal;
}
}else{ //This is a flat stat modifier.
lhs-=statModifierTotal;
}
}else{
ERR("WARNING! Did not specify a target using the Get() ItemAttribute parameter!");
}
return lhs;
}
const bool ItemAttribute::operator==(const ItemAttribute&rhs)const{
return name==rhs.name;
};

@ -37,67 +37,43 @@ All rights reserved.
#pragma endregion
#pragma once
#include "olcPixelGameEngine.h"
#include "safemap.h"
#include <variant>
using namespace std::string_view_literals;
#define A(attr) get(attr)
#define A_Read(attr) get_readOnly(attr)
namespace DisplayType{
enum DisplayType{
DISPLAY_AS_NUMBER=0,
DISPLAY_AS_PERCENT=1,
};
}
struct AttributeData{
std::string name;
DisplayType::DisplayType displayAsPercent;
};
enum class ItemAttribute{
/*////////////////////////////////////////////*/
/*//////*/ENUM_START,/*///////////////////////*/
/*////////////////////////////////////////////*/
//////////////
defense,
health,
attack,
moveSpdPct,
cdrPct,
critPct,
critDmgPct,
healthPct, //Percentage of health boost
healthPctRecoveryPer6sec, //Percentage of health recovered every 6 seconds.
healthPctRecoveryPer4sec, //Percentage of health recovered every 4 seconds.
damageReductionPct, //Percentage of damage reduced directly.
attackPct, //Percentage of damage increased by.
class Player;
class Monster;
/////////NOTE: When adding a new item stat, provide its display name in the map below.
/*////////////////////////////////////////////*/
/*//////*/ENUM_END/*//////////////////////////*/
/*////////////////////////////////////////////*/
class ItemAttribute{
friend class Crawler;
std::string name;
bool isPct;
std::string modifies="";
std::optional<std::variant<Player*,Monster*>>target;
static void Initialize();
public:
inline static safemap<std::string,ItemAttribute>attributes;
ItemAttribute(std::string_view name,bool isPct,std::string_view modifies=""sv);
static ItemAttribute&Get(const std::string_view name,const std::optional<std::variant<Player*,Monster*>>target={});
const std::string_view Name()const;
const std::string_view Modifies()const;
const bool DisplayAsPercent()const;
const bool operator<(const ItemAttribute&rhs)const; //WARNING! Implemented for map sorting!!!
friend float&operator+=(float&lhs,const ItemAttribute&rhs);
friend float&operator-=(float&lhs,const ItemAttribute&rhs);
const bool operator==(const ItemAttribute&rhs)const;
};
class Stats;
struct Stats;
class ItemAttributable{
friend class ItemInfo;
protected:
std::map<ItemAttribute,int>attributes;
private:
inline static std::map<std::string,ItemAttribute>stringToAttribute;
inline static std::map<ItemAttribute,AttributeData>data{
{ItemAttribute::defense,{"Defense",DisplayType::DISPLAY_AS_NUMBER}},
{ItemAttribute::health,{"Health",DisplayType::DISPLAY_AS_NUMBER}},
{ItemAttribute::attack,{"Attack",DisplayType::DISPLAY_AS_NUMBER}},
{ItemAttribute::moveSpdPct,{"Move Spd",DisplayType::DISPLAY_AS_PERCENT}},
{ItemAttribute::cdrPct,{"CDR",DisplayType::DISPLAY_AS_PERCENT}},
{ItemAttribute::critPct,{"Crit Rate",DisplayType::DISPLAY_AS_PERCENT}},
{ItemAttribute::critDmgPct,{"Crit Dmg",DisplayType::DISPLAY_AS_PERCENT}},
{ItemAttribute::healthPct,{"Health %",DisplayType::DISPLAY_AS_PERCENT}}, //Percentage of health boost
{ItemAttribute::healthPctRecoveryPer6sec,{"HP6 Recovery %",DisplayType::DISPLAY_AS_NUMBER}}, //Percentage of health recovered every 6 seconds.
{ItemAttribute::healthPctRecoveryPer4sec,{"HP4 Recovery %",DisplayType::DISPLAY_AS_NUMBER}}, //Percentage of health recovered every 4 seconds.
{ItemAttribute::damageReductionPct,{"Damage Reduction",DisplayType::DISPLAY_AS_PERCENT}}, //Percentage of damage reduced directly.
{ItemAttribute::attackPct,{"Attack %",DisplayType::DISPLAY_AS_PERCENT}}, //Percentage of damage increased by.
};
std::map<ItemAttribute,float>attributes;
public:
ItemAttributable&operator+=(ItemAttributable&rhs);
ItemAttributable&operator+=(Stats&rhs);
@ -106,22 +82,20 @@ public:
inline void copyTo(ItemAttributable&target){
target.attributes=attributes;
}
inline int&get(ItemAttribute a){
inline float&get(std::string_view a){
return get(ItemAttribute::Get(a));
}
inline float&get(const ItemAttribute&a){
return attributes[a];
}
inline const int get_readOnly(ItemAttribute a)const{
inline const float get_readOnly(std::string_view a)const{
return get_readOnly(ItemAttribute::Get(a));
}
inline const float get_readOnly(const ItemAttribute&a)const{
if(attributes.count(a)){
return attributes.at(a);
}
return 0;
}
inline static AttributeData GetDisplayInfo(ItemAttribute a){
return data[a];
}
inline static std::string GetAttributeName(ItemAttribute attr){
return data[attr].name;
}
inline static ItemAttribute GetAttributeFromString(std::string attrName){
return stringToAttribute[attrName];
};
};
};

@ -36,9 +36,11 @@ All rights reserved.
*/
#pragma endregion
#pragma once
#include <set>
#include "AttributableStat.h"
enum BuffType{
ATTACK_UP,
ATTACK_PCT_UP,
STAT_UP,
DAMAGE_REDUCTION,
SLOWDOWN,
BLOCK_SLOWDOWN,
@ -54,9 +56,18 @@ struct Buff{
float timeBetweenTicks=1;
float intensity=1;
float nextTick=0;
std::set<ItemAttribute> attr;
std::function<void(Crawler*,int)>repeatAction;
inline Buff(BuffType type,float duration,float intensity)
:type(type),duration(duration),intensity(intensity){}
inline Buff(BuffType type,float duration,float intensity,std::set<ItemAttribute> attr)
:type(type),duration(duration),intensity(intensity),attr(attr){}
inline Buff(BuffType type,float duration,float intensity,std::set<std::string> attr)
:type(type),duration(duration),intensity(intensity){
for(const std::string&s:attr){
this->attr.insert(ItemAttribute::attributes.at(s));
}
}
inline Buff(BuffType type,float duration,float intensity,float timeBetweenTicks,std::function<void(Crawler*,int)>repeatAction)
:type(type),duration(duration),intensity(intensity),nextTick(duration-timeBetweenTicks),timeBetweenTicks(timeBetweenTicks),repeatAction(repeatAction){}
};

@ -61,14 +61,14 @@ void Menu::InitializeCharacterMenuWindow(){
characterMenuWindow->ADD("Equip Slot Outline",MenuComponent)({{0,28},{120,windowSize.y-37}},"",DO_NOTHING,ButtonAttr::UNSELECTABLE)END;
characterMenuWindow->ADD("Character Rotating Display",CharacterRotatingDisplay)({{135,28},{90,windowSize.y-48}},GFX[classutils::GetClassInfo(game->GetPlayer()->GetClassName()).classFullImgName].Decal())END;
const static std::array<ItemAttribute,7>displayAttrs{
ItemAttribute::health,
ItemAttribute::attack,
ItemAttribute::defense,
ItemAttribute::moveSpdPct,
ItemAttribute::cdrPct,
ItemAttribute::critPct,
ItemAttribute::critDmgPct,
const static std::array<std::string,7>displayAttrs{
"Health",
"Attack",
"Defense",
"Move Spd %",
"CDR",
"Crit Rate",
"Crit Dmg",
};
@ -85,8 +85,8 @@ void Menu::InitializeCharacterMenuWindow(){
Component<MenuComponent>(data.component->parentMenu,"Equip Selection Bottom Outline")->Enable(false);
Component<MenuComponent>(data.component->parentMenu,"Equip Selection Select Button")->Enable(false);
Component<CharacterRotatingDisplay>(data.component->parentMenu,"Character Rotating Display")->Enable(true);
for(int counter=0;ItemAttribute attribute:displayAttrs){
StatLabel*statDisplayLabel=Component<StatLabel>(CHARACTER_MENU,"Attribute "+ItemAttributable::GetDisplayInfo(attribute).name+" Label");
for(int counter=0;const std::string&attribute:displayAttrs){
StatLabel*statDisplayLabel=Component<StatLabel>(CHARACTER_MENU,"Attribute "+std::string(ItemAttribute::Get(attribute).Name())+" Label");
statDisplayLabel->SetStatChangeAmt(0);
}
equipmentWindowOpened=false;
@ -96,10 +96,9 @@ void Menu::InitializeCharacterMenuWindow(){
equipSelectionSelectButton->Enable(false);
const static auto GetLabelText=[](ItemAttribute attribute){
AttributeData data=ItemAttributable::GetDisplayInfo(attribute);
std::string attrStr=data.name+":\n ";
std::string attrStr=std::string(attribute.Name())+":\n ";
attrStr+=std::to_string(game->GetPlayer()->GetStat(attribute));
if(data.displayAsPercent){
if(attribute.DisplayAsPercent()){
attrStr+="%";
}
return attrStr;
@ -149,8 +148,8 @@ void Menu::InitializeCharacterMenuWindow(){
}
}
comp->SetSelected(true);
for(int counter=0;ItemAttribute attribute:displayAttrs){
StatLabel*statDisplayLabel=Component<StatLabel>(CHARACTER_MENU,"Attribute "+ItemAttributable::GetDisplayInfo(attribute).name+" Label");
for(int counter=0;const std::string&attribute:displayAttrs){
StatLabel*statDisplayLabel=Component<StatLabel>(CHARACTER_MENU,"Attribute "+std::string(ItemAttribute::Get(attribute).Name())+" Label");
statDisplayLabel->SetStatChangeAmt(0);
}
MenuItemItemButton*equipButton=Component<MenuItemItemButton>(CHARACTER_MENU,"Equip Slot "+slotNames[data.parentComponent->I(A::INDEXED_THEME)]);
@ -168,13 +167,13 @@ void Menu::InitializeCharacterMenuWindow(){
const std::weak_ptr<Item>buttonItem=button->GetItem();
std::vector<int>statsBeforeEquip;
EquipSlot slot=EquipSlot(button->I(Attribute::EQUIP_TYPE));
for(ItemAttribute attribute:displayAttrs){
for(const std::string&attribute:displayAttrs){
statsBeforeEquip.push_back(game->GetPlayer()->GetStat(attribute));
}
std::weak_ptr<Item>equippedItem=Inventory::GetEquip(slot);
Inventory::EquipItem(buttonItem,slot);
for(int counter=0;ItemAttribute attribute:displayAttrs){
StatLabel*statDisplayLabel=Component<StatLabel>(CHARACTER_MENU,"Attribute "+ItemAttributable::GetDisplayInfo(attribute).name+" Label");
for(int counter=0;const std::string&attribute:displayAttrs){
StatLabel*statDisplayLabel=Component<StatLabel>(CHARACTER_MENU,"Attribute "+std::string(ItemAttribute::Get(attribute).Name())+" Label");
int statChangeAmt=game->GetPlayer()->GetStat(attribute)-statsBeforeEquip[counter];
statDisplayLabel->SetStatChangeAmt(statChangeAmt);
counter++;
@ -190,8 +189,8 @@ void Menu::InitializeCharacterMenuWindow(){
});
equip->SetMouseOutFunc(
[](MenuFuncData data){
for(int counter=0;ItemAttribute attribute:displayAttrs){
StatLabel*statDisplayLabel=Component<StatLabel>(CHARACTER_MENU,"Attribute "+ItemAttributable::GetDisplayInfo(attribute).name+" Label");
for(int counter=0;const std::string&attribute:displayAttrs){
StatLabel*statDisplayLabel=Component<StatLabel>(CHARACTER_MENU,"Attribute "+std::string(ItemAttribute::Get(attribute).Name())+" Label");
statDisplayLabel->SetStatChangeAmt(0);
counter++;
}
@ -241,10 +240,9 @@ void Menu::InitializeCharacterMenuWindow(){
characterMenuWindow->ADD("Stat Display Outline",MenuComponent)({{245,28},{62,windowSize.y-37}},"",DO_NOTHING,ButtonAttr::UNSELECTABLE)END;
int yOffset=0;
for(ItemAttribute attribute:displayAttrs){
std::string attrStr=GetLabelText(attribute);
AttributeData data=ItemAttributable::GetDisplayInfo(attribute);
auto attrLabel=characterMenuWindow->ADD("Attribute "+data.name+" Label",StatLabel)({{245,28+2+float(yOffset)},{62,18}},attribute,1,ComponentAttr::SHADOW|ComponentAttr::LEFT_ALIGN)END;
for(const std::string&attribute:displayAttrs){
std::string attrStr=GetLabelText(ItemAttribute::Get(attribute));
auto attrLabel=characterMenuWindow->ADD("Attribute "+attribute+" Label",StatLabel)({{245,28+2+float(yOffset)},{62,18}},ItemAttribute::Get(attribute),1,ComponentAttr::SHADOW|ComponentAttr::LEFT_ALIGN)END;
Menu::AddEquipStatListener(attrLabel);
yOffset+=20;
}

@ -131,6 +131,9 @@ Crawler::Crawler()
std::string ITEM_SET_CONFIG = CONFIG_PATH + "item_set_config"_S;
utils::datafile::Read(DATA,ITEM_SET_CONFIG);
std::string ITEM_STATS_CONFIG = CONFIG_PATH + "item_stats_config"_S;
utils::datafile::Read(DATA,ITEM_STATS_CONFIG);
for(auto&[key,value]:DATA.GetProperty("ItemConfiguration").GetKeys()){
std::string config = DATA["ItemConfiguration"][key].GetString();
utils::datafile::Read(DATA,CONFIG_PATH + "item_directory"_S + config);
@ -165,6 +168,8 @@ bool Crawler::OnUserCreate(){
camera.SetTarget(player->GetPos());
camera.SetWorldBoundary({0,0},GetCurrentMap().MapSize*GetCurrentMap().TileSize);
camera.EnableWorldBoundary(false);
ItemAttribute::Initialize();
ItemInfo::InitializeItems();
@ -195,6 +200,7 @@ bool Crawler::OnUserCreate(){
Inventory::AddItem("Shell Shoes");
Inventory::AddItem("Bone Pants");
Inventory::AddItem("Bone Gloves");
Inventory::AddItem("Elixir of Bear Strength",3);
LoadLevel(LEVEL_NAMES["starting_map"_S]);
ChangePlayerClass(WARRIOR);
@ -206,6 +212,8 @@ bool Crawler::OnUserCreate(){
Merchant::Initialize();
Stats::InitializeDamageReductionTable();
utils::datafile::INITIAL_SETUP_COMPLETE=true;
ValidateGameStatus(); //Checks to make sure everything has been initialized properly.
@ -722,7 +730,8 @@ void Crawler::RenderWorld(float fElapsedTime){
playerScale.x=120*float(abs(pow(player->teleportAnimationTimer-0.175f,3)));
pos=player->teleportStartPosition.lerp(player->teleportTarget,(0.35f-player->teleportAnimationTimer)/0.35f);
}
view.DrawPartialRotatedDecal(pos+vf2d{0,-player->GetZ()*(std::signbit(scale.y)?-1:1)},player->GetFrame().GetSourceImage()->Decal(),player->GetSpinAngle(),{12,12},player->GetFrame().GetSourceRect().pos,player->GetFrame().GetSourceRect().size,playerScale*scale,player->GetBuffs(BuffType::ATTACK_UP).size()>0?Pixel{255,uint8_t(255*abs(sin(1.4*player->GetBuffs(BuffType::ATTACK_UP)[0].duration))),uint8_t(255*abs(sin(1.4*player->GetBuffs(BuffType::ATTACK_UP)[0].duration)))}:WHITE);
const std::vector<Buff>attackBuffs=player->GetStatBuffs({"Attack","Attack %"});
view.DrawPartialRotatedDecal(pos+vf2d{0,-player->GetZ()*(std::signbit(scale.y)?-1:1)},player->GetFrame().GetSourceImage()->Decal(),player->GetSpinAngle(),{12,12},player->GetFrame().GetSourceRect().pos,player->GetFrame().GetSourceRect().size,playerScale*scale,attackBuffs.size()>0?Pixel{255,uint8_t(255*abs(sin(1.4*attackBuffs[0].duration))),uint8_t(255*abs(sin(1.4*attackBuffs[0].duration)))}:WHITE);
SetDecalMode(DecalMode::NORMAL);
if(player->GetState()==State::BLOCK){
view.DrawDecal(player->GetPos()-vf2d{12,12},GFX["block.png"].Decal());
@ -1244,7 +1253,9 @@ void Crawler::RenderHud(){
if("debug_player_info"_I){
DrawShadowStringDecal({0,128},player->GetPos().str());
DrawShadowStringDecal({0,136},"Spd: "+std::to_string(player->GetMoveSpdMult()));
DrawShadowStringDecal({0,92},"Loadout Slot 1 Qty: "+std::to_string(GetLoadoutItem(0).lock()->Amt()));
if(!ISBLANK(GetLoadoutItem(0))){
DrawShadowStringDecal({0,92},"Loadout Slot 1 Qty: "+std::to_string(GetLoadoutItem(0).lock()->Amt()));
}
DrawShadowStringDecal({0,1},"Selection: "+Menu::menus[INVENTORY_CONSUMABLES]->selection.str());
DrawShadowStringDecal({0,12},"Button Hold Time: "+std::to_string(Menu::menus[INVENTORY_CONSUMABLES]->buttonHoldTime));
}}
@ -1844,9 +1855,9 @@ void Crawler::ChangePlayerClass(Class cl){
player.reset(NEW Witch(player.get()));
}break;
}
player->SetBaseStat(ItemAttribute::health,DATA.GetProperty(player->GetClassName()+".BaseHealth").GetInt());
player->hp=player->GetBaseStat(ItemAttribute::health);
player->SetBaseStat(ItemAttribute::attack,DATA.GetProperty(player->GetClassName()+".BaseAtk").GetInt());
player->SetBaseStat("Health",DATA.GetProperty(player->GetClassName()+".BaseHealth").GetInt());
player->hp=player->GetBaseStat("Health");
player->SetBaseStat("Attack",DATA.GetProperty(player->GetClassName()+".BaseAtk").GetInt());
player->hpGrowthRate=float(DATA.GetProperty(player->GetClassName()+".HealthGrowthRate").GetReal());
player->atkGrowthRate=float(DATA.GetProperty(player->GetClassName()+".AtkGrowthRate").GetReal());
player->money=oldMoney;

@ -568,6 +568,7 @@
<Text Include="assets\config\items\items.txt" />
<Text Include="assets\config\items\ItemScript.txt" />
<Text Include="assets\config\items\ItemSets.txt" />
<Text Include="assets\config\items\ItemStats.txt" />
<Text Include="assets\config\levels.txt" />
<Text Include="assets\config\Monsters.txt" />
<Text Include="assets\config\MonsterStrategies.txt" />
@ -581,10 +582,10 @@
<Text Include="assets\config\story\Chapter 1.txt" />
<Text Include="Crawler_Story_Chapter_1 (2).txt" />
<Text Include="Crawler_System_Overview.txt" />
<Text Include="ItemStats.txt" />
<Text Include="NewClasses.txt" />
<Text Include="InitialConcept.txt" />
<Text Include="Slime_King_Encounter.txt" />
<Text Include="StatCalculations.txt" />
<Text Include="TODO.txt" />
</ItemGroup>
<ItemGroup>

@ -82,6 +82,9 @@
<Filter Include="Configurations\Item Merchants">
<UniqueIdentifier>{8423ea80-23c9-48cb-a506-6b3bdfa8000e}</UniqueIdentifier>
</Filter>
<Filter Include="Documentation\Mechanics">
<UniqueIdentifier>{a54ab812-9474-465f-9f0d-4dceecb9e963}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="olcPixelGameEngine.h">
@ -725,9 +728,12 @@
<Text Include="TODO.txt">
<Filter>Documentation</Filter>
</Text>
<Text Include="ItemStats.txt">
<Text Include="assets\config\items\ItemStats.txt">
<Filter>Configurations\Items</Filter>
</Text>
<Text Include="StatCalculations.txt">
<Filter>Documentation\Mechanics</Filter>
</Text>
</ItemGroup>
<ItemGroup>
<Image Include="assets\heart.ico">

@ -41,6 +41,7 @@ All rights reserved.
#include "Crawler.h"
#include "Menu.h"
#include "Ability.h"
#include "AttributableStat.h"
INCLUDE_game
INCLUDE_DATA
@ -57,6 +58,7 @@ std::map<std::string,ItemSet>ItemSet::sets;
std::map<EquipSlot,std::weak_ptr<Item>>Inventory::equipment;
std::map<std::string,EquipSlot>ItemInfo::nameToEquipSlot;
int Item::IsBlankStaticCallCounter=0;
safemap<int,float>Stats::maxDamageReductionTable;
ItemInfo::ItemInfo()
:customProps({nullptr,nullptr}),img(nullptr){}
@ -76,11 +78,6 @@ void ItemInfo::InitializeItems(){
nameToEquipSlot["Ring2"]=EquipSlot::RING2;
InitializeScripts();
//Setup Strings to Attribute map.
for(ItemAttribute attr=ItemAttribute(int(ItemAttribute::ENUM_START)+1);attr<ItemAttribute::ENUM_END;attr=ItemAttribute(int(attr)+1)){
ItemAttributable::stringToAttribute[ItemAttributable::GetDisplayInfo(attr).name]=attr;
}
InitializeSets();
@ -130,7 +127,7 @@ void ItemInfo::InitializeItems(){
}else
if(keyName=="StatValues"){
for(int i=0;i<data[key]["StatValues"].GetValueCount();i++){
statValueList.push_back(ItemAttributable::GetAttributeFromString(data[key]["StatValues"].GetString(i)));
statValueList.push_back(ItemAttribute::Get(data[key]["StatValues"].GetString(i)));
}
}else
if(keyName=="PartofSet"){
@ -259,8 +256,19 @@ void ItemInfo::InitializeScripts(){
});
return true;
};
for(auto&[key,value]:ItemAttribute::attributes){
if(!DATA.GetProperty("ItemScript.Buff").HasProperty(key)){
ERR("WARNING! Buff Item Script does not support Buff "<<std::quoted(value.Name())<<"!");
}
}
ITEM_SCRIPTS["Buff"]=[](Crawler*game,ItemProps props){
game->GetPlayer()->AddBuff(BuffType::ATTACK_PCT_UP,props.GetFloatProp("Attack %",1),props.GetFloatProp("Attack %",0)/100);
for(auto&[key,value]:ItemAttribute::attributes){
float intensity=props.GetFloatProp(key,0);
if(ItemAttribute::Get(key).DisplayAsPercent())intensity/=100;
game->GetPlayer()->AddBuff(BuffType::STAT_UP,props.GetFloatProp(key,1),intensity,{ItemAttribute::Get(key)});
}
return true;
};
ITEM_SCRIPTS["RestoreDuringCast"]=[&](Crawler*game,ItemProps props){
@ -612,11 +620,11 @@ void Inventory::EquipItem(const std::weak_ptr<Item>it,EquipSlot slot){
if(equippedSlot!=EquipSlot::NONE)UnequipItem(equippedSlot);
if(!GetEquip(slot).expired())UnequipItem(slot);
Inventory::equipment[slot]=it;
PlayerStats::RecalculateEquipStats();
game->GetPlayer()->RecalculateEquipStats();
};
void Inventory::UnequipItem(EquipSlot slot){
Inventory::equipment[slot]=Item::BLANK;
PlayerStats::RecalculateEquipStats();
game->GetPlayer()->RecalculateEquipStats();
};
EquipSlot Inventory::GetSlotEquippedIn(const std::weak_ptr<Item>it){
for(int i=int(EquipSlot::HELMET);i<=int(EquipSlot::RING2);i<<=1){
@ -654,7 +662,7 @@ void ItemInfo::InitializeSets(){
datafile&statInfo=setInfo[std::to_string(pieceCount)];
Stats bonuses;
for(auto&[key,value]:statInfo.GetKeys()){
ItemAttribute attr=bonuses.GetAttributeFromString(key);
const ItemAttribute&attr=ItemAttribute::Get(key);
bonuses.A(attr)=statInfo[key].GetInt(0);
}
ItemSet::AddSetBonus(setName,pieceCount,bonuses);
@ -718,7 +726,7 @@ const std::string Stats::GetStatsString(CompactText compact)const{
description+="\n";
}
}
description+=ItemAttributable::GetAttributeName(attr)+": "+std::to_string(val)+(ItemAttributable::GetDisplayInfo(attr).displayAsPercent?"%":"");
description+=std::string(attr.Name())+": "+std::to_string(int(val))+(attr.DisplayAsPercent()?"%":"");
first=false;
}
return description;
@ -783,4 +791,32 @@ const bool Item::UseDuringCast()const{
const bool ItemInfo::UseDuringCast()const{
return useDuringCast;
}
void Stats::InitializeDamageReductionTable(){
float totalReduction=0;
maxDamageReductionTable[0]=0;
for(int i=1;i<=1000;i++){
if(i<=10){
totalReduction+=0.01;
}else
if(i<=30){
totalReduction+=0.00'5;
}else
if(i<=70){
totalReduction+=0.00'25;
}else
if(i<=150){
totalReduction+=0.00'125;
}else
if(i<=310){
totalReduction+=0.00'0625;
}else
if(i<=950){
totalReduction+=0.00'03125;
}else{
totalReduction+=0.00'1;
}
maxDamageReductionTable[i]=totalReduction;
}
}

@ -75,16 +75,21 @@ enum class CompactText{
using enum CompactText;
struct Stats:ItemAttributable{
static safemap<int,float>maxDamageReductionTable;
static void InitializeDamageReductionTable();
public:
inline static const float&GetDamageReductionPct(int defense){
return maxDamageReductionTable.at(std::clamp(defense,0,1000));
}
inline Stats&operator+=(Stats&rhs){
for(ItemAttribute a=ItemAttribute(int(ItemAttribute::ENUM_START)+1);a<ItemAttribute::ENUM_END;a=ItemAttribute(int(a)+1)){
A(a)+=rhs.get_readOnly(a);
for(auto&[key,value]:ItemAttribute::attributes){
A(value)+=rhs.get_readOnly(value);
}
return *this;
};
inline Stats&operator+=(ItemAttributable&rhs){
for(ItemAttribute a=ItemAttribute(int(ItemAttribute::ENUM_START)+1);a<ItemAttribute::ENUM_END;a=ItemAttribute(int(a)+1)){
A(a)+=rhs.get_readOnly(a);
for(auto&[key,value]:ItemAttribute::attributes){
A(value)+=rhs.get_readOnly(value);
}
return *this;
};

@ -1,13 +0,0 @@
# Provide any stat name followed by its increase amount to add it to the set. Valid stat names and value examples are below:
#
# Defense = 5 # Adds 5 defense
# Health = 3 # Adds 3 Health
# Attack = 7 # Adds 7 attack
# Move Spd = 10% # Grants 10% more movement speed.
# CDR = 14% # Grants 14% cooldown reduction.
# Crit Rate = 5% # Grants 5% crit rate.
# Crit Dmg = 25% # Grants 25% bonus crit damage.
# Health % = 30% # Grants 30% Max health
# HP6 Recovery % = 4% # Grants 4% HP recovery every 6 seconds.
# HP4 Recovery % = 2% # Grants 2% HP recovery every 4 seconds.
# Damage Reduction = 2% # Grants 2% direct damage reduction

@ -100,7 +100,7 @@ class Menu:public IAttributable{
friend class Crawler;
friend struct Player;
friend class ItemInfo;
friend class PlayerStats;
friend class EntityStats;
float buttonHoldTime=0;
vi2d selection={-1,-1};

@ -60,7 +60,7 @@ safemap<std::string,std::function<void(Monster&,float,std::string)>>STRATEGY_DAT
std::map<std::string,Renderable*>MonsterData::imgs;
Monster::Monster(vf2d pos,MonsterData data,bool upperLevel,bool bossMob):
pos(pos),hp(data.GetHealth()),maxhp(data.GetHealth()),atk(data.GetAttack()),moveSpd(data.GetMoveSpdMult()),size(data.GetSizeMult()),targetSize(data.GetSizeMult()),strategy(data.GetAIStrategy()),name(data.GetDisplayName()),upperLevel(upperLevel),isBoss(bossMob),facingDirection(DOWN){
pos(pos),maxhp(data.GetHealth()),size(data.GetSizeMult()),targetSize(data.GetSizeMult()),strategy(data.GetAIStrategy()),name(data.GetDisplayName()),upperLevel(upperLevel),isBoss(bossMob),facingDirection(DOWN){
bool firstAnimation=true;
for(std::string&anim:data.GetAnimations()){
animation.AddState(anim,ANIMATION_DATA[anim]);
@ -69,25 +69,27 @@ Monster::Monster(vf2d pos,MonsterData data,bool upperLevel,bool bossMob):
firstAnimation=false;
}
}
stats.A("Health")=data.GetHealth();
stats.A("Attack")=data.GetAttack();
stats.A("Move Spd %")=data.GetMoveSpdMult();
randomFrameOffset=(rand()%1000)/1000.f;
}
vf2d&Monster::GetPos(){
return pos;
}
int Monster::GetHealth(){
return hp;
return stats.A("Health");
}
int Monster::GetAttack(){
float mod_atk=float(atk);
for(Buff&b:GetBuffs(ATTACK_UP)){
mod_atk+=atk*b.intensity;
}
float mod_atk=float(stats.A("Attack"));
mod_atk+=Get("Attack %");
mod_atk+=Get("Attack");
return int(mod_atk);
}
float Monster::GetMoveSpdMult(){
float mod_moveSpd=moveSpd;
float mod_moveSpd=stats.A("Move Spd %");
for(Buff&b:GetBuffs(SLOWDOWN)){
mod_moveSpd-=moveSpd*b.intensity;
mod_moveSpd-=stats.A("Move Spd %")*b.intensity;
}
return mod_moveSpd;
}
@ -287,7 +289,8 @@ bool Monster::Hurt(int damage,bool onUpperLevel,float z){
for(Buff&b:GetBuffs(BuffType::DAMAGE_REDUCTION)){
mod_dmg-=damage*b.intensity;
}
hp=std::max(0,hp-int(mod_dmg));
mod_dmg=std::ceil(mod_dmg);
stats.A("Health")=std::max(0.f,stats.A("Health")-int(mod_dmg));
if(lastHitTimer>0){
damageNumberPtr.get()->damage+=int(mod_dmg);
damageNumberPtr.get()->pauseTime=0.4f;
@ -299,7 +302,7 @@ bool Monster::Hurt(int damage,bool onUpperLevel,float z){
if(!IsAlive()){
OnDeath();
}else{
hp=std::max(1,hp); //Make sure it stays alive if it's supposed to be alive...
stats.A("Health")=std::max(1.f,stats.A("Health")); //Make sure it stays alive if it's supposed to be alive...
}
if(game->InBossEncounter()){
game->BossDamageDealt(int(mod_dmg));
@ -310,7 +313,7 @@ bool Monster::Hurt(int damage,bool onUpperLevel,float z){
}
bool Monster::IsAlive(){
return hp>0||!diesNormally;
return stats.A("Health")>0||!diesNormally;
}
vf2d&Monster::GetTargetPos(){
return target;
@ -456,4 +459,12 @@ void Monster::OnDeath(){
}
}
}
}
const ItemAttributable&Monster::GetStats()const{
return stats;
}
ItemAttribute&Monster::Get(std::string_view attr){
return ItemAttribute::Get(attr,this);
}

@ -109,44 +109,6 @@ struct MonsterData{
struct Monster:IAttributable{
friend struct STRATEGY;
private:
std::string name;
vf2d pos;
vf2d vel={0,0};
float friction=400;
vf2d target={0,0};
float targetAcquireTimer=0;
int hp,maxhp;
int atk;
float moveSpd;
float size;
float attackCooldownTimer=0;
float queueShotTimer=0;
float z=0;
float iframe_timer=0;
Key facingDirection=DOWN;
std::string strategy;
State::State state=State::NORMAL;
Animate2D::Animation<std::string>animation;
Animate2D::AnimationState internal_animState;
float randomFrameOffset=0.f;
float deathTimer=0.f;
std::vector<Buff>buffList;
std::string GetDeathAnimationName();
bool hasHitPlayer=false;
bool canMove=true; //Set to false when stuck due to collisions.
bool upperLevel=false;
vf2d pathTarget={};
std::vector<vf2d>path;
int pathIndex=0;
float lastHitTimer=0;
std::shared_ptr<DamageNumber>damageNumberPtr;
int phase=0;
bool diesNormally=true; //If set to false, the monster death is handled in a special way. Set it to true when it's time to die.
float targetSize=0;
bool isBoss=false;
void OnDeath();
protected:
public:
Monster()=delete;
Monster(vf2d pos,MonsterData data,bool upperLevel=false,bool bossMob=false);
@ -195,6 +157,45 @@ public:
void SetSize(float newSize,bool immediate=true);
void SetStrategyDrawFunction(std::function<void(Crawler*)>func);
std::function<void(Crawler*)>strategyDraw=[](Crawler*pge){};
const ItemAttributable&GetStats()const;
private:
std::string name;
vf2d pos;
vf2d vel={0,0};
float friction=400;
vf2d target={0,0};
float targetAcquireTimer=0;
int maxhp;
ItemAttributable stats;
float size;
float attackCooldownTimer=0;
float queueShotTimer=0;
float z=0;
float iframe_timer=0;
Key facingDirection=DOWN;
std::string strategy;
State::State state=State::NORMAL;
Animate2D::Animation<std::string>animation;
Animate2D::AnimationState internal_animState;
float randomFrameOffset=0.f;
float deathTimer=0.f;
std::vector<Buff>buffList;
std::string GetDeathAnimationName();
bool hasHitPlayer=false;
bool canMove=true; //Set to false when stuck due to collisions.
bool upperLevel=false;
vf2d pathTarget={};
std::vector<vf2d>path;
int pathIndex=0;
float lastHitTimer=0;
std::shared_ptr<DamageNumber>damageNumberPtr;
int phase=0;
bool diesNormally=true; //If set to false, the monster death is handled in a special way. Set it to true when it's time to die.
float targetSize=0;
bool isBoss=false;
void OnDeath();
ItemAttribute&Get(std::string_view attr);
private:
struct STRATEGY{
static int _GetInt(Monster&m,std::string param,std::string strategy,int index=0);

@ -48,6 +48,8 @@ All rights reserved.
#include "Menu.h"
#include "GameState.h"
#include "MenuComponent.h"
#include <string_view>
INCLUDE_MONSTER_DATA
INCLUDE_MONSTER_LIST
@ -69,9 +71,6 @@ InputGroup Player::KEY_ITEM1;
InputGroup Player::KEY_ITEM2;
InputGroup Player::KEY_ITEM3;
ItemAttributable PlayerStats::equipStats;
ItemAttributable PlayerStats::baseStats;
std::set<MenuComponent*>Player::moneyListeners;
Player::Player()
@ -87,15 +86,15 @@ Player::Player(Player*player)
void Player::Initialize(){
Player::GROUND_SLAM_SPIN_TIME="Warrior.Ability 2.SpinTime"_F;
SetBaseStat(ItemAttribute::health,hp);
SetBaseStat(ItemAttribute::defense,0);
SetBaseStat(ItemAttribute::attack,"Warrior.BaseAtk"_I);
SetBaseStat(ItemAttribute::moveSpdPct,100);
SetBaseStat(ItemAttribute::cdrPct,0);
SetBaseStat(ItemAttribute::critPct,0);
SetBaseStat(ItemAttribute::critDmgPct,0);
SetBaseStat(ItemAttribute::healthPct,0);
SetBaseStat(ItemAttribute::healthPctRecoveryPer6sec,0);
SetBaseStat("Health",hp);
SetBaseStat("Defense",0);
SetBaseStat("Attack","Warrior.BaseAtk"_I);
SetBaseStat("Move Spd %",100);
SetBaseStat("CDR",0);
SetBaseStat("Crit Rate",0);
SetBaseStat("Crit Dmg",0);
SetBaseStat("Health %",0);
SetBaseStat("HP6 Recovery %",0);
}
bool Player::SetX(float x){
@ -184,7 +183,7 @@ const int Player::GetHealth()const{
}
const int Player::GetMaxHealth()const{
return GetStat(ItemAttribute::health);
return GetStat("Health");
}
const int Player::GetMana()const{
@ -196,22 +195,19 @@ const int Player::GetMaxMana()const{
}
const int Player::GetAttack(){
float mod_atk=float(GetStat(ItemAttribute::attack));
for(Buff&b:GetBuffs(BuffType::ATTACK_UP)){
mod_atk+=GetStat(ItemAttribute::attack)*b.intensity;
}
for(Buff&b:GetBuffs(BuffType::ATTACK_PCT_UP)){
mod_atk+=GetStat(ItemAttribute::attack)*b.intensity;
}
float mod_atk=float(GetStat("Attack"));
mod_atk+=Get("Attack");
mod_atk+=Get("Attack %");
return int(mod_atk);
}
float Player::GetMoveSpdMult(){
float mod_moveSpd=GetStat(ItemAttribute::moveSpdPct)/100.f;
for(Buff&b:GetBuffs(BuffType::SLOWDOWN)){
float mod_moveSpd=GetStat("Move Spd %")/100.f;
mod_moveSpd+=ItemAttribute::Get("Move Spd %",this);
for(const Buff&b:GetBuffs(BuffType::SLOWDOWN)){
mod_moveSpd-=mod_moveSpd*b.intensity;
}
for(Buff&b:GetBuffs(BuffType::BLOCK_SLOWDOWN)){
for(const Buff&b:GetBuffs(BuffType::BLOCK_SLOWDOWN)){
mod_moveSpd-=mod_moveSpd*b.intensity;
}
return mod_moveSpd;
@ -606,11 +602,31 @@ bool Player::HasIframes(){
bool Player::Hurt(int damage,bool onUpperLevel,float z){
if(hp<=0||HasIframes()||OnUpperLevel()!=onUpperLevel||abs(GetZ()-z)>1) return false;
if(GetState()==State::BLOCK)damage*=int(1-"Warrior.Right Click Ability.DamageReduction"_F);
float mod_dmg=float(damage);
lastCombatTime=0;
for(Buff&b:GetBuffs(BuffType::DAMAGE_REDUCTION)){
mod_dmg-=damage*b.intensity;
if(GetState()==State::BLOCK){
mod_dmg=0;
}else{
float otherDmgTaken=1-GetDamageReductionFromBuffs();
float armorDmgTaken=1-GetDamageReductionFromArmor();
lastCombatTime=0;
float finalPctDmgTaken=armorDmgTaken*otherDmgTaken;
if(finalPctDmgTaken<=0.06f){
std::cout<<"WARNING! Damage Reduction has somehow ended up below 6%, which is over the cap!"<<std::endl;
}
finalPctDmgTaken=std::max(0.0625f,finalPctDmgTaken);//Apply Damage Cap.
float minPctDmgReduction=0.00'05f*GetStat("Defense");
float finalPctDmgReduction=1-finalPctDmgTaken;
float pctDmgReductionDiff=finalPctDmgReduction-minPctDmgReduction;
float dmgRoll=minPctDmgReduction+util::random(pctDmgReductionDiff);
mod_dmg*=1-dmgRoll;
mod_dmg=std::ceil(mod_dmg);
}
hp=std::max(0,hp-int(mod_dmg));
if(lastHitTimer>0){
@ -719,6 +735,12 @@ void Player::UpdateIdleAnimation(Key direction){
void Player::AddBuff(BuffType type,float duration,float intensity){
buffList.push_back(Buff{type,duration,intensity});
}
void Player::AddBuff(BuffType type,float duration,float intensity,std::set<ItemAttribute>attr){
buffList.push_back(Buff{type,duration,intensity,attr});
}
void Player::AddBuff(BuffType type,float duration,float intensity,std::set<std::string>attr){
buffList.push_back(Buff{type,duration,intensity,attr});
}
void Player::AddBuff(BuffType type,float duration,float intensity,float timeBetweenTicks,std::function<void(Crawler*,int)>repeatAction){
buffList.push_back(Buff{type,duration,intensity,timeBetweenTicks,repeatAction});
}
@ -727,9 +749,9 @@ bool Player::OnUpperLevel(){
return upperLevel;
}
std::vector<Buff>Player::GetBuffs(BuffType buff){
const std::vector<Buff>Player::GetBuffs(BuffType buff)const{
std::vector<Buff>filteredBuffs;
std::copy_if(buffList.begin(),buffList.end(),std::back_inserter(filteredBuffs),[buff](Buff&b){return b.type==buff;});
std::copy_if(buffList.begin(),buffList.end(),std::back_inserter(filteredBuffs),[buff](const Buff b){return b.type==buff;});
return filteredBuffs;
}
@ -809,7 +831,7 @@ void Player::SetIframes(float duration){
}
bool Player::Heal(int damage){
hp=std::clamp(hp+damage,0,GetStat(ItemAttribute::health));
hp=std::clamp(hp+damage,0,GetStat("Health"));
if(damage>0){
DAMAGENUMBER_LIST.push_back(std::make_shared<DamageNumber>(GetPos(),damage,true,HEALTH_GAIN));
}
@ -873,23 +895,31 @@ void Player::SetItem3UseFunc(Ability a){
useItem3=a;
};
const int Player::GetStat(ItemAttribute a)const{
return PlayerStats::GetStat(a);
const int&Player::GetStat(std::string_view a)const{
return GetStat(ItemAttribute::Get(a));
}
const int Player::GetBaseStat(ItemAttribute a)const{
return PlayerStats::GetBaseStat(a);
const int&Player::GetStat(ItemAttribute a)const{
return stats.GetStat(a);
}
const int&Player::GetBaseStat(std::string_view a)const{
return GetBaseStat(ItemAttribute::Get(a));
}
const int&Player::GetBaseStat(ItemAttribute a)const{
return stats.GetBaseStat(a);
}
void PlayerStats::RecalculateEquipStats(){
void EntityStats::RecalculateEquipStats(){
baseStats.copyTo(equipStats);
for(int i=int(EquipSlot::HELMET);i<=int(EquipSlot::RING2);i<<=1){
EquipSlot slot=EquipSlot(i);
std::weak_ptr<Item>equip=Inventory::GetEquip(slot);
if(ISBLANK(equip))continue;
for(ItemAttribute a=ItemAttribute(int(ItemAttribute::ENUM_START)+1);a<ItemAttribute::ENUM_END;a=ItemAttribute(int(a)+1)){
equipStats.A(a)+=equip.lock()->GetStats().A_Read(a);
for(auto&[key,size]:ItemAttribute::attributes){
equipStats.A(key)+=equip.lock()->GetStats().A_Read(key);
}
}
@ -897,27 +927,36 @@ void PlayerStats::RecalculateEquipStats(){
for(const auto&[set,equipCount]:setCounts){
const Stats&setStats=set[equipCount];
for(ItemAttribute a=ItemAttribute(int(ItemAttribute::ENUM_START)+1);a<ItemAttribute::ENUM_END;a=ItemAttribute(int(a)+1)){
equipStats.A(a)+=setStats.A_Read(a);
for(auto&[key,size]:ItemAttribute::attributes){
equipStats.A(key)+=setStats.A_Read(key);
}
}
for(MenuComponent*component:Menu::equipStatListeners){
component->OnEquipStatsUpdate();
}
}
const int PlayerStats::GetStat(ItemAttribute stat){
return equipStats.A(stat);
const int&EntityStats::GetStat(ItemAttribute stat)const{
return equipStats.A_Read(stat);
}
const int&EntityStats::GetStat(std::string_view stat)const{
return equipStats.A_Read(stat);
}
const int PlayerStats::GetBaseStat(ItemAttribute stat){
return baseStats.A(stat);
const int&EntityStats::GetBaseStat(ItemAttribute stat)const{
return baseStats.A_Read(stat);
}
const int&EntityStats::GetBaseStat(std::string_view stat)const{
return baseStats.A_Read(stat);
}
void PlayerStats::SetBaseStat(ItemAttribute stat,int val){
void EntityStats::SetBaseStat(ItemAttribute stat,int val){
baseStats.A(stat)=val;
RecalculateEquipStats();
}
void Player::SetBaseStat(std::string_view a,int val){
stats.SetBaseStat(ItemAttribute::Get(a),val);
}
void Player::SetBaseStat(ItemAttribute a,int val){
PlayerStats::SetBaseStat(a,val);
stats.SetBaseStat(a,val);
}
const std::string&ItemSet::GetSetName()const{
@ -926,14 +965,52 @@ const std::string&ItemSet::GetSetName()const{
uint32_t Player::GetMoney()const{
return money;
};
}
void Player::SetMoney(uint32_t newMoney){
money=newMoney;
for(auto&component:moneyListeners){
component->OnPlayerMoneyUpdate(newMoney);
}
};
}
void Player::AddMoneyListener(MenuComponent*component){
if(moneyListeners.count(component))ERR("WARNING! Trying to add a second copy of component "<<std::quoted(component->GetName()));
moneyListeners.insert(component);
}
const float Player::GetDamageReductionFromBuffs()const{
float dmgReduction=0;
for(const Buff&b:GetBuffs(BuffType::DAMAGE_REDUCTION)){
dmgReduction+=b.intensity;
}
return std::min(0.75f,dmgReduction);
};
const float Player::GetDamageReductionFromArmor()const{
float dmgReduction=0;
dmgReduction+=Stats::GetDamageReductionPct(GetStat("Defense"));
return std::min(0.75f,dmgReduction);
};
void Player::RecalculateEquipStats(){
stats.RecalculateEquipStats();
}
const std::vector<Buff>Player::GetStatBuffs(const std::vector<std::string>&attr)const{
std::vector<Buff>statUpBuffs;
std::copy_if(buffList.begin(),buffList.end(),std::back_inserter(statUpBuffs),[&](const Buff b){
return b.type==STAT_UP&&std::find_if(attr.begin(),attr.end(),[&](const std::string&attr){return b.attr.count(ItemAttribute::Get(attr));})!=attr.end();
});
return statUpBuffs;
}
const ItemAttributable&EntityStats::GetStats()const{
return equipStats;
}
const ItemAttributable&Player::GetStats()const{
return stats.GetStats();
};
ItemAttribute&Player::Get(std::string_view attr){
return ItemAttribute::Get(attr,this);
}

@ -61,16 +61,18 @@ struct CastInfo{
vf2d castPos;
};
class PlayerStats{
class EntityStats{
friend class Inventory;
static ItemAttributable equipStats; //The stats after gear calculations are applied.
static ItemAttributable baseStats;
static void RecalculateEquipStats(); //Called when equipment is updated.
ItemAttributable equipStats; //The stats after gear calculations are applied.
ItemAttributable baseStats;
public:
static const int GetStat(ItemAttribute stat); //Get stats with equipment applied.
static const int GetBaseStat(ItemAttribute stat);
static void SetBaseStat(ItemAttribute stat,int val);
void RecalculateEquipStats(); //Called when equipment is updated.
const ItemAttributable&GetStats()const;
const int&GetStat(ItemAttribute stat)const; //Get stats with equipment applied.
const int&GetStat(std::string_view stat)const; //Get stats with equipment applied.
const int&GetBaseStat(ItemAttribute stat)const;
const int&GetBaseStat(std::string_view stat)const;
void SetBaseStat(ItemAttribute a,int val);
};
struct Player{
@ -85,76 +87,6 @@ struct Player{
friend class State_GameRun;
friend class Inventory;
friend void ItemOverlay::Draw();
private:
int hp="Warrior.BaseHealth"_I;
int mana="Player.BaseMana"_I,maxmana=mana;
float hpGrowthRate="Warrior.HealthGrowthRate"_F;
float atkGrowthRate="Warrior.AtkGrowthRate"_F;
vf2d pos;
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;
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;
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;
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
@ -166,14 +98,19 @@ public:
float GetX();
float GetY();
float GetZ();
const int GetStat(ItemAttribute a)const;
const int GetBaseStat(ItemAttribute a)const;
const int&GetStat(ItemAttribute a)const;
const int&GetStat(std::string_view a)const;
const int&GetBaseStat(ItemAttribute a)const;
const int&GetBaseStat(std::string_view a)const;
void SetBaseStat(std::string_view a,int val);
void SetBaseStat(ItemAttribute a,int val);
const int 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();
void SetSizeMult(float size);
@ -205,12 +142,17 @@ public:
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(Crawler*,int)>repeatAction);
std::vector<Buff>GetBuffs(BuffType buff);
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);
@ -261,8 +203,82 @@ public:
void SetMoney(uint32_t newMoney);
void CancelCast();
const ItemAttributable&GetStats()const;
private:
int hp="Warrior.BaseHealth"_I;
int mana="Player.BaseMana"_I,maxmana=mana;
float hpGrowthRate="Warrior.HealthGrowthRate"_F;
float atkGrowthRate="Warrior.AtkGrowthRate"_F;
vf2d pos;
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;
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;
};
#pragma region Warrior
struct Warrior:Player{
static std::string name;
static Class cl;
@ -291,7 +307,9 @@ struct Warrior:Player{
std::string&GetIdleSAnimation()override;
std::string&GetIdleWAnimation()override;
};
#pragma endregion
#pragma region Thief
struct Thief:Player{
static std::string name;
static Class cl;
@ -320,7 +338,9 @@ struct Thief:Player{
std::string&GetIdleSAnimation()override;
std::string&GetIdleWAnimation()override;
};
#pragma endregion
#pragma region Ranger
struct Ranger:Player{
static std::string name;
static Class cl;
@ -349,7 +369,9 @@ struct Ranger:Player{
std::string&GetIdleSAnimation()override;
std::string&GetIdleWAnimation()override;
};
#pragma endregion
#pragma region Trapper
struct Trapper:Player{
static std::string name;
static Class cl;
@ -378,7 +400,9 @@ struct Trapper:Player{
std::string&GetIdleSAnimation()override;
std::string&GetIdleWAnimation()override;
};
#pragma endregion
#pragma region Wizard
struct Wizard:Player{
static std::string name;
static Class cl;
@ -407,7 +431,9 @@ struct Wizard:Player{
std::string&GetIdleSAnimation()override;
std::string&GetIdleWAnimation()override;
};
#pragma endregion
#pragma region Witch
struct Witch:Player{
static std::string name;
static Class cl;
@ -436,6 +462,7 @@ struct Witch:Player{
std::string&GetIdleSAnimation()override;
std::string&GetIdleWAnimation()override;
};
#pragma endregion
#define READFROMCONFIG(class,enum) \
class::name=#class".ClassName"_S; \

@ -106,12 +106,12 @@ void Monster::STRATEGY::SHOOT_AFAR(Monster&m,float fElapsedTime,std::string stra
pathfindingDecision=movedX||movedY;
m.canMove=movedX&&movedY;
}
if(!pathfindingDecision){
if(!pathfindingDecision&&m.targetAcquireTimer==0){
m.StartPathfinding(2.5);
}else
if(line.length()>=24.f*ConfigInt("Range")/100.f){
m.SetState(State::NORMAL);
}
if((m.path.size()==0&&!m.canMove)||line.length()>=24.f*ConfigInt("Range")/100.f){
m.SetState(State::NORMAL);
}
if(moveTowardsLine.vector().x>0){
m.facingDirection=RIGHT;
} else {

@ -240,7 +240,7 @@ void Monster::STRATEGY::SLIMEKING(Monster&m,float fElapsedTime,std::string strat
m.phase=ConfigInt("StartPhase");
}break;
case 1:{
if(m.hp<=m.maxhp*ConfigFloat("Phase2.Change")/100){
if(m.stats.A("Health")<=m.maxhp*ConfigFloat("Phase2.Change")/100){
m.phase=2;
m.SetSize(ConfigFloat("Phase2.Size")/100,false);
TransitionPhase(m.phase);
@ -273,7 +273,7 @@ void Monster::STRATEGY::SLIMEKING(Monster&m,float fElapsedTime,std::string strat
}
}break;
case 2:{
if(m.hp<=m.maxhp*ConfigFloat("Phase3.Change")/100){
if(m.stats.A("Health")<=m.maxhp*ConfigFloat("Phase3.Change")/100){
m.phase=3;
m.SetSize(ConfigFloat("Phase3.Size")/100,false);
TransitionPhase(m.phase);
@ -297,7 +297,7 @@ void Monster::STRATEGY::SLIMEKING(Monster&m,float fElapsedTime,std::string strat
}
}break;
case 3:{
if(m.hp<=m.maxhp*ConfigFloat("Phase4.Change")/100){
if(m.stats.A("Health")<=m.maxhp*ConfigFloat("Phase4.Change")/100){
m.phase=4;
m.SetSize(ConfigFloat("Phase4.Size")/100,false);
m.AddBuff(BuffType::SLOWDOWN,99999,ConfigFloat("Phase4.MoveSpdModifier")/100);
@ -325,7 +325,7 @@ void Monster::STRATEGY::SLIMEKING(Monster&m,float fElapsedTime,std::string strat
}
}break;
case 4:{
if(m.hp<=1){ //HP can't reach 0 when the dies normally flag is on.
if(m.stats.A("Health")<=1){ //HP can't reach 0 when the dies normally flag is on.
m.phase=5;
m.F(A::IFRAME_TIME_UPON_HIT)=1;
m.I(A::HITS_UNTIL_DEATH)=int(m.GetSizeMult()*100/ConfigFloat("Phase5.SizeLossPerHit"))-1;

@ -0,0 +1,37 @@
Level Up stats:
Sword Class: Hp 9 Atk 2
Bow Class: Hp 7 Atk 3
Staff Class: Hp 5 Atk 4
Armor Damage Reduction has 2 values. min. reduction and max. reduction.
Every time the player takes damage the damage gets reduced by a random amount between those 2 values.
min reduction:
1 def = 0.05%
max reduction:
first 10 def = 1 def = 1%
10%
10-30 = 1 def = 0.5%
10%
30-70 = 1 def = 0.25%
10%
70-150 = 1 def = 0.125%
10%
150-310 = 1 def = 0.0625%
10%
310-950 = 1 def = 0.03125% (20% instead of 10% in this bracket)
20%
950-1000 = 1 def = 0.1%
5%
1000 def = cap
75% total
Damage Reduction with Armor is applied multiplicative with other Damage Reduction effects.
Every other Damage Redcution is Additive and capped at 75%.
(All Dmg. Reduction buffs)
Max Armor Roll + capped damage reduction therefore reduce damage by 93.75 (87.5%??).
Every dmg number after reduction is rounded up. dmg cant be 0.

@ -72,10 +72,9 @@ protected:
SetValue(game->GetPlayer()->GetStat(stat));
}
inline void UpdateLabel(){
AttributeData data=ItemAttributable::GetDisplayInfo(stat);
std::string attrStr=data.name+":\n ";
std::string attrStr=std::string(stat.Name())+":\n ";
attrStr+=std::to_string(value);
if(data.displayAsPercent){
if(stat.DisplayAsPercent()){
attrStr+="%";
}
if(statChangeAmt>0){

@ -1,6 +1,6 @@
January 1st
===========
Buff Item Script
Buff Item Script (Implement Other Stats)
Sell Item Merchant Screen
Blacksmith Item Crafting Screen
Randomized Item Stats

@ -39,7 +39,7 @@ All rights reserved.
#define VERSION_MAJOR 0
#define VERSION_MINOR 2
#define VERSION_PATCH 1
#define VERSION_BUILD 4574
#define VERSION_BUILD 4643
#define stringify(a) stringify_(a)
#define stringify_(a) #a

@ -118,7 +118,7 @@ void Warrior::InitializeClassAbilities(){
Warrior::ability1.action=
[](Player*p,vf2d pos={}){
game->AddEffect(std::make_unique<Effect>(p->GetPos(),"Warrior.Ability 1.EffectLifetime"_F,"battlecry_effect.png",p->upperLevel,"Warrior.Ability 1.Range"_F/350,"Warrior.Ability 1.EffectFadetime"_F));
p->AddBuff(BuffType::ATTACK_UP,"Warrior.Ability 1.AttackUpDuration"_F,"Warrior.Ability 1.AttackIncrease"_F);
p->AddBuff(BuffType::STAT_UP,"Warrior.Ability 1.AttackUpDuration"_F,"Warrior.Ability 1.AttackIncrease"_F,{"Attack %"});
p->AddBuff(BuffType::DAMAGE_REDUCTION,"Warrior.Ability 1.DamageReductionDuration"_F,"Warrior.Ability 1.DamageReduction"_F);
for(Monster&m:MONSTER_LIST){
if(m.GetSizeMult()>="Warrior.Ability 1.AffectedSizeRange"_f[0]&&m.GetSizeMult()<="Warrior.Ability 1.AffectedSizeRange"_f[1]&&geom2d::overlaps(geom2d::circle<float>(p->GetPos(),12*"Warrior.Ability 1.Range"_I/100.f),geom2d::circle<float>(m.GetPos(),m.GetSizeMult()*12))){

@ -46,9 +46,6 @@ Warrior
# Percentage of player's normal movement speed while block is active.
SlowAmt = 0.3
# Percentage of damage to reduce by. (1.0 = 100%)
DamageReduction = 1.00
}
Ability 1
{

@ -68,6 +68,9 @@ story_player_name = You
# The path to the merchant configuration files in the game
merchant_directory = config/shops/
# Item Stats Config
item_stats_config = items/ItemStats.txt
# Dialog font and size.
dialog_font_size = c64esque,12

@ -21,13 +21,19 @@ ItemScript
Buff
{
Attack = 0,0.0
Attack % = 0,0.0
Attack % = 0%,0.0
Defense = 0,0.0
Defense % = 0,0.0
Defense % = 0%,0.0
Health = 0,0.0
Health % = 0,0.0
Move Spd = 0,0.0
Move Spd % = 0,0.0
Health % = 0%,0.0
Move Spd % = 0%,0.0
CDR = 0%,0.0
Crit Rate = 0%,0.0
Crit Dmg = 0%,0.0
HP Recovery % = 0%,0.0
HP6 Recovery % = 0%,0.0
HP4 Recovery % = 0%,0.0
Damage Reduction = 0%,0.0
}
# Unlike an item or ability that requires a cast to perform, this applies a buff immediately and then moving cancels the buff mid-application.

@ -20,7 +20,9 @@ ItemSet
# Defense = 5 # Adds 5 defense
# Health = 3 # Adds 3 Health
# Attack = 7 # Adds 7 attack
# Move Spd = 10% # Grants 10% more movement speed.
# Defense % = 2% # Adds 2% defense
# Attack % = 5% # Adds 5% attack
# Move Spd % = 10% # Grants 10% more movement speed.
# CDR = 14% # Grants 14% cooldown reduction.
# Crit Rate = 5% # Grants 5% crit rate.
# Crit Dmg = 25% # Grants 25% bonus crit damage.
@ -29,6 +31,9 @@ ItemSet
# HP4 Recovery % = 2% # Grants 2% HP recovery every 4 seconds.
# Damage Reduction = 2% # Grants 2% direct damage reduction
#
#
# All Possible Stat modifications can be found in ItemStats.txt
#
Leather
{
2
@ -55,7 +60,7 @@ ItemSet
{
2
{
Move Spd = 5%
Move Spd % = 5%
}
4
{
@ -78,7 +83,7 @@ ItemSet
2
{
Crit Rate = 3%
Move Spd = 5%
Move Spd % = 5%
Attack = 2%
}
4
@ -114,7 +119,7 @@ ItemSet
{
2
{
Move Spd = 10%
Move Spd % = 10%
}
4
{

@ -0,0 +1,62 @@
Stats
{
Defense
{
Percentage = False
}
Health
{
Percentage = False
}
Attack
{
Percentage = False
}
Defense %
{
Modifies = Defense
Percentage = True
}
Attack %
{
Modifies = Attack
Percentage = True
}
Health %
{
Modifies = Health
Percentage = True
}
Move Spd %
{
Percentage = True
}
CDR
{
Percentage = True
}
Crit Rate
{
Percentage = True
}
Crit Dmg
{
Percentage = True
}
HP Recovery %
{
Percentage = True
}
HP6 Recovery %
{
Percentage = True
}
HP4 Recovery %
{
Percentage = True
}
Damage Reduction
{
Percentage = True
}
}

@ -114,6 +114,12 @@ namespace olc::utils
return std::atoi(GetString(nItem).c_str());
}
// Retrieves the Boolean Value of a Property (for a given index) or 0
inline const bool GetBool(const size_t nItem = 0) const
{
return GetString(nItem).starts_with('T')||GetString(nItem).starts_with('t');
}
// Sets the Integer Value of a Property (for a given index)
inline void SetInt(const int32_t n, const size_t nItem = 0)
{

Loading…
Cancel
Save