The open source repository for the action RPG game in development by Sig Productions titled 'Adventures in Lestoria'!
https://forums.lestoria.net
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
716 lines
22 KiB
716 lines
22 KiB
#pragma region License
|
|
/*
|
|
License (OLC-3)
|
|
~~~~~~~~~~~~~~~
|
|
|
|
Copyright 2018 - 2023 OneLoneCoder.com
|
|
|
|
Redistribution and use in source and binary forms, with or without modification,
|
|
are permitted provided that the following conditions are met:
|
|
|
|
1. Redistributions or derivations of source code must retain the above copyright
|
|
notice, this list of conditions and the following disclaimer.
|
|
|
|
2. Redistributions or derivative works in binary form must reproduce the above
|
|
copyright notice. This list of conditions and the following disclaimer must be
|
|
reproduced in the documentation and/or other materials provided with the distribution.
|
|
|
|
3. Neither the name of the copyright holder nor the names of its contributors may
|
|
be used to endorse or promote products derived from this software without specific
|
|
prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
|
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
|
|
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
|
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
|
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
SUCH DAMAGE.
|
|
|
|
Portions of this software are copyright © 2023 The FreeType
|
|
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
|
|
All rights reserved.
|
|
*/
|
|
#pragma endregion
|
|
#include "Item.h"
|
|
#include "safemap.h"
|
|
#include "DEFINES.h"
|
|
#include "Crawler.h"
|
|
#include "Menu.h"
|
|
#include "Ability.h"
|
|
|
|
INCLUDE_game
|
|
INCLUDE_DATA
|
|
INCLUDE_GFX
|
|
|
|
safemap<std::string,ItemInfo>ITEM_DATA;
|
|
safemap<std::string,ItemScript>ITEM_SCRIPTS;
|
|
safemap<std::string,std::set<std::string>>ITEM_CATEGORIES;
|
|
Item Item::BLANK;
|
|
std::map<IT,Item>Inventory::_inventory;
|
|
std::map<ITCategory,std::vector<Item>>Inventory::sortedInv;
|
|
std::vector<ItemOverlay>ItemOverlay::items;
|
|
std::map<std::string,ItemSet>ItemSet::sets;
|
|
std::map<EquipSlot,Item*>Inventory::equipment;
|
|
std::map<std::string,EquipSlot>ItemInfo::nameToEquipSlot;
|
|
|
|
ItemInfo::ItemInfo()
|
|
:customProps({nullptr,nullptr}),img(nullptr){}
|
|
|
|
void ItemInfo::InitializeItems(){
|
|
for(int i=int(EquipSlot::HELMET);i<=int(EquipSlot::RING2);i<<=1){
|
|
Inventory::equipment[EquipSlot(i)]=&Item::BLANK;
|
|
}
|
|
|
|
nameToEquipSlot["Helmet"]=EquipSlot::HELMET;
|
|
nameToEquipSlot["Weapon"]=EquipSlot::WEAPON;
|
|
nameToEquipSlot["Armor"]=EquipSlot::ARMOR;
|
|
nameToEquipSlot["Gloves"]=EquipSlot::GLOVES;
|
|
nameToEquipSlot["Pants"]=EquipSlot::PANTS;
|
|
nameToEquipSlot["Shoes"]=EquipSlot::SHOES;
|
|
nameToEquipSlot["Ring1"]=EquipSlot::RING1;
|
|
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();
|
|
|
|
for(auto&[key,value]:DATA["ItemCategory"].GetKeys()){
|
|
ITEM_CATEGORIES[key];
|
|
Inventory::sortedInv[key];
|
|
Menu::inventoryListeners[key];
|
|
}
|
|
|
|
auto ReadItems=[&](datafile&data){
|
|
for(auto&[key,value]:data.GetKeys()){
|
|
if(key=="")ERR("Failed to read an item block ,no name specified!");
|
|
std::string imgPath="assets/"+"item_img_directory"_S+key+".png";
|
|
Renderable&img=GFX["item_img_directory"_S+key+".png"];
|
|
img.Load(imgPath);
|
|
|
|
std::string scriptName="",description="",category="";
|
|
std::string setName="";
|
|
float castTime=0;
|
|
std::vector<std::string> slot;
|
|
float cooldownTime="Item.Item Cooldown Time"_F;
|
|
std::vector<ItemAttribute>statValueList;
|
|
uint32_t sellValue=0;
|
|
uint32_t buyValue=0;
|
|
for(auto&[itemKey,itemValue]:data[key].GetKeys()){
|
|
std::string keyName=itemKey;
|
|
if(keyName=="Description"){
|
|
description=data[key][keyName].GetString();
|
|
}else
|
|
if(keyName=="ItemCategory"){
|
|
category=data[key][keyName].GetString();
|
|
}else
|
|
if(keyName=="ItemScript"){
|
|
scriptName=data[key][keyName].GetString();
|
|
}else
|
|
if(keyName=="Cast Time"){
|
|
castTime=float(data[key][keyName].GetReal());
|
|
}else
|
|
if(keyName=="Cooldown Time"){
|
|
castTime=float(data[key][keyName].GetReal());
|
|
}else
|
|
if(keyName=="Slot"){
|
|
for(auto&val:data[key][keyName].GetValues()){
|
|
slot.push_back(val);
|
|
}
|
|
}else
|
|
if(keyName=="StatValues"){
|
|
for(int i=0;i<data[key]["StatValues"].GetValueCount();i++){
|
|
statValueList.push_back(ItemAttributable::GetAttributeFromString(data[key]["StatValues"].GetString(i)));
|
|
}
|
|
}else
|
|
if(keyName=="PartofSet"){
|
|
setName=data[key][keyName].GetString();
|
|
}
|
|
if(keyName=="BuyValue"){
|
|
buyValue=data[key][keyName].GetInt();
|
|
}else
|
|
if(keyName=="SellValue"){
|
|
sellValue=data[key][keyName].GetInt();
|
|
}else{ //THis is a custom override modifier for a script. NO-OP
|
|
}
|
|
}
|
|
|
|
ItemInfo&it=ITEM_DATA[key];
|
|
|
|
if(statValueList.size()>0){
|
|
EnhancementInfo enhancementStats;
|
|
for(int enhancementLevel=0;enhancementLevel<=10;enhancementLevel++){
|
|
datafile&dat=data[key]["StatValues["+std::to_string(enhancementLevel)+"]"];
|
|
int attrIndex=0;
|
|
for(ItemAttribute&attr:statValueList){
|
|
enhancementStats.SetAttribute(enhancementLevel,attr,dat.GetInt(attrIndex));
|
|
attrIndex++;
|
|
}
|
|
}
|
|
it.enhancement=enhancementStats;
|
|
}
|
|
if(scriptName!=""){
|
|
if(!ITEM_SCRIPTS.count(scriptName)){
|
|
ERR("Could not load script "<<scriptName<<" for Item "<<key<<"!")
|
|
}
|
|
}
|
|
|
|
it.name=key;
|
|
it.description=description;
|
|
it.category=category;
|
|
it.castTime=castTime;
|
|
it.cooldownTime=cooldownTime;
|
|
it.slot=EquipSlot::NONE;
|
|
it.set=setName;
|
|
it.buyValue=buyValue;
|
|
it.sellValue=sellValue;
|
|
if(slot.size()>0){
|
|
for(std::string&s:slot){
|
|
if(!nameToEquipSlot.count(s))ERR("WARNING! Tried to add item "<<it.name<<" to slot "<<s<<" which doesn't exist!");
|
|
it.slot|=nameToEquipSlot[s];
|
|
}
|
|
}
|
|
if(!ITEM_CATEGORIES.count(it.category)){
|
|
ERR("WARNING! Tried to add item "<<it.name<<" to category "<<it.category<<" which does not exist!")
|
|
}
|
|
ITEM_CATEGORIES.at(it.category).insert(it.name);
|
|
it.img=img.Decal();
|
|
ItemProps&props=it.customProps;
|
|
if(scriptName!=""){
|
|
props.scriptProps=&DATA["ItemScript"][scriptName];
|
|
props.customProps=&data[key];
|
|
}
|
|
it.useFunc=scriptName;
|
|
}
|
|
};
|
|
|
|
ReadItems(DATA["ItemDatabase"]);
|
|
ReadItems(DATA["Equipment"]);
|
|
|
|
ITEM_DATA.SetInitialized();
|
|
ITEM_CATEGORIES.SetInitialized();
|
|
Menu::inventoryListeners.SetInitialized();
|
|
|
|
for(auto&[name,info]:ITEM_DATA){
|
|
Item tempItem{1,name};
|
|
if(tempItem.Description().length()==0)ERR("WARNING! Item "<<info.name<<" does not have a description!");
|
|
}
|
|
|
|
std::cout<<ITEM_DATA.size()<<" items have been loaded."<<std::endl;
|
|
std::cout<<ITEM_CATEGORIES.size()<<" item categories have been loaded."<<std::endl;
|
|
}
|
|
|
|
ItemProps::ItemProps(utils::datafile*scriptProps,utils::datafile*customProps)
|
|
:scriptProps(scriptProps),customProps(customProps){}
|
|
|
|
int ItemProps::GetIntProp(std::string prop,size_t index=0){
|
|
if(customProps->HasProperty(prop)) return (*customProps)[prop].GetInt(index);
|
|
else return (*scriptProps)[prop].GetInt(index);
|
|
};
|
|
float ItemProps::GetFloatProp(std::string prop,size_t index=0){
|
|
if(customProps->HasProperty(prop)) return float((*customProps)[prop].GetReal(index));
|
|
else return float((*scriptProps)[prop].GetReal(index));
|
|
};
|
|
std::string ItemProps::GetStringProp(std::string prop,size_t index=0){
|
|
if(customProps->HasProperty(prop)) return (*customProps)[prop].GetString(index);
|
|
else return (*scriptProps)[prop].GetString(index);
|
|
};
|
|
|
|
void ItemInfo::InitializeScripts(){
|
|
ITEM_SCRIPTS["Restore"]=[](Crawler*game,ItemProps props){
|
|
game->GetPlayer()->Heal(props.GetIntProp("HP Restore"));
|
|
game->GetPlayer()->Heal(int(game->GetPlayer()->GetMaxHealth()*props.GetIntProp("HP % Restore")/100.f));
|
|
game->GetPlayer()->RestoreMana(props.GetIntProp("MP Restore"));
|
|
game->GetPlayer()->RestoreMana(int(game->GetPlayer()->GetMaxMana()*props.GetIntProp("MP % Restore")/100.f));
|
|
return true;
|
|
};
|
|
ITEM_SCRIPTS["Buff"]=[](Crawler*game,ItemProps props){
|
|
game->GetPlayer()->AddBuff(BuffType::ATTACK_PCT_UP,props.GetFloatProp("Attack %",1),props.GetFloatProp("Attack %",0)/100);
|
|
return true;
|
|
};
|
|
|
|
ITEM_SCRIPTS.SetInitialized();
|
|
std::cout<<ITEM_SCRIPTS.size()<<" item scripts have been loaded."<<std::endl;
|
|
}
|
|
|
|
Item::Item()
|
|
:amt(0),it(nullptr),enhancementLevel(0){}
|
|
|
|
Item::Item(uint32_t amt,IT item,uint8_t enhancementLevel)
|
|
:amt(amt),it(&ITEM_DATA.at(item)),enhancementLevel(enhancementLevel){}
|
|
|
|
void Inventory::AddItem(IT it,uint32_t amt,bool monsterDrop){
|
|
if(!ITEM_DATA.count(it))ERR("Item "<<std::quoted(it)<<" does not exist in Item Database!");
|
|
//There are two places to manipulate items in (Both the sorted inventory and the actual inventory)
|
|
if(!_inventory.count(it)){
|
|
_inventory[it]=Item{amt,it};
|
|
InsertIntoSortedInv(it);
|
|
}else{
|
|
_inventory.at(it).amt+=amt;
|
|
}
|
|
InsertIntoStageInventoryCategory(it,amt,monsterDrop);
|
|
}
|
|
|
|
Item Inventory::CopyItem(IT it){
|
|
if(!_inventory.count(it))return Item::BLANK;
|
|
return _inventory.at(it);
|
|
}
|
|
|
|
Item&Inventory::GetItem(IT it){
|
|
if(!_inventory.count(it))return Item::BLANK;
|
|
return _inventory.at(it);
|
|
}
|
|
|
|
uint32_t Inventory::GetItemCount(IT it){
|
|
if(!_inventory.count(it)){
|
|
return 0;
|
|
}else{
|
|
return _inventory.at(it).Amt();
|
|
}
|
|
}
|
|
|
|
//Returns true if the item has been consumed completely and there are 0 remaining of that type in our inventory.
|
|
bool Inventory::UseItem(IT it,uint32_t amt){
|
|
if(!_inventory.count(it))return false;
|
|
//There are two places to manipulate items in (Both the sorted inventory and the actual inventory)
|
|
for(uint32_t i=0;i<amt;i++){
|
|
if(ExecuteAction(it)){
|
|
return RemoveItem(it);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//Returns true if the item has been consumed completely and there are 0 remaining of that type in our inventory.
|
|
bool Inventory::RemoveItem(IT it,ITCategory inventory,uint32_t amt){
|
|
#pragma region Calculate Inventory to Manipulate
|
|
std::vector<Item>&inv=sortedInv.at(inventory);
|
|
bool eraseFromLootWindow=false;
|
|
if(inventory=="Monster Loot") {
|
|
inv=sortedInv.at("Monster Loot");
|
|
eraseFromLootWindow=true;
|
|
}else
|
|
if(inventory=="Stage Loot"){
|
|
inv=sortedInv.at("Stage Loot");
|
|
eraseFromLootWindow=true;
|
|
}
|
|
int count=0;
|
|
for(Item&item:inv){
|
|
if(item==it)break;
|
|
count++;
|
|
}
|
|
#pragma endregion
|
|
|
|
uint32_t itemAmt=GetItemCount(it);
|
|
if(inventory=="Monster Loot"||inventory=="Stage Loot"){
|
|
itemAmt=inv.at(count).Amt();
|
|
}
|
|
|
|
//There are two places to manipulate items in (Both the sorted inventory and the actual inventory)
|
|
if (!itemAmt)return false;
|
|
|
|
if (amt>=itemAmt){
|
|
inv.erase(inv.begin()+count);
|
|
if(!eraseFromLootWindow){
|
|
_inventory.erase(it);
|
|
}
|
|
//Callback for GUI inventories.
|
|
Menu::InventorySlotsUpdated(inventory);
|
|
return true;
|
|
}else{
|
|
inv.at(count).amt-=amt;
|
|
if(!eraseFromLootWindow){
|
|
_inventory.at(it).amt-=amt;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//Returns true if the item has been consumed completely and there are 0 remaining of that type in our inventory.
|
|
bool Inventory::RemoveItem(IT it,uint32_t amt){
|
|
ITCategory cat = ITEM_DATA[it].category;
|
|
return RemoveItem(it, cat, amt);
|
|
}
|
|
|
|
std::vector<Item>&Inventory::get(ITCategory itemCategory){
|
|
return sortedInv.at(itemCategory);
|
|
}
|
|
|
|
void Inventory::InsertIntoSortedInv(IT item){
|
|
sortedInv.at(ITEM_DATA[item].category).push_back(Item{1,item});
|
|
//This should be a callback to menus that we need to update the interface with another item slot since a new one has appeared.
|
|
Menu::InventorySlotsUpdated(ITEM_DATA[item].category);
|
|
}
|
|
|
|
void Inventory::InsertIntoStageInventoryCategory(IT item,uint32_t amt,bool monsterDrop){
|
|
std::string stageInventoryCategory="Stage Loot";
|
|
if(monsterDrop){
|
|
stageInventoryCategory="Monster Loot";
|
|
}
|
|
std::vector<Item>&inv=sortedInv.at(stageInventoryCategory);
|
|
std::vector<Item>::iterator it=std::find(inv.begin(),inv.end(),Item{amt,item});
|
|
if(it!=inv.end()){
|
|
(*it).amt+=amt;
|
|
}else{
|
|
inv.push_back(Item{amt,item});
|
|
}
|
|
Menu::InventorySlotsUpdated(stageInventoryCategory);
|
|
}
|
|
|
|
bool Inventory::ExecuteAction(IT item){
|
|
if(ITEM_SCRIPTS.count(ITEM_DATA.at(item).useFunc)){
|
|
return ITEM_SCRIPTS.at(ITEM_DATA.at(item).useFunc)(game,ITEM_DATA[item].customProps);
|
|
}else{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool Inventory::SwapItems(ITCategory itemCategory,uint32_t slot1,uint32_t slot2){
|
|
std::vector<Item>&inv=sortedInv.at(itemCategory);
|
|
int largestSlot=std::max(slot1,slot2);
|
|
if(inv.size()<=largestSlot){
|
|
//The inventory is too small, so expand out blank slots to accomodate.
|
|
inv.resize(largestSlot+size_t(1));
|
|
}
|
|
Item&item1=inv.at(slot1);
|
|
Item&item2=inv.at(slot2);
|
|
std::swap(item1,item2);
|
|
return true;
|
|
}
|
|
|
|
uint32_t Item::Amt()const{
|
|
return amt;
|
|
};
|
|
const std::string&Item::ActualName()const{
|
|
if(IsBlank())return "";
|
|
return it->Name();
|
|
};
|
|
const std::string Item::DisplayName()const{
|
|
if(IsBlank())return "";
|
|
std::string name=ActualName();
|
|
if(IsEquippable()&&EnhancementLevel()>0){
|
|
name+=" [+"+std::to_string(EnhancementLevel())+"]";
|
|
}
|
|
return name;
|
|
}
|
|
const bool Item::IsEquippable()const{
|
|
return Category()=="Equipment"||Category()=="Accessories";
|
|
}
|
|
const std::string Item::Description(CompactText compact)const{
|
|
std::string description=it->Description();
|
|
if(IsEquippable()){
|
|
description+='\n';
|
|
description+=GetStats().GetStatsString(compact);
|
|
if(ItemSet()){
|
|
const::ItemSet*const set=ItemSet().value();
|
|
if(compact==COMPACT){
|
|
description+="\n"+set->GetSetName()+" Set - ";
|
|
}else{
|
|
description+="\n\n"+set->GetSetName()+" Set\n";
|
|
}
|
|
const std::map<::ItemSet,int>&itemSetCounts=Inventory::GetEquippedItemSets();
|
|
for(bool first=true;const auto&[pieceCount,bonuses]:set->GetSetBonusBreakpoints()){
|
|
if(itemSetCounts.count(*set)&&pieceCount<=itemSetCounts.at(*set)){
|
|
description+="#56E346";
|
|
}
|
|
if(!first){
|
|
if(compact==COMPACT){
|
|
description+=" ";
|
|
}else{
|
|
description+="\n";
|
|
}
|
|
}
|
|
description+="("+std::to_string(pieceCount)+"): "+bonuses.GetStatsString(compact)+"#FFFFFF";
|
|
first=false;
|
|
}
|
|
}
|
|
}
|
|
return description;
|
|
};
|
|
const ITCategory Item::Category()const{
|
|
return it->Category();
|
|
};
|
|
const::Decal*const Item::Decal()const{
|
|
return it->Decal();
|
|
};
|
|
const ItemScript&Item::OnUseAction()const{
|
|
return it->OnUseAction();
|
|
};
|
|
|
|
const std::string&ItemInfo::Name()const{
|
|
return name;
|
|
};
|
|
const std::string&ItemInfo::Description()const{
|
|
return description;
|
|
};
|
|
const ITCategory ItemInfo::Category()const{
|
|
return category;
|
|
};
|
|
const::Decal*const ItemInfo::Decal()const{
|
|
return img;
|
|
};
|
|
const ItemScript&ItemInfo::OnUseAction()const{
|
|
return ITEM_SCRIPTS.at(useFunc);
|
|
};
|
|
|
|
const bool Item::IsBlank()const{
|
|
return amt==0||it==nullptr;
|
|
}
|
|
|
|
void Inventory::Clear(ITCategory itemCategory){
|
|
std::vector<Item>itemList=get(itemCategory); //We have to make a copy here because RemoveItem() will modify the list provided by get() inline.
|
|
for(Item&item:itemList){
|
|
size_t itemQuantity=GetItemCount(item.ActualName());//Normally we want to clear all the items that are actually in our inventory...But...
|
|
if(itemCategory=="Monster Loot"||itemCategory=="Stage Loot"){//These do not affect the actual inventory, we just clear the lists.
|
|
itemQuantity=item.Amt();
|
|
}
|
|
RemoveItem(item.ActualName(),itemCategory,uint32_t(itemQuantity));
|
|
}
|
|
}
|
|
|
|
const bool Item::operator==(const Item&rhs)const{
|
|
return it==rhs.it;
|
|
}
|
|
|
|
const bool Item::operator==(const IT&rhs)const{
|
|
return it->Name()==rhs;
|
|
}
|
|
|
|
ItemOverlay::ItemOverlay(ItemInfo item)
|
|
:it(item),width("ItemDrop.Item Drop Scale"_F*24+4+game->GetTextSizeProp(item.Name()).x*0.5f){
|
|
xOffset=-width;
|
|
}
|
|
|
|
void ItemOverlay::Update(){
|
|
for(ItemOverlay&item:items){
|
|
item.timer+=game->GetElapsedTime();
|
|
item.xOffset=std::min(item.xOffset+game->GetElapsedTime()*"ItemOverlay.Item Overlay Speed"_F,0.f);
|
|
}
|
|
std::erase_if(items,[](ItemOverlay&item){return item.timer>"ItemOverlay.Item Overlay Time"_F;});
|
|
}
|
|
|
|
void ItemOverlay::Draw(){
|
|
float itemScale="ItemDrop.Item Drop Scale"_F;
|
|
for(int counter=0;ItemOverlay&item:items){
|
|
vf2d pos={item.xOffset,96.f+counter*10};
|
|
Pixel darkCol=Menu::GetCurrentTheme().GetButtonCol();
|
|
Pixel lightCol=Menu::GetCurrentTheme().GetButtonCol()*1.2f;
|
|
game->GradientFillRectDecal(pos,{item.width,8},darkCol,darkCol,darkCol,lightCol);
|
|
game->DrawRectDecal(pos,{item.width,8},Menu::GetCurrentTheme().GetHighlightCol());
|
|
game->DrawDecal(pos,const_cast<Decal*>(item.it.Decal()),{itemScale,itemScale});
|
|
game->DrawShadowStringPropDecal(pos+vf2d{itemScale*24+2,2},item.it.Name(),WHITE,BLACK,{0.5f,0.7f});
|
|
counter++;
|
|
}
|
|
}
|
|
|
|
void ItemOverlay::AddToItemOverlay(const ItemInfo&it){
|
|
items.push_back(ItemOverlay{it});
|
|
std::for_each(items.begin(),items.end(),[](ItemOverlay&it){it.ResetTimer();});
|
|
}
|
|
|
|
const float ItemInfo::CastTime()const{
|
|
return castTime;
|
|
}
|
|
|
|
const float ItemInfo::CooldownTime()const{
|
|
return cooldownTime;
|
|
}
|
|
|
|
void ItemOverlay::ResetTimer(){
|
|
timer=0;
|
|
}
|
|
|
|
const float Item::CastTime()const{
|
|
return it->CastTime();
|
|
}
|
|
|
|
const float Item::CooldownTime()const{
|
|
return it->CooldownTime();
|
|
}
|
|
|
|
const Stats&EnhancementInfo::operator[](int level)const{
|
|
return enhancementStats[level];
|
|
}
|
|
|
|
const std::optional<const ItemSet *const>ItemInfo::ItemSet()const{
|
|
if(ItemSet::sets.count(set)){
|
|
return &ItemSet::sets.at(set);
|
|
}
|
|
return {};
|
|
};
|
|
|
|
const Stats&ItemSet::operator[](int setPieces)const{
|
|
if(setPieces<=0||setPieces>=9)ERR("Piece count is invalid! Expecting a value (1-8) but got "<<setPieces);
|
|
return setBonuses[setPieces];
|
|
};
|
|
|
|
void ItemSet::AddSetBonus(std::string setName,int pieceCount,Stats&bonuses){
|
|
if(pieceCount<=0||pieceCount>=9)ERR("Piece count is invalid! Expecting a value (1-8) but got "<<pieceCount);
|
|
sets[setName].name=setName;
|
|
for(int i=pieceCount;i<sets[setName].setBonuses.size();i++){
|
|
sets[setName].setBonuses[i]+=bonuses;
|
|
}
|
|
sets[setName].setBonusBreakpoints.push_back({pieceCount,bonuses});
|
|
}
|
|
|
|
void Inventory::EquipItem(Item&it,EquipSlot slot){
|
|
if(!(it.it->slot&slot))return;
|
|
EquipSlot equippedSlot=GetSlotEquippedIn(it);
|
|
if(equippedSlot!=EquipSlot::NONE)UnequipItem(equippedSlot);
|
|
if(GetEquip(slot)!=nullptr)UnequipItem(slot);
|
|
Inventory::equipment[slot]=⁢
|
|
PlayerStats::RecalculateEquipStats();
|
|
};
|
|
void Inventory::UnequipItem(EquipSlot slot){
|
|
Inventory::equipment[slot]=&Item::BLANK;
|
|
PlayerStats::RecalculateEquipStats();
|
|
};
|
|
EquipSlot Inventory::GetSlotEquippedIn(Item&it){
|
|
for(int i=int(EquipSlot::HELMET);i<=int(EquipSlot::RING2);i<<=1){
|
|
EquipSlot slot=EquipSlot(i);
|
|
Item*equip=GetEquip(slot);
|
|
if(equip==nullptr)continue;
|
|
if(equip->ActualName()==it.ActualName())return slot;
|
|
}
|
|
return EquipSlot::NONE;
|
|
};
|
|
Item*Inventory::GetEquip(EquipSlot slot){
|
|
return Inventory::equipment[slot];
|
|
}
|
|
const EquipSlot Item::GetEquipSlot()const{
|
|
return it->Slot();
|
|
}
|
|
const EquipSlot ItemInfo::Slot()const{
|
|
return slot;
|
|
}
|
|
|
|
void EnhancementInfo::SetAttribute(int enhanceLevel,ItemAttribute attribute,int value){
|
|
while(enhancementStats.size()<=enhanceLevel){
|
|
enhancementStats.push_back({});
|
|
}
|
|
enhancementStats[enhanceLevel].A(attribute)=value;
|
|
}
|
|
|
|
void ItemInfo::InitializeSets(){
|
|
for(auto&[key,value]:DATA["ItemSet"].GetKeys()){
|
|
std::string setName=key;
|
|
datafile&setInfo=DATA["ItemSet"][setName];
|
|
|
|
for(int pieceCount=1;pieceCount<=8;pieceCount++){
|
|
if(setInfo.HasProperty(std::to_string(pieceCount))){
|
|
datafile&statInfo=setInfo[std::to_string(pieceCount)];
|
|
Stats bonuses;
|
|
for(auto&[key,value]:statInfo.GetKeys()){
|
|
ItemAttribute attr=bonuses.GetAttributeFromString(key);
|
|
bonuses.A(attr)=statInfo[key].GetInt(0);
|
|
}
|
|
ItemSet::AddSetBonus(setName,pieceCount,bonuses);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const Stats&ItemInfo::GetStats(int enhancementLevel)const{
|
|
if(enhancement.size()<=enhancementLevel){
|
|
return {};
|
|
}
|
|
return enhancement[enhancementLevel];
|
|
}
|
|
|
|
const Stats Item::GetStats()const{
|
|
if(it==nullptr)return{};
|
|
return it->GetStats(enhancementLevel);
|
|
};
|
|
|
|
const size_t EnhancementInfo::size()const{
|
|
return enhancementStats.size();
|
|
};
|
|
|
|
const std::optional<const ItemSet*const>Item::ItemSet()const{
|
|
return it->ItemSet();
|
|
};
|
|
|
|
const uint8_t Item::EnhancementLevel()const{
|
|
return enhancementLevel;
|
|
};
|
|
void Item::EnhanceItem(){
|
|
if(enhancementLevel+1>"Item.Item Max Enhancement Level"_I)ERR("WARNING! Attempted to enhance "<<DisplayName()<<" beyond the cap of "<<"Item.Item Max Enhancement Level"_I);
|
|
enhancementLevel++;
|
|
};
|
|
|
|
const std::vector<std::pair<int,Stats>>&ItemSet::GetSetBonusBreakpoints()const{
|
|
return setBonusBreakpoints;
|
|
};
|
|
|
|
const std::map<ItemSet,int>Inventory::GetEquippedItemSets(){
|
|
std::map<ItemSet,int>setCounts;
|
|
for(int i=int(EquipSlot::HELMET);i<=int(EquipSlot::RING2);i<<=1){
|
|
EquipSlot slot=EquipSlot(i);
|
|
Item*equip=Inventory::GetEquip(slot);
|
|
if(equip->IsBlank())continue;
|
|
if(equip->ItemSet()){
|
|
setCounts[*equip->ItemSet().value()]++;
|
|
}
|
|
}
|
|
return setCounts;
|
|
}
|
|
|
|
const std::string Stats::GetStatsString(CompactText compact)const{
|
|
std::string description="";
|
|
for(bool first=true;const auto&[attr,val]:attributes){
|
|
if(!first){
|
|
if(compact==COMPACT){
|
|
description+=" ";
|
|
}else{
|
|
description+="\n";
|
|
}
|
|
}
|
|
description+=ItemAttributable::GetAttributeName(attr)+": "+std::to_string(val)+(ItemAttributable::GetDisplayInfo(attr).displayAsPercent?"%":"");
|
|
first=false;
|
|
}
|
|
return description;
|
|
}
|
|
|
|
ItemInfo&ItemInfo::operator[](const IT&item){
|
|
if(!ITEM_DATA.count(item))ERR("Item "<<std::quoted(item)<<" does not exist in the item database!");
|
|
return ITEM_DATA[item];
|
|
}
|
|
|
|
const uint32_t ItemInfo::GetBuyValue()const{
|
|
return buyValue;
|
|
}
|
|
const uint32_t ItemInfo::GetSellValue()const{
|
|
return sellValue;
|
|
}
|
|
const bool ItemInfo::CanBeSold()const{
|
|
return GetSellValue()>0;
|
|
}
|
|
const bool ItemInfo::CanBePurchased()const{
|
|
return GetBuyValue()>0;
|
|
}
|
|
|
|
const uint32_t Item::BuyValue()const{
|
|
return it->GetBuyValue();
|
|
}
|
|
const uint32_t Item::SellValue()const{
|
|
return it->GetSellValue();
|
|
}
|
|
const bool Item::CanBeSold()const{
|
|
return it->CanBeSold();
|
|
}
|
|
const bool Item::CanBePurchased()const{
|
|
return it->CanBePurchased();
|
|
}
|
|
|
|
|
|
void Item::SetAmt(uint32_t newAmt){
|
|
amt=newAmt;
|
|
} |