From 5afeb265cdbefffa8fce46675a1073fbaf25f448 Mon Sep 17 00:00:00 2001 From: EmosewaMC <39972741+EmosewaMC@users.noreply.github.com> Date: Tue, 26 Apr 2022 03:41:16 -0700 Subject: [PATCH] Updated vendor component Fixed a few issues in VendorComponent. - Corrected serialization to only happen on construction. - Added functionality to refresh the vendor based on info from the vendor component table - some whitespaceing inconsistencies. - Sorted includes. Tested the vendor in Nimbus Station and when the player re-enters the world, the vendor inventory refreshes, as opposed to previously where the world would need to reset in order to refresh the inventory. --- dGame/dComponents/VendorComponent.cpp | 187 ++++++++++++++------------ dGame/dComponents/VendorComponent.h | 30 ++++- dGame/dGameMessages/GameMessages.cpp | 6 +- dGame/dGameMessages/GameMessages.h | 2 +- dNet/dMessageIdentifiers.h | 1 + 5 files changed, 133 insertions(+), 93 deletions(-) diff --git a/dGame/dComponents/VendorComponent.cpp b/dGame/dComponents/VendorComponent.cpp index b4346bb7..4dd88e71 100644 --- a/dGame/dComponents/VendorComponent.cpp +++ b/dGame/dComponents/VendorComponent.cpp @@ -1,116 +1,135 @@ #include "VendorComponent.h" -#include "Game.h" -#include "dServer.h" #include +#include "Game.h" +#include "dServer.h" + VendorComponent::VendorComponent(Entity* parent) : Component(parent) { - auto* compRegistryTable = CDClientManager::Instance()->GetTable("ComponentsRegistry"); - auto* vendorComponentTable = CDClientManager::Instance()->GetTable("VendorComponent"); - auto* lootMatrixTable = CDClientManager::Instance()->GetTable("LootMatrix"); - auto* lootTableTable = CDClientManager::Instance()->GetTable("LootTable"); - - int componentID = compRegistryTable->GetByIDAndType(m_Parent->GetLOT(), COMPONENT_TYPE_VENDOR); - 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; - int lootMatrixID = vendorComps[0].LootMatrixIndex; - std::vector lootMatrices = lootMatrixTable->Query([=](CDLootMatrix entry) { return (entry.LootMatrixIndex == lootMatrixID); }); - if (lootMatrices.empty()) { - return; - } - for (const auto& lootMatrix : lootMatrices) { - int lootTableID = lootMatrix.LootTableIndex; - std::vector 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}); - } - } 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]; - - vendorItems.erase(vendorItems.begin() + randomItemIndex); - - m_Inventory.insert({randomItem.itemid, randomItem.sortPriority}); - } - } - } - - //Because I want a vendor to sell these cameras - if (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; - } - } - - //Custom code for Max vanity NPC - if (parent->GetLOT() == 9749 && Game::server->GetZoneID() == 1201) { - m_Inventory.clear(); - 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) - } + SetupConstants(); + RefreshInventory(true); } VendorComponent::~VendorComponent() = default; void VendorComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) { - outBitStream->Write1(); - outBitStream->Write1(); // this bit is REQUIRED for vendor + mission multiinteract - outBitStream->Write(HasCraftingStation()); + // Only serialize this entity on construction. + // [bool] hasVendorInfo + // [bool] hasStandardItems (always true?) + // [bool] hasMulticostItems (only true for umami with their cooking for now.) + if (!bIsInitialUpdate) return; + outBitStream->Write1(); + outBitStream->Write1(); + outBitStream->Write(HasCraftingStation()); } void VendorComponent::OnUse(Entity* originator) { - GameMessages::SendVendorOpenWindow(m_Parent, originator->GetSystemAddress()); - GameMessages::SendVendorStatusUpdate(m_Parent, originator->GetSystemAddress()); + GameMessages::SendVendorOpenWindow(m_Parent, originator->GetSystemAddress()); + GameMessages::SendVendorStatusUpdate(m_Parent, originator->GetSystemAddress()); } float VendorComponent::GetBuyScalar() const { - return m_BuyScalar; + return m_BuyScalar; } float VendorComponent::GetSellScalar() const { - return m_SellScalar; + return m_SellScalar; } void VendorComponent::SetBuyScalar(float value) { - m_BuyScalar = value; + m_BuyScalar = value; } void VendorComponent::SetSellScalar(float value) { - m_SellScalar = value; + m_SellScalar = value; } std::map& VendorComponent::GetInventory() { - return m_Inventory; + return m_Inventory; } bool VendorComponent::HasCraftingStation() { - // As far as we know, only Umami has a crafting station - return m_Parent->GetLOT() == 13800; + // 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; + } + m_Inventory.clear(); + auto* lootMatrixTable = CDClientManager::Instance()->GetTable("LootMatrix"); + std::vector lootMatrices = lootMatrixTable->Query([=](CDLootMatrix entry) { return (entry.LootMatrixIndex == m_LootMatrixID); }); + + if (lootMatrices.empty()) return; + // Done with lootMatrix table + + auto* lootTableTable = CDClientManager::Instance()->GetTable("LootTable"); + + for (const auto& lootMatrix : lootMatrices) { + int lootTableID = lootMatrix.LootTableIndex; + std::vector 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}); + } + } 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]; + + vendorItems.erase(vendorItems.begin() + randomItemIndex); + + m_Inventory.insert({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(); + }); +} + +void VendorComponent::SetupConstants() { + auto* compRegistryTable = CDClientManager::Instance()->GetTable("ComponentsRegistry"); + int componentID = compRegistryTable->GetByIDAndType(m_Parent->GetLOT(), COMPONENT_TYPE_VENDOR); + + auto* vendorComponentTable = CDClientManager::Instance()->GetTable("VendorComponent"); + 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; +} \ No newline at end of file diff --git a/dGame/dComponents/VendorComponent.h b/dGame/dComponents/VendorComponent.h index fb9dbf6b..c037d875 100644 --- a/dGame/dComponents/VendorComponent.h +++ b/dGame/dComponents/VendorComponent.h @@ -1,11 +1,12 @@ +#pragma once #ifndef VENDORCOMPONENT_H #define VENDORCOMPONENT_H -#include "RakNetTypes.h" -#include "Entity.h" -#include "GameMessages.h" #include "CDClientManager.h" #include "Component.h" +#include "Entity.h" +#include "GameMessages.h" +#include "RakNetTypes.h" /** * A component for vendor NPCs. A vendor sells items to the player. @@ -56,17 +57,36 @@ public: */ 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(); private: /** - * The buy scaler. + * The buy scalar. */ float m_BuyScalar; /** - * The sell scaler. + * 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. */ diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index a1fbdb47..e8e84931 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -1252,8 +1252,7 @@ void GameMessages::SendVendorOpenWindow(Entity* entity, const SystemAddress& sys SEND_PACKET } -// ah yes, impl code in a send function, beautiful! -void GameMessages::SendVendorStatusUpdate(Entity* entity, const SystemAddress& sysAddr) { +void GameMessages::SendVendorStatusUpdate(Entity* entity, const SystemAddress& sysAddr, bool bUpdateOnly) { CBITSTREAM CMSGHEADER @@ -1265,7 +1264,7 @@ void GameMessages::SendVendorStatusUpdate(Entity* entity, const SystemAddress& s bitStream.Write(entity->GetObjectID()); bitStream.Write(GAME_MSG::GAME_MSG_VENDOR_STATUS_UPDATE); - bitStream.Write(false); + bitStream.Write(bUpdateOnly); bitStream.Write(static_cast(vendorItems.size())); for (std::pair item : vendorItems) { @@ -1273,6 +1272,7 @@ void GameMessages::SendVendorStatusUpdate(Entity* entity, const SystemAddress& s bitStream.Write(static_cast(item.second)); } + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST SEND_PACKET } diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index f3997200..602cb4b2 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -109,7 +109,7 @@ namespace GameMessages { void SendModularBuildEnd(Entity* entity); void SendVendorOpenWindow(Entity* entity, const SystemAddress& sysAddr); - void SendVendorStatusUpdate(Entity* entity, const SystemAddress& sysAddr); + void SendVendorStatusUpdate(Entity* entity, const SystemAddress& sysAddr, bool bUpdateOnly = false); void SendVendorTransactionResult(Entity* entity, const SystemAddress& sysAddr); void SendRemoveItemFromInventory(Entity* entity, const SystemAddress& sysAddr, LWOOBJID iObjID, LOT templateID, int inventoryType, uint32_t stackCount, uint32_t stackRemaining); diff --git a/dNet/dMessageIdentifiers.h b/dNet/dMessageIdentifiers.h index eae3f88a..8e20ab54 100644 --- a/dNet/dMessageIdentifiers.h +++ b/dNet/dMessageIdentifiers.h @@ -324,6 +324,7 @@ enum GAME_MSG : unsigned short { GAME_MSG_ACTIVITY_STOP = 408, GAME_MSG_SHOOTING_GALLERY_CLIENT_AIM_UPDATE = 409, GAME_MSG_SHOOTING_GALLERY_FIRE = 411, + GAME_MSG_REQUEST_VENDOR_STATUS_UPDATE = 416, GAME_MSG_VENDOR_STATUS_UPDATE = 417, GAME_MSG_NOTIFY_CLIENT_SHOOTING_GALLERY_SCORE = 425, GAME_MSG_CONSUME_CLIENT_ITEM = 427,