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. All rights reserved.
*/ */
#pragma endregion #pragma endregion
#include "AttributableStat.h"
#include "olcUTIL_DataFile.h"
#include "DEFINES.h"
#include "Item.h" #include "Item.h"
#include "Player.h"
#include "Monster.h"
INCLUDE_DATA
ItemAttributable&ItemAttributable::operator+=(Stats&rhs){ ItemAttributable&ItemAttributable::operator+=(Stats&rhs){
for(ItemAttribute a=ItemAttribute(int(ItemAttribute::ENUM_START)+1);a<ItemAttribute::ENUM_END;a=ItemAttribute(int(a)+1)){ for(auto&[key,value]:ItemAttribute::attributes){
A(a)+=rhs.get_readOnly(a); A(value)+=rhs.get_readOnly(value);
} }
return *this; return *this;
}; };
ItemAttributable&ItemAttributable::operator+=(ItemAttributable&rhs){ ItemAttributable&ItemAttributable::operator+=(ItemAttributable&rhs){
for(ItemAttribute a=ItemAttribute(int(ItemAttribute::ENUM_START)+1);a<ItemAttribute::ENUM_END;a=ItemAttribute(int(a)+1)){ for(auto&[key,value]:ItemAttribute::attributes){
A(a)+=rhs.get_readOnly(a); A(value)+=rhs.get_readOnly(value);
} }
return *this; 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 endregion
#pragma once #pragma once
#include "olcPixelGameEngine.h" #include "olcPixelGameEngine.h"
#include "safemap.h"
#include <variant>
using namespace std::string_view_literals;
#define A(attr) get(attr) #define A(attr) get(attr)
#define A_Read(attr) get_readOnly(attr) #define A_Read(attr) get_readOnly(attr)
namespace DisplayType{ class Player;
enum DisplayType{ class Monster;
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.
/////////NOTE: When adding a new item stat, provide its display name in the map below. class ItemAttribute{
/*////////////////////////////////////////////*/ friend class Crawler;
/*//////*/ENUM_END/*//////////////////////////*/ 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{ class ItemAttributable{
friend class ItemInfo; friend class ItemInfo;
protected: protected:
std::map<ItemAttribute,int>attributes; std::map<ItemAttribute,float>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.
};
public: public:
ItemAttributable&operator+=(ItemAttributable&rhs); ItemAttributable&operator+=(ItemAttributable&rhs);
ItemAttributable&operator+=(Stats&rhs); ItemAttributable&operator+=(Stats&rhs);
@ -106,22 +82,20 @@ public:
inline void copyTo(ItemAttributable&target){ inline void copyTo(ItemAttributable&target){
target.attributes=attributes; 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]; 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)){ if(attributes.count(a)){
return attributes.at(a); return attributes.at(a);
} }
return 0; 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 endregion
#pragma once #pragma once
#include <set>
#include "AttributableStat.h"
enum BuffType{ enum BuffType{
ATTACK_UP, STAT_UP,
ATTACK_PCT_UP,
DAMAGE_REDUCTION, DAMAGE_REDUCTION,
SLOWDOWN, SLOWDOWN,
BLOCK_SLOWDOWN, BLOCK_SLOWDOWN,
@ -54,9 +56,18 @@ struct Buff{
float timeBetweenTicks=1; float timeBetweenTicks=1;
float intensity=1; float intensity=1;
float nextTick=0; float nextTick=0;
std::set<ItemAttribute> attr;
std::function<void(Crawler*,int)>repeatAction; std::function<void(Crawler*,int)>repeatAction;
inline Buff(BuffType type,float duration,float intensity) inline Buff(BuffType type,float duration,float intensity)
:type(type),duration(duration),intensity(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) 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){} :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("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; 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{ const static std::array<std::string,7>displayAttrs{
ItemAttribute::health, "Health",
ItemAttribute::attack, "Attack",
ItemAttribute::defense, "Defense",
ItemAttribute::moveSpdPct, "Move Spd %",
ItemAttribute::cdrPct, "CDR",
ItemAttribute::critPct, "Crit Rate",
ItemAttribute::critDmgPct, "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 Bottom Outline")->Enable(false);
Component<MenuComponent>(data.component->parentMenu,"Equip Selection Select Button")->Enable(false); Component<MenuComponent>(data.component->parentMenu,"Equip Selection Select Button")->Enable(false);
Component<CharacterRotatingDisplay>(data.component->parentMenu,"Character Rotating Display")->Enable(true); Component<CharacterRotatingDisplay>(data.component->parentMenu,"Character Rotating Display")->Enable(true);
for(int counter=0;ItemAttribute attribute:displayAttrs){ for(int counter=0;const std::string&attribute:displayAttrs){
StatLabel*statDisplayLabel=Component<StatLabel>(CHARACTER_MENU,"Attribute "+ItemAttributable::GetDisplayInfo(attribute).name+" Label"); StatLabel*statDisplayLabel=Component<StatLabel>(CHARACTER_MENU,"Attribute "+std::string(ItemAttribute::Get(attribute).Name())+" Label");
statDisplayLabel->SetStatChangeAmt(0); statDisplayLabel->SetStatChangeAmt(0);
} }
equipmentWindowOpened=false; equipmentWindowOpened=false;
@ -96,10 +96,9 @@ void Menu::InitializeCharacterMenuWindow(){
equipSelectionSelectButton->Enable(false); equipSelectionSelectButton->Enable(false);
const static auto GetLabelText=[](ItemAttribute attribute){ const static auto GetLabelText=[](ItemAttribute attribute){
AttributeData data=ItemAttributable::GetDisplayInfo(attribute); std::string attrStr=std::string(attribute.Name())+":\n ";
std::string attrStr=data.name+":\n ";
attrStr+=std::to_string(game->GetPlayer()->GetStat(attribute)); attrStr+=std::to_string(game->GetPlayer()->GetStat(attribute));
if(data.displayAsPercent){ if(attribute.DisplayAsPercent()){
attrStr+="%"; attrStr+="%";
} }
return attrStr; return attrStr;
@ -149,8 +148,8 @@ void Menu::InitializeCharacterMenuWindow(){
} }
} }
comp->SetSelected(true); comp->SetSelected(true);
for(int counter=0;ItemAttribute attribute:displayAttrs){ for(int counter=0;const std::string&attribute:displayAttrs){
StatLabel*statDisplayLabel=Component<StatLabel>(CHARACTER_MENU,"Attribute "+ItemAttributable::GetDisplayInfo(attribute).name+" Label"); StatLabel*statDisplayLabel=Component<StatLabel>(CHARACTER_MENU,"Attribute "+std::string(ItemAttribute::Get(attribute).Name())+" Label");
statDisplayLabel->SetStatChangeAmt(0); statDisplayLabel->SetStatChangeAmt(0);
} }
MenuItemItemButton*equipButton=Component<MenuItemItemButton>(CHARACTER_MENU,"Equip Slot "+slotNames[data.parentComponent->I(A::INDEXED_THEME)]); 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(); const std::weak_ptr<Item>buttonItem=button->GetItem();
std::vector<int>statsBeforeEquip; std::vector<int>statsBeforeEquip;
EquipSlot slot=EquipSlot(button->I(Attribute::EQUIP_TYPE)); 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)); statsBeforeEquip.push_back(game->GetPlayer()->GetStat(attribute));
} }
std::weak_ptr<Item>equippedItem=Inventory::GetEquip(slot); std::weak_ptr<Item>equippedItem=Inventory::GetEquip(slot);
Inventory::EquipItem(buttonItem,slot); Inventory::EquipItem(buttonItem,slot);
for(int counter=0;ItemAttribute attribute:displayAttrs){ for(int counter=0;const std::string&attribute:displayAttrs){
StatLabel*statDisplayLabel=Component<StatLabel>(CHARACTER_MENU,"Attribute "+ItemAttributable::GetDisplayInfo(attribute).name+" Label"); StatLabel*statDisplayLabel=Component<StatLabel>(CHARACTER_MENU,"Attribute "+std::string(ItemAttribute::Get(attribute).Name())+" Label");
int statChangeAmt=game->GetPlayer()->GetStat(attribute)-statsBeforeEquip[counter]; int statChangeAmt=game->GetPlayer()->GetStat(attribute)-statsBeforeEquip[counter];
statDisplayLabel->SetStatChangeAmt(statChangeAmt); statDisplayLabel->SetStatChangeAmt(statChangeAmt);
counter++; counter++;
@ -190,8 +189,8 @@ void Menu::InitializeCharacterMenuWindow(){
}); });
equip->SetMouseOutFunc( equip->SetMouseOutFunc(
[](MenuFuncData data){ [](MenuFuncData data){
for(int counter=0;ItemAttribute attribute:displayAttrs){ for(int counter=0;const std::string&attribute:displayAttrs){
StatLabel*statDisplayLabel=Component<StatLabel>(CHARACTER_MENU,"Attribute "+ItemAttributable::GetDisplayInfo(attribute).name+" Label"); StatLabel*statDisplayLabel=Component<StatLabel>(CHARACTER_MENU,"Attribute "+std::string(ItemAttribute::Get(attribute).Name())+" Label");
statDisplayLabel->SetStatChangeAmt(0); statDisplayLabel->SetStatChangeAmt(0);
counter++; 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; characterMenuWindow->ADD("Stat Display Outline",MenuComponent)({{245,28},{62,windowSize.y-37}},"",DO_NOTHING,ButtonAttr::UNSELECTABLE)END;
int yOffset=0; int yOffset=0;
for(ItemAttribute attribute:displayAttrs){ for(const std::string&attribute:displayAttrs){
std::string attrStr=GetLabelText(attribute); std::string attrStr=GetLabelText(ItemAttribute::Get(attribute));
AttributeData data=ItemAttributable::GetDisplayInfo(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;
auto attrLabel=characterMenuWindow->ADD("Attribute "+data.name+" Label",StatLabel)({{245,28+2+float(yOffset)},{62,18}},attribute,1,ComponentAttr::SHADOW|ComponentAttr::LEFT_ALIGN)END;
Menu::AddEquipStatListener(attrLabel); Menu::AddEquipStatListener(attrLabel);
yOffset+=20; yOffset+=20;
} }

@ -131,6 +131,9 @@ Crawler::Crawler()
std::string ITEM_SET_CONFIG = CONFIG_PATH + "item_set_config"_S; std::string ITEM_SET_CONFIG = CONFIG_PATH + "item_set_config"_S;
utils::datafile::Read(DATA,ITEM_SET_CONFIG); 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()){ for(auto&[key,value]:DATA.GetProperty("ItemConfiguration").GetKeys()){
std::string config = DATA["ItemConfiguration"][key].GetString(); std::string config = DATA["ItemConfiguration"][key].GetString();
utils::datafile::Read(DATA,CONFIG_PATH + "item_directory"_S + config); utils::datafile::Read(DATA,CONFIG_PATH + "item_directory"_S + config);
@ -165,6 +168,8 @@ bool Crawler::OnUserCreate(){
camera.SetTarget(player->GetPos()); camera.SetTarget(player->GetPos());
camera.SetWorldBoundary({0,0},GetCurrentMap().MapSize*GetCurrentMap().TileSize); camera.SetWorldBoundary({0,0},GetCurrentMap().MapSize*GetCurrentMap().TileSize);
camera.EnableWorldBoundary(false); camera.EnableWorldBoundary(false);
ItemAttribute::Initialize();
ItemInfo::InitializeItems(); ItemInfo::InitializeItems();
@ -195,6 +200,7 @@ bool Crawler::OnUserCreate(){
Inventory::AddItem("Shell Shoes"); Inventory::AddItem("Shell Shoes");
Inventory::AddItem("Bone Pants"); Inventory::AddItem("Bone Pants");
Inventory::AddItem("Bone Gloves"); Inventory::AddItem("Bone Gloves");
Inventory::AddItem("Elixir of Bear Strength",3);
LoadLevel(LEVEL_NAMES["starting_map"_S]); LoadLevel(LEVEL_NAMES["starting_map"_S]);
ChangePlayerClass(WARRIOR); ChangePlayerClass(WARRIOR);
@ -206,6 +212,8 @@ bool Crawler::OnUserCreate(){
Merchant::Initialize(); Merchant::Initialize();
Stats::InitializeDamageReductionTable();
utils::datafile::INITIAL_SETUP_COMPLETE=true; utils::datafile::INITIAL_SETUP_COMPLETE=true;
ValidateGameStatus(); //Checks to make sure everything has been initialized properly. 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))); playerScale.x=120*float(abs(pow(player->teleportAnimationTimer-0.175f,3)));
pos=player->teleportStartPosition.lerp(player->teleportTarget,(0.35f-player->teleportAnimationTimer)/0.35f); 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); SetDecalMode(DecalMode::NORMAL);
if(player->GetState()==State::BLOCK){ if(player->GetState()==State::BLOCK){
view.DrawDecal(player->GetPos()-vf2d{12,12},GFX["block.png"].Decal()); view.DrawDecal(player->GetPos()-vf2d{12,12},GFX["block.png"].Decal());
@ -1244,7 +1253,9 @@ void Crawler::RenderHud(){
if("debug_player_info"_I){ if("debug_player_info"_I){
DrawShadowStringDecal({0,128},player->GetPos().str()); DrawShadowStringDecal({0,128},player->GetPos().str());
DrawShadowStringDecal({0,136},"Spd: "+std::to_string(player->GetMoveSpdMult())); 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,1},"Selection: "+Menu::menus[INVENTORY_CONSUMABLES]->selection.str());
DrawShadowStringDecal({0,12},"Button Hold Time: "+std::to_string(Menu::menus[INVENTORY_CONSUMABLES]->buttonHoldTime)); 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())); player.reset(NEW Witch(player.get()));
}break; }break;
} }
player->SetBaseStat(ItemAttribute::health,DATA.GetProperty(player->GetClassName()+".BaseHealth").GetInt()); player->SetBaseStat("Health",DATA.GetProperty(player->GetClassName()+".BaseHealth").GetInt());
player->hp=player->GetBaseStat(ItemAttribute::health); player->hp=player->GetBaseStat("Health");
player->SetBaseStat(ItemAttribute::attack,DATA.GetProperty(player->GetClassName()+".BaseAtk").GetInt()); player->SetBaseStat("Attack",DATA.GetProperty(player->GetClassName()+".BaseAtk").GetInt());
player->hpGrowthRate=float(DATA.GetProperty(player->GetClassName()+".HealthGrowthRate").GetReal()); player->hpGrowthRate=float(DATA.GetProperty(player->GetClassName()+".HealthGrowthRate").GetReal());
player->atkGrowthRate=float(DATA.GetProperty(player->GetClassName()+".AtkGrowthRate").GetReal()); player->atkGrowthRate=float(DATA.GetProperty(player->GetClassName()+".AtkGrowthRate").GetReal());
player->money=oldMoney; player->money=oldMoney;

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

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

@ -41,6 +41,7 @@ All rights reserved.
#include "Crawler.h" #include "Crawler.h"
#include "Menu.h" #include "Menu.h"
#include "Ability.h" #include "Ability.h"
#include "AttributableStat.h"
INCLUDE_game INCLUDE_game
INCLUDE_DATA INCLUDE_DATA
@ -57,6 +58,7 @@ std::map<std::string,ItemSet>ItemSet::sets;
std::map<EquipSlot,std::weak_ptr<Item>>Inventory::equipment; std::map<EquipSlot,std::weak_ptr<Item>>Inventory::equipment;
std::map<std::string,EquipSlot>ItemInfo::nameToEquipSlot; std::map<std::string,EquipSlot>ItemInfo::nameToEquipSlot;
int Item::IsBlankStaticCallCounter=0; int Item::IsBlankStaticCallCounter=0;
safemap<int,float>Stats::maxDamageReductionTable;
ItemInfo::ItemInfo() ItemInfo::ItemInfo()
:customProps({nullptr,nullptr}),img(nullptr){} :customProps({nullptr,nullptr}),img(nullptr){}
@ -76,11 +78,6 @@ void ItemInfo::InitializeItems(){
nameToEquipSlot["Ring2"]=EquipSlot::RING2; nameToEquipSlot["Ring2"]=EquipSlot::RING2;
InitializeScripts(); 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(); InitializeSets();
@ -130,7 +127,7 @@ void ItemInfo::InitializeItems(){
}else }else
if(keyName=="StatValues"){ if(keyName=="StatValues"){
for(int i=0;i<data[key]["StatValues"].GetValueCount();i++){ 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 }else
if(keyName=="PartofSet"){ if(keyName=="PartofSet"){
@ -259,8 +256,19 @@ void ItemInfo::InitializeScripts(){
}); });
return true; 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){ 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; return true;
}; };
ITEM_SCRIPTS["RestoreDuringCast"]=[&](Crawler*game,ItemProps props){ 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(equippedSlot!=EquipSlot::NONE)UnequipItem(equippedSlot);
if(!GetEquip(slot).expired())UnequipItem(slot); if(!GetEquip(slot).expired())UnequipItem(slot);
Inventory::equipment[slot]=it; Inventory::equipment[slot]=it;
PlayerStats::RecalculateEquipStats(); game->GetPlayer()->RecalculateEquipStats();
}; };
void Inventory::UnequipItem(EquipSlot slot){ void Inventory::UnequipItem(EquipSlot slot){
Inventory::equipment[slot]=Item::BLANK; Inventory::equipment[slot]=Item::BLANK;
PlayerStats::RecalculateEquipStats(); game->GetPlayer()->RecalculateEquipStats();
}; };
EquipSlot Inventory::GetSlotEquippedIn(const std::weak_ptr<Item>it){ EquipSlot Inventory::GetSlotEquippedIn(const std::weak_ptr<Item>it){
for(int i=int(EquipSlot::HELMET);i<=int(EquipSlot::RING2);i<<=1){ 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)]; datafile&statInfo=setInfo[std::to_string(pieceCount)];
Stats bonuses; Stats bonuses;
for(auto&[key,value]:statInfo.GetKeys()){ for(auto&[key,value]:statInfo.GetKeys()){
ItemAttribute attr=bonuses.GetAttributeFromString(key); const ItemAttribute&attr=ItemAttribute::Get(key);
bonuses.A(attr)=statInfo[key].GetInt(0); bonuses.A(attr)=statInfo[key].GetInt(0);
} }
ItemSet::AddSetBonus(setName,pieceCount,bonuses); ItemSet::AddSetBonus(setName,pieceCount,bonuses);
@ -718,7 +726,7 @@ const std::string Stats::GetStatsString(CompactText compact)const{
description+="\n"; 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; first=false;
} }
return description; return description;
@ -783,4 +791,32 @@ const bool Item::UseDuringCast()const{
const bool ItemInfo::UseDuringCast()const{ const bool ItemInfo::UseDuringCast()const{
return useDuringCast; 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; using enum CompactText;
struct Stats:ItemAttributable{ struct Stats:ItemAttributable{
static safemap<int,float>maxDamageReductionTable;
static void InitializeDamageReductionTable();
public: public:
inline static const float&GetDamageReductionPct(int defense){
return maxDamageReductionTable.at(std::clamp(defense,0,1000));
}
inline Stats&operator+=(Stats&rhs){ inline Stats&operator+=(Stats&rhs){
for(ItemAttribute a=ItemAttribute(int(ItemAttribute::ENUM_START)+1);a<ItemAttribute::ENUM_END;a=ItemAttribute(int(a)+1)){ for(auto&[key,value]:ItemAttribute::attributes){
A(a)+=rhs.get_readOnly(a); A(value)+=rhs.get_readOnly(value);
} }
return *this; return *this;
}; };
inline Stats&operator+=(ItemAttributable&rhs){ inline Stats&operator+=(ItemAttributable&rhs){
for(ItemAttribute a=ItemAttribute(int(ItemAttribute::ENUM_START)+1);a<ItemAttribute::ENUM_END;a=ItemAttribute(int(a)+1)){ for(auto&[key,value]:ItemAttribute::attributes){
A(a)+=rhs.get_readOnly(a); A(value)+=rhs.get_readOnly(value);
} }
return *this; 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 class Crawler;
friend struct Player; friend struct Player;
friend class ItemInfo; friend class ItemInfo;
friend class PlayerStats; friend class EntityStats;
float buttonHoldTime=0; float buttonHoldTime=0;
vi2d selection={-1,-1}; 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; std::map<std::string,Renderable*>MonsterData::imgs;
Monster::Monster(vf2d pos,MonsterData data,bool upperLevel,bool bossMob): 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; bool firstAnimation=true;
for(std::string&anim:data.GetAnimations()){ for(std::string&anim:data.GetAnimations()){
animation.AddState(anim,ANIMATION_DATA[anim]); animation.AddState(anim,ANIMATION_DATA[anim]);
@ -69,25 +69,27 @@ Monster::Monster(vf2d pos,MonsterData data,bool upperLevel,bool bossMob):
firstAnimation=false; firstAnimation=false;
} }
} }
stats.A("Health")=data.GetHealth();
stats.A("Attack")=data.GetAttack();
stats.A("Move Spd %")=data.GetMoveSpdMult();
randomFrameOffset=(rand()%1000)/1000.f; randomFrameOffset=(rand()%1000)/1000.f;
} }
vf2d&Monster::GetPos(){ vf2d&Monster::GetPos(){
return pos; return pos;
} }
int Monster::GetHealth(){ int Monster::GetHealth(){
return hp; return stats.A("Health");
} }
int Monster::GetAttack(){ int Monster::GetAttack(){
float mod_atk=float(atk); float mod_atk=float(stats.A("Attack"));
for(Buff&b:GetBuffs(ATTACK_UP)){ mod_atk+=Get("Attack %");
mod_atk+=atk*b.intensity; mod_atk+=Get("Attack");
}
return int(mod_atk); return int(mod_atk);
} }
float Monster::GetMoveSpdMult(){ float Monster::GetMoveSpdMult(){
float mod_moveSpd=moveSpd; float mod_moveSpd=stats.A("Move Spd %");
for(Buff&b:GetBuffs(SLOWDOWN)){ for(Buff&b:GetBuffs(SLOWDOWN)){
mod_moveSpd-=moveSpd*b.intensity; mod_moveSpd-=stats.A("Move Spd %")*b.intensity;
} }
return mod_moveSpd; return mod_moveSpd;
} }
@ -287,7 +289,8 @@ bool Monster::Hurt(int damage,bool onUpperLevel,float z){
for(Buff&b:GetBuffs(BuffType::DAMAGE_REDUCTION)){ for(Buff&b:GetBuffs(BuffType::DAMAGE_REDUCTION)){
mod_dmg-=damage*b.intensity; 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){ if(lastHitTimer>0){
damageNumberPtr.get()->damage+=int(mod_dmg); damageNumberPtr.get()->damage+=int(mod_dmg);
damageNumberPtr.get()->pauseTime=0.4f; damageNumberPtr.get()->pauseTime=0.4f;
@ -299,7 +302,7 @@ bool Monster::Hurt(int damage,bool onUpperLevel,float z){
if(!IsAlive()){ if(!IsAlive()){
OnDeath(); OnDeath();
}else{ }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()){ if(game->InBossEncounter()){
game->BossDamageDealt(int(mod_dmg)); game->BossDamageDealt(int(mod_dmg));
@ -310,7 +313,7 @@ bool Monster::Hurt(int damage,bool onUpperLevel,float z){
} }
bool Monster::IsAlive(){ bool Monster::IsAlive(){
return hp>0||!diesNormally; return stats.A("Health")>0||!diesNormally;
} }
vf2d&Monster::GetTargetPos(){ vf2d&Monster::GetTargetPos(){
return target; 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{ struct Monster:IAttributable{
friend struct STRATEGY; 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: public:
Monster()=delete; Monster()=delete;
Monster(vf2d pos,MonsterData data,bool upperLevel=false,bool bossMob=false); Monster(vf2d pos,MonsterData data,bool upperLevel=false,bool bossMob=false);
@ -195,6 +157,45 @@ public:
void SetSize(float newSize,bool immediate=true); void SetSize(float newSize,bool immediate=true);
void SetStrategyDrawFunction(std::function<void(Crawler*)>func); void SetStrategyDrawFunction(std::function<void(Crawler*)>func);
std::function<void(Crawler*)>strategyDraw=[](Crawler*pge){}; 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: private:
struct STRATEGY{ struct STRATEGY{
static int _GetInt(Monster&m,std::string param,std::string strategy,int index=0); 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 "Menu.h"
#include "GameState.h" #include "GameState.h"
#include "MenuComponent.h" #include "MenuComponent.h"
#include <string_view>
INCLUDE_MONSTER_DATA INCLUDE_MONSTER_DATA
INCLUDE_MONSTER_LIST INCLUDE_MONSTER_LIST
@ -69,9 +71,6 @@ InputGroup Player::KEY_ITEM1;
InputGroup Player::KEY_ITEM2; InputGroup Player::KEY_ITEM2;
InputGroup Player::KEY_ITEM3; InputGroup Player::KEY_ITEM3;
ItemAttributable PlayerStats::equipStats;
ItemAttributable PlayerStats::baseStats;
std::set<MenuComponent*>Player::moneyListeners; std::set<MenuComponent*>Player::moneyListeners;
Player::Player() Player::Player()
@ -87,15 +86,15 @@ Player::Player(Player*player)
void Player::Initialize(){ void Player::Initialize(){
Player::GROUND_SLAM_SPIN_TIME="Warrior.Ability 2.SpinTime"_F; Player::GROUND_SLAM_SPIN_TIME="Warrior.Ability 2.SpinTime"_F;
SetBaseStat(ItemAttribute::health,hp); SetBaseStat("Health",hp);
SetBaseStat(ItemAttribute::defense,0); SetBaseStat("Defense",0);
SetBaseStat(ItemAttribute::attack,"Warrior.BaseAtk"_I); SetBaseStat("Attack","Warrior.BaseAtk"_I);
SetBaseStat(ItemAttribute::moveSpdPct,100); SetBaseStat("Move Spd %",100);
SetBaseStat(ItemAttribute::cdrPct,0); SetBaseStat("CDR",0);
SetBaseStat(ItemAttribute::critPct,0); SetBaseStat("Crit Rate",0);
SetBaseStat(ItemAttribute::critDmgPct,0); SetBaseStat("Crit Dmg",0);
SetBaseStat(ItemAttribute::healthPct,0); SetBaseStat("Health %",0);
SetBaseStat(ItemAttribute::healthPctRecoveryPer6sec,0); SetBaseStat("HP6 Recovery %",0);
} }
bool Player::SetX(float x){ bool Player::SetX(float x){
@ -184,7 +183,7 @@ const int Player::GetHealth()const{
} }
const int Player::GetMaxHealth()const{ const int Player::GetMaxHealth()const{
return GetStat(ItemAttribute::health); return GetStat("Health");
} }
const int Player::GetMana()const{ const int Player::GetMana()const{
@ -196,22 +195,19 @@ const int Player::GetMaxMana()const{
} }
const int Player::GetAttack(){ const int Player::GetAttack(){
float mod_atk=float(GetStat(ItemAttribute::attack)); float mod_atk=float(GetStat("Attack"));
for(Buff&b:GetBuffs(BuffType::ATTACK_UP)){ mod_atk+=Get("Attack");
mod_atk+=GetStat(ItemAttribute::attack)*b.intensity; mod_atk+=Get("Attack %");
}
for(Buff&b:GetBuffs(BuffType::ATTACK_PCT_UP)){
mod_atk+=GetStat(ItemAttribute::attack)*b.intensity;
}
return int(mod_atk); return int(mod_atk);
} }
float Player::GetMoveSpdMult(){ float Player::GetMoveSpdMult(){
float mod_moveSpd=GetStat(ItemAttribute::moveSpdPct)/100.f; float mod_moveSpd=GetStat("Move Spd %")/100.f;
for(Buff&b:GetBuffs(BuffType::SLOWDOWN)){ mod_moveSpd+=ItemAttribute::Get("Move Spd %",this);
for(const Buff&b:GetBuffs(BuffType::SLOWDOWN)){
mod_moveSpd-=mod_moveSpd*b.intensity; 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; mod_moveSpd-=mod_moveSpd*b.intensity;
} }
return mod_moveSpd; return mod_moveSpd;
@ -606,11 +602,31 @@ bool Player::HasIframes(){
bool Player::Hurt(int damage,bool onUpperLevel,float z){ bool Player::Hurt(int damage,bool onUpperLevel,float z){
if(hp<=0||HasIframes()||OnUpperLevel()!=onUpperLevel||abs(GetZ()-z)>1) return false; 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); float mod_dmg=float(damage);
lastCombatTime=0; if(GetState()==State::BLOCK){
for(Buff&b:GetBuffs(BuffType::DAMAGE_REDUCTION)){ mod_dmg=0;
mod_dmg-=damage*b.intensity; }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)); hp=std::max(0,hp-int(mod_dmg));
if(lastHitTimer>0){ if(lastHitTimer>0){
@ -719,6 +735,12 @@ void Player::UpdateIdleAnimation(Key direction){
void Player::AddBuff(BuffType type,float duration,float intensity){ void Player::AddBuff(BuffType type,float duration,float intensity){
buffList.push_back(Buff{type,duration,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){ 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}); buffList.push_back(Buff{type,duration,intensity,timeBetweenTicks,repeatAction});
} }
@ -727,9 +749,9 @@ bool Player::OnUpperLevel(){
return upperLevel; return upperLevel;
} }
std::vector<Buff>Player::GetBuffs(BuffType buff){ const std::vector<Buff>Player::GetBuffs(BuffType buff)const{
std::vector<Buff>filteredBuffs; 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; return filteredBuffs;
} }
@ -809,7 +831,7 @@ void Player::SetIframes(float duration){
} }
bool Player::Heal(int damage){ 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){ if(damage>0){
DAMAGENUMBER_LIST.push_back(std::make_shared<DamageNumber>(GetPos(),damage,true,HEALTH_GAIN)); DAMAGENUMBER_LIST.push_back(std::make_shared<DamageNumber>(GetPos(),damage,true,HEALTH_GAIN));
} }
@ -873,23 +895,31 @@ void Player::SetItem3UseFunc(Ability a){
useItem3=a; useItem3=a;
}; };
const int Player::GetStat(ItemAttribute a)const{ const int&Player::GetStat(std::string_view a)const{
return PlayerStats::GetStat(a); return GetStat(ItemAttribute::Get(a));
} }
const int Player::GetBaseStat(ItemAttribute a)const{ const int&Player::GetStat(ItemAttribute a)const{
return PlayerStats::GetBaseStat(a); 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); baseStats.copyTo(equipStats);
for(int i=int(EquipSlot::HELMET);i<=int(EquipSlot::RING2);i<<=1){ for(int i=int(EquipSlot::HELMET);i<=int(EquipSlot::RING2);i<<=1){
EquipSlot slot=EquipSlot(i); EquipSlot slot=EquipSlot(i);
std::weak_ptr<Item>equip=Inventory::GetEquip(slot); std::weak_ptr<Item>equip=Inventory::GetEquip(slot);
if(ISBLANK(equip))continue; if(ISBLANK(equip))continue;
for(ItemAttribute a=ItemAttribute(int(ItemAttribute::ENUM_START)+1);a<ItemAttribute::ENUM_END;a=ItemAttribute(int(a)+1)){ for(auto&[key,size]:ItemAttribute::attributes){
equipStats.A(a)+=equip.lock()->GetStats().A_Read(a); equipStats.A(key)+=equip.lock()->GetStats().A_Read(key);
} }
} }
@ -897,27 +927,36 @@ void PlayerStats::RecalculateEquipStats(){
for(const auto&[set,equipCount]:setCounts){ for(const auto&[set,equipCount]:setCounts){
const Stats&setStats=set[equipCount]; const Stats&setStats=set[equipCount];
for(ItemAttribute a=ItemAttribute(int(ItemAttribute::ENUM_START)+1);a<ItemAttribute::ENUM_END;a=ItemAttribute(int(a)+1)){ for(auto&[key,size]:ItemAttribute::attributes){
equipStats.A(a)+=setStats.A_Read(a); equipStats.A(key)+=setStats.A_Read(key);
} }
} }
for(MenuComponent*component:Menu::equipStatListeners){ for(MenuComponent*component:Menu::equipStatListeners){
component->OnEquipStatsUpdate(); component->OnEquipStatsUpdate();
} }
} }
const int PlayerStats::GetStat(ItemAttribute stat){ const int&EntityStats::GetStat(ItemAttribute stat)const{
return equipStats.A(stat); 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){ const int&EntityStats::GetBaseStat(ItemAttribute stat)const{
return baseStats.A(stat); 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; baseStats.A(stat)=val;
RecalculateEquipStats(); RecalculateEquipStats();
} }
void Player::SetBaseStat(std::string_view a,int val){
stats.SetBaseStat(ItemAttribute::Get(a),val);
}
void Player::SetBaseStat(ItemAttribute a,int val){ void Player::SetBaseStat(ItemAttribute a,int val){
PlayerStats::SetBaseStat(a,val); stats.SetBaseStat(a,val);
} }
const std::string&ItemSet::GetSetName()const{ const std::string&ItemSet::GetSetName()const{
@ -926,14 +965,52 @@ const std::string&ItemSet::GetSetName()const{
uint32_t Player::GetMoney()const{ uint32_t Player::GetMoney()const{
return money; return money;
}; }
void Player::SetMoney(uint32_t newMoney){ void Player::SetMoney(uint32_t newMoney){
money=newMoney; money=newMoney;
for(auto&component:moneyListeners){ for(auto&component:moneyListeners){
component->OnPlayerMoneyUpdate(newMoney); component->OnPlayerMoneyUpdate(newMoney);
} }
}; }
void Player::AddMoneyListener(MenuComponent*component){ void Player::AddMoneyListener(MenuComponent*component){
if(moneyListeners.count(component))ERR("WARNING! Trying to add a second copy of component "<<std::quoted(component->GetName())); if(moneyListeners.count(component))ERR("WARNING! Trying to add a second copy of component "<<std::quoted(component->GetName()));
moneyListeners.insert(component); 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; vf2d castPos;
}; };
class PlayerStats{ class EntityStats{
friend class Inventory; friend class Inventory;
static ItemAttributable equipStats; //The stats after gear calculations are applied. ItemAttributable equipStats; //The stats after gear calculations are applied.
static ItemAttributable baseStats; ItemAttributable baseStats;
static void RecalculateEquipStats(); //Called when equipment is updated.
public: public:
static const int GetStat(ItemAttribute stat); //Get stats with equipment applied. void RecalculateEquipStats(); //Called when equipment is updated.
static const int GetBaseStat(ItemAttribute stat); const ItemAttributable&GetStats()const;
static void SetBaseStat(ItemAttribute stat,int val); 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{ struct Player{
@ -85,76 +87,6 @@ struct Player{
friend class State_GameRun; friend class State_GameRun;
friend class Inventory; friend class Inventory;
friend void ItemOverlay::Draw(); 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: public:
Player(); 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 //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 GetX();
float GetY(); float GetY();
float GetZ(); float GetZ();
const int GetStat(ItemAttribute a)const; const int&GetStat(ItemAttribute a)const;
const int GetBaseStat(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); void SetBaseStat(ItemAttribute a,int val);
const int GetMaxHealth()const; const int GetMaxHealth()const;
const int GetHealth()const; const int GetHealth()const;
const int GetMana()const; const int GetMana()const;
const int GetMaxMana()const; const int GetMaxMana()const;
const int GetAttack(); const int GetAttack();
const float GetDamageReductionFromBuffs()const;
const float GetDamageReductionFromArmor()const;
float GetMoveSpdMult(); float GetMoveSpdMult();
float GetSizeMult(); float GetSizeMult();
void SetSizeMult(float size); void SetSizeMult(float size);
@ -205,12 +142,17 @@ public:
void SetState(State::State newState); void SetState(State::State newState);
void AddBuff(BuffType type,float duration,float intensity); 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); 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 RemoveBuff(BuffType type); //Removes the first buff found.
void RemoveAllBuffs(BuffType type); //Removes all buffs of a certain type. void RemoveAllBuffs(BuffType type); //Removes all buffs of a certain type.
void RemoveAllBuffs(); //Remove every buff. void RemoveAllBuffs(); //Remove every buff.
void RecalculateEquipStats();
bool Hurt(int damage,bool onUpperLevel,float z); bool Hurt(int damage,bool onUpperLevel,float z);
//Return false if healing was not possible. //Return false if healing was not possible.
bool Heal(int damage); bool Heal(int damage);
@ -261,8 +203,82 @@ public:
void SetMoney(uint32_t newMoney); void SetMoney(uint32_t newMoney);
void CancelCast(); 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{ struct Warrior:Player{
static std::string name; static std::string name;
static Class cl; static Class cl;
@ -291,7 +307,9 @@ struct Warrior:Player{
std::string&GetIdleSAnimation()override; std::string&GetIdleSAnimation()override;
std::string&GetIdleWAnimation()override; std::string&GetIdleWAnimation()override;
}; };
#pragma endregion
#pragma region Thief
struct Thief:Player{ struct Thief:Player{
static std::string name; static std::string name;
static Class cl; static Class cl;
@ -320,7 +338,9 @@ struct Thief:Player{
std::string&GetIdleSAnimation()override; std::string&GetIdleSAnimation()override;
std::string&GetIdleWAnimation()override; std::string&GetIdleWAnimation()override;
}; };
#pragma endregion
#pragma region Ranger
struct Ranger:Player{ struct Ranger:Player{
static std::string name; static std::string name;
static Class cl; static Class cl;
@ -349,7 +369,9 @@ struct Ranger:Player{
std::string&GetIdleSAnimation()override; std::string&GetIdleSAnimation()override;
std::string&GetIdleWAnimation()override; std::string&GetIdleWAnimation()override;
}; };
#pragma endregion
#pragma region Trapper
struct Trapper:Player{ struct Trapper:Player{
static std::string name; static std::string name;
static Class cl; static Class cl;
@ -378,7 +400,9 @@ struct Trapper:Player{
std::string&GetIdleSAnimation()override; std::string&GetIdleSAnimation()override;
std::string&GetIdleWAnimation()override; std::string&GetIdleWAnimation()override;
}; };
#pragma endregion
#pragma region Wizard
struct Wizard:Player{ struct Wizard:Player{
static std::string name; static std::string name;
static Class cl; static Class cl;
@ -407,7 +431,9 @@ struct Wizard:Player{
std::string&GetIdleSAnimation()override; std::string&GetIdleSAnimation()override;
std::string&GetIdleWAnimation()override; std::string&GetIdleWAnimation()override;
}; };
#pragma endregion
#pragma region Witch
struct Witch:Player{ struct Witch:Player{
static std::string name; static std::string name;
static Class cl; static Class cl;
@ -436,6 +462,7 @@ struct Witch:Player{
std::string&GetIdleSAnimation()override; std::string&GetIdleSAnimation()override;
std::string&GetIdleWAnimation()override; std::string&GetIdleWAnimation()override;
}; };
#pragma endregion
#define READFROMCONFIG(class,enum) \ #define READFROMCONFIG(class,enum) \
class::name=#class".ClassName"_S; \ class::name=#class".ClassName"_S; \

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

@ -240,7 +240,7 @@ void Monster::STRATEGY::SLIMEKING(Monster&m,float fElapsedTime,std::string strat
m.phase=ConfigInt("StartPhase"); m.phase=ConfigInt("StartPhase");
}break; }break;
case 1:{ 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.phase=2;
m.SetSize(ConfigFloat("Phase2.Size")/100,false); m.SetSize(ConfigFloat("Phase2.Size")/100,false);
TransitionPhase(m.phase); TransitionPhase(m.phase);
@ -273,7 +273,7 @@ void Monster::STRATEGY::SLIMEKING(Monster&m,float fElapsedTime,std::string strat
} }
}break; }break;
case 2:{ 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.phase=3;
m.SetSize(ConfigFloat("Phase3.Size")/100,false); m.SetSize(ConfigFloat("Phase3.Size")/100,false);
TransitionPhase(m.phase); TransitionPhase(m.phase);
@ -297,7 +297,7 @@ void Monster::STRATEGY::SLIMEKING(Monster&m,float fElapsedTime,std::string strat
} }
}break; }break;
case 3:{ 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.phase=4;
m.SetSize(ConfigFloat("Phase4.Size")/100,false); m.SetSize(ConfigFloat("Phase4.Size")/100,false);
m.AddBuff(BuffType::SLOWDOWN,99999,ConfigFloat("Phase4.MoveSpdModifier")/100); 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; }break;
case 4:{ 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.phase=5;
m.F(A::IFRAME_TIME_UPON_HIT)=1; m.F(A::IFRAME_TIME_UPON_HIT)=1;
m.I(A::HITS_UNTIL_DEATH)=int(m.GetSizeMult()*100/ConfigFloat("Phase5.SizeLossPerHit"))-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)); SetValue(game->GetPlayer()->GetStat(stat));
} }
inline void UpdateLabel(){ inline void UpdateLabel(){
AttributeData data=ItemAttributable::GetDisplayInfo(stat); std::string attrStr=std::string(stat.Name())+":\n ";
std::string attrStr=data.name+":\n ";
attrStr+=std::to_string(value); attrStr+=std::to_string(value);
if(data.displayAsPercent){ if(stat.DisplayAsPercent()){
attrStr+="%"; attrStr+="%";
} }
if(statChangeAmt>0){ if(statChangeAmt>0){

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

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

@ -118,7 +118,7 @@ void Warrior::InitializeClassAbilities(){
Warrior::ability1.action= Warrior::ability1.action=
[](Player*p,vf2d pos={}){ [](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)); 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); p->AddBuff(BuffType::DAMAGE_REDUCTION,"Warrior.Ability 1.DamageReductionDuration"_F,"Warrior.Ability 1.DamageReduction"_F);
for(Monster&m:MONSTER_LIST){ 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))){ 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. # Percentage of player's normal movement speed while block is active.
SlowAmt = 0.3 SlowAmt = 0.3
# Percentage of damage to reduce by. (1.0 = 100%)
DamageReduction = 1.00
} }
Ability 1 Ability 1
{ {

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

@ -21,13 +21,19 @@ ItemScript
Buff Buff
{ {
Attack = 0,0.0 Attack = 0,0.0
Attack % = 0,0.0 Attack % = 0%,0.0
Defense = 0,0.0 Defense = 0,0.0
Defense % = 0,0.0 Defense % = 0%,0.0
Health = 0,0.0 Health = 0,0.0
Health % = 0,0.0 Health % = 0%,0.0
Move Spd = 0,0.0 Move Spd % = 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. # 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 # Defense = 5 # Adds 5 defense
# Health = 3 # Adds 3 Health # Health = 3 # Adds 3 Health
# Attack = 7 # Adds 7 attack # 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. # CDR = 14% # Grants 14% cooldown reduction.
# Crit Rate = 5% # Grants 5% crit rate. # Crit Rate = 5% # Grants 5% crit rate.
# Crit Dmg = 25% # Grants 25% bonus crit damage. # Crit Dmg = 25% # Grants 25% bonus crit damage.
@ -29,6 +31,9 @@ ItemSet
# HP4 Recovery % = 2% # Grants 2% HP recovery every 4 seconds. # HP4 Recovery % = 2% # Grants 2% HP recovery every 4 seconds.
# Damage Reduction = 2% # Grants 2% direct damage reduction # Damage Reduction = 2% # Grants 2% direct damage reduction
# #
#
# All Possible Stat modifications can be found in ItemStats.txt
#
Leather Leather
{ {
2 2
@ -55,7 +60,7 @@ ItemSet
{ {
2 2
{ {
Move Spd = 5% Move Spd % = 5%
} }
4 4
{ {
@ -78,7 +83,7 @@ ItemSet
2 2
{ {
Crit Rate = 3% Crit Rate = 3%
Move Spd = 5% Move Spd % = 5%
Attack = 2% Attack = 2%
} }
4 4
@ -114,7 +119,7 @@ ItemSet
{ {
2 2
{ {
Move Spd = 10% Move Spd % = 10%
} }
4 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()); 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) // Sets the Integer Value of a Property (for a given index)
inline void SetInt(const int32_t n, const size_t nItem = 0) inline void SetInt(const int32_t n, const size_t nItem = 0)
{ {

Loading…
Cancel
Save