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.
261 lines
9.1 KiB
261 lines
9.1 KiB
#pragma region License
|
|
/*
|
|
License (OLC-3)
|
|
~~~~~~~~~~~~~~~
|
|
|
|
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
|
|
|
|
Redistribution and use in source and binary forms, with or without modification,
|
|
are permitted provided that the following conditions are met:
|
|
|
|
1. Redistributions or derivations of source code must retain the above copyright
|
|
notice, this list of conditions and the following disclaimer.
|
|
|
|
2. Redistributions or derivative works in binary form must reproduce the above
|
|
copyright notice. This list of conditions and the following disclaimer must be
|
|
reproduced in the documentation and/or other materials provided with the distribution.
|
|
|
|
3. Neither the name of the copyright holder nor the names of its contributors may
|
|
be used to endorse or promote products derived from this software without specific
|
|
prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
|
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
|
|
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
|
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
|
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
SUCH DAMAGE.
|
|
|
|
Portions of this software are copyright © 2024 The FreeType
|
|
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
|
|
All rights reserved.
|
|
*/
|
|
#pragma endregion
|
|
|
|
#include "Menu.h"
|
|
#include "Merchant.h"
|
|
#include "AdventuresInLestoria.h"
|
|
#include "util.h"
|
|
|
|
INCLUDE_game
|
|
INCLUDE_ITEM_CATEGORIES
|
|
|
|
std::map<Chapter,std::vector<Merchant>>Merchant::merchants;
|
|
std::map<ITCategory,std::vector<std::weak_ptr<Item>>>Merchant::sortedItems;
|
|
MerchantFunctionPrimingData Merchant::purchaseFunctionPrimed("CanPurchaseItem()");
|
|
MerchantFunctionPrimingData Merchant::sellFunctionPrimed("CanSellItem()");
|
|
Merchant Merchant::travelingMerchant;
|
|
|
|
const Merchant&Merchant::GetRandomMerchant(Chapter chapter){
|
|
const Merchant&newMerchant=merchants[chapter][util::random()%(merchants[chapter].size())];
|
|
return newMerchant;
|
|
}
|
|
|
|
const std::string&Merchant::GetDisplayName()const{
|
|
return displayName;
|
|
}
|
|
|
|
const std::vector<std::shared_ptr<Item>>&Merchant::GetShopItems()const{
|
|
return shopItems;
|
|
}
|
|
|
|
const std::vector<std::weak_ptr<Item>>&Merchant::GetShopItems(ITCategory category){
|
|
return sortedItems[category];
|
|
}
|
|
|
|
Merchant&Merchant::AddMerchant(Chapter chapter,std::string_view keyName){
|
|
merchants[chapter].push_back({});
|
|
merchants[chapter].back().keyName=keyName;
|
|
return merchants[chapter].back();
|
|
}
|
|
|
|
void Merchant::AddItem(IT item,uint32_t amt,uint8_t enhancementLevel){
|
|
shopItems.push_back(std::make_shared<Item>(amt,item,enhancementLevel));
|
|
UpdateSortedItemsList();
|
|
if(&GetCurrentTravelingMerchant()==this){
|
|
Menu::MerchantInventorySlotsUpdated(ITEM_DATA[item].Category());
|
|
}
|
|
}
|
|
|
|
void Merchant::UpdateSortedItemsList(){
|
|
const Merchant&merchant=GetCurrentTravelingMerchant();
|
|
for(auto&[key,items]:ITEM_CATEGORIES){
|
|
sortedItems[key].clear();
|
|
}
|
|
std::for_each(merchant.shopItems.begin(),merchant.shopItems.end(),[](const std::shared_ptr<Item>item){
|
|
sortedItems[item->Category()].push_back(item);
|
|
});
|
|
}
|
|
|
|
INCLUDE_DATA
|
|
|
|
void Merchant::Initialize(){
|
|
for(int chapter=1;chapter<=6;chapter++){
|
|
std::string merchantChapterFilename="assets/"+"merchant_directory"_S+"Chapter "+std::to_string(chapter)+" Merchants.txt";
|
|
if(!std::filesystem::exists(merchantChapterFilename))ERR("WARNING! Could not find file "<<std::quoted(merchantChapterFilename)<<" for merchant reading!");
|
|
utils::datafile::Read(DATA,merchantChapterFilename);
|
|
}
|
|
for(int chapter=1;chapter<=6;chapter++){
|
|
int merchantCount=0;
|
|
utils::datafile&chapterMerchantInfo=DATA["Merchant"][std::format("Chapter {}",chapter)];
|
|
for(auto&[key,size]:chapterMerchantInfo){
|
|
utils::datafile&data=chapterMerchantInfo.GetProperty(key);
|
|
Merchant&newMerchant=AddMerchant(chapter,key);
|
|
for(int itemNumber=1;auto&[key,size]:data){
|
|
if(key=="DisplayName")newMerchant.displayName=data[key].GetString();
|
|
else
|
|
if(key.starts_with("Item[")){
|
|
std::string itemKey=std::format("Item[{}]",itemNumber);
|
|
if(data.HasProperty(itemKey)){
|
|
IT itemName=data[itemKey].GetString();
|
|
if(data[itemKey].GetValueCount()>1){
|
|
int qty=data[itemKey].GetInt(1);
|
|
newMerchant.AddItem(itemName,qty);
|
|
}else{
|
|
newMerchant.AddItem(itemName,INFINITE);
|
|
}
|
|
}else{
|
|
ERR("Could not find item "<<itemNumber<<" in Merchant "<<merchantCount<<" of Chapter "<<chapter<<"!");
|
|
}
|
|
itemNumber++;
|
|
}else{
|
|
ERR("Unhandled key "<<std::quoted(key)<<" inside of Merchant "<<merchantCount<<" of Chapter "<<chapter<<"!");
|
|
}
|
|
}
|
|
merchantCount++;
|
|
}
|
|
if(merchantCount==0)ERR(std::format("WARNING! No merchants available for Chapter {}!",chapter));
|
|
std::cout<<std::format("Added {} merchants to Chapter {}",merchantCount,chapter)<<std::endl;
|
|
}
|
|
Merchant::RandomizeTravelingMerchant();
|
|
}
|
|
bool Merchant::CanPurchaseItem(IT item,uint32_t amt)const{
|
|
bool itemAvailable=false;
|
|
std::weak_ptr<Item>foundItem;
|
|
for(const std::shared_ptr<Item>it:shopItems){
|
|
if(it==item&&it->Amt()>=amt&&it->CanBePurchased()){
|
|
itemAvailable=true;
|
|
foundItem=it;
|
|
break;
|
|
}
|
|
}
|
|
|
|
purchaseFunctionPrimed.amt=amt;
|
|
purchaseFunctionPrimed.item=item;
|
|
return purchaseFunctionPrimed=
|
|
itemAvailable&&
|
|
!foundItem.expired()&&
|
|
game->GetPlayer()->GetMoney()>=foundItem.lock()->BuyValue()*amt;
|
|
};
|
|
bool Merchant::CanSellItem(std::weak_ptr<Item>item,uint32_t amt)const{
|
|
sellFunctionPrimed.amt=amt;
|
|
sellFunctionPrimed.item=item.lock()->ActualName();
|
|
return sellFunctionPrimed=
|
|
item.lock()->CanBeSold()&&
|
|
item.lock()->Amt()>=amt;
|
|
};
|
|
void Merchant::PurchaseItem(IT item,uint32_t amt){
|
|
purchaseFunctionPrimed.Validate(item,amt);
|
|
|
|
uint32_t totalCost=0U;
|
|
bool finiteItemPurchased=false;
|
|
for(std::shared_ptr<Item>it:shopItems){
|
|
if(it==item){
|
|
if(it->Amt()!=INFINITE){
|
|
it->SetAmt(it->Amt()-amt);
|
|
finiteItemPurchased=true;
|
|
}
|
|
totalCost=it->BuyValue()*amt;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(finiteItemPurchased){
|
|
size_t removeCount=std::erase_if(shopItems,[](std::shared_ptr<Item> it){return it->Amt()==0;});
|
|
if(removeCount>1)ERR("WARNING! Somehow we sold more than one item???")
|
|
if(removeCount){
|
|
UpdateSortedItemsList();
|
|
Menu::MerchantInventorySlotsUpdated(ITEM_DATA[item].Category());
|
|
}
|
|
}
|
|
|
|
Inventory::AddItem(item,amt);
|
|
game->GetPlayer()->SetMoney(game->GetPlayer()->GetMoney()-totalCost);
|
|
};
|
|
void Merchant::SellItem(std::weak_ptr<Item>item,uint32_t amt){
|
|
sellFunctionPrimed.Validate(item.lock()->ActualName(),amt);
|
|
|
|
uint32_t totalCost=0U;
|
|
bool itemFound=false;
|
|
for(std::shared_ptr<Item>it:shopItems){
|
|
if(it==item){
|
|
if(it->Amt()!=INFINITE){
|
|
it->SetAmt(it->Amt()+amt);
|
|
}
|
|
itemFound=true;
|
|
break;
|
|
}
|
|
}
|
|
if(!itemFound&&item.lock()->CanBePurchased()){
|
|
AddItem(item.lock()->ActualName(),amt); //This may not be a feature we include in future versions of the game. For now let's allow it.
|
|
}
|
|
totalCost=item.lock()->SellValue()*amt;
|
|
|
|
//If the item is equipped on our character, remove it.
|
|
EquipSlot equippedItemSlot=Inventory::GetSlotEquippedIn(item);
|
|
if(equippedItemSlot!=EquipSlot::NONE)Inventory::UnequipItem(equippedItemSlot);
|
|
|
|
int foundLoadoutSlot=-1;
|
|
//If the item exists in our loadout, remove it.
|
|
for(int i=0;i<game->GetLoadoutSize();i++){
|
|
if(!ISBLANK(game->GetLoadoutItem(i))&&game->GetLoadoutItem(i).lock()->ActualName()==item.lock()->ActualName()){
|
|
game->ClearLoadoutItem(i);
|
|
if(foundLoadoutSlot!=-1)ERR("WARNING! Found two loadout item slots with the same item! THIS SHOULD NOT BE HAPPENING!")
|
|
foundLoadoutSlot=i;
|
|
}
|
|
}
|
|
|
|
std::string itemName=item.lock()->ActualName(); //We need the item name since our reference to the item is about to be deleted.
|
|
|
|
Inventory::RemoveItem(item,amt);
|
|
game->GetPlayer()->SetMoney(game->GetPlayer()->GetMoney()+totalCost);
|
|
|
|
//If we still have some in our inventory, we'll add them back in.
|
|
if(Inventory::GetItemCount(itemName)>0){
|
|
game->SetLoadoutItem(foundLoadoutSlot,itemName);
|
|
}
|
|
|
|
sellFunctionPrimed=false;
|
|
};
|
|
|
|
void Merchant::RandomizeTravelingMerchant(){
|
|
SetTravelingMerchant(const_cast<Merchant&>(GetRandomMerchant(game->GetCurrentChapter())).GetKeyName());
|
|
};
|
|
Merchant&Merchant::GetCurrentTravelingMerchant(){
|
|
return travelingMerchant;
|
|
};
|
|
|
|
std::string_view Merchant::GetKeyName()const{
|
|
return keyName;
|
|
}
|
|
|
|
void Merchant::SetTravelingMerchant(std::string_view key){
|
|
for(auto&[merchantKey,merchantList]:merchants){
|
|
auto it=std::find_if(merchantList.begin(),merchantList.end(),[&](const Merchant&merchant){return merchant.GetKeyName()==key;});
|
|
if(it!=merchantList.end()){
|
|
travelingMerchant=*it;
|
|
for(auto&[key,items]:ITEM_CATEGORIES){
|
|
Menu::MerchantInventorySlotsUpdated(key);
|
|
}
|
|
UpdateSortedItemsList();
|
|
return;
|
|
}
|
|
}
|
|
std::cout<<std::format("WARNING! Could not set traveling merchant with key {}!",std::string(key))<<std::endl;
|
|
std::cout<<"Falling back to a randomized merchant."<<std::endl;
|
|
RandomizeTravelingMerchant();
|
|
} |