From 91df00a242f176f074eceba79cc7133a8be8ea6f Mon Sep 17 00:00:00 2001 From: Aaron Kimbre Date: Tue, 25 Jul 2023 21:35:45 -0500 Subject: [PATCH] refactor: Vendor inventory loading Implement proper delta compression dynamically determine multicostitems and standard cost items Quatantine max's custom code --- dGame/dComponents/VendorComponent.cpp | 164 +++++++++++++------------- dGame/dComponents/VendorComponent.h | 120 +++++++------------ dGame/dGameMessages/GameMessages.cpp | 14 +-- 3 files changed, 133 insertions(+), 165 deletions(-) diff --git a/dGame/dComponents/VendorComponent.cpp b/dGame/dComponents/VendorComponent.cpp index e89cc926..f7d77f48 100644 --- a/dGame/dComponents/VendorComponent.cpp +++ b/dGame/dComponents/VendorComponent.cpp @@ -1,26 +1,27 @@ #include "VendorComponent.h" - -#include - +#include "BitStream.h" #include "Game.h" #include "dServer.h" - #include "CDComponentsRegistryTable.h" #include "CDVendorComponentTable.h" #include "CDLootMatrixTable.h" #include "CDLootTableTable.h" +#include "CDItemComponentTable.h" VendorComponent::VendorComponent(Entity* parent) : Component(parent) { + m_HasStandardCostItems = false; + m_HasMultiCostItems = false; SetupConstants(); RefreshInventory(true); } -VendorComponent::~VendorComponent() = default; - void VendorComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) { - outBitStream->Write1(); - outBitStream->Write1(); // Has standard items (Required for vendors with missions.) - outBitStream->Write(HasCraftingStation()); // Has multi use items + outBitStream->Write(bIsInitialUpdate || m_DirtyVendor); + if (bIsInitialUpdate || m_DirtyVendor) { + outBitStream->Write(m_HasStandardCostItems); + outBitStream->Write(m_HasMultiCostItems); + if (!bIsInitialUpdate) m_DirtyVendor = false; + } } void VendorComponent::OnUse(Entity* originator) { @@ -28,97 +29,68 @@ void VendorComponent::OnUse(Entity* originator) { GameMessages::SendVendorStatusUpdate(m_Parent, originator->GetSystemAddress()); } -float VendorComponent::GetBuyScalar() const { - return m_BuyScalar; -} - -float VendorComponent::GetSellScalar() const { - return m_SellScalar; -} - -void VendorComponent::SetBuyScalar(float value) { - m_BuyScalar = value; -} - -void VendorComponent::SetSellScalar(float value) { - m_SellScalar = value; -} - -std::map& VendorComponent::GetInventory() { - return m_Inventory; -} - -bool VendorComponent::HasCraftingStation() { - // As far as we know, only Umami has a crafting station - return m_Parent->GetLOT() == 13800; -} - void VendorComponent::RefreshInventory(bool isCreation) { - //Custom code for Max vanity NPC - if (m_Parent->GetLOT() == 9749 && Game::server->GetZoneID() == 1201) { - if (!isCreation) return; - m_Inventory.insert({ 11909, 0 }); //Top hat w frog - m_Inventory.insert({ 7785, 0 }); //Flash bulb - m_Inventory.insert({ 12764, 0 }); //Big fountain soda - m_Inventory.insert({ 12241, 0 }); //Hot cocoa (from fb) - return; - } + SetHasStandardCostItems(false); + SetHasMultiCostItems(false); + + // Custom code for Max vanity NPC and Mr.Ree cameras + MaxCustomVendorRefreshInventory(isCreation); + if(m_Parent->GetLOT() == 9749 && Game::server->GetZoneID() == 1201) return; + m_Inventory.clear(); auto* lootMatrixTable = CDClientManager::Instance().GetTable(); - std::vector lootMatrices = lootMatrixTable->Query([=](CDLootMatrix entry) { return (entry.LootMatrixIndex == m_LootMatrixID); }); + const auto lootMatrices = lootMatrixTable->Query([=](CDLootMatrix entry) { return (entry.LootMatrixIndex == m_LootMatrixID); }); if (lootMatrices.empty()) return; - // Done with lootMatrix table auto* lootTableTable = CDClientManager::Instance().GetTable(); + auto* itemComponentTable = CDClientManager::Instance().GetTable(); + auto* compRegistryTable = CDClientManager::Instance().GetTable(); for (const auto& lootMatrix : lootMatrices) { int lootTableID = lootMatrix.LootTableIndex; - std::vector vendorItems = lootTableTable->Query([=](CDLootTable entry) { return (entry.LootTableIndex == lootTableID); }); + auto vendorItems = lootTableTable->Query([=](CDLootTable entry) { return (entry.LootTableIndex == lootTableID); }); if (lootMatrix.maxToDrop == 0 || lootMatrix.minToDrop == 0) { - for (CDLootTable item : vendorItems) { - m_Inventory.insert({ item.itemid, item.sortPriority }); + for (const auto& item : vendorItems) { + if (!m_HasStandardCostItems || !m_HasMultiCostItems) { + auto itemComponentID = compRegistryTable->GetByIDAndType(item.itemid, eReplicaComponentType::ITEM); + 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)); } } else { auto randomCount = GeneralUtils::GenerateRandomNumber(lootMatrix.minToDrop, lootMatrix.maxToDrop); for (size_t i = 0; i < randomCount; i++) { if (vendorItems.empty()) break; - auto randomItemIndex = GeneralUtils::GenerateRandomNumber(0, vendorItems.size() - 1); - - const auto& randomItem = vendorItems[randomItemIndex]; - + const auto& randomItem = vendorItems.at(randomItemIndex); vendorItems.erase(vendorItems.begin() + randomItemIndex); - - m_Inventory.insert({ randomItem.itemid, randomItem.sortPriority }); + if (!m_HasStandardCostItems || !m_HasMultiCostItems) { + auto itemComponentID = compRegistryTable->GetByIDAndType(randomItem.itemid, eReplicaComponentType::ITEM, -1); + if (itemComponentID == -1) { + Game::logger->Log("VendorComponent", "Attempted to add item %i with ItemComponent ID -1 to vendor %i inventory. Not adding item!", itemComponentID, m_Parent->GetLOT()); + 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)); } } } - //Because I want a vendor to sell these cameras - if (m_Parent->GetLOT() == 13569) { - auto randomCamera = GeneralUtils::GenerateRandomNumber(0, 2); - - switch (randomCamera) { - case 0: - m_Inventory.insert({ 16253, 0 }); //Grungagroid - break; - case 1: - m_Inventory.insert({ 16254, 0 }); //Hipstabrick - break; - case 2: - m_Inventory.insert({ 16204, 0 }); //Megabrixel snapshot - break; - default: - break; - } - } - // Callback timer to refresh this inventory. - m_Parent->AddCallbackTimer(m_RefreshTimeSeconds, [this]() { - RefreshInventory(); - }); + if (m_RefreshTimeSeconds > 0.0) { + m_Parent->AddCallbackTimer(m_RefreshTimeSeconds, [this]() { + RefreshInventory(); + } + ); + } + Game::entityManager->SerializeEntity(m_Parent); GameMessages::SendVendorStatusUpdate(m_Parent, UNASSIGNED_SYSTEM_ADDRESS); } @@ -129,12 +101,40 @@ void VendorComponent::SetupConstants() { auto* vendorComponentTable = CDClientManager::Instance().GetTable(); std::vector vendorComps = vendorComponentTable->Query([=](CDVendorComponent entry) { return (entry.id == componentID); }); if (vendorComps.empty()) return; - m_BuyScalar = vendorComps[0].buyScalar; - m_SellScalar = vendorComps[0].sellScalar; - m_RefreshTimeSeconds = vendorComps[0].refreshTimeSeconds; - m_LootMatrixID = vendorComps[0].LootMatrixIndex; + auto vendorData = vendorComps.at(0); + m_BuyScalar = vendorData.buyScalar; + m_SellScalar = vendorData.sellScalar; + m_RefreshTimeSeconds = vendorData.refreshTimeSeconds; + m_LootMatrixID = vendorData.LootMatrixIndex; } -bool VendorComponent::SellsItem(const LOT item) const { - return m_Inventory.find(item) != m_Inventory.end(); + +void VendorComponent::MaxCustomVendorRefreshInventory(bool isCreation){ + if(isCreation && m_Parent->GetLOT() == 9749 && Game::server->GetZoneID() == 1201) { + 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) + return; + } + + if (m_Parent->GetLOT() == 13569) { + auto randomCamera = GeneralUtils::GenerateRandomNumber(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)); + } } diff --git a/dGame/dComponents/VendorComponent.h b/dGame/dComponents/VendorComponent.h index cbff0cfd..f5a66af7 100644 --- a/dGame/dComponents/VendorComponent.h +++ b/dGame/dComponents/VendorComponent.h @@ -2,6 +2,7 @@ #ifndef VENDORCOMPONENT_H #define VENDORCOMPONENT_H +#include #include "CDClientManager.h" #include "Component.h" #include "Entity.h" @@ -9,91 +10,58 @@ #include "RakNetTypes.h" #include "eReplicaComponentType.h" -/** - * A component for vendor NPCs. A vendor sells items to the player. - */ +struct SoldItem { + SoldItem(const LOT lot, const int32_t sortPriority) { + this->lot = lot; + this->sortPriority = sortPriority; + }; + LOT lot = 0; + int32_t sortPriority = 0; +}; + class VendorComponent : public Component { public: - static const eReplicaComponentType ComponentType = eReplicaComponentType::VENDOR; - + inline static const eReplicaComponentType ComponentType = eReplicaComponentType::VENDOR; VendorComponent(Entity* parent); - ~VendorComponent() override; - void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags); - void OnUse(Entity* originator) override; - - /** - * Gets the buy scaler - * @return the buy scaler - */ - float GetBuyScalar() const; - - /** - * Sets the buy scalar. - * @param value the new value. - */ - void SetBuyScalar(float value); - - /** - * Gets the buy scaler - * @return the buy scaler - */ - float GetSellScalar() const; - - /** - * Sets the sell scalar. - * @param value the new value. - */ - void SetSellScalar(float value); - - /** - * True if the NPC LOT is 13800, the only NPC with a crafting station. - */ - bool HasCraftingStation(); - - /** - * Gets the list if items the vendor sells. - * @return the list of items. - */ - std::map& GetInventory(); - - /** - * Refresh the inventory of this vendor. - */ void RefreshInventory(bool isCreation = false); - - /** - * Called on startup of vendor to setup the variables for the component. - */ void SetupConstants(); + float GetBuyScalar() const { return m_BuyScalar; } + float GetSellScalar() const { return m_SellScalar; } + void SetBuyScalar(const float value) { m_BuyScalar = value; } + void SetSellScalar(const float value) { m_SellScalar = value; } - bool SellsItem(const LOT item) const; + std::vector& GetInventory() { return m_Inventory; } + + void SetHasMultiCostItems(const bool hasMultiCostItems) { + if (m_HasMultiCostItems == hasMultiCostItems) return; + m_HasMultiCostItems = hasMultiCostItems; + m_DirtyVendor = true; + } + + void SetHasStandardCostItems(const bool hasStandardCostItems) { + if (m_HasStandardCostItems == hasStandardCostItems) return; + m_HasStandardCostItems = hasStandardCostItems; + m_DirtyVendor = true; + } + + bool SellsItem(const LOT item) const { + return std::count_if(m_Inventory.begin(), m_Inventory.end(), [item](const SoldItem& lhs) { + return lhs.lot == item; + }) > 0; + } + + void MaxCustomVendorRefreshInventory(bool isCreation = false); private: - /** - * The buy scalar. - */ - float m_BuyScalar; - - /** - * The sell scalar. - */ - float m_SellScalar; - - /** - * The refresh time of this vendors' inventory. - */ - float m_RefreshTimeSeconds; - - /** - * Loot matrix id of this vendor. - */ - uint32_t m_LootMatrixID; - - /** - * The list of items the vendor sells. - */ - std::map m_Inventory; + float m_BuyScalar = 0.0f; + float m_SellScalar = 0.0f; + float m_RefreshTimeSeconds = 0.0f; + uint32_t m_LootMatrixID = 0; + std::vector m_Inventory; + bool m_DirtyVendor = false; + bool m_HasStandardCostItems = false; + bool m_HasMultiCostItems = false; }; #endif // VENDORCOMPONENT_H diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 8d8085a9..ab95ae49 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -1286,21 +1286,21 @@ void GameMessages::SendVendorStatusUpdate(Entity* entity, const SystemAddress& s VendorComponent* vendor = static_cast(entity->GetComponent(eReplicaComponentType::VENDOR)); if (!vendor) return; - std::map vendorItems = vendor->GetInventory(); + auto vendorItems = vendor->GetInventory(); bitStream.Write(entity->GetObjectID()); bitStream.Write(eGameMessageType::VENDOR_STATUS_UPDATE); bitStream.Write(bUpdateOnly); - bitStream.Write(static_cast(vendorItems.size())); + bitStream.Write(vendorItems.size()); - for (std::pair item : vendorItems) { - bitStream.Write(static_cast(item.first)); - bitStream.Write(static_cast(item.second)); + for (auto item : vendorItems) { + bitStream.Write(item.lot); + bitStream.Write(item.sortPriority); } - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST - SEND_PACKET; + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; } void GameMessages::SendVendorTransactionResult(Entity* entity, const SystemAddress& sysAddr) {