2021-12-05 17:54:36 +00:00
# include "VendorComponent.h"
2023-08-03 10:51:52 +00:00
# include "BitStream.h"
2022-04-26 10:41:16 +00:00
# include "dServer.h"
2023-08-03 10:51:52 +00:00
# include "dZoneManager.h"
# include "WorldConfig.h"
2023-03-17 14:36:21 +00:00
# include "CDComponentsRegistryTable.h"
# include "CDVendorComponentTable.h"
# include "CDLootMatrixTable.h"
# include "CDLootTableTable.h"
2023-08-03 10:51:52 +00:00
# include "CDItemComponentTable.h"
2024-02-25 07:47:05 +00:00
# include "InventoryComponent.h"
# include "Character.h"
# include "eVendorTransactionResult.h"
# include "UserManager.h"
# include "CheatDetection.h"
2023-03-17 14:36:21 +00:00
2022-04-26 10:41:16 +00:00
VendorComponent : : VendorComponent ( Entity * parent ) : Component ( parent ) {
2023-08-03 10:51:52 +00:00
m_HasStandardCostItems = false ;
m_HasMultiCostItems = false ;
2022-04-26 10:41:16 +00:00
SetupConstants ( ) ;
RefreshInventory ( true ) ;
2021-12-05 17:54:36 +00:00
}
2024-02-27 07:25:44 +00:00
void VendorComponent : : Serialize ( RakNet : : BitStream & outBitStream , bool bIsInitialUpdate ) {
outBitStream . Write ( bIsInitialUpdate | | m_DirtyVendor ) ;
2023-08-03 10:51:52 +00:00
if ( bIsInitialUpdate | | m_DirtyVendor ) {
2024-02-27 07:25:44 +00:00
outBitStream . Write ( m_HasStandardCostItems ) ;
outBitStream . Write ( m_HasMultiCostItems ) ;
2023-08-03 10:51:52 +00:00
if ( ! bIsInitialUpdate ) m_DirtyVendor = false ;
}
2021-12-05 17:54:36 +00:00
}
void VendorComponent : : OnUse ( Entity * originator ) {
2022-04-26 10:41:16 +00:00
GameMessages : : SendVendorOpenWindow ( m_Parent , originator - > GetSystemAddress ( ) ) ;
GameMessages : : SendVendorStatusUpdate ( m_Parent , originator - > GetSystemAddress ( ) ) ;
2021-12-05 17:54:36 +00:00
}
2022-04-26 10:41:16 +00:00
void VendorComponent : : RefreshInventory ( bool isCreation ) {
2023-08-03 10:51:52 +00:00
SetHasStandardCostItems ( false ) ;
SetHasMultiCostItems ( false ) ;
m_Inventory . clear ( ) ;
// Custom code for Max vanity NPC and Mr.Ree cameras
if ( isCreation & & m_Parent - > GetLOT ( ) = = 9749 & & Game : : server - > GetZoneID ( ) = = 1201 ) {
SetupMaxCustomVendor ( ) ;
2022-04-26 10:41:16 +00:00
return ;
}
2023-08-03 10:51:52 +00:00
2024-02-09 05:40:43 +00:00
auto * lootMatrixTable = CDClientManager : : GetTable < CDLootMatrixTable > ( ) ;
2023-10-09 20:33:22 +00:00
const auto & lootMatrices = lootMatrixTable - > GetMatrix ( m_LootMatrixID ) ;
2022-04-26 10:41:16 +00:00
if ( lootMatrices . empty ( ) ) return ;
2024-02-09 05:40:43 +00:00
auto * lootTableTable = CDClientManager : : GetTable < CDLootTableTable > ( ) ;
auto * itemComponentTable = CDClientManager : : GetTable < CDItemComponentTable > ( ) ;
auto * compRegistryTable = CDClientManager : : GetTable < CDComponentsRegistryTable > ( ) ;
2022-04-26 10:41:16 +00:00
for ( const auto & lootMatrix : lootMatrices ) {
2023-10-09 20:33:22 +00:00
auto vendorItems = lootTableTable - > GetTable ( lootMatrix . LootTableIndex ) ;
2022-04-26 10:41:16 +00:00
if ( lootMatrix . maxToDrop = = 0 | | lootMatrix . minToDrop = = 0 ) {
2023-08-03 10:51:52 +00:00
for ( const auto & item : vendorItems ) {
if ( ! m_HasStandardCostItems | | ! m_HasMultiCostItems ) {
auto itemComponentID = compRegistryTable - > GetByIDAndType ( item . itemid , eReplicaComponentType : : ITEM , - 1 ) ;
if ( itemComponentID = = - 1 ) {
2023-10-21 23:31:55 +00:00
LOG ( " Attempted to add item %i with ItemComponent ID -1 to vendor %i inventory. Not adding item! " , itemComponentID , m_Parent - > GetLOT ( ) ) ;
2023-08-03 10:51:52 +00:00
continue ;
}
auto itemComponent = itemComponentTable - > GetItemComponentByID ( itemComponentID ) ;
if ( ! m_HasStandardCostItems & & itemComponent . baseValue ! = - 1 ) SetHasStandardCostItems ( true ) ;
if ( ! m_HasMultiCostItems & & ! itemComponent . currencyCosts . empty ( ) ) SetHasMultiCostItems ( true ) ;
}
m_Inventory . push_back ( SoldItem ( item . itemid , item . sortPriority ) ) ;
2022-04-26 10:41:16 +00:00
}
} else {
auto randomCount = GeneralUtils : : GenerateRandomNumber < int32_t > ( lootMatrix . minToDrop , lootMatrix . maxToDrop ) ;
for ( size_t i = 0 ; i < randomCount ; i + + ) {
if ( vendorItems . empty ( ) ) break ;
auto randomItemIndex = GeneralUtils : : GenerateRandomNumber < int32_t > ( 0 , vendorItems . size ( ) - 1 ) ;
2023-08-03 10:51:52 +00:00
const auto & randomItem = vendorItems . at ( randomItemIndex ) ;
2022-04-26 10:41:16 +00:00
vendorItems . erase ( vendorItems . begin ( ) + randomItemIndex ) ;
2023-08-03 10:51:52 +00:00
if ( ! m_HasStandardCostItems | | ! m_HasMultiCostItems ) {
auto itemComponentID = compRegistryTable - > GetByIDAndType ( randomItem . itemid , eReplicaComponentType : : ITEM , - 1 ) ;
if ( itemComponentID = = - 1 ) {
2023-10-21 23:31:55 +00:00
LOG ( " Attempted to add item %i with ItemComponent ID -1 to vendor %i inventory. Not adding item! " , itemComponentID , m_Parent - > GetLOT ( ) ) ;
2023-08-03 10:51:52 +00:00
continue ;
}
auto itemComponent = itemComponentTable - > GetItemComponentByID ( itemComponentID ) ;
if ( ! m_HasStandardCostItems & & itemComponent . baseValue ! = - 1 ) SetHasStandardCostItems ( true ) ;
if ( ! m_HasMultiCostItems & & ! itemComponent . currencyCosts . empty ( ) ) SetHasMultiCostItems ( true ) ;
}
m_Inventory . push_back ( SoldItem ( randomItem . itemid , randomItem . sortPriority ) ) ;
2022-04-26 10:41:16 +00:00
}
}
}
2023-08-03 10:51:52 +00:00
HandleMrReeCameras ( ) ;
2022-04-26 10:41:16 +00:00
// Callback timer to refresh this inventory.
2023-08-03 10:51:52 +00:00
if ( m_RefreshTimeSeconds > 0.0 ) {
m_Parent - > AddCallbackTimer ( m_RefreshTimeSeconds , [ this ] ( ) {
RefreshInventory ( ) ;
2022-04-26 10:41:16 +00:00
} ) ;
2023-08-03 10:51:52 +00:00
}
Game : : entityManager - > SerializeEntity ( m_Parent ) ;
2022-04-27 08:35:46 +00:00
GameMessages : : SendVendorStatusUpdate ( m_Parent , UNASSIGNED_SYSTEM_ADDRESS ) ;
2021-12-05 17:54:36 +00:00
}
2022-04-26 10:41:16 +00:00
void VendorComponent : : SetupConstants ( ) {
2024-02-09 05:40:43 +00:00
auto * compRegistryTable = CDClientManager : : GetTable < CDComponentsRegistryTable > ( ) ;
2023-03-04 07:16:37 +00:00
int componentID = compRegistryTable - > GetByIDAndType ( m_Parent - > GetLOT ( ) , eReplicaComponentType : : VENDOR ) ;
2022-04-26 10:41:16 +00:00
2024-02-09 05:40:43 +00:00
auto * vendorComponentTable = CDClientManager : : GetTable < CDVendorComponentTable > ( ) ;
2022-04-26 10:41:16 +00:00
std : : vector < CDVendorComponent > vendorComps = vendorComponentTable - > Query ( [ = ] ( CDVendorComponent entry ) { return ( entry . id = = componentID ) ; } ) ;
if ( vendorComps . empty ( ) ) return ;
2023-08-03 10:51:52 +00:00
auto vendorData = vendorComps . at ( 0 ) ;
if ( vendorData . buyScalar = = 0.0 ) m_BuyScalar = Game : : zoneManager - > GetWorldConfig ( ) - > vendorBuyMultiplier ;
else m_BuyScalar = vendorData . buyScalar ;
m_SellScalar = vendorData . sellScalar ;
m_RefreshTimeSeconds = vendorData . refreshTimeSeconds ;
m_LootMatrixID = vendorData . LootMatrixIndex ;
2022-04-26 10:41:16 +00:00
}
2023-06-03 07:40:46 +00:00
bool VendorComponent : : SellsItem ( const LOT item ) const {
2023-08-03 10:51:52 +00:00
return std : : count_if ( m_Inventory . begin ( ) , m_Inventory . end ( ) , [ item ] ( const SoldItem & lhs ) {
return lhs . lot = = item ;
} ) > 0 ;
}
void VendorComponent : : SetupMaxCustomVendor ( ) {
SetHasStandardCostItems ( true ) ;
m_Inventory . push_back ( SoldItem ( 11909 , 0 ) ) ; // Top hat w frog
m_Inventory . push_back ( SoldItem ( 7785 , 0 ) ) ; // Flash bulb
m_Inventory . push_back ( SoldItem ( 12764 , 0 ) ) ; // Big fountain soda
m_Inventory . push_back ( SoldItem ( 12241 , 0 ) ) ; // Hot cocoa (from fb)
}
void VendorComponent : : HandleMrReeCameras ( ) {
if ( m_Parent - > GetLOT ( ) = = 13569 ) {
SetHasStandardCostItems ( true ) ;
auto randomCamera = GeneralUtils : : GenerateRandomNumber < int32_t > ( 0 , 2 ) ;
LOT camera = 0 ;
DluAssert ( randomCamera > = 0 & & randomCamera < = 2 ) ;
switch ( randomCamera ) {
case 0 :
camera = 16253 ; // Grungagroid
break ;
case 1 :
camera = 16254 ; // Hipstabrick
break ;
case 2 :
camera = 16204 ; // Megabrixel snapshot
break ;
}
m_Inventory . push_back ( SoldItem ( camera , 0 ) ) ;
}
2023-06-03 07:40:46 +00:00
}
2024-02-25 07:47:05 +00:00
void VendorComponent : : Buy ( Entity * buyer , LOT lot , uint32_t count ) {
if ( ! SellsItem ( lot ) ) {
auto * user = UserManager : : Instance ( ) - > GetUser ( buyer - > GetSystemAddress ( ) ) ;
CheatDetection : : ReportCheat ( user , buyer - > GetSystemAddress ( ) , " Attempted to buy item %i from achievement vendor %i that is not purchasable " , lot , m_Parent - > GetLOT ( ) ) ;
GameMessages : : SendVendorTransactionResult ( buyer , buyer - > GetSystemAddress ( ) , eVendorTransactionResult : : PURCHASE_FAIL ) ;
return ;
}
auto * inventoryComponent = buyer - > GetComponent < InventoryComponent > ( ) ;
if ( ! inventoryComponent ) {
GameMessages : : SendVendorTransactionResult ( buyer , buyer - > GetSystemAddress ( ) , eVendorTransactionResult : : PURCHASE_FAIL ) ;
return ;
}
CDComponentsRegistryTable * compRegistryTable = CDClientManager : : GetTable < CDComponentsRegistryTable > ( ) ;
CDItemComponentTable * itemComponentTable = CDClientManager : : GetTable < CDItemComponentTable > ( ) ;
int itemCompID = compRegistryTable - > GetByIDAndType ( lot , eReplicaComponentType : : ITEM ) ;
CDItemComponent itemComp = itemComponentTable - > GetItemComponentByID ( itemCompID ) ;
// Extra currency that needs to be deducted in case of crafting
auto craftingCurrencies = CDItemComponentTable : : ParseCraftingCurrencies ( itemComp ) ;
for ( const auto & [ crafintCurrencyLOT , crafintCurrencyCount ] : craftingCurrencies ) {
if ( inventoryComponent - > GetLotCount ( crafintCurrencyLOT ) < ( crafintCurrencyCount * count ) ) {
GameMessages : : SendVendorTransactionResult ( buyer , buyer - > GetSystemAddress ( ) , eVendorTransactionResult : : PURCHASE_FAIL ) ;
return ;
}
}
for ( const auto & [ crafintCurrencyLOT , crafintCurrencyCount ] : craftingCurrencies ) {
inventoryComponent - > RemoveItem ( crafintCurrencyLOT , crafintCurrencyCount * count ) ;
}
float buyScalar = GetBuyScalar ( ) ;
const auto coinCost = static_cast < uint32_t > ( std : : floor ( ( itemComp . baseValue * buyScalar ) * count ) ) ;
Character * character = buyer - > GetCharacter ( ) ;
if ( ! character | | character - > GetCoins ( ) < coinCost ) {
GameMessages : : SendVendorTransactionResult ( buyer , buyer - > GetSystemAddress ( ) , eVendorTransactionResult : : PURCHASE_FAIL ) ;
return ;
}
if ( Inventory : : IsValidItem ( itemComp . currencyLOT ) ) {
const uint32_t altCurrencyCost = std : : floor ( itemComp . altCurrencyCost * buyScalar ) * count ;
if ( inventoryComponent - > GetLotCount ( itemComp . currencyLOT ) < altCurrencyCost ) {
GameMessages : : SendVendorTransactionResult ( buyer , buyer - > GetSystemAddress ( ) , eVendorTransactionResult : : PURCHASE_FAIL ) ;
return ;
}
inventoryComponent - > RemoveItem ( itemComp . currencyLOT , altCurrencyCost ) ;
}
character - > SetCoins ( character - > GetCoins ( ) - ( coinCost ) , eLootSourceType : : VENDOR ) ;
inventoryComponent - > AddItem ( lot , count , eLootSourceType : : VENDOR ) ;
GameMessages : : SendVendorTransactionResult ( buyer , buyer - > GetSystemAddress ( ) , eVendorTransactionResult : : PURCHASE_SUCCESS ) ;
}