#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 "AttributableStat.h" #include #include "util.h" INCLUDE_game INCLUDE_DATA INCLUDE_GFX safemapITEM_DATA; safemapITEM_SCRIPTS; safemap>ITEM_CATEGORIES; std::shared_ptrItem::BLANK=std::make_shared(); std::multimap>Inventory::_inventory; std::map>>Inventory::sortedInv; std::vectorItemOverlay::items; std::mapItemSet::sets; std::map>Inventory::equipment; std::mapItemInfo::nameToEquipSlot; int Item::IsBlankStaticCallCounter=0; safemapStats::maxDamageReductionTable; ItemEnhancementFunctionPrimingData Item::enhanceFunctionPrimed("CanEnhanceItem()"); std::vector>ItemInfo::craftableConsumables; 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(); InitializeSets(); for(auto&[key,value]:DATA["ItemCategory"].GetKeys()){ ITEM_CATEGORIES[key]; Inventory::sortedInv[key]; Menu::InitializeMenuListenerCategory(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 slot; float cooldownTime="Item.Item Cooldown Time"_F; std::vectorstatValueList; uint32_t sellValue=0; uint32_t buyValue=0; Stats minStats; Stats maxStats; bool useDuringCast=false; 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>itemsRequired; uint32_t goldCost=0; if(enhancementLevel!=0){ //The first level does not require any crafting, skip this level. while(data[key]["Crafting"][std::format("Level[{}]",enhancementLevel)].HasProperty(std::format("Item[{}]",itemsRequired.size()))){ datafile&item=data[key]["Crafting"][std::format("Level[{}]",enhancementLevel)][std::format("Item[{}]",itemsRequired.size())]; itemsRequired.push_back({item.GetString(0),item.GetInt(1)}); } goldCost=data[key]["Crafting"][std::format("Level[{}]",enhancementLevel)]["Gold"].GetInt(); if(data[key]["Crafting"][std::format("Level[{}]",enhancementLevel)].HasProperty("AvailableChapter")){ availableChapter=data[key]["Crafting"][std::format("Level[{}]",enhancementLevel)]["AvailableChapter"].GetInt(); } enhancementStats.SetCraftingRequirements(enhancementLevel,itemsRequired,goldCost,availableChapter); } } it.enhancement=enhancementStats; } if(data[key].HasProperty("Crafting")&&!data[key]["Crafting"].HasProperty("Level[1]")){ std::vector>itemsRequired; uint32_t goldCost=0; uint8_t availableChapter=1; //If the item has a key called "Crafting" but did not specify a level number, then we assume this item just has one base level for normal crafting and thus we will add the requirements to craft this item under enhancement level 1. while(data[key]["Crafting"].HasProperty(std::format("Item[{}]",itemsRequired.size()))){ datafile&item=data[key]["Crafting"][std::format("Item[{}]",itemsRequired.size())]; itemsRequired.push_back({item.GetString(0),item.GetInt(1)}); } goldCost=data[key]["Crafting"]["Gold"].GetInt(); if(data[key]["Crafting"].HasProperty("AvailableChapter")){ availableChapter=data[key]["Crafting"]["AvailableChapter"].GetInt(); } craftableConsumables.push_back(std::make_shared(1,key)); it.enhancement.SetAttribute(1,ItemAttribute::Get("Attack"),0.f); it.enhancement.SetCraftingRequirements(1,itemsRequired,goldCost,availableChapter); } if(data[key].HasProperty("MinStats")){ if(data[key]["MinStats"].GetValueCount()!=statValueList.size())ERR(std::format("MinStats attribute count does not match statValueList attribute count {}!={}",data[key]["MinStats"].GetValueCount(),statValueList.size())); for(int attrCount=0;ItemAttribute&attr:statValueList){ minStats.A(attr)=data[key]["MinStats"].GetReal(attrCount); attrCount++; } } if(data[key].HasProperty("MaxStats")){ if(data[key]["MaxStats"].GetValueCount()!=statValueList.size())ERR(std::format("MaxStats attribute count does not match statValueList attribute count {}!={}",data[key]["MaxStats"].GetValueCount(),statValueList.size())); for(int attrCount=0;ItemAttribute&attr:statValueList){ maxStats.A(attr)=data[key]["MaxStats"].GetReal(attrCount); attrCount++; } } if(data[key].HasProperty("MinStats")^data[key].HasProperty("MaxStats"))ERR("Only one of MinStats/MaxStats was provided! Both are required!"); if(scriptName!=""){ if(scriptName=="RestoreDuringCast"){ useDuringCast=true; } if(!ITEM_SCRIPTS.count(scriptName)){ ERR("Could not load script "<0){ for(std::string&s:slot){ if(!nameToEquipSlot.count(s))ERR("WARNING! Tried to add item "<&item1,std::shared_ptr&item2){return item1.get()->GetEnhancementInfo().AvailableChapter()GetEnhancementInfo().AvailableChapter();}); ITEM_DATA.SetInitialized(); ITEM_CATEGORIES.SetInitialized(); Menu::LockInListeners(); for(auto&[name,info]:ITEM_DATA){ Item tempItem{1,name}; if(tempItem.Description().length()==0)ERR("WARNING! Item "<HasProperty(prop)) return (*customProps)[prop].GetInt(index); else return (*scriptProps)[prop].GetInt(index); }; float ItemProps::GetFloatProp(const std::string&prop,size_t index)const{ if(customProps->HasProperty(prop)) return float((*customProps)[prop].GetReal(index)); else return float((*scriptProps)[prop].GetReal(index)); }; std::string ItemProps::GetStringProp(const std::string&prop,size_t index)const{ if(customProps->HasProperty(prop)) return (*customProps)[prop].GetString(index); else return (*scriptProps)[prop].GetString(index); }; const uint32_t ItemProps::PropCount(const std::string&prop)const{ if(customProps->HasProperty(prop)) return (*customProps)[prop].GetValueCount(); else return (*scriptProps)[prop].GetValueCount(); } void ItemInfo::InitializeScripts(){ ITEM_SCRIPTS["Restore"]=[](Crawler*game,ItemProps props){ auto ParseItemScriptData=[&](const std::string&propName,std::functionaction){ int restoreAmt=props.GetIntProp(propName); action(game,restoreAmt); if(restoreAmt>0&&props.PropCount(propName)==3){ game->GetPlayer()->AddBuff(RESTORATION,props.GetFloatProp(propName,2),restoreAmt,props.GetFloatProp(propName,1),action); } }; ParseItemScriptData("HP Restore",[&](Crawler*game,int restoreAmt){ game->GetPlayer()->Heal(restoreAmt); }); ParseItemScriptData("HP % Restore",[&](Crawler*game,int restoreAmt){ game->GetPlayer()->Heal(int(game->GetPlayer()->GetMaxHealth()*restoreAmt/100.0f)); }); ParseItemScriptData("MP Restore",[&](Crawler*game,int restoreAmt){ game->GetPlayer()->RestoreMana(restoreAmt); }); ParseItemScriptData("MP % Restore",[&](Crawler*game,int restoreAmt){ game->GetPlayer()->RestoreMana(int(game->GetPlayer()->GetMaxMana()*props.GetIntProp("MP % Restore")/100.f)); }); return true; }; for(auto&[key,value]:ItemAttribute::attributes){ if(!DATA.GetProperty("ItemScript.Buff").HasProperty(key)){ ERR("WARNING! Buff Item Script does not support Buff "<GetPlayer()->AddBuff(BuffType::STAT_UP,props.GetFloatProp(key,1),intensity,{ItemAttribute::Get(key)}); } return true; }; ITEM_SCRIPTS["RestoreDuringCast"]=[&](Crawler*game,ItemProps props){ auto ParseItemScriptData=[&](const std::string&propName,std::functionaction){ int restoreAmt=props.GetIntProp(propName); action(game,restoreAmt); if(restoreAmt>0&&props.PropCount(propName)==3){ game->GetPlayer()->AddBuff(RESTORATION_DURING_CAST,props.GetFloatProp(propName,2),restoreAmt,props.GetFloatProp(propName,1),action); } }; ParseItemScriptData("HP Restore",[&](Crawler*game,int restoreAmt){ game->GetPlayer()->Heal(restoreAmt); }); ParseItemScriptData("HP % Restore",[&](Crawler*game,int restoreAmt){ game->GetPlayer()->Heal(int(game->GetPlayer()->GetMaxHealth()*restoreAmt/100.0f)); }); ParseItemScriptData("MP Restore",[&](Crawler*game,int restoreAmt){ game->GetPlayer()->RestoreMana(restoreAmt); }); ParseItemScriptData("MP % Restore",[&](Crawler*game,int restoreAmt){ game->GetPlayer()->RestoreMana(int(game->GetPlayer()->GetMaxMana()*props.GetIntProp("MP % Restore")/100.f)); }); return true; }; ITEM_SCRIPTS.SetInitialized(); std::cout<newItem=(*_inventory.insert({it,std::make_shared(1,it)})).second; newItem->RandomizeStats(); InsertIntoSortedInv(newItem); } goto SkipAddingStackableItem; } else //There are two places to manipulate items in (Both the sorted inventory and the actual inventory) if(!_inventory.count(it)){ InsertIntoSortedInv((*_inventory.insert({it,std::make_shared(amt,it)})).second); }else{ auto inventory=_inventory.equal_range(it); if(std::accumulate(inventory.first,inventory.second,0, [&](int counter,std::pair>item){ (*item.second).amt+=amt; return counter+1;})>1)ERR("WARNING! We should not have more than 1 instance of a stackable item!"); } SkipAddingStackableItem: InsertIntoStageInventoryCategory(it,amt,monsterDrop); } std::vector>Inventory::CopyItem(IT it){ if(!_inventory.count(it))return{}; std::vector>copiedItems; auto inventory=_inventory.equal_range(it); std::for_each(inventory.first,inventory.second,[&](std::pair>it){copiedItems.push_back(std::make_shared(*it.second));}); return copiedItems; } std::vector>Inventory::GetItem(IT it){ if(!_inventory.count(it))return{}; std::vector>items; auto inventory=_inventory.equal_range(it); std::for_each(inventory.first,inventory.second,[&](std::pair>it){items.push_back(it.second);}); return items; } uint32_t Inventory::GetItemCount(IT it){ if(!_inventory.count(it)){ return 0; }else{ auto inventory=_inventory.equal_range(it); return std::accumulate(inventory.first,inventory.second,0,[](int val,std::pair>it){return val+(*it.second).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;iitemRef,ITCategory inventory,uint32_t amt){ if(amt==0)ERR("WARNING! Trying to remove zero of an item makes no sense."); #pragma region Calculate Inventory to Manipulate std::vector>&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(std::weak_ptritem:inv){ if(item==itemRef)break; count++; } #pragma endregion uint32_t itemAmt=GetItemCount(itemRef.lock()->ActualName()); 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); //Clears it from the detected sorted inventory as well! if(!eraseFromLootWindow){ //We must clear out the item AFTER we've updated context-sensitive inventories because they may be borrowing a ref from this structure!!! _inventory.erase(itemRef.lock()->ActualName()); } //Callback for GUI inventories. Menu::InventorySlotsUpdated(inventory); return true; }else{ if(itemRef.lock()->IsEquippable()){ //Since equipment doesn't stack, if we have more than one piece we have to still remove that piece. size_t erased=std::erase_if(_inventory,[&](const std::pair>data){return data.second==itemRef;}); if(erased!=1)ERR(std::format("Did not erase a single element, instead erased {} elements.",erased)); inv.erase(inv.begin()+count); //Clears it from the detected sorted inventory as well! Menu::InventorySlotsUpdated(inventory); return true; }else{ itemRef.lock()->amt-=amt; //Don't touch the sorted inventory unless this is monster loot or stage loot because there's only "1" of this item in the entry list. } 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(std::weak_ptritemRef,uint32_t amt){ ITCategory cat = itemRef.lock()->Category(); return RemoveItem(itemRef, cat, amt); } const std::vector>&Inventory::get(ITCategory itemCategory){ return sortedInv.at(itemCategory); } void Inventory::InsertIntoSortedInv(std::shared_ptritemRef){ sortedInv.at(itemRef->Category()).push_back(itemRef); //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(itemRef->Category()); } void Inventory::InsertIntoStageInventoryCategory(IT item,uint32_t amt,bool monsterDrop){ std::string stageInventoryCategory="Stage Loot"; if(monsterDrop){ stageInventoryCategory="Monster Loot"; } std::vector>&inv=sortedInv.at(stageInventoryCategory); std::vector>::iterator it=std::find(inv.begin(),inv.end(),std::make_shared(amt,item)); //Uses operator== to compare if this item does exist in a stage/monster loot inventory already. We just make an in-place shared pointer of an item to compare with. if(it!=inv.end()){ (*it)->amt+=amt; }else{ inv.push_back(std::make_shared(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>&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)); } std::weak_ptritem1=inv.at(slot1); std::weak_ptritem2=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 ItemInfo::IsEquippable()const{ return slot!=EquipSlot::NONE&&(category=="Equipment"||category=="Accessories"); } const bool Item::IsEquippable()const{ return it->IsEquippable(); } const std::string Item::Description(CompactText compact)const{ std::string description=it->Description(); if(IsEquippable()){ if(HasRandomizedStats()){ description+='\n'; description+=randomizedStats.GetStatsString(); } 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; } } } if(compact==CRAFTING_INFO){ description+="\n\nCrafting Requirements:\n---------\n"; if(IsCraftable()){ const EnhancementLevelInfo&info=GetEnhancementInfo()[EnhancementLevel()+1]; for(const auto&[name,amt]:info.craftingRequirement.GetItems()){ description+=std::format("{}{} x{} ({})\n", Inventory::GetItemCount(name)0){ description+=std::format("{}Crafting Cost: {} {}", game->GetPlayer()->GetMoney()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{ if(Item::IsBlankStaticCallCounter!=1)ERR("WARNING! You should not call the IsBlank() function directly! Use ISBLANK() macro instead!") Item::IsBlankStaticCallCounter--; return amt==0||it==nullptr; } void Inventory::Clear(ITCategory itemCategory){ std::vector>itemList=get(itemCategory); //We have to make a copy here because RemoveItem() will modify the list provided by get() inline. for(std::shared_ptr&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,itemCategory,uint32_t(itemQuantity)); } } 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(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 EnhancementLevelInfo EnhancementInfo::operator[](int level)const{ return EnhancementLevelInfo{const_cast(enhancementStats.at(level)),const_cast(craftingRequirements.at(level))}; } const std::optionalItemInfo::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 "<=9)ERR("Piece count is invalid! Expecting a value (1-8) but got "<it,EquipSlot slot){ if(!(it.lock()->it->slot&slot))return; EquipSlot equippedSlot=GetSlotEquippedIn(it); if(equippedSlot!=EquipSlot::NONE)UnequipItem(equippedSlot); if(!GetEquip(slot).expired())UnequipItem(slot); Inventory::equipment[slot]=it; game->GetPlayer()->RecalculateEquipStats(); }; void Inventory::UnequipItem(EquipSlot slot){ Inventory::equipment[slot]=Item::BLANK; game->GetPlayer()->RecalculateEquipStats(); }; EquipSlot Inventory::GetSlotEquippedIn(const std::weak_ptrit){ for(int i=int(EquipSlot::HELMET);i<=int(EquipSlot::RING2);i<<=1){ EquipSlot slot=EquipSlot(i); std::weak_ptrequip=GetEquip(slot); if(equip.expired())continue; if(&*equip.lock()==&*it.lock())return slot; } return EquipSlot::NONE; }; std::weak_ptrInventory::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,float 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()){ const ItemAttribute&attr=ItemAttribute::Get(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].stats; } const Stats Item::GetStats()const{ if(it==nullptr)return{}; return it->GetStats(enhancementLevel); }; const size_t EnhancementInfo::size()const{ return enhancementStats.size(); }; const std::optionalItem::ItemSet()const{ return it->ItemSet(); }; const uint8_t Item::EnhancementLevel()const{ return enhancementLevel; }; void Item::EnhanceItem(uint8_t qty){ if(qty==0)ERR("WARNING! You must specify at least 1 item to be enhanced!") enhanceFunctionPrimed.Validate(ActualName(),EnhancementLevel(),qty); for(uint8_t i=0;i"Item.Item Max Enhancement Level"_I)ERR("WARNING! Attempted to enhance "<GetPlayer()->SetMoney(game->GetPlayer()->GetMoney()-consumedResources.GetCost()); }else{ //This is a craftable, so we have to give the player the item they crafted. Inventory::AddItem(ActualName()); const CraftingRequirement&consumedResources=GetEnhancementInfo()[1].craftingRequirement; for(const auto&[name,amt]:consumedResources.GetItems()){ Inventory::RemoveItem(Inventory::GetItem(name)[0],amt); } game->GetPlayer()->SetMoney(game->GetPlayer()->GetMoney()-consumedResources.GetCost()); } } }; const std::vector>&ItemSet::GetSetBonusBreakpoints()const{ return setBonusBreakpoints; }; const std::mapInventory::GetEquippedItemSets(){ std::mapsetCounts; for(int i=int(EquipSlot::HELMET);i<=int(EquipSlot::RING2);i<<=1){ EquipSlot slot=EquipSlot(i); std::weak_ptrequip=Inventory::GetEquip(slot); if(ISBLANK(equip))continue; if(equip.lock()->ItemSet()){ setCounts[*equip.lock()->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"; } } std::string statNumber=std::to_string(int(val)); if(attr.ShowAsDecimal()){ statNumber=std::format("{:.2f}",val); } description+=std::string(attr.Name())+": "+statNumber+(attr.DisplayAsPercent()?"%":""); first=false; } return description; } ItemInfo&ItemInfo::operator[](const IT&item){ if(!ITEM_DATA.count(item))ERR("Item "<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; } const std::weak_ptrInventory::GetInventorySlot(ITCategory itemCategory,size_t slot){ auto GetFirstIndex = [](ITCategory itemCategory,size_t slot){ auto firstIter=std::find_if(get(itemCategory).begin(),get(itemCategory).end(),[&](std::shared_ptritem){ if(item->ActualName()==get(itemCategory).at(slot)->ActualName())return true; return false; }); size_t firstIndex=firstIter-get(itemCategory).begin(); if(firstIter==get(itemCategory).end())ERR(std::format("Invalid slot {} for category {} specified!",slot,itemCategory)); return firstIndex; }; return GetItem(get(itemCategory).at(slot)->ActualName())[slot-GetFirstIndex(itemCategory,slot)]; } bool Item::IsBlank(std::shared_ptritem){ Item::IsBlankStaticCallCounter++; return item->IsBlank(); }; bool Item::IsBlank(std::weak_ptritem){ if(!item.expired())Item::IsBlankStaticCallCounter++;else return true; return item.lock()->IsBlank(); }; const bool Item::_IsBlank()const{ Item::IsBlankStaticCallCounter++; return IsBlank(); }; const bool Item::UseDuringCast()const{ return it->UseDuringCast(); } const bool ItemInfo::UseDuringCast()const{ return useDuringCast; } void Stats::InitializeDamageReductionTable(){ float totalReduction=0; maxDamageReductionTable[0]=0; for(int i=1;i<=1000;i++){ if(i<=10){ totalReduction+=1._Pct; }else if(i<=30){ totalReduction+=0.5_Pct; }else if(i<=70){ totalReduction+=0.25_Pct; }else if(i<=150){ totalReduction+=0.125_Pct; }else if(i<=310){ totalReduction+=0.0625_Pct; }else if(i<=950){ totalReduction+=0.03125_Pct; }else{ totalReduction+=0.1_Pct; } maxDamageReductionTable[i]=totalReduction; } } void EnhancementInfo::SetCraftingRequirements(const int enhanceLevel,const std::vector>&requiredItems,const uint32_t goldCost,const uint8_t availableChapter){ while(craftingRequirements.size()<=enhanceLevel){ craftingRequirements.push_back(CraftingRequirement({},0)); } this->availableChapter=availableChapter; craftingRequirements[enhanceLevel]=CraftingRequirement(requiredItems,goldCost); } EnhancementLevelInfo::EnhancementLevelInfo(const Stats&stats,const CraftingRequirement&craftingRequirement) :stats(stats),craftingRequirement(craftingRequirement){} const bool Item::CanEnhanceItem(uint8_t qty)const{ if(qty==0)ERR("WARNING! Must specify at least 1 for the quantity!") if(ISBLANK(std::make_shared(*this)))return false; if(!GetEnhancementInfo().CanBeEnhanced())return false; if(GetEnhancementInfo().AvailableChapter()GetCurrentChapter())return false; const EnhancementLevelInfo&enhanceInfo=GetEnhancementInfo()[EnhancementLevel()+1]; if(GetEnhancementInfo().size()>2){ //If the item has exactly 2 enhancement levels, then it's an item that can only simply be crafted. Therefore, don't do the max enhancement level check. if(EnhancementLevel()>="Item.Item Max Enhancement Level"_I)return false; } if(game->GetPlayer()->GetMoney()GetEnhancementInfo(); } const bool EnhancementInfo::CanBeEnhanced()const{ return craftingRequirements.size()>0; }; const bool ItemInfo::IsWeapon()const{ return slot==EquipSlot::WEAPON; } const bool ItemInfo::IsArmor()const{ return IsEquippable()&&!IsWeapon()&&!IsAccessory(); } const bool ItemInfo::IsAccessory()const{ return slot==EquipSlot::RING1||slot==EquipSlot::RING2; } const bool Item::IsWeapon()const{ return it->IsWeapon(); } const bool Item::IsArmor()const{ return it->IsArmor(); } const bool Item::IsAccessory()const{ return it->IsAccessory(); } const bool Item::IsCraftable()const{ return it->IsCraftable(); } const bool ItemInfo::IsCraftable()const{ return GetEnhancementInfo().CanBeEnhanced(); } const bool Item::EnhancementIsPossible()const{ return it->IsCraftable(); } const uint8_t EnhancementInfo::AvailableChapter()const{ return availableChapter; } const Stats&Item::RandomStats()const{ return randomizedStats; }; void Item::RandomizeStats(){ randomizedStats=it->RandomizeStats(); }; const Stats ItemInfo::GetMinStats()const{ return minStats; } const Stats ItemInfo::GetMaxStats()const{ return maxStats; } Stats ItemInfo::RandomizeStats(){ Stats randomRolls; for(auto&[attr,minVal]:minStats){ float diff=maxStats.A(attr)-minVal; if(attr.ShowAsDecimal()){ randomRolls.A(attr)=util::random(diff)+minVal; }else{ randomRolls.A(attr)=floor(util::random(diff+1)+minVal); //We must add 1 because we are going to floor the value to prevent the edges from having half as likely probability as all other numbers. } } return randomRolls; } const bool Item::HasRandomizedStats()const{ return randomizedStats.size()>0; }