From e92007de6046834f824caaa71e597b320b25d0f6 Mon Sep 17 00:00:00 2001 From: Aaron Kimbre Date: Thu, 22 Feb 2024 12:43:20 -0600 Subject: [PATCH 1/8] Groundwork --- dCommon/dEnums/eReplicaComponentType.h | 2 +- .../AchievementVendorComponent.cpp | 7 ++ .../dComponents/AchievementVendorComponent.h | 19 +++++ dGame/dComponents/CMakeLists.txt | 1 + dGame/dComponents/VendorComponent.h | 2 +- dGame/dGameMessages/GameMessages.cpp | 82 ++++++++----------- 6 files changed, 64 insertions(+), 49 deletions(-) create mode 100644 dGame/dComponents/AchievementVendorComponent.cpp create mode 100644 dGame/dComponents/AchievementVendorComponent.h diff --git a/dCommon/dEnums/eReplicaComponentType.h b/dCommon/dEnums/eReplicaComponentType.h index 83acbf897..2b991dfbf 100644 --- a/dCommon/dEnums/eReplicaComponentType.h +++ b/dCommon/dEnums/eReplicaComponentType.h @@ -106,7 +106,7 @@ enum class eReplicaComponentType : uint32_t { INTERACTION_MANAGER, DONATION_VENDOR, COMBAT_MEDIATOR, - COMMENDATION_VENDOR, + ACHIEVEMENT_VENDOR, GATE_RUSH_CONTROL, RAIL_ACTIVATOR, ROLLER, diff --git a/dGame/dComponents/AchievementVendorComponent.cpp b/dGame/dComponents/AchievementVendorComponent.cpp new file mode 100644 index 000000000..01e5e5fb0 --- /dev/null +++ b/dGame/dComponents/AchievementVendorComponent.cpp @@ -0,0 +1,7 @@ +#include "AchievementVendorComponent.h" +#include "MissionComponent.h" + +bool AchievementVendorComponent::SellsItem(const LOT item) const { + // TODO: Logic for checking if player has completed an achievement to be able to by the lot + return true; +} \ No newline at end of file diff --git a/dGame/dComponents/AchievementVendorComponent.h b/dGame/dComponents/AchievementVendorComponent.h new file mode 100644 index 000000000..a16c3fc17 --- /dev/null +++ b/dGame/dComponents/AchievementVendorComponent.h @@ -0,0 +1,19 @@ +#ifndef __ACHIEVEMENTVENDORCOMPONENT__H__ +#define __ACHIEVEMENTVENDORCOMPONENT__H__ + +#include "VendorComponent.h" +#include "eReplicaComponentType.h" + +class Entity; + +class AchievementVendorComponent final : public VendorComponent { +public: + static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::ACHIEVEMENT_VENDOR; + AchievementVendorComponent(Entity* parent) : VendorComponent(parent) {}; + bool SellsItem(const LOT item) const override; +private: + +}; + + +#endif //!__ACHIEVEMENTVENDORCOMPONENT__H__ diff --git a/dGame/dComponents/CMakeLists.txt b/dGame/dComponents/CMakeLists.txt index ac509e11e..21fe92071 100644 --- a/dGame/dComponents/CMakeLists.txt +++ b/dGame/dComponents/CMakeLists.txt @@ -1,4 +1,5 @@ set(DGAME_DCOMPONENTS_SOURCES + "AchievementVendorComponent.cpp" "ActivityComponent.cpp" "BaseCombatAIComponent.cpp" "BouncerComponent.cpp" diff --git a/dGame/dComponents/VendorComponent.h b/dGame/dComponents/VendorComponent.h index 48b766d2f..a0cfb68a9 100644 --- a/dGame/dComponents/VendorComponent.h +++ b/dGame/dComponents/VendorComponent.h @@ -28,7 +28,7 @@ class VendorComponent : public Component { void OnUse(Entity* originator) override; void RefreshInventory(bool isCreation = false); void SetupConstants(); - bool SellsItem(const LOT item) const; + virtual bool SellsItem(const LOT item) const; float GetBuyScalar() const { return m_BuyScalar; } float GetSellScalar() const { return m_SellScalar; } void SetBuyScalar(const float value) { m_BuyScalar = value; } diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index c38ef8807..fa5de0cf5 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -78,6 +78,7 @@ #include "LevelProgressionComponent.h" #include "DonationVendorComponent.h" #include "GhostComponent.h" +#include "AchievementVendorComponent.h" // Message includes: #include "dZoneManager.h" @@ -4665,84 +4666,71 @@ void GameMessages::HandleBuyFromVendor(RakNet::BitStream* inStream, Entity* enti Entity* player = Game::entityManager->GetEntity(user->GetLoggedInChar()); if (!player) return; + // Handle buying properties auto* propertyVendorComponent = static_cast(entity->GetComponent(eReplicaComponentType::PROPERTY_VENDOR)); - - if (propertyVendorComponent != nullptr) { + if (propertyVendorComponent) { propertyVendorComponent->OnBuyFromVendor(player, bConfirmed, item, count); - return; } - const auto isCommendationVendor = entity->GetLOT() == 13806; - + // handle buying items + auto* achVend = entity->GetComponent(); auto* vend = entity->GetComponent(); - if (!vend && !isCommendationVendor) return; + if (!vend && !achVend) return; auto* inv = player->GetComponent(); if (!inv) return; - if (!isCommendationVendor && !vend->SellsItem(item)) { - LOG("User %llu %s tried to buy an item %i from a vendor when they do not sell said item", player->GetObjectID(), user->GetUsername().c_str(), item); - return; - } - + // get the item Comp from the item LOT CDComponentsRegistryTable* compRegistryTable = CDClientManager::GetTable(); CDItemComponentTable* itemComponentTable = CDClientManager::GetTable(); - int itemCompID = compRegistryTable->GetByIDAndType(item, eReplicaComponentType::ITEM); CDItemComponent itemComp = itemComponentTable->GetItemComponentByID(itemCompID); - Character* character = player->GetCharacter(); - if (!character) return; - - // Extra currency that needs to be deducted in case of crafting - auto craftingCurrencies = CDItemComponentTable::ParseCraftingCurrencies(itemComp); - for (const auto& craftingCurrency : craftingCurrencies) { - inv->RemoveItem(craftingCurrency.first, craftingCurrency.second * count); - } - - if (isCommendationVendor) { - if (itemComp.commendationLOT != 13763) { + if (achVend) { + if (!achVend->SellsItem(item)) { + LOG("User %llu %s tried to buy an item %i from a achievement vendor when they do not sell said item", player->GetObjectID(), user->GetUsername().c_str(), item); return; } - - auto* missionComponent = player->GetComponent(); - - if (missionComponent == nullptr) { - return; + uint32_t costLOT = itemComp.commendationLOT; + if (costLOT == 13763) { // Faction Token Proxy + auto* missionComponent = player->GetComponent(); + if (!missionComponent) return; + + if (missionComponent->GetMissionState(545) == eMissionState::COMPLETE) costLOT = 8318; // "Assembly Token" + if (missionComponent->GetMissionState(556) == eMissionState::COMPLETE) costLOT = 8321; // "Venture League Token" + if (missionComponent->GetMissionState(567) == eMissionState::COMPLETE) costLOT = 8319; // "Sentinels Token" + if (missionComponent->GetMissionState(578) == eMissionState::COMPLETE) costLOT = 8320; // "Paradox Token" } - - LOT tokenId = -1; - - if (missionComponent->GetMissionState(545) == eMissionState::COMPLETE) tokenId = 8318; // "Assembly Token" - if (missionComponent->GetMissionState(556) == eMissionState::COMPLETE) tokenId = 8321; // "Venture League Token" - if (missionComponent->GetMissionState(567) == eMissionState::COMPLETE) tokenId = 8319; // "Sentinels Token" - if (missionComponent->GetMissionState(578) == eMissionState::COMPLETE) tokenId = 8320; // "Paradox Token" - const uint32_t altCurrencyCost = itemComp.commendationCost * count; - if (inv->GetLotCount(tokenId) < altCurrencyCost) { + if (inv->GetLotCount(costLOT) < altCurrencyCost) return; + inv->RemoveItem(costLOT, altCurrencyCost); + inv->AddItem(item, count, eLootSourceType::VENDOR); + } else { + if (!vend->SellsItem(item)) { + LOG("User %llu %s tried to buy an item %i from a vendor when they do not sell said item", player->GetObjectID(), user->GetUsername().c_str(), item); return; } - inv->RemoveItem(tokenId, altCurrencyCost); + // Extra currency that needs to be deducted in case of crafting + // TODO: Don't Blindly remove everything without first checking if they do have all the items to craft + auto craftingCurrencies = CDItemComponentTable::ParseCraftingCurrencies(itemComp); + for (const auto& [crafintCurrencyLOT, crafintCurrencyCount]: craftingCurrencies) { + inv->RemoveItem(crafintCurrencyLOT, crafintCurrencyCount * count); + } - inv->AddItem(item, count, eLootSourceType::VENDOR); - } else { float buyScalar = vend->GetBuyScalar(); - const auto coinCost = static_cast(std::floor((itemComp.baseValue * buyScalar) * count)); - if (character->GetCoins() < coinCost) { - return; - } + Character* character = player->GetCharacter(); + if (!character) return; + if (character->GetCoins() < coinCost) return; if (Inventory::IsValidItem(itemComp.currencyLOT)) { const uint32_t altCurrencyCost = std::floor(itemComp.altCurrencyCost * buyScalar) * count; - if (inv->GetLotCount(itemComp.currencyLOT) < altCurrencyCost) { - return; - } + if (inv->GetLotCount(itemComp.currencyLOT) < altCurrencyCost) return; inv->RemoveItem(itemComp.currencyLOT, altCurrencyCost); } From 45d0cd401b3e1b048bde621702e6ad02ebf55298 Mon Sep 17 00:00:00 2001 From: Aaron Kimbre Date: Sat, 24 Feb 2024 02:51:36 -0600 Subject: [PATCH 2/8] movie buying logic out of gm handler make transaction result more useful --- dCommon/dEnums/eVendorTransactionResult.h | 15 ++++ .../AchievementVendorComponent.cpp | 45 ++++++++++ .../dComponents/AchievementVendorComponent.h | 2 + dGame/dComponents/VendorComponent.cpp | 58 ++++++++++++ dGame/dComponents/VendorComponent.h | 1 + dGame/dGameMessages/GameMessages.cpp | 90 ++++--------------- dGame/dGameMessages/GameMessages.h | 3 +- 7 files changed, 141 insertions(+), 73 deletions(-) create mode 100644 dCommon/dEnums/eVendorTransactionResult.h diff --git a/dCommon/dEnums/eVendorTransactionResult.h b/dCommon/dEnums/eVendorTransactionResult.h new file mode 100644 index 000000000..c9a65a851 --- /dev/null +++ b/dCommon/dEnums/eVendorTransactionResult.h @@ -0,0 +1,15 @@ +#ifndef __EVENDORTRANSACTIONRESULT__ +#define __EVENDORTRANSACTIONRESULT__ + +#include + +enum class eVendorTransactionResult : uint32_t { + SELL_SUCCESS = 0, + SELL_FAIL, + PURCHASE_SUCCESS, + PURCHASE_FAIL, + DONATION_FAIL, + DONATION_FULL +}; + +#endif // !__EVENDORTRANSACTIONRESULT__ \ No newline at end of file diff --git a/dGame/dComponents/AchievementVendorComponent.cpp b/dGame/dComponents/AchievementVendorComponent.cpp index 01e5e5fb0..2941c9ac7 100644 --- a/dGame/dComponents/AchievementVendorComponent.cpp +++ b/dGame/dComponents/AchievementVendorComponent.cpp @@ -1,7 +1,52 @@ #include "AchievementVendorComponent.h" #include "MissionComponent.h" +#include "InventoryComponent.h" +#include "eMissionState.h" +#include "CDComponentsRegistryTable.h" +#include "CDItemComponentTable.h" +#include "eVendorTransactionResult.h" + bool AchievementVendorComponent::SellsItem(const LOT item) const { // TODO: Logic for checking if player has completed an achievement to be able to by the lot return true; +} +void AchievementVendorComponent::Buy(Entity* buyer, LOT lot, uint32_t count) { + if (SellsItem(lot)) { + GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL); + return; + } + + auto* inventoryComponent = buyer->GetComponent(); + if (!inventoryComponent) { + GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL); + return; + } + + // get the item Comp from the item LOT + CDComponentsRegistryTable* compRegistryTable = CDClientManager::GetTable(); + CDItemComponentTable* itemComponentTable = CDClientManager::GetTable(); + int itemCompID = compRegistryTable->GetByIDAndType(lot, eReplicaComponentType::ITEM); + CDItemComponent itemComp = itemComponentTable->GetItemComponentByID(itemCompID); + uint32_t costLOT = itemComp.commendationLOT; + + if (costLOT == 13763) { // Faction Token Proxy + auto* missionComponent = buyer->GetComponent(); + if (!missionComponent) return; + + if (missionComponent->GetMissionState(545) == eMissionState::COMPLETE) costLOT = 8318; // "Assembly Token" + if (missionComponent->GetMissionState(556) == eMissionState::COMPLETE) costLOT = 8321; // "Venture League Token" + if (missionComponent->GetMissionState(567) == eMissionState::COMPLETE) costLOT = 8319; // "Sentinels Token" + if (missionComponent->GetMissionState(578) == eMissionState::COMPLETE) costLOT = 8320; // "Paradox Token" + } + + const uint32_t altCurrencyCost = itemComp.commendationCost * count; + if (inventoryComponent->GetLotCount(costLOT) < altCurrencyCost) { + GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL); + return; + } + inventoryComponent->RemoveItem(costLOT, altCurrencyCost); + inventoryComponent->AddItem(lot, count, eLootSourceType::VENDOR); + GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_SUCCESS); + } \ No newline at end of file diff --git a/dGame/dComponents/AchievementVendorComponent.h b/dGame/dComponents/AchievementVendorComponent.h index a16c3fc17..1d16de2d8 100644 --- a/dGame/dComponents/AchievementVendorComponent.h +++ b/dGame/dComponents/AchievementVendorComponent.h @@ -11,6 +11,8 @@ class AchievementVendorComponent final : public VendorComponent { static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::ACHIEVEMENT_VENDOR; AchievementVendorComponent(Entity* parent) : VendorComponent(parent) {}; bool SellsItem(const LOT item) const override; + void Buy(Entity* buyer, LOT lot, uint32_t count) override; + private: }; diff --git a/dGame/dComponents/VendorComponent.cpp b/dGame/dComponents/VendorComponent.cpp index afa3d0132..7db223566 100644 --- a/dGame/dComponents/VendorComponent.cpp +++ b/dGame/dComponents/VendorComponent.cpp @@ -8,6 +8,9 @@ #include "CDLootMatrixTable.h" #include "CDLootTableTable.h" #include "CDItemComponentTable.h" +#include "InventoryComponent.h" +#include "Character.h" +#include "eVendorTransactionResult.h" VendorComponent::VendorComponent(Entity* parent) : Component(parent) { m_HasStandardCostItems = false; @@ -151,3 +154,58 @@ void VendorComponent::HandleMrReeCameras(){ m_Inventory.push_back(SoldItem(camera, 0)); } } + + +void VendorComponent::Buy(Entity* buyer, LOT lot, uint32_t count) { + + if (SellsItem(lot)) { + + GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL); + return; + } + auto* inventoryComponent = buyer->GetComponent(); + if (!inventoryComponent) { + GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL); + return; + } + CDComponentsRegistryTable* compRegistryTable = CDClientManager::GetTable(); + CDItemComponentTable* itemComponentTable = CDClientManager::GetTable(); + 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(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); + +} \ No newline at end of file diff --git a/dGame/dComponents/VendorComponent.h b/dGame/dComponents/VendorComponent.h index a0cfb68a9..9d2671d82 100644 --- a/dGame/dComponents/VendorComponent.h +++ b/dGame/dComponents/VendorComponent.h @@ -47,6 +47,7 @@ class VendorComponent : public Component { m_DirtyVendor = true; } + virtual void Buy(Entity* buyer, LOT lot, uint32_t count); private: void SetupMaxCustomVendor(); diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index fa5de0cf5..d5375be12 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -98,6 +98,7 @@ #include "ePetAbilityType.h" #include "ActivityManager.h" #include "PlayerManager.h" +#include "eVendorTransactionResult.h" #include "CDComponentsRegistryTable.h" #include "CDObjectsTable.h" @@ -1324,15 +1325,14 @@ void GameMessages::SendVendorStatusUpdate(Entity* entity, const SystemAddress& s SEND_PACKET; } -void GameMessages::SendVendorTransactionResult(Entity* entity, const SystemAddress& sysAddr) { +void GameMessages::SendVendorTransactionResult(Entity* entity, const SystemAddress& sysAddr, eVendorTransactionResult result) { CBITSTREAM; CMSGHEADER; - int iResult = 0x02; // success, seems to be the only relevant one bitStream.Write(entity->GetObjectID()); bitStream.Write(eGameMessageType::VENDOR_TRANSACTION_RESULT); - bitStream.Write(iResult); + bitStream.Write(result); SEND_PACKET; } @@ -4665,81 +4665,27 @@ void GameMessages::HandleBuyFromVendor(RakNet::BitStream* inStream, Entity* enti if (!user) return; Entity* player = Game::entityManager->GetEntity(user->GetLoggedInChar()); if (!player) return; + + // handle buying normal items + auto* vendorComponent = entity->GetComponent(); + if (vendorComponent) { + vendorComponent->Buy(player, item, count); + return; + } + + // handle buying achievement items + auto* achievementVendorComponent = entity->GetComponent(); + if (achievementVendorComponent) { + achievementVendorComponent->Buy(player, item, count); + return; + } // Handle buying properties - auto* propertyVendorComponent = static_cast(entity->GetComponent(eReplicaComponentType::PROPERTY_VENDOR)); + auto* propertyVendorComponent = entity->GetComponent(); if (propertyVendorComponent) { propertyVendorComponent->OnBuyFromVendor(player, bConfirmed, item, count); return; } - - // handle buying items - auto* achVend = entity->GetComponent(); - auto* vend = entity->GetComponent(); - if (!vend && !achVend) return; - - auto* inv = player->GetComponent(); - if (!inv) return; - - // get the item Comp from the item LOT - CDComponentsRegistryTable* compRegistryTable = CDClientManager::GetTable(); - CDItemComponentTable* itemComponentTable = CDClientManager::GetTable(); - int itemCompID = compRegistryTable->GetByIDAndType(item, eReplicaComponentType::ITEM); - CDItemComponent itemComp = itemComponentTable->GetItemComponentByID(itemCompID); - - if (achVend) { - if (!achVend->SellsItem(item)) { - LOG("User %llu %s tried to buy an item %i from a achievement vendor when they do not sell said item", player->GetObjectID(), user->GetUsername().c_str(), item); - return; - } - uint32_t costLOT = itemComp.commendationLOT; - if (costLOT == 13763) { // Faction Token Proxy - auto* missionComponent = player->GetComponent(); - if (!missionComponent) return; - - if (missionComponent->GetMissionState(545) == eMissionState::COMPLETE) costLOT = 8318; // "Assembly Token" - if (missionComponent->GetMissionState(556) == eMissionState::COMPLETE) costLOT = 8321; // "Venture League Token" - if (missionComponent->GetMissionState(567) == eMissionState::COMPLETE) costLOT = 8319; // "Sentinels Token" - if (missionComponent->GetMissionState(578) == eMissionState::COMPLETE) costLOT = 8320; // "Paradox Token" - } - const uint32_t altCurrencyCost = itemComp.commendationCost * count; - - if (inv->GetLotCount(costLOT) < altCurrencyCost) return; - inv->RemoveItem(costLOT, altCurrencyCost); - inv->AddItem(item, count, eLootSourceType::VENDOR); - } else { - if (!vend->SellsItem(item)) { - LOG("User %llu %s tried to buy an item %i from a vendor when they do not sell said item", player->GetObjectID(), user->GetUsername().c_str(), item); - return; - } - - // Extra currency that needs to be deducted in case of crafting - // TODO: Don't Blindly remove everything without first checking if they do have all the items to craft - auto craftingCurrencies = CDItemComponentTable::ParseCraftingCurrencies(itemComp); - for (const auto& [crafintCurrencyLOT, crafintCurrencyCount]: craftingCurrencies) { - inv->RemoveItem(crafintCurrencyLOT, crafintCurrencyCount * count); - } - - float buyScalar = vend->GetBuyScalar(); - const auto coinCost = static_cast(std::floor((itemComp.baseValue * buyScalar) * count)); - - Character* character = player->GetCharacter(); - if (!character) return; - if (character->GetCoins() < coinCost) return; - - if (Inventory::IsValidItem(itemComp.currencyLOT)) { - const uint32_t altCurrencyCost = std::floor(itemComp.altCurrencyCost * buyScalar) * count; - - if (inv->GetLotCount(itemComp.currencyLOT) < altCurrencyCost) return; - - inv->RemoveItem(itemComp.currencyLOT, altCurrencyCost); - } - - character->SetCoins(character->GetCoins() - (coinCost), eLootSourceType::VENDOR); - inv->AddItem(item, count, eLootSourceType::VENDOR); - } - - GameMessages::SendVendorTransactionResult(entity, sysAddr); } void GameMessages::HandleSellToVendor(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) { diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index a96bbf602..9604d261d 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -38,6 +38,7 @@ enum class eUseItemResponse : uint32_t; enum class eQuickBuildFailReason : uint32_t; enum class eQuickBuildState : uint32_t; enum class BehaviorSlot : int32_t; +enum class eVendorTransactionResult : uint32_t; namespace GameMessages { class PropertyDataMessage; @@ -135,7 +136,7 @@ namespace GameMessages { void SendVendorOpenWindow(Entity* entity, const SystemAddress& sysAddr); void SendVendorStatusUpdate(Entity* entity, const SystemAddress& sysAddr, bool bUpdateOnly = false); - void SendVendorTransactionResult(Entity* entity, const SystemAddress& sysAddr); + void SendVendorTransactionResult(Entity* entity, const SystemAddress& sysAddr, eVendorTransactionResult result); void SendRemoveItemFromInventory(Entity* entity, const SystemAddress& sysAddr, LWOOBJID iObjID, LOT templateID, int inventoryType, uint32_t stackCount, uint32_t stackRemaining); void SendConsumeClientItem(Entity* entity, bool bSuccess, LWOOBJID item); From f4f16a3413486b9546db6fdc1ba6a3816550fd18 Mon Sep 17 00:00:00 2001 From: Aaron Kimbre Date: Sat, 24 Feb 2024 04:09:32 -0600 Subject: [PATCH 3/8] Full implementation Cleanup and fix some calls in gamemessages --- .../CDClientTables/CDMissionsTable.cpp | 24 +++++++++++- .../CDClientTables/CDMissionsTable.h | 3 ++ .../AchievementVendorComponent.cpp | 37 ++++++++++++------- .../dComponents/AchievementVendorComponent.h | 4 +- dGame/dComponents/VendorComponent.cpp | 8 +++- dGame/dComponents/VendorComponent.h | 4 +- dGame/dGameMessages/GameMessages.cpp | 15 ++++---- 7 files changed, 68 insertions(+), 27 deletions(-) diff --git a/dDatabase/CDClientDatabase/CDClientTables/CDMissionsTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDMissionsTable.cpp index 8862b1dba..75cd2015b 100644 --- a/dDatabase/CDClientDatabase/CDClientTables/CDMissionsTable.cpp +++ b/dDatabase/CDClientDatabase/CDClientTables/CDMissionsTable.cpp @@ -79,7 +79,7 @@ void CDMissionsTable::LoadValuesFromDatabase() { entries.push_back(entry); tableData.nextRow(); } - + std::ranges::sort(entries, [](const CDMissions& lhs, const CDMissions& rhs) { return lhs.id < rhs.id; }); tableData.finalize(); Default.id = -1; @@ -118,3 +118,25 @@ const CDMissions& CDMissionsTable::GetByMissionID(uint32_t missionID, bool& foun return Default; } +const std::set CDMissionsTable::GetMissionsForReward(LOT lot) { + std::set toReturn {}; + for (const auto& entry : GetEntries()) { + if (lot == entry.reward_item1) { + toReturn.insert(entry.id); + continue; + } + if (lot == entry.reward_item2) { + toReturn.insert(entry.id); + continue; + } + if (lot == entry.reward_item3) { + toReturn.insert(entry.id); + continue; + } + if (lot == entry.reward_item4) { + toReturn.insert(entry.id); + continue; + } + } + return toReturn; +} diff --git a/dDatabase/CDClientDatabase/CDClientTables/CDMissionsTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDMissionsTable.h index 6ba7b19e9..5067f2e2b 100644 --- a/dDatabase/CDClientDatabase/CDClientTables/CDMissionsTable.h +++ b/dDatabase/CDClientDatabase/CDClientTables/CDMissionsTable.h @@ -70,6 +70,9 @@ class CDMissionsTable : public CDTable> const CDMissions& GetByMissionID(uint32_t missionID, bool& found) const; + const std::set GetMissionsForReward(LOT lot); + + static CDMissions Default; }; diff --git a/dGame/dComponents/AchievementVendorComponent.cpp b/dGame/dComponents/AchievementVendorComponent.cpp index 2941c9ac7..79b1f2b13 100644 --- a/dGame/dComponents/AchievementVendorComponent.cpp +++ b/dGame/dComponents/AchievementVendorComponent.cpp @@ -5,14 +5,31 @@ #include "CDComponentsRegistryTable.h" #include "CDItemComponentTable.h" #include "eVendorTransactionResult.h" - - -bool AchievementVendorComponent::SellsItem(const LOT item) const { - // TODO: Logic for checking if player has completed an achievement to be able to by the lot - return true; +#include "CheatDetection.h" +#include "UserManager.h" +#include "CDMissionsTable.h" + +bool AchievementVendorComponent::SellsItem( Entity* buyer, const LOT lot) const { + auto* missionComponent = buyer->GetComponent(); + CDMissionsTable* missionsTable = CDClientManager::GetTable(); + const auto missions = missionsTable->GetMissionsForReward(lot); + for (const auto mission : missions) { + if (missionComponent->GetMissionState(mission) == eMissionState::COMPLETE) return true; + } + return false; } + void AchievementVendorComponent::Buy(Entity* buyer, LOT lot, uint32_t count) { - if (SellsItem(lot)) { + // get the item Comp from the item LOT + CDComponentsRegistryTable* compRegistryTable = CDClientManager::GetTable(); + CDItemComponentTable* itemComponentTable = CDClientManager::GetTable(); + int itemCompID = compRegistryTable->GetByIDAndType(lot, eReplicaComponentType::ITEM); + CDItemComponent itemComp = itemComponentTable->GetItemComponentByID(itemCompID); + uint32_t costLOT = itemComp.commendationLOT; + + if (costLOT == -1 || !SellsItem(buyer, 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; } @@ -23,13 +40,6 @@ void AchievementVendorComponent::Buy(Entity* buyer, LOT lot, uint32_t count) { return; } - // get the item Comp from the item LOT - CDComponentsRegistryTable* compRegistryTable = CDClientManager::GetTable(); - CDItemComponentTable* itemComponentTable = CDClientManager::GetTable(); - int itemCompID = compRegistryTable->GetByIDAndType(lot, eReplicaComponentType::ITEM); - CDItemComponent itemComp = itemComponentTable->GetItemComponentByID(itemCompID); - uint32_t costLOT = itemComp.commendationLOT; - if (costLOT == 13763) { // Faction Token Proxy auto* missionComponent = buyer->GetComponent(); if (!missionComponent) return; @@ -45,6 +55,7 @@ void AchievementVendorComponent::Buy(Entity* buyer, LOT lot, uint32_t count) { GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL); return; } + inventoryComponent->RemoveItem(costLOT, altCurrencyCost); inventoryComponent->AddItem(lot, count, eLootSourceType::VENDOR); GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_SUCCESS); diff --git a/dGame/dComponents/AchievementVendorComponent.h b/dGame/dComponents/AchievementVendorComponent.h index 1d16de2d8..c5985f048 100644 --- a/dGame/dComponents/AchievementVendorComponent.h +++ b/dGame/dComponents/AchievementVendorComponent.h @@ -10,8 +10,8 @@ class AchievementVendorComponent final : public VendorComponent { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::ACHIEVEMENT_VENDOR; AchievementVendorComponent(Entity* parent) : VendorComponent(parent) {}; - bool SellsItem(const LOT item) const override; - void Buy(Entity* buyer, LOT lot, uint32_t count) override; + bool SellsItem(Entity* buyer, const LOT lot) const; + void Buy(Entity* buyer, LOT lot, uint32_t count); private: diff --git a/dGame/dComponents/VendorComponent.cpp b/dGame/dComponents/VendorComponent.cpp index 7db223566..9e9428f74 100644 --- a/dGame/dComponents/VendorComponent.cpp +++ b/dGame/dComponents/VendorComponent.cpp @@ -11,6 +11,8 @@ #include "InventoryComponent.h" #include "Character.h" #include "eVendorTransactionResult.h" +#include "UserManager.h" +#include "CheatDetection.h" VendorComponent::VendorComponent(Entity* parent) : Component(parent) { m_HasStandardCostItems = false; @@ -158,11 +160,13 @@ void VendorComponent::HandleMrReeCameras(){ void VendorComponent::Buy(Entity* buyer, LOT lot, uint32_t count) { - if (SellsItem(lot)) { - + 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(); if (!inventoryComponent) { GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL); diff --git a/dGame/dComponents/VendorComponent.h b/dGame/dComponents/VendorComponent.h index 9d2671d82..9025148b3 100644 --- a/dGame/dComponents/VendorComponent.h +++ b/dGame/dComponents/VendorComponent.h @@ -28,7 +28,7 @@ class VendorComponent : public Component { void OnUse(Entity* originator) override; void RefreshInventory(bool isCreation = false); void SetupConstants(); - virtual bool SellsItem(const LOT item) const; + bool SellsItem(const LOT item) const; float GetBuyScalar() const { return m_BuyScalar; } float GetSellScalar() const { return m_SellScalar; } void SetBuyScalar(const float value) { m_BuyScalar = value; } @@ -47,7 +47,7 @@ class VendorComponent : public Component { m_DirtyVendor = true; } - virtual void Buy(Entity* buyer, LOT lot, uint32_t count); + void Buy(Entity* buyer, LOT lot, uint32_t count); private: void SetupMaxCustomVendor(); diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index d5375be12..a62bf3827 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -4719,7 +4719,10 @@ void GameMessages::HandleSellToVendor(RakNet::BitStream* inStream, Entity* entit CDItemComponent itemComp = itemComponentTable->GetItemComponentByID(itemCompID); // Items with a base value of 0 or max int are special items that should not be sold if they're not sub items - if (itemComp.baseValue == 0 || itemComp.baseValue == UINT_MAX) return; + if (itemComp.baseValue == 0 || itemComp.baseValue == UINT_MAX) { + GameMessages::SendVendorTransactionResult(entity, sysAddr, eVendorTransactionResult::SELL_FAIL); + return; + } float sellScalar = vend->GetSellScalar(); if (Inventory::IsValidItem(itemComp.currencyLOT)) { @@ -4727,11 +4730,9 @@ void GameMessages::HandleSellToVendor(RakNet::BitStream* inStream, Entity* entit inv->AddItem(itemComp.currencyLOT, std::floor(altCurrency), eLootSourceType::VENDOR); // Return alt currencies like faction tokens. } - //inv->RemoveItem(count, -1, iObjID); inv->MoveItemToInventory(item, eInventoryType::VENDOR_BUYBACK, count, true, false, true); character->SetCoins(std::floor(character->GetCoins() + (static_cast(itemComp.baseValue * sellScalar) * count)), eLootSourceType::VENDOR); - //Game::entityManager->SerializeEntity(player); // so inventory updates - GameMessages::SendVendorTransactionResult(entity, sysAddr); + GameMessages::SendVendorTransactionResult(entity, sysAddr, eVendorTransactionResult::SELL_SUCCESS); } void GameMessages::HandleBuybackFromVendor(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) { @@ -4773,16 +4774,16 @@ void GameMessages::HandleBuybackFromVendor(RakNet::BitStream* inStream, Entity* const auto cost = static_cast(std::floor(((itemComp.baseValue * sellScalar) * count))); if (character->GetCoins() < cost) { + GameMessages::SendVendorTransactionResult(entity, sysAddr, eVendorTransactionResult::PURCHASE_FAIL); return; } if (Inventory::IsValidItem(itemComp.currencyLOT)) { const uint32_t altCurrencyCost = std::floor(itemComp.altCurrencyCost * sellScalar) * count; - if (inv->GetLotCount(itemComp.currencyLOT) < altCurrencyCost) { + GameMessages::SendVendorTransactionResult(entity, sysAddr, eVendorTransactionResult::PURCHASE_FAIL); return; } - inv->RemoveItem(itemComp.currencyLOT, altCurrencyCost); } @@ -4790,7 +4791,7 @@ void GameMessages::HandleBuybackFromVendor(RakNet::BitStream* inStream, Entity* inv->MoveItemToInventory(item, Inventory::FindInventoryTypeForLot(item->GetLot()), count, true, false); character->SetCoins(character->GetCoins() - cost, eLootSourceType::VENDOR); //Game::entityManager->SerializeEntity(player); // so inventory updates - GameMessages::SendVendorTransactionResult(entity, sysAddr); + GameMessages::SendVendorTransactionResult(entity, sysAddr, eVendorTransactionResult::PURCHASE_SUCCESS); } void GameMessages::HandleParseChatMessage(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) { From 70a328126aa055d13469180149b6e053572159ca Mon Sep 17 00:00:00 2001 From: Aaron Kimbre Date: Sat, 24 Feb 2024 04:52:24 -0600 Subject: [PATCH 4/8] Load the component in the entity Patch Auth --- dGame/Entity.cpp | 8 ++++++++ dNet/AuthPackets.cpp | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index 269b4cc44..bb932991d 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -82,6 +82,7 @@ #include "CollectibleComponent.h" #include "ItemComponent.h" #include "GhostComponent.h" +#include "AchievementVendorComponent.h" // Table includes #include "CDComponentsRegistryTable.h" @@ -615,6 +616,8 @@ void Entity::Initialize() { AddComponent(); } else if ((compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::DONATION_VENDOR, -1) != -1)) { AddComponent(); + } else if ((compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::ACHIEVEMENT_VENDOR, -1) != -1)) { + AddComponent(); } if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PROPERTY_VENDOR, -1) != -1) { @@ -1191,6 +1194,11 @@ void Entity::WriteComponents(RakNet::BitStream* outBitStream, eReplicaPacketType donationVendorComponent->Serialize(outBitStream, bIsInitialUpdate); } + AchievementVendorComponent* achievementVendorComponent; + if (TryGetComponent(eReplicaComponentType::ACHIEVEMENT_VENDOR, achievementVendorComponent)) { + achievementVendorComponent->Serialize(outBitStream, bIsInitialUpdate); + } + BouncerComponent* bouncerComponent; if (TryGetComponent(eReplicaComponentType::BOUNCER, bouncerComponent)) { bouncerComponent->Serialize(outBitStream, bIsInitialUpdate); diff --git a/dNet/AuthPackets.cpp b/dNet/AuthPackets.cpp index 25ccc9029..54c28299f 100644 --- a/dNet/AuthPackets.cpp +++ b/dNet/AuthPackets.cpp @@ -82,7 +82,7 @@ void AuthPackets::SendHandshake(dServer* server, const SystemAddress& sysAddr, c if (serverType == ServerType::Auth) bitStream.Write(ServiceId::Auth); else if (serverType == ServerType::World) bitStream.Write(ServiceId::World); else bitStream.Write(ServiceId::General); - bitStream.Write(774909490); + bitStream.Write(215523405360); server->Send(&bitStream, sysAddr, false); } From 5674f76568a9d232500b1e69c654088ac3a79c3c Mon Sep 17 00:00:00 2001 From: Aaron Kimbre Date: Sat, 24 Feb 2024 04:58:02 -0600 Subject: [PATCH 5/8] new line at eof --- dCommon/dEnums/eVendorTransactionResult.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dCommon/dEnums/eVendorTransactionResult.h b/dCommon/dEnums/eVendorTransactionResult.h index c9a65a851..e61ee0ee2 100644 --- a/dCommon/dEnums/eVendorTransactionResult.h +++ b/dCommon/dEnums/eVendorTransactionResult.h @@ -12,4 +12,4 @@ enum class eVendorTransactionResult : uint32_t { DONATION_FULL }; -#endif // !__EVENDORTRANSACTIONRESULT__ \ No newline at end of file +#endif // !__EVENDORTRANSACTIONRESULT__ From c4d1a09cc8df43e13af71e0b21632bef1aab2da2 Mon Sep 17 00:00:00 2001 From: Aaron Kimbre Date: Sun, 25 Feb 2024 00:55:03 -0600 Subject: [PATCH 6/8] cache lookups --- .../CDClientTables/CDMissionsTable.cpp | 15 +-------------- dGame/dComponents/AchievementVendorComponent.cpp | 13 +++++++++++-- dGame/dComponents/AchievementVendorComponent.h | 2 +- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/dDatabase/CDClientDatabase/CDClientTables/CDMissionsTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDMissionsTable.cpp index 75cd2015b..fb5760164 100644 --- a/dDatabase/CDClientDatabase/CDClientTables/CDMissionsTable.cpp +++ b/dDatabase/CDClientDatabase/CDClientTables/CDMissionsTable.cpp @@ -121,21 +121,8 @@ const CDMissions& CDMissionsTable::GetByMissionID(uint32_t missionID, bool& foun const std::set CDMissionsTable::GetMissionsForReward(LOT lot) { std::set toReturn {}; for (const auto& entry : GetEntries()) { - if (lot == entry.reward_item1) { + if (lot == entry.reward_item1 || lot == entry.reward_item2 || lot == entry.reward_item3 || lot == entry.reward_item4) { toReturn.insert(entry.id); - continue; - } - if (lot == entry.reward_item2) { - toReturn.insert(entry.id); - continue; - } - if (lot == entry.reward_item3) { - toReturn.insert(entry.id); - continue; - } - if (lot == entry.reward_item4) { - toReturn.insert(entry.id); - continue; } } return toReturn; diff --git a/dGame/dComponents/AchievementVendorComponent.cpp b/dGame/dComponents/AchievementVendorComponent.cpp index 79b1f2b13..16d165a24 100644 --- a/dGame/dComponents/AchievementVendorComponent.cpp +++ b/dGame/dComponents/AchievementVendorComponent.cpp @@ -9,12 +9,21 @@ #include "UserManager.h" #include "CDMissionsTable.h" -bool AchievementVendorComponent::SellsItem( Entity* buyer, const LOT lot) const { +bool AchievementVendorComponent::SellsItem(Entity* buyer, const LOT lot) const { auto* missionComponent = buyer->GetComponent(); + if (!missionComponent) return false; + + if (m_PlayerPurchasableItems[buyer->GetObjectID()].contains(lot)){ + return true; + } + CDMissionsTable* missionsTable = CDClientManager::GetTable(); const auto missions = missionsTable->GetMissionsForReward(lot); for (const auto mission : missions) { - if (missionComponent->GetMissionState(mission) == eMissionState::COMPLETE) return true; + if (missionComponent->GetMissionState(mission) == eMissionState::COMPLETE) { + m_PlayerPurchasableItems[buyer->GetObjectID()].insert(lot); + return true; + } } return false; } diff --git a/dGame/dComponents/AchievementVendorComponent.h b/dGame/dComponents/AchievementVendorComponent.h index c5985f048..5b919ad98 100644 --- a/dGame/dComponents/AchievementVendorComponent.h +++ b/dGame/dComponents/AchievementVendorComponent.h @@ -14,7 +14,7 @@ class AchievementVendorComponent final : public VendorComponent { void Buy(Entity* buyer, LOT lot, uint32_t count); private: - + std::map> m_PlayerPurchasableItems; }; From c4a485a3d3856f63170fd5c8257205b2efc95bdc Mon Sep 17 00:00:00 2001 From: Aaron Kimbre Date: Sun, 25 Feb 2024 00:55:40 -0600 Subject: [PATCH 7/8] remove sort --- dDatabase/CDClientDatabase/CDClientTables/CDMissionsTable.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/dDatabase/CDClientDatabase/CDClientTables/CDMissionsTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDMissionsTable.cpp index fb5760164..97dcde9fc 100644 --- a/dDatabase/CDClientDatabase/CDClientTables/CDMissionsTable.cpp +++ b/dDatabase/CDClientDatabase/CDClientTables/CDMissionsTable.cpp @@ -79,7 +79,6 @@ void CDMissionsTable::LoadValuesFromDatabase() { entries.push_back(entry); tableData.nextRow(); } - std::ranges::sort(entries, [](const CDMissions& lhs, const CDMissions& rhs) { return lhs.id < rhs.id; }); tableData.finalize(); Default.id = -1; From 44cd0351cf98e3494afc4bd804fa22cfa1489da2 Mon Sep 17 00:00:00 2001 From: Aaron Kimbre Date: Sun, 25 Feb 2024 01:06:44 -0600 Subject: [PATCH 8/8] fix includes --- dGame/dComponents/AchievementVendorComponent.cpp | 2 +- dGame/dComponents/AchievementVendorComponent.h | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/dGame/dComponents/AchievementVendorComponent.cpp b/dGame/dComponents/AchievementVendorComponent.cpp index 16d165a24..10a0ca29d 100644 --- a/dGame/dComponents/AchievementVendorComponent.cpp +++ b/dGame/dComponents/AchievementVendorComponent.cpp @@ -9,7 +9,7 @@ #include "UserManager.h" #include "CDMissionsTable.h" -bool AchievementVendorComponent::SellsItem(Entity* buyer, const LOT lot) const { +bool AchievementVendorComponent::SellsItem(Entity* buyer, const LOT lot) { auto* missionComponent = buyer->GetComponent(); if (!missionComponent) return false; diff --git a/dGame/dComponents/AchievementVendorComponent.h b/dGame/dComponents/AchievementVendorComponent.h index 5b919ad98..bffd39836 100644 --- a/dGame/dComponents/AchievementVendorComponent.h +++ b/dGame/dComponents/AchievementVendorComponent.h @@ -3,6 +3,8 @@ #include "VendorComponent.h" #include "eReplicaComponentType.h" +#include +#include class Entity; @@ -10,7 +12,7 @@ class AchievementVendorComponent final : public VendorComponent { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::ACHIEVEMENT_VENDOR; AchievementVendorComponent(Entity* parent) : VendorComponent(parent) {}; - bool SellsItem(Entity* buyer, const LOT lot) const; + bool SellsItem(Entity* buyer, const LOT lot); void Buy(Entity* buyer, LOT lot, uint32_t count); private: