# 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 ) ) ;
LOG ( std : : format ( " Added {} merchants to Chapter {} " , merchantCount , chapter ) ) ;
}
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 ( ) - > RemoveMoney ( 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 ( ) - > AddMoney ( totalCost ) ;
//If we still have some in our inventory, we'll add them back in.
if ( foundLoadoutSlot ! = - 1 & & 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 ;
}
}
LOG ( std : : format ( " WARNING! Could not set traveling merchant with key {}! " , std : : string ( key ) ) ) ;
LOG ( " Falling back to a randomized merchant. " ) ;
RandomizeTravelingMerchant ( ) ;
}