mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 02:43:44 +00:00
refactor: vendor, donation and trade game messages as structs
Converts the vendor, donation vendor and trade game messages to
NetGameMsgs in VendorMessages.{h,cpp} and TradeMessages.{h,cpp}:
VendorOpenWindow and VendorTransactionResult (SendToClient: the old
functions never broadcast), VendorStatusUpdate, ServerTradeInvite,
ServerTradeInitialReply, ServerTradeFinalReply, ServerTradeAccept,
ServerTradeCancel and ServerTradeUpdate, and the received
RequestVendorStatusUpdate, BuyFromVendor, SellToVendor,
BuybackFromVendor, AddDonationItem, RemoveDonationItem,
ConfirmDonationOnPlayer, CancelDonationOnPlayer, ClientTradeRequest,
ClientTradeCancel, ClientTradeAccept and ClientTradeUpdate. A trade
offer entry (the client's inventory item layout, optional fields and
config block included) is TradeItemEntry, shared by both trade updates.
Selling and buying back move into VendorComponent (SellToVendor,
BuybackFromVendor), adding and confirming donations into
DonationVendorComponent, and VendorComponent builds its own status
update and transaction results. Only the VENDOR component sends its
stock, as before. The received messages are registered in
GameMessageHandler's map, the switch cases and the old functions are
deleted. Handler logic is unchanged.
No wire change and no change in recipients. Verified byte for byte
against a frozen verbatim copy of the old functions over an input grid
(to one client and broadcast), received messages compared with the old
handlers' read sequences (every optional field of a trade entry,
raw and compressed config blocks) and truncated payloads rejected, hand
computed golden bytes, round trips, and a deliberate width mutation made
the tests fail. ConfirmDonationOnPlayer still accepts a message without
its vendor ID, since the old handler read nothing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
#include "dServer.h"
|
||||
#include "EntityManager.h"
|
||||
#include "GameMessages.h"
|
||||
#include "TradeMessages.h"
|
||||
#include "InventoryComponent.h"
|
||||
#include "ObjectIDManager.h"
|
||||
#include "Game.h"
|
||||
@@ -79,7 +80,10 @@ void Trade::SetAccepted(LWOOBJID participant, bool value) {
|
||||
auto* entityB = GetParticipantBEntity();
|
||||
|
||||
if (entityB != nullptr) {
|
||||
GameMessages::SendServerTradeAccept(m_ParticipantB, value, entityB->GetSystemAddress());
|
||||
GameMessages::ServerTradeAccept accept;
|
||||
accept.target = m_ParticipantB;
|
||||
accept.bFirst = value;
|
||||
accept.Send(entityB->GetSystemAddress());
|
||||
}
|
||||
} else if (participant == m_ParticipantB) {
|
||||
m_AcceptedB = !value;
|
||||
@@ -89,7 +93,10 @@ void Trade::SetAccepted(LWOOBJID participant, bool value) {
|
||||
auto* entityA = GetParticipantAEntity();
|
||||
|
||||
if (entityA != nullptr) {
|
||||
GameMessages::SendServerTradeAccept(m_ParticipantA, value, entityA->GetSystemAddress());
|
||||
GameMessages::ServerTradeAccept accept;
|
||||
accept.target = m_ParticipantA;
|
||||
accept.bFirst = value;
|
||||
accept.Send(entityA->GetSystemAddress());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +104,10 @@ void Trade::SetAccepted(LWOOBJID participant, bool value) {
|
||||
auto* entityB = GetParticipantBEntity();
|
||||
|
||||
if (entityB != nullptr) {
|
||||
GameMessages::SendServerTradeAccept(m_ParticipantB, false, entityB->GetSystemAddress());
|
||||
GameMessages::ServerTradeAccept accept;
|
||||
accept.target = m_ParticipantB;
|
||||
accept.bFirst = false;
|
||||
accept.Send(entityB->GetSystemAddress());
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
@@ -105,7 +115,10 @@ void Trade::SetAccepted(LWOOBJID participant, bool value) {
|
||||
auto* entityA = GetParticipantAEntity();
|
||||
|
||||
if (entityA != nullptr) {
|
||||
GameMessages::SendServerTradeAccept(m_ParticipantA, false, entityA->GetSystemAddress());
|
||||
GameMessages::ServerTradeAccept accept;
|
||||
accept.target = m_ParticipantA;
|
||||
accept.bFirst = false;
|
||||
accept.Send(entityA->GetSystemAddress());
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
@@ -214,8 +227,16 @@ void Trade::Cancel(const LWOOBJID canceller) {
|
||||
|
||||
if (entityA == nullptr || entityB == nullptr) return;
|
||||
|
||||
if (entityA->GetObjectID() != canceller || canceller == LWOOBJID_EMPTY) GameMessages::SendServerTradeCancel(entityA->GetObjectID(), entityA->GetSystemAddress());
|
||||
if (entityB->GetObjectID() != canceller || canceller == LWOOBJID_EMPTY) GameMessages::SendServerTradeCancel(entityB->GetObjectID(), entityB->GetSystemAddress());
|
||||
if (entityA->GetObjectID() != canceller || canceller == LWOOBJID_EMPTY) {
|
||||
GameMessages::ServerTradeCancel cancel;
|
||||
cancel.target = entityA->GetObjectID();
|
||||
cancel.Send(entityA->GetSystemAddress());
|
||||
}
|
||||
if (entityB->GetObjectID() != canceller || canceller == LWOOBJID_EMPTY) {
|
||||
GameMessages::ServerTradeCancel cancel;
|
||||
cancel.target = entityB->GetObjectID();
|
||||
cancel.Send(entityB->GetSystemAddress());
|
||||
}
|
||||
}
|
||||
|
||||
void Trade::SendUpdateToOther(LWOOBJID participant) {
|
||||
@@ -260,7 +281,18 @@ void Trade::SendUpdateToOther(LWOOBJID participant) {
|
||||
|
||||
LOG("Sending trade update");
|
||||
|
||||
GameMessages::SendServerTradeUpdate(other->GetObjectID(), coins, items, other->GetSystemAddress());
|
||||
GameMessages::ServerTradeUpdate update;
|
||||
update.target = other->GetObjectID();
|
||||
update.i64Currency = coins;
|
||||
for (const auto& item : items) {
|
||||
GameMessages::TradeItemEntry entry;
|
||||
entry.key = item.itemId;
|
||||
entry.itemID = item.itemId;
|
||||
entry.templateID = item.itemLot;
|
||||
entry.count = item.itemCount; // the count is always written, even when it is 1
|
||||
update.inventoryMap.push_back(entry);
|
||||
}
|
||||
update.Send(other->GetSystemAddress());
|
||||
}
|
||||
|
||||
const std::unique_ptr<Trade>& TradingManager::GetTrade(LWOOBJID tradeId) const {
|
||||
|
||||
@@ -43,13 +43,13 @@ void AchievementVendorComponent::Buy(Entity* buyer, LOT lot, uint32_t count) {
|
||||
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);
|
||||
SendTransactionResult(buyer->GetObjectID(), buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
auto* inventoryComponent = buyer->GetComponent<InventoryComponent>();
|
||||
if (!inventoryComponent) {
|
||||
GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL);
|
||||
SendTransactionResult(buyer->GetObjectID(), buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -65,12 +65,12 @@ void AchievementVendorComponent::Buy(Entity* buyer, LOT lot, uint32_t count) {
|
||||
|
||||
const uint32_t altCurrencyCost = itemComp.commendationCost * count;
|
||||
if (inventoryComponent->GetLotCount(costLOT) < altCurrencyCost || !inventoryComponent->RemoveItem(costLOT, altCurrencyCost, eInventoryType::ALL)) {
|
||||
GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL);
|
||||
SendTransactionResult(buyer->GetObjectID(), buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
inventoryComponent->AddItem(lot, count, eLootSourceType::VENDOR);
|
||||
GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_SUCCESS);
|
||||
SendTransactionResult(buyer->GetObjectID(), buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_SUCCESS);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
#include "DonationVendorComponent.h"
|
||||
#include "Database.h"
|
||||
#include "CharacterComponent.h"
|
||||
#include "EconomyLedger.h"
|
||||
#include "eInventoryType.h"
|
||||
#include "eMissionTaskType.h"
|
||||
#include "EntityManager.h"
|
||||
#include "Game.h"
|
||||
#include "InventoryComponent.h"
|
||||
#include "Item.h"
|
||||
#include "LeaderboardManager.h"
|
||||
#include "Logger.h"
|
||||
#include "MissionComponent.h"
|
||||
#include "User.h"
|
||||
#include "UserManager.h"
|
||||
|
||||
DonationVendorComponent::DonationVendorComponent(Entity* parent, const int32_t componentID) : VendorComponent(parent, componentID) {
|
||||
//LoadConfigData
|
||||
@@ -46,3 +59,49 @@ void DonationVendorComponent::Serialize(RakNet::BitStream& outBitStream, bool bI
|
||||
if (!bIsInitialUpdate) m_DirtyDonationVendor = false;
|
||||
}
|
||||
}
|
||||
|
||||
void DonationVendorComponent::AddDonationItem(const SystemAddress& sysAddr, LWOOBJID itemObjID, uint32_t count) {
|
||||
if (GetActivityID() == 0) {
|
||||
LOG("WARNING: Trying to dontate to a vendor with no activity");
|
||||
return;
|
||||
}
|
||||
User* user = UserManager::Instance()->GetUser(sysAddr);
|
||||
if (!user) return;
|
||||
Entity* player = Game::entityManager->GetEntity(user->GetLoggedInChar());
|
||||
if (!player) return;
|
||||
auto* characterComponent = player->GetComponent<CharacterComponent>();
|
||||
if (!characterComponent) return;
|
||||
auto* inventoryComponent = player->GetComponent<InventoryComponent>();
|
||||
if (!inventoryComponent) return;
|
||||
Item* item = inventoryComponent->FindItemById(itemObjID);
|
||||
if (!item) return;
|
||||
if (item->GetCount() < count) return;
|
||||
characterComponent->SetCurrentInteracting(m_Parent->GetObjectID());
|
||||
inventoryComponent->MoveItemToInventory(item, eInventoryType::DONATION, count, true, false, true);
|
||||
}
|
||||
|
||||
void DonationVendorComponent::ConfirmDonation(Entity& player) {
|
||||
const auto [inventoryComponent, missionComponent, characterComponent] = player.GetComponentsMut<InventoryComponent, MissionComponent, CharacterComponent>();
|
||||
if (!inventoryComponent || !missionComponent || !characterComponent || !characterComponent->GetCurrentInteracting()) return;
|
||||
|
||||
if (GetActivityID() == 0) {
|
||||
LOG("WARNING: Trying to dontate to a vendor with no activity");
|
||||
return;
|
||||
}
|
||||
auto* inventory = inventoryComponent->GetInventory(eInventoryType::DONATION);
|
||||
if (!inventory) return;
|
||||
auto items = inventory->GetItems();
|
||||
if (!items.empty()) {
|
||||
uint32_t count = 0;
|
||||
for (auto& [itemID, item] : items) {
|
||||
count += item->GetCount();
|
||||
EconomyLedger::RecordItemsUnsuppressed(&player, item->GetLot(), -static_cast<int64_t>(item->GetCount()), EconomyLedger::DONATION_SOURCE);
|
||||
item->RemoveFromInventory();
|
||||
}
|
||||
missionComponent->Progress(eMissionTaskType::DONATION, 0, LWOOBJID_EMPTY, "", count);
|
||||
LeaderboardManager::SaveScore(player.GetObjectID(), GetActivityID(), count);
|
||||
SubmitDonation(count);
|
||||
Game::entityManager->SerializeEntity(m_Parent);
|
||||
}
|
||||
characterComponent->SetCurrentInteracting(LWOOBJID_EMPTY);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,12 @@ public:
|
||||
uint32_t GetActivityID() {return m_ActivityId;};
|
||||
void SubmitDonation(uint32_t count);
|
||||
|
||||
// AddDonationItem from the player at sysAddr: moves count of the item into their donation inventory.
|
||||
void AddDonationItem(const SystemAddress& sysAddr, LWOOBJID itemObjID, uint32_t count);
|
||||
|
||||
// ConfirmDonationOnPlayer: the player donates everything in their donation inventory to this vendor.
|
||||
void ConfirmDonation(Entity& player);
|
||||
|
||||
private:
|
||||
bool m_DirtyDonationVendor = false;
|
||||
float m_PercentComplete = 0.0;
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
#include "eVendorTransactionResult.h"
|
||||
#include "UserManager.h"
|
||||
#include "CheatDetection.h"
|
||||
#include "VendorMessages.h"
|
||||
#include "Item.h"
|
||||
|
||||
VendorComponent::VendorComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
|
||||
m_HasStandardCostItems = false;
|
||||
@@ -31,8 +33,13 @@ void VendorComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitial
|
||||
}
|
||||
|
||||
void VendorComponent::OnUse(Entity* originator) {
|
||||
GameMessages::SendVendorOpenWindow(m_Parent, originator->GetSystemAddress());
|
||||
GameMessages::SendVendorStatusUpdate(m_Parent, originator->GetSystemAddress());
|
||||
GameMessages::VendorOpenWindow openWindow;
|
||||
openWindow.target = m_Parent->GetObjectID();
|
||||
openWindow.SendToClient(originator->GetSystemAddress());
|
||||
|
||||
// Only the entity's VENDOR component sends its stock (achievement and donation vendors never did).
|
||||
auto* vendor = static_cast<VendorComponent*>(m_Parent->GetComponent(eReplicaComponentType::VENDOR));
|
||||
if (vendor) vendor->SendStatusUpdate(originator->GetSystemAddress());
|
||||
}
|
||||
|
||||
void VendorComponent::RefreshInventory(bool isCreation) {
|
||||
@@ -90,7 +97,9 @@ void VendorComponent::RefreshInventory(bool isCreation) {
|
||||
});
|
||||
}
|
||||
Game::entityManager->SerializeEntity(m_Parent);
|
||||
GameMessages::SendVendorStatusUpdate(m_Parent, UNASSIGNED_SYSTEM_ADDRESS);
|
||||
// Only the entity's VENDOR component sends its stock (achievement and donation vendors never did).
|
||||
auto* vendor = static_cast<VendorComponent*>(m_Parent->GetComponent(eReplicaComponentType::VENDOR));
|
||||
if (vendor) vendor->SendStatusUpdate(UNASSIGNED_SYSTEM_ADDRESS);
|
||||
}
|
||||
|
||||
void VendorComponent::SetupConstants() {
|
||||
@@ -142,13 +151,13 @@ 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);
|
||||
SendTransactionResult(buyer->GetObjectID(), buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
auto* inventoryComponent = buyer->GetComponent<InventoryComponent>();
|
||||
if (!inventoryComponent) {
|
||||
GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL);
|
||||
SendTransactionResult(buyer->GetObjectID(), buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL);
|
||||
return;
|
||||
}
|
||||
CDComponentsRegistryTable* compRegistryTable = CDClientManager::GetTable<CDComponentsRegistryTable>();
|
||||
@@ -160,7 +169,7 @@ void VendorComponent::Buy(Entity* buyer, LOT lot, uint32_t count) {
|
||||
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);
|
||||
SendTransactionResult(buyer->GetObjectID(), buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -172,7 +181,7 @@ void VendorComponent::Buy(Entity* buyer, LOT lot, uint32_t count) {
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL);
|
||||
SendTransactionResult(buyer->GetObjectID(), buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -181,14 +190,14 @@ void VendorComponent::Buy(Entity* buyer, LOT lot, uint32_t count) {
|
||||
|
||||
Character* character = buyer->GetCharacter();
|
||||
if (!character || character->GetCoins() < coinCost) {
|
||||
GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL);
|
||||
SendTransactionResult(buyer->GetObjectID(), 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);
|
||||
SendTransactionResult(buyer->GetObjectID(), buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL);
|
||||
return;
|
||||
}
|
||||
inventoryComponent->RemoveItem(itemComp.currencyLOT, altCurrencyCost, eInventoryType::ALL);
|
||||
@@ -196,7 +205,7 @@ void VendorComponent::Buy(Entity* buyer, LOT lot, uint32_t count) {
|
||||
|
||||
character->SetCoins(character->GetCoins() - (coinCost), eLootSourceType::VENDOR);
|
||||
inventoryComponent->AddItem(lot, count, eLootSourceType::VENDOR);
|
||||
GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_SUCCESS);
|
||||
SendTransactionResult(buyer->GetObjectID(), buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_SUCCESS);
|
||||
}
|
||||
|
||||
bool VendorComponent::SetupItem(LOT item) {
|
||||
@@ -219,3 +228,90 @@ bool VendorComponent::SetupItem(LOT item) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void VendorComponent::SendStatusUpdate(const SystemAddress& sysAddr, bool bUpdateOnly) {
|
||||
GameMessages::VendorStatusUpdate statusUpdate;
|
||||
statusUpdate.target = m_Parent->GetObjectID();
|
||||
statusUpdate.bUpdateOnly = bUpdateOnly;
|
||||
statusUpdate.inventoryList.reserve(m_Inventory.size());
|
||||
for (const auto& item : m_Inventory) statusUpdate.inventoryList.push_back({ item.lot, item.sortPriority });
|
||||
statusUpdate.Send(sysAddr);
|
||||
}
|
||||
|
||||
void VendorComponent::SendTransactionResult(LWOOBJID target, const SystemAddress& sysAddr, eVendorTransactionResult result) {
|
||||
GameMessages::VendorTransactionResult transactionResult;
|
||||
transactionResult.target = target;
|
||||
transactionResult.iResult = result;
|
||||
transactionResult.SendToClient(sysAddr);
|
||||
}
|
||||
|
||||
void VendorComponent::SellToVendor(Entity& player, const SystemAddress& sysAddr, LWOOBJID itemObjID, int32_t count) {
|
||||
Character* character = player.GetCharacter();
|
||||
if (!character) return;
|
||||
InventoryComponent* inv = static_cast<InventoryComponent*>(player.GetComponent(eReplicaComponentType::INVENTORY));
|
||||
if (!inv) return;
|
||||
|
||||
Item* item = inv->FindItemById(itemObjID);
|
||||
if (!item) return;
|
||||
|
||||
CDComponentsRegistryTable* compRegistryTable = CDClientManager::GetTable<CDComponentsRegistryTable>();
|
||||
CDItemComponentTable* itemComponentTable = CDClientManager::GetTable<CDItemComponentTable>();
|
||||
|
||||
int itemCompID = compRegistryTable->GetByIDAndType(item->GetLot(), eReplicaComponentType::ITEM);
|
||||
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) {
|
||||
SendTransactionResult(m_Parent->GetObjectID(), sysAddr, eVendorTransactionResult::SELL_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
float sellScalar = GetSellScalar();
|
||||
if (Inventory::IsValidItem(itemComp.currencyLOT)) {
|
||||
const auto altCurrency = static_cast<uint32_t>(itemComp.altCurrencyCost * sellScalar) * count;
|
||||
inv->AddItem(itemComp.currencyLOT, std::floor(altCurrency), eLootSourceType::VENDOR); // Return alt currencies like faction tokens.
|
||||
}
|
||||
|
||||
inv->MoveItemToInventory(item, eInventoryType::VENDOR_BUYBACK, count, true, false, true);
|
||||
character->SetCoins(std::floor(character->GetCoins() + (static_cast<uint32_t>(itemComp.baseValue * sellScalar) * count)), eLootSourceType::VENDOR);
|
||||
SendTransactionResult(m_Parent->GetObjectID(), sysAddr, eVendorTransactionResult::SELL_SUCCESS);
|
||||
}
|
||||
|
||||
void VendorComponent::BuybackFromVendor(Entity& player, const SystemAddress& sysAddr, LWOOBJID itemObjID, int32_t count) {
|
||||
Character* character = player.GetCharacter();
|
||||
if (!character) return;
|
||||
InventoryComponent* inv = static_cast<InventoryComponent*>(player.GetComponent(eReplicaComponentType::INVENTORY));
|
||||
if (!inv) return;
|
||||
|
||||
Item* item = inv->FindItemById(itemObjID);
|
||||
if (!item) return;
|
||||
|
||||
CDComponentsRegistryTable* compRegistryTable = CDClientManager::GetTable<CDComponentsRegistryTable>();
|
||||
CDItemComponentTable* itemComponentTable = CDClientManager::GetTable<CDItemComponentTable>();
|
||||
|
||||
int itemCompID = compRegistryTable->GetByIDAndType(item->GetLot(), eReplicaComponentType::ITEM);
|
||||
CDItemComponent itemComp = itemComponentTable->GetItemComponentByID(itemCompID);
|
||||
|
||||
float sellScalar = GetSellScalar();
|
||||
|
||||
const auto cost = static_cast<uint32_t>(std::floor(((itemComp.baseValue * sellScalar) * count)));
|
||||
|
||||
if (character->GetCoins() < cost) {
|
||||
SendTransactionResult(m_Parent->GetObjectID(), 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 || !inv->RemoveItem(itemComp.currencyLOT, altCurrencyCost, eInventoryType::ALL)) {
|
||||
SendTransactionResult(m_Parent->GetObjectID(), sysAddr, eVendorTransactionResult::PURCHASE_FAIL);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//inv->RemoveItem(count, -1, iObjID);
|
||||
inv->MoveItemToInventory(item, Inventory::FindInventoryTypeForLot(item->GetLot()), count, true, false);
|
||||
character->SetCoins(character->GetCoins() - cost, eLootSourceType::VENDOR);
|
||||
//Game::entityManager->SerializeEntity(player); // so inventory updates
|
||||
SendTransactionResult(m_Parent->GetObjectID(), sysAddr, eVendorTransactionResult::PURCHASE_SUCCESS);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#include "RakNetTypes.h"
|
||||
#include "eReplicaComponentType.h"
|
||||
|
||||
enum class eVendorTransactionResult : uint32_t;
|
||||
|
||||
struct SoldItem {
|
||||
SoldItem(const LOT lot, const int32_t sortPriority) {
|
||||
this->lot = lot;
|
||||
@@ -49,6 +51,16 @@ public:
|
||||
|
||||
void Buy(Entity* buyer, LOT lot, uint32_t count);
|
||||
|
||||
// SellToVendor / BuybackFromVendor from player (sent from sysAddr), for the item with object ID itemObjID.
|
||||
void SellToVendor(Entity& player, const SystemAddress& sysAddr, LWOOBJID itemObjID, int32_t count);
|
||||
void BuybackFromVendor(Entity& player, const SystemAddress& sysAddr, LWOOBJID itemObjID, int32_t count);
|
||||
|
||||
// Sends this vendor's stock (VendorStatusUpdate) to sysAddr; UNASSIGNED_SYSTEM_ADDRESS broadcasts.
|
||||
void SendStatusUpdate(const SystemAddress& sysAddr, bool bUpdateOnly = false);
|
||||
|
||||
// Sends a VendorTransactionResult targeting target to sysAddr only.
|
||||
static void SendTransactionResult(LWOOBJID target, const SystemAddress& sysAddr, eVendorTransactionResult result);
|
||||
|
||||
private:
|
||||
void HandleMrReeCameras();
|
||||
bool SetupItem(LOT item);
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
set(DGAME_DGAMEMESSAGES_SOURCES
|
||||
"ActivityMessages.cpp"
|
||||
"BuildingMessages.cpp"
|
||||
"EffectsMessages.cpp"
|
||||
"GameMessageDecoder.cpp"
|
||||
"GameMessageHandler.cpp"
|
||||
"EffectsMessages.cpp"
|
||||
"GameMessages.cpp"
|
||||
"InventoryMessages.cpp"
|
||||
"MissionMessages.cpp"
|
||||
"PetMessages.cpp"
|
||||
"PropertyMessages.cpp"
|
||||
"RacingMessages.cpp")
|
||||
"RacingMessages.cpp"
|
||||
"TradeMessages.cpp"
|
||||
"VendorMessages.cpp")
|
||||
|
||||
add_library(dGameMessages OBJECT ${DGAME_DGAMEMESSAGES_SOURCES})
|
||||
target_link_libraries(dGameMessages
|
||||
|
||||
@@ -35,6 +35,8 @@
|
||||
#include "BuildingMessages.h"
|
||||
#include "RacingMessages.h"
|
||||
#include "MissionMessages.h"
|
||||
#include "TradeMessages.h"
|
||||
#include "VendorMessages.h"
|
||||
#include "EffectsMessages.h"
|
||||
#include "InventoryMessages.h"
|
||||
#include "PetMessages.h"
|
||||
@@ -139,6 +141,22 @@ namespace {
|
||||
{ UN_USE_BBB_MODEL, []() { return std::make_unique<UnUseBBBModel>(); } },
|
||||
{ BBB_LOAD_ITEM_REQUEST, []() { return std::make_unique<BBBLoadItemRequest>(); } },
|
||||
{ BBB_SAVE_REQUEST, []() { return std::make_unique<BBBSaveRequest>(); } },
|
||||
|
||||
// Vendors and donation vendors
|
||||
{ REQUEST_VENDOR_STATUS_UPDATE, []() { return std::make_unique<RequestVendorStatusUpdate>(); } },
|
||||
{ BUY_FROM_VENDOR, []() { return std::make_unique<BuyFromVendor>(); } },
|
||||
{ SELL_TO_VENDOR, []() { return std::make_unique<SellToVendor>(); } },
|
||||
{ BUYBACK_FROM_VENDOR, []() { return std::make_unique<BuybackFromVendor>(); } },
|
||||
{ ADD_DONATION_ITEM, []() { return std::make_unique<AddDonationItem>(); } },
|
||||
{ REMOVE_DONATION_ITEM, []() { return std::make_unique<RemoveDonationItem>(); } },
|
||||
{ CONFIRM_DONATION_ON_PLAYER, []() { return std::make_unique<ConfirmDonationOnPlayer>(); } },
|
||||
{ CANCEL_DONATION_ON_PLAYER, []() { return std::make_unique<CancelDonationOnPlayer>(); } },
|
||||
|
||||
// Trading
|
||||
{ CLIENT_TRADE_REQUEST, []() { return std::make_unique<ClientTradeRequest>(); } },
|
||||
{ CLIENT_TRADE_CANCEL, []() { return std::make_unique<ClientTradeCancel>(); } },
|
||||
{ CLIENT_TRADE_ACCEPT, []() { return std::make_unique<ClientTradeAccept>(); } },
|
||||
{ CLIENT_TRADE_UPDATE, []() { return std::make_unique<ClientTradeUpdate>(); } },
|
||||
};
|
||||
};
|
||||
|
||||
@@ -464,18 +482,6 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
|
||||
entity->Smash(entity->GetObjectID());
|
||||
break;
|
||||
|
||||
case MessageType::Game::BUY_FROM_VENDOR:
|
||||
GameMessages::HandleBuyFromVendor(inStream, entity, sysAddr);
|
||||
break;
|
||||
|
||||
case MessageType::Game::SELL_TO_VENDOR:
|
||||
GameMessages::HandleSellToVendor(inStream, entity, sysAddr);
|
||||
break;
|
||||
|
||||
case MessageType::Game::BUYBACK_FROM_VENDOR:
|
||||
GameMessages::HandleBuybackFromVendor(inStream, entity, sysAddr);
|
||||
break;
|
||||
|
||||
case MessageType::Game::REBUILD_CANCEL:
|
||||
GameMessages::HandleQuickBuildCancel(inStream, entity);
|
||||
break;
|
||||
@@ -489,20 +495,6 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
|
||||
break;
|
||||
|
||||
// Trading
|
||||
case MessageType::Game::CLIENT_TRADE_REQUEST:
|
||||
GameMessages::HandleClientTradeRequest(inStream, entity, sysAddr);
|
||||
break;
|
||||
case MessageType::Game::CLIENT_TRADE_CANCEL:
|
||||
GameMessages::HandleClientTradeCancel(inStream, entity, sysAddr);
|
||||
break;
|
||||
case MessageType::Game::CLIENT_TRADE_ACCEPT:
|
||||
GameMessages::HandleClientTradeAccept(inStream, entity, sysAddr);
|
||||
break;
|
||||
case MessageType::Game::CLIENT_TRADE_UPDATE:
|
||||
GameMessages::HandleClientTradeUpdate(inStream, entity, sysAddr);
|
||||
break;
|
||||
|
||||
// Racing: most racing messages are registered in g_MessageHandlers
|
||||
case MessageType::Game::ACKNOWLEDGE_POSSESSION:
|
||||
GameMessages::HandleAcknowledgePossession(inStream, entity, sysAddr);
|
||||
break;
|
||||
@@ -565,21 +557,6 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
|
||||
case MessageType::Game::ZONE_SUMMARY_DISMISSED:
|
||||
GameMessages::HandleZoneSummaryDismissed(inStream, entity);
|
||||
break;
|
||||
case MessageType::Game::ADD_DONATION_ITEM:
|
||||
GameMessages::HandleAddDonationItem(inStream, entity, sysAddr);
|
||||
break;
|
||||
case MessageType::Game::REMOVE_DONATION_ITEM:
|
||||
GameMessages::HandleRemoveDonationItem(inStream, entity, sysAddr);
|
||||
break;
|
||||
case MessageType::Game::CONFIRM_DONATION_ON_PLAYER:
|
||||
GameMessages::HandleConfirmDonationOnPlayer(inStream, entity);
|
||||
break;
|
||||
case MessageType::Game::CANCEL_DONATION_ON_PLAYER:
|
||||
GameMessages::HandleCancelDonationOnPlayer(inStream, entity);
|
||||
break;
|
||||
case MessageType::Game::REQUEST_VENDOR_STATUS_UPDATE:
|
||||
GameMessages::SendVendorStatusUpdate(entity, sysAddr, true);
|
||||
break;
|
||||
default:
|
||||
LOG_DEBUG("Received Unknown GM with ID: %4i, %s", messageID, StringifiedEnum::ToString(messageID).data());
|
||||
break;
|
||||
|
||||
@@ -710,53 +710,6 @@ void GameMessages::SendRemoveSkill(Entity* entity, TSkillID skillID) {
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void GameMessages::SendVendorOpenWindow(Entity* entity, const SystemAddress& sysAddr) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(entity->GetObjectID());
|
||||
bitStream.Write(MessageType::Game::VENDOR_OPEN_WINDOW);
|
||||
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void GameMessages::SendVendorStatusUpdate(Entity* entity, const SystemAddress& sysAddr, bool bUpdateOnly) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
|
||||
VendorComponent* vendor = static_cast<VendorComponent*>(entity->GetComponent(eReplicaComponentType::VENDOR));
|
||||
if (!vendor) return;
|
||||
|
||||
auto vendorItems = vendor->GetInventory();
|
||||
|
||||
bitStream.Write(entity->GetObjectID());
|
||||
bitStream.Write(MessageType::Game::VENDOR_STATUS_UPDATE);
|
||||
|
||||
bitStream.Write(bUpdateOnly);
|
||||
bitStream.Write<uint32_t>(vendorItems.size());
|
||||
|
||||
|
||||
for (const auto& item : vendorItems) {
|
||||
bitStream.Write(item.lot);
|
||||
bitStream.Write(item.sortPriority);
|
||||
}
|
||||
|
||||
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void GameMessages::SendVendorTransactionResult(Entity* entity, const SystemAddress& sysAddr, eVendorTransactionResult result) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
|
||||
|
||||
bitStream.Write(entity->GetObjectID());
|
||||
bitStream.Write(MessageType::Game::VENDOR_TRANSACTION_RESULT);
|
||||
bitStream.Write(result);
|
||||
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void GameMessages::SendMatchResponse(Entity* entity, const SystemAddress& sysAddr, int response) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
@@ -1405,264 +1358,6 @@ void GameMessages::SendTeamPickupItem(LWOOBJID objectId, LWOOBJID lootID, LWOOBJ
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
//Trading:
|
||||
|
||||
void GameMessages::SendServerTradeInvite(LWOOBJID objectId, bool bNeedInvitePopUp, LWOOBJID i64Requestor, std::u16string wsName, const SystemAddress& sysAddr) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(objectId);
|
||||
bitStream.Write(MessageType::Game::SERVER_TRADE_INVITE);
|
||||
|
||||
bitStream.Write(bNeedInvitePopUp);
|
||||
bitStream.Write(i64Requestor);
|
||||
bitStream.Write<uint32_t>(wsName.size());
|
||||
for (const auto character : wsName) {
|
||||
bitStream.Write(character);
|
||||
}
|
||||
|
||||
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void GameMessages::SendServerTradeInitialReply(LWOOBJID objectId, LWOOBJID i64Invitee, int32_t resultType, std::u16string wsName, const SystemAddress& sysAddr) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(objectId);
|
||||
bitStream.Write(MessageType::Game::SERVER_TRADE_INITIAL_REPLY);
|
||||
|
||||
bitStream.Write(i64Invitee);
|
||||
bitStream.Write(resultType);
|
||||
bitStream.Write<uint32_t>(wsName.size());
|
||||
for (const auto character : wsName) {
|
||||
bitStream.Write(character);
|
||||
}
|
||||
|
||||
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void GameMessages::SendServerTradeFinalReply(LWOOBJID objectId, bool bResult, LWOOBJID i64Invitee, std::u16string wsName, const SystemAddress& sysAddr) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(objectId);
|
||||
bitStream.Write(MessageType::Game::SERVER_TRADE_FINAL_REPLY);
|
||||
|
||||
bitStream.Write(bResult);
|
||||
bitStream.Write(i64Invitee);
|
||||
bitStream.Write<uint32_t>(wsName.size());
|
||||
for (const auto character : wsName) {
|
||||
bitStream.Write(character);
|
||||
}
|
||||
|
||||
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void GameMessages::SendServerTradeAccept(LWOOBJID objectId, bool bFirst, const SystemAddress& sysAddr) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(objectId);
|
||||
bitStream.Write(MessageType::Game::SERVER_TRADE_ACCEPT);
|
||||
|
||||
bitStream.Write(bFirst);
|
||||
|
||||
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void GameMessages::SendServerTradeCancel(LWOOBJID objectId, const SystemAddress& sysAddr) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(objectId);
|
||||
bitStream.Write(MessageType::Game::SERVER_TRADE_CANCEL);
|
||||
|
||||
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void GameMessages::SendServerTradeUpdate(LWOOBJID objectId, uint64_t coins, const std::vector<TradeItem>& items, const SystemAddress& sysAddr) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(objectId);
|
||||
bitStream.Write(MessageType::Game::SERVER_TRADE_UPDATE);
|
||||
|
||||
bitStream.Write(false);
|
||||
bitStream.Write(coins);
|
||||
bitStream.Write<uint32_t>(items.size());
|
||||
|
||||
for (const auto& item : items) {
|
||||
bitStream.Write(item.itemId);
|
||||
bitStream.Write(item.itemId);
|
||||
|
||||
bitStream.Write(item.itemLot);
|
||||
bitStream.Write0();
|
||||
bitStream.Write1();
|
||||
bitStream.Write(item.itemCount);
|
||||
bitStream.Write0();
|
||||
bitStream.Write0();
|
||||
bitStream.Write0();
|
||||
bitStream.Write0();
|
||||
}
|
||||
|
||||
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void GameMessages::HandleClientTradeRequest(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) {
|
||||
// Check if the player has restricted trade access
|
||||
auto* character = entity->GetCharacter();
|
||||
const bool restrictTradeOnMute = UserManager::Instance()->GetMuteRestrictTrade();
|
||||
|
||||
if (character->HasPermission(ePermissionMap::RestrictedTradeAccess) || (restrictTradeOnMute && character->GetParentUser()->GetIsMuted())) {
|
||||
// Send a message to the player
|
||||
ChatPackets::SendSystemMessage(
|
||||
sysAddr,
|
||||
u"Your character has restricted trade access."
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
bool bNeedInvitePopUp = inStream.ReadBit();
|
||||
LWOOBJID i64Invitee;
|
||||
|
||||
inStream.Read(i64Invitee);
|
||||
|
||||
auto* invitee = Game::entityManager->GetEntity(i64Invitee);
|
||||
|
||||
if (invitee != nullptr && invitee->IsPlayer()) {
|
||||
character = invitee->GetCharacter();
|
||||
|
||||
if (character->HasPermission(ePermissionMap::RestrictedTradeAccess) || (restrictTradeOnMute && character->GetParentUser()->GetIsMuted())) {
|
||||
// Send a message to the player
|
||||
ChatPackets::SendSystemMessage(
|
||||
sysAddr,
|
||||
u"The character you are trying to trade with has restricted trade access."
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
LOG("Trade request to (%llu)", i64Invitee);
|
||||
|
||||
const auto& trade = TradingManager::Instance()->GetPlayerTrade(entity->GetObjectID());
|
||||
|
||||
if (trade != nullptr) {
|
||||
if (!trade->IsParticipant(i64Invitee)) {
|
||||
TradingManager::Instance()->CancelTrade(entity->GetObjectID(), trade->GetTradeId());
|
||||
|
||||
TradingManager::Instance()->NewTrade(entity->GetObjectID(), i64Invitee);
|
||||
}
|
||||
} else {
|
||||
TradingManager::Instance()->NewTrade(entity->GetObjectID(), i64Invitee);
|
||||
}
|
||||
|
||||
SendServerTradeInvite(
|
||||
i64Invitee,
|
||||
bNeedInvitePopUp,
|
||||
entity->GetObjectID(),
|
||||
GeneralUtils::UTF8ToUTF16(entity->GetCharacter()->GetName()),
|
||||
invitee->GetSystemAddress()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void GameMessages::HandleClientTradeCancel(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) {
|
||||
const auto& trade = TradingManager::Instance()->GetPlayerTrade(entity->GetObjectID());
|
||||
|
||||
if (trade == nullptr) return;
|
||||
|
||||
LOG("Trade canceled from (%llu)", entity->GetObjectID());
|
||||
|
||||
TradingManager::Instance()->CancelTrade(entity->GetObjectID(), trade->GetTradeId());
|
||||
}
|
||||
|
||||
void GameMessages::HandleClientTradeAccept(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) {
|
||||
bool bFirst = inStream.ReadBit();
|
||||
|
||||
LOG("Trade accepted from (%llu) -> (%d)", entity->GetObjectID(), bFirst);
|
||||
|
||||
const auto& trade = TradingManager::Instance()->GetPlayerTrade(entity->GetObjectID());
|
||||
|
||||
if (trade == nullptr) return;
|
||||
|
||||
trade->SetAccepted(entity->GetObjectID(), bFirst);
|
||||
}
|
||||
|
||||
void GameMessages::HandleClientTradeUpdate(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) {
|
||||
uint64_t currency;
|
||||
uint32_t itemCount;
|
||||
|
||||
inStream.Read(currency);
|
||||
inStream.Read(itemCount);
|
||||
if (itemCount > MAX_MESSAGE_LENGTH) return;
|
||||
|
||||
LOG("Trade update from (%llu) -> (%llu), (%i)", entity->GetObjectID(), currency, itemCount);
|
||||
|
||||
std::vector<TradeItem> items{};
|
||||
|
||||
for (size_t i = 0; i < itemCount; i++) {
|
||||
LWOOBJID itemId;
|
||||
LWOOBJID itemId2;
|
||||
|
||||
inStream.Read(itemId);
|
||||
inStream.Read(itemId2);
|
||||
|
||||
LOT lot = 0;
|
||||
LWOOBJID unknown1 = 0;
|
||||
uint32_t unknown2 = 0;
|
||||
uint16_t slot = 0;
|
||||
uint32_t unknown3 = 0;
|
||||
uint32_t ldfSize = 0;
|
||||
bool unknown4;
|
||||
|
||||
inStream.Read(lot);
|
||||
if (inStream.ReadBit()) {
|
||||
inStream.Read(unknown1);
|
||||
}
|
||||
if (inStream.ReadBit()) {
|
||||
inStream.Read(unknown2);
|
||||
}
|
||||
if (inStream.ReadBit()) {
|
||||
inStream.Read(slot);
|
||||
}
|
||||
if (inStream.ReadBit()) {
|
||||
inStream.Read(unknown3);
|
||||
}
|
||||
if (inStream.ReadBit()) // No
|
||||
{
|
||||
inStream.Read(ldfSize);
|
||||
bool compressed = inStream.ReadBit();
|
||||
if (compressed) {
|
||||
uint32_t ldfCompressedSize = 0;
|
||||
inStream.Read(ldfCompressedSize);
|
||||
inStream.IgnoreBytes(ldfCompressedSize);
|
||||
} else {
|
||||
inStream.IgnoreBytes(ldfSize);
|
||||
}
|
||||
}
|
||||
unknown4 = inStream.ReadBit();
|
||||
|
||||
items.push_back({ itemId, lot, unknown2 });
|
||||
|
||||
LOG("Trade item from (%llu) -> (%llu)/(%llu), (%i), (%llu), (%i), (%i)", entity->GetObjectID(), itemId, itemId2, lot, unknown1, unknown2, unknown3);
|
||||
}
|
||||
|
||||
const auto& trade = TradingManager::Instance()->GetPlayerTrade(entity->GetObjectID());
|
||||
|
||||
if (trade == nullptr) return;
|
||||
|
||||
trade->SetCoins(entity->GetObjectID(), currency);
|
||||
trade->SetItems(entity->GetObjectID(), items);
|
||||
trade->SendUpdateToOther(entity->GetObjectID());
|
||||
}
|
||||
|
||||
//Pets:
|
||||
|
||||
void GameMessages::SendRemoveBuff(Entity* entity, bool fromUnEquip, bool removeImmunity, uint32_t buffId) {
|
||||
@@ -1966,149 +1661,6 @@ void GameMessages::HandleSetGhostReferencePosition(RakNet::BitStream& inStream,
|
||||
}
|
||||
|
||||
|
||||
void GameMessages::HandleBuyFromVendor(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) {
|
||||
bool bConfirmed{}; // This doesn't appear to do anything. Further research is needed.
|
||||
bool countIsDefault{};
|
||||
int count = 1;
|
||||
LOT item;
|
||||
|
||||
inStream.Read(bConfirmed);
|
||||
inStream.Read(countIsDefault);
|
||||
if (countIsDefault) inStream.Read(count);
|
||||
inStream.Read(item);
|
||||
|
||||
User* user = UserManager::Instance()->GetUser(sysAddr);
|
||||
if (!user) return;
|
||||
Entity* player = Game::entityManager->GetEntity(user->GetLoggedInChar());
|
||||
if (!player) return;
|
||||
|
||||
// handle buying normal items
|
||||
auto* vendorComponent = entity->GetComponent<VendorComponent>();
|
||||
if (vendorComponent) {
|
||||
vendorComponent->Buy(player, item, count);
|
||||
return;
|
||||
}
|
||||
|
||||
// handle buying achievement items
|
||||
auto* achievementVendorComponent = entity->GetComponent<AchievementVendorComponent>();
|
||||
if (achievementVendorComponent) {
|
||||
achievementVendorComponent->Buy(player, item, count);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle buying properties
|
||||
auto* propertyVendorComponent = entity->GetComponent<PropertyVendorComponent>();
|
||||
if (propertyVendorComponent) {
|
||||
propertyVendorComponent->OnBuyFromVendor(player, bConfirmed, item, count);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void GameMessages::HandleSellToVendor(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) {
|
||||
bool countIsDefault{};
|
||||
int count = 1;
|
||||
LWOOBJID iObjID;
|
||||
|
||||
inStream.Read(countIsDefault);
|
||||
if (countIsDefault) inStream.Read(count);
|
||||
inStream.Read(iObjID);
|
||||
|
||||
User* user = UserManager::Instance()->GetUser(sysAddr);
|
||||
if (!user) return;
|
||||
Entity* player = Game::entityManager->GetEntity(user->GetLoggedInChar());
|
||||
if (!player) return;
|
||||
Character* character = player->GetCharacter();
|
||||
if (!character) return;
|
||||
InventoryComponent* inv = static_cast<InventoryComponent*>(player->GetComponent(eReplicaComponentType::INVENTORY));
|
||||
if (!inv) return;
|
||||
|
||||
VendorComponent* vend = static_cast<VendorComponent*>(entity->GetComponent(eReplicaComponentType::VENDOR));
|
||||
if (!vend) return;
|
||||
|
||||
Item* item = inv->FindItemById(iObjID);
|
||||
if (!item) return;
|
||||
|
||||
CDComponentsRegistryTable* compRegistryTable = CDClientManager::GetTable<CDComponentsRegistryTable>();
|
||||
CDItemComponentTable* itemComponentTable = CDClientManager::GetTable<CDItemComponentTable>();
|
||||
|
||||
int itemCompID = compRegistryTable->GetByIDAndType(item->GetLot(), eReplicaComponentType::ITEM);
|
||||
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) {
|
||||
GameMessages::SendVendorTransactionResult(entity, sysAddr, eVendorTransactionResult::SELL_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
float sellScalar = vend->GetSellScalar();
|
||||
if (Inventory::IsValidItem(itemComp.currencyLOT)) {
|
||||
const auto altCurrency = static_cast<uint32_t>(itemComp.altCurrencyCost * sellScalar) * count;
|
||||
inv->AddItem(itemComp.currencyLOT, std::floor(altCurrency), eLootSourceType::VENDOR); // Return alt currencies like faction tokens.
|
||||
}
|
||||
|
||||
inv->MoveItemToInventory(item, eInventoryType::VENDOR_BUYBACK, count, true, false, true);
|
||||
character->SetCoins(std::floor(character->GetCoins() + (static_cast<uint32_t>(itemComp.baseValue * sellScalar) * count)), eLootSourceType::VENDOR);
|
||||
GameMessages::SendVendorTransactionResult(entity, sysAddr, eVendorTransactionResult::SELL_SUCCESS);
|
||||
}
|
||||
|
||||
void GameMessages::HandleBuybackFromVendor(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) {
|
||||
bool confirmed = false;
|
||||
bool countIsDefault{};
|
||||
int count = 1;
|
||||
LWOOBJID iObjID;
|
||||
|
||||
inStream.Read(confirmed);
|
||||
inStream.Read(countIsDefault);
|
||||
if (countIsDefault) inStream.Read(count);
|
||||
inStream.Read(iObjID);
|
||||
|
||||
//if (!confirmed) return; they always built in this confirmed garbage... but never used it?
|
||||
|
||||
User* user = UserManager::Instance()->GetUser(sysAddr);
|
||||
if (!user) return;
|
||||
Entity* player = Game::entityManager->GetEntity(user->GetLoggedInChar());
|
||||
if (!player) return;
|
||||
Character* character = player->GetCharacter();
|
||||
if (!character) return;
|
||||
InventoryComponent* inv = static_cast<InventoryComponent*>(player->GetComponent(eReplicaComponentType::INVENTORY));
|
||||
if (!inv) return;
|
||||
|
||||
VendorComponent* vend = static_cast<VendorComponent*>(entity->GetComponent(eReplicaComponentType::VENDOR));
|
||||
if (!vend) return;
|
||||
|
||||
Item* item = inv->FindItemById(iObjID);
|
||||
if (!item) return;
|
||||
|
||||
CDComponentsRegistryTable* compRegistryTable = CDClientManager::GetTable<CDComponentsRegistryTable>();
|
||||
CDItemComponentTable* itemComponentTable = CDClientManager::GetTable<CDItemComponentTable>();
|
||||
|
||||
int itemCompID = compRegistryTable->GetByIDAndType(item->GetLot(), eReplicaComponentType::ITEM);
|
||||
CDItemComponent itemComp = itemComponentTable->GetItemComponentByID(itemCompID);
|
||||
|
||||
float sellScalar = vend->GetSellScalar();
|
||||
|
||||
const auto cost = static_cast<uint32_t>(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 || !inv->RemoveItem(itemComp.currencyLOT, altCurrencyCost, eInventoryType::ALL)) {
|
||||
GameMessages::SendVendorTransactionResult(entity, sysAddr, eVendorTransactionResult::PURCHASE_FAIL);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//inv->RemoveItem(count, -1, iObjID);
|
||||
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, eVendorTransactionResult::PURCHASE_SUCCESS);
|
||||
}
|
||||
|
||||
void GameMessages::HandleParseChatMessage(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) {
|
||||
std::u16string wsString;
|
||||
int iClientState;
|
||||
@@ -2603,105 +2155,6 @@ void GameMessages::HandleZoneSummaryDismissed(RakNet::BitStream& inStream, Entit
|
||||
entity->TriggerEvent(eTriggerEventType::ZONE_SUMMARY_DISMISSED, target);
|
||||
};
|
||||
|
||||
void GameMessages::HandleAddDonationItem(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) {
|
||||
uint32_t count = 1;
|
||||
bool hasCount = false;
|
||||
inStream.Read(hasCount);
|
||||
if (hasCount) inStream.Read(count);
|
||||
LWOOBJID itemId = LWOOBJID_EMPTY;
|
||||
inStream.Read(itemId);
|
||||
if (!itemId) return;
|
||||
|
||||
auto* donationVendorComponent = entity->GetComponent<DonationVendorComponent>();
|
||||
if (!donationVendorComponent) return;
|
||||
if (donationVendorComponent->GetActivityID() == 0) {
|
||||
LOG("WARNING: Trying to dontate to a vendor with no activity");
|
||||
return;
|
||||
}
|
||||
User* user = UserManager::Instance()->GetUser(sysAddr);
|
||||
if (!user) return;
|
||||
Entity* player = Game::entityManager->GetEntity(user->GetLoggedInChar());
|
||||
if (!player) return;
|
||||
auto* characterComponent = player->GetComponent<CharacterComponent>();
|
||||
if (!characterComponent) return;
|
||||
auto* inventoryComponent = player->GetComponent<InventoryComponent>();
|
||||
if (!inventoryComponent) return;
|
||||
Item* item = inventoryComponent->FindItemById(itemId);
|
||||
if (!item) return;
|
||||
if (item->GetCount() < count) return;
|
||||
characterComponent->SetCurrentInteracting(entity->GetObjectID());
|
||||
inventoryComponent->MoveItemToInventory(item, eInventoryType::DONATION, count, true, false, true);
|
||||
}
|
||||
|
||||
void GameMessages::HandleRemoveDonationItem(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) {
|
||||
bool confirmed = false;
|
||||
inStream.Read(confirmed);
|
||||
uint32_t count = 1;
|
||||
bool hasCount = false;
|
||||
inStream.Read(hasCount);
|
||||
if (hasCount) inStream.Read(count);
|
||||
LWOOBJID itemId = LWOOBJID_EMPTY;
|
||||
inStream.Read(itemId);
|
||||
if (!itemId) return;
|
||||
|
||||
User* user = UserManager::Instance()->GetUser(sysAddr);
|
||||
if (!user) return;
|
||||
Entity* player = Game::entityManager->GetEntity(user->GetLoggedInChar());
|
||||
if (!player) return;
|
||||
|
||||
auto* inventoryComponent = player->GetComponent<InventoryComponent>();
|
||||
if (!inventoryComponent) return;
|
||||
|
||||
Item* item = inventoryComponent->FindItemById(itemId);
|
||||
if (!item) return;
|
||||
if (item->GetCount() < count) return;
|
||||
inventoryComponent->MoveItemToInventory(item, eInventoryType::BRICKS, count, true, false, true);
|
||||
}
|
||||
|
||||
void GameMessages::HandleConfirmDonationOnPlayer(RakNet::BitStream& inStream, Entity* entity) {
|
||||
const auto [inventoryComponent, missionComponent, characterComponent] = entity->GetComponentsMut<InventoryComponent, MissionComponent, CharacterComponent>();
|
||||
if (!inventoryComponent || !missionComponent || !characterComponent || !characterComponent->GetCurrentInteracting()) return;
|
||||
|
||||
auto* donationEntity = Game::entityManager->GetEntity(characterComponent->GetCurrentInteracting());
|
||||
if (!donationEntity) return;
|
||||
auto* donationVendorComponent = donationEntity->GetComponent<DonationVendorComponent>();
|
||||
if (!donationVendorComponent) return;
|
||||
if (donationVendorComponent->GetActivityID() == 0) {
|
||||
LOG("WARNING: Trying to dontate to a vendor with no activity");
|
||||
return;
|
||||
}
|
||||
auto* inventory = inventoryComponent->GetInventory(eInventoryType::DONATION);
|
||||
if (!inventory) return;
|
||||
auto items = inventory->GetItems();
|
||||
if (!items.empty()) {
|
||||
uint32_t count = 0;
|
||||
for (auto& [itemID, item] : items) {
|
||||
count += item->GetCount();
|
||||
EconomyLedger::RecordItemsUnsuppressed(entity, item->GetLot(), -static_cast<int64_t>(item->GetCount()), EconomyLedger::DONATION_SOURCE);
|
||||
item->RemoveFromInventory();
|
||||
}
|
||||
missionComponent->Progress(eMissionTaskType::DONATION, 0, LWOOBJID_EMPTY, "", count);
|
||||
LeaderboardManager::SaveScore(entity->GetObjectID(), donationVendorComponent->GetActivityID(), count);
|
||||
donationVendorComponent->SubmitDonation(count);
|
||||
Game::entityManager->SerializeEntity(donationEntity);
|
||||
}
|
||||
characterComponent->SetCurrentInteracting(LWOOBJID_EMPTY);
|
||||
}
|
||||
|
||||
void GameMessages::HandleCancelDonationOnPlayer(RakNet::BitStream& inStream, Entity* entity) {
|
||||
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
|
||||
if (!inventoryComponent) return;
|
||||
auto* inventory = inventoryComponent->GetInventory(eInventoryType::DONATION);
|
||||
if (!inventory) return;
|
||||
auto items = inventory->GetItems();
|
||||
for (auto& [itemID, item] : items) {
|
||||
inventoryComponent->MoveItemToInventory(item, eInventoryType::BRICKS, item->GetCount(), false, false, true);
|
||||
}
|
||||
auto* characterComponent = entity->GetComponent<CharacterComponent>();
|
||||
if (!characterComponent) return;
|
||||
characterComponent->SetCurrentInteracting(LWOOBJID_EMPTY);
|
||||
}
|
||||
|
||||
void GameMessages::SendForceCameraTargetCycle(Entity* entity, bool bForceCycling, eCameraTargetCyclingMode cyclingMode, LWOOBJID optionalTargetID) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
|
||||
@@ -172,14 +172,9 @@ namespace GameMessages {
|
||||
void SendAddSkill(Entity* entity, TSkillID skillID, BehaviorSlot slotID);
|
||||
void SendRemoveSkill(Entity* entity, TSkillID skillID);
|
||||
|
||||
void SendVendorOpenWindow(Entity* entity, const SystemAddress& sysAddr);
|
||||
void SendVendorStatusUpdate(Entity* entity, const SystemAddress& sysAddr, bool bUpdateOnly = false);
|
||||
void SendVendorTransactionResult(Entity* entity, const SystemAddress& sysAddr, eVendorTransactionResult result);
|
||||
|
||||
void SendMatchResponse(Entity* entity, const SystemAddress& sysAddr, int response);
|
||||
void SendMatchUpdate(Entity* entity, const SystemAddress& sysAddr, std::string data, eMatchUpdate type);
|
||||
|
||||
// https://lcdruniverse.org/lu_packets/lu_packets/world/gm/client/struct.SetResurrectRestoreValues.html
|
||||
void SendSetResurrectRestoreValues(Entity* targetEntity, int32_t armorRestore, int32_t healthRestore, int32_t imaginationRestore);
|
||||
|
||||
/**
|
||||
@@ -282,27 +277,6 @@ namespace GameMessages {
|
||||
|
||||
void SendTeamPickupItem(LWOOBJID objectId, LWOOBJID lootID, LWOOBJID lootOwnerID, const SystemAddress& sysAddr);
|
||||
|
||||
//Trading:
|
||||
void SendServerTradeInvite(LWOOBJID objectId, bool bNeedInvitePopUp, LWOOBJID i64Requestor, std::u16string wsName, const SystemAddress& sysAddr);
|
||||
|
||||
void SendServerTradeInitialReply(LWOOBJID objectId, LWOOBJID i64Invitee, int32_t resultType, std::u16string wsName, const SystemAddress& sysAddr);
|
||||
|
||||
void SendServerTradeFinalReply(LWOOBJID objectId, bool bResult, LWOOBJID i64Invitee, std::u16string wsName, const SystemAddress& sysAddr);
|
||||
|
||||
void SendServerTradeAccept(LWOOBJID objectId, bool bFirst, const SystemAddress& sysAddr);
|
||||
|
||||
void SendServerTradeCancel(LWOOBJID objectId, const SystemAddress& sysAddr);
|
||||
|
||||
void SendServerTradeUpdate(LWOOBJID objectId, uint64_t coins, const std::vector<TradeItem>& items, const SystemAddress& sysAddr);
|
||||
|
||||
void HandleClientTradeRequest(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
|
||||
|
||||
void HandleClientTradeCancel(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
|
||||
|
||||
void HandleClientTradeAccept(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
|
||||
|
||||
void HandleClientTradeUpdate(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
|
||||
|
||||
//Pets:
|
||||
|
||||
void SendDisplayZoneSummary(LWOOBJID objectId, const SystemAddress& sysAddr, bool isPropertyMap = false, bool isZoneStart = false, LWOOBJID sender = LWOOBJID_EMPTY);
|
||||
@@ -380,9 +354,6 @@ namespace GameMessages {
|
||||
void HandleToggleGhostReferenceOverride(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
|
||||
void HandleSetGhostReferencePosition(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
|
||||
|
||||
void HandleBuyFromVendor(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
|
||||
void HandleSellToVendor(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
|
||||
void HandleBuybackFromVendor(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
|
||||
void HandleParseChatMessage(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
|
||||
void HandleToggleGhostReffrenceOverride(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
|
||||
void HandleSetGhostReffrenceOverride(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
|
||||
@@ -414,12 +385,6 @@ namespace GameMessages {
|
||||
|
||||
void HandleZoneSummaryDismissed(RakNet::BitStream& inStream, Entity* entity);
|
||||
|
||||
// Donation vendor
|
||||
void HandleAddDonationItem(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
|
||||
void HandleRemoveDonationItem(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
|
||||
void HandleConfirmDonationOnPlayer(RakNet::BitStream& inStream, Entity* entity);
|
||||
void HandleCancelDonationOnPlayer(RakNet::BitStream& inStream, Entity* entity);
|
||||
|
||||
void SendForceCameraTargetCycle(Entity* entity, bool bForceCycling, eCameraTargetCyclingMode cyclingMode, LWOOBJID optionalTargetID);
|
||||
|
||||
struct DisplayTooltip : public NetGameMsg {
|
||||
|
||||
303
dGame/dGameMessages/TradeMessages.cpp
Normal file
303
dGame/dGameMessages/TradeMessages.cpp
Normal file
@@ -0,0 +1,303 @@
|
||||
#include "TradeMessages.h"
|
||||
|
||||
#include "BitStreamUtils.h"
|
||||
#include "Character.h"
|
||||
#include "ChatPackets.h"
|
||||
#include "dCommonVars.h"
|
||||
#include "Entity.h"
|
||||
#include "EntityManager.h"
|
||||
#include "ePermissionMap.h"
|
||||
#include "Game.h"
|
||||
#include "GeneralUtils.h"
|
||||
#include "Logger.h"
|
||||
#include "TradingManager.h"
|
||||
#include "User.h"
|
||||
#include "UserManager.h"
|
||||
|
||||
namespace {
|
||||
template<typename T>
|
||||
void WriteFlagged(RakNet::BitStream& bitStream, const std::optional<T>& value) {
|
||||
bitStream.Write(value.has_value());
|
||||
if (value) bitStream.Write(*value);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool ReadFlagged(RakNet::BitStream& bitStream, std::optional<T>& value) {
|
||||
bool present{};
|
||||
if (!bitStream.Read(present)) return false;
|
||||
if (!present) {
|
||||
value.reset();
|
||||
return true;
|
||||
}
|
||||
T read{};
|
||||
if (!bitStream.Read(read)) return false;
|
||||
value = read;
|
||||
return true;
|
||||
}
|
||||
|
||||
void WriteEntries(RakNet::BitStream& bitStream, const std::vector<GameMessages::TradeItemEntry>& entries) {
|
||||
bitStream.Write<uint32_t>(entries.size());
|
||||
for (const auto& entry : entries) entry.Serialize(bitStream);
|
||||
}
|
||||
|
||||
bool ReadEntries(RakNet::BitStream& bitStream, std::vector<GameMessages::TradeItemEntry>& entries) {
|
||||
uint32_t count{};
|
||||
if (!bitStream.Read(count)) return false;
|
||||
if (count > MAX_MESSAGE_LENGTH) return false;
|
||||
entries.clear();
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
GameMessages::TradeItemEntry entry;
|
||||
if (!entry.Deserialize(bitStream)) return false;
|
||||
entries.push_back(std::move(entry));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
namespace GameMessages {
|
||||
void TradeItemEntry::Serialize(RakNet::BitStream& bitStream) const {
|
||||
bitStream.Write(key);
|
||||
bitStream.Write(itemID);
|
||||
bitStream.Write(templateID);
|
||||
WriteFlagged(bitStream, subkey);
|
||||
WriteFlagged(bitStream, count);
|
||||
WriteFlagged(bitStream, slot);
|
||||
WriteFlagged(bitStream, inventoryType);
|
||||
bitStream.Write(config.has_value());
|
||||
if (config) {
|
||||
bitStream.Write(config->size);
|
||||
bitStream.Write(config->compressed);
|
||||
if (config->compressed) bitStream.Write(config->compressedSize);
|
||||
bitStream.WriteBits(config->data.data(), BYTES_TO_BITS(config->data.size()));
|
||||
}
|
||||
bitStream.Write(unknownFlag);
|
||||
}
|
||||
|
||||
bool TradeItemEntry::Deserialize(RakNet::BitStream& bitStream) {
|
||||
VALIDATE_READ(bitStream.Read(key));
|
||||
VALIDATE_READ(bitStream.Read(itemID));
|
||||
VALIDATE_READ(bitStream.Read(templateID));
|
||||
VALIDATE_READ(ReadFlagged(bitStream, subkey));
|
||||
VALIDATE_READ(ReadFlagged(bitStream, count));
|
||||
VALIDATE_READ(ReadFlagged(bitStream, slot));
|
||||
VALIDATE_READ(ReadFlagged(bitStream, inventoryType));
|
||||
bool hasConfig{};
|
||||
VALIDATE_READ(bitStream.Read(hasConfig));
|
||||
if (hasConfig) {
|
||||
Config readConfig;
|
||||
VALIDATE_READ(bitStream.Read(readConfig.size));
|
||||
VALIDATE_READ(bitStream.Read(readConfig.compressed));
|
||||
if (readConfig.compressed) VALIDATE_READ(bitStream.Read(readConfig.compressedSize));
|
||||
const uint32_t dataSize = readConfig.compressed ? readConfig.compressedSize : readConfig.size;
|
||||
if (BYTES_TO_BITS(static_cast<uint64_t>(dataSize)) > bitStream.GetNumberOfUnreadBits()) return false;
|
||||
readConfig.data.resize(dataSize);
|
||||
if (dataSize > 0) VALIDATE_READ(bitStream.ReadBits(readConfig.data.data(), BYTES_TO_BITS(dataSize), true));
|
||||
config = std::move(readConfig);
|
||||
} else {
|
||||
config.reset();
|
||||
}
|
||||
VALIDATE_READ(bitStream.Read(unknownFlag));
|
||||
return true;
|
||||
}
|
||||
|
||||
void ClientTradeRequest::Serialize(RakNet::BitStream& bitStream) const {
|
||||
bitStream.Write(bNeedInvitePopUp);
|
||||
bitStream.Write(i64Invitee);
|
||||
}
|
||||
|
||||
bool ClientTradeRequest::Deserialize(RakNet::BitStream& bitStream) {
|
||||
VALIDATE_READ(bitStream.Read(bNeedInvitePopUp));
|
||||
VALIDATE_READ(bitStream.Read(i64Invitee));
|
||||
return true;
|
||||
}
|
||||
|
||||
void ClientTradeRequest::Handle(Entity& entity, const SystemAddress& sysAddr) {
|
||||
// Check if the player has restricted trade access
|
||||
auto* character = entity.GetCharacter();
|
||||
const bool restrictTradeOnMute = UserManager::Instance()->GetMuteRestrictTrade();
|
||||
|
||||
if (character->HasPermission(ePermissionMap::RestrictedTradeAccess) || (restrictTradeOnMute && character->GetParentUser()->GetIsMuted())) {
|
||||
// Send a message to the player
|
||||
ChatPackets::SendSystemMessage(
|
||||
sysAddr,
|
||||
u"Your character has restricted trade access."
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto* invitee = Game::entityManager->GetEntity(i64Invitee);
|
||||
|
||||
if (invitee != nullptr && invitee->IsPlayer()) {
|
||||
character = invitee->GetCharacter();
|
||||
|
||||
if (character->HasPermission(ePermissionMap::RestrictedTradeAccess) || (restrictTradeOnMute && character->GetParentUser()->GetIsMuted())) {
|
||||
// Send a message to the player
|
||||
ChatPackets::SendSystemMessage(
|
||||
sysAddr,
|
||||
u"The character you are trying to trade with has restricted trade access."
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
LOG("Trade request to (%llu)", i64Invitee);
|
||||
|
||||
const auto& trade = TradingManager::Instance()->GetPlayerTrade(entity.GetObjectID());
|
||||
|
||||
if (trade != nullptr) {
|
||||
if (!trade->IsParticipant(i64Invitee)) {
|
||||
TradingManager::Instance()->CancelTrade(entity.GetObjectID(), trade->GetTradeId());
|
||||
|
||||
TradingManager::Instance()->NewTrade(entity.GetObjectID(), i64Invitee);
|
||||
}
|
||||
} else {
|
||||
TradingManager::Instance()->NewTrade(entity.GetObjectID(), i64Invitee);
|
||||
}
|
||||
|
||||
ServerTradeInvite invite;
|
||||
invite.target = i64Invitee;
|
||||
invite.bNeedInvitePopUp = bNeedInvitePopUp;
|
||||
invite.i64Requestor = entity.GetObjectID();
|
||||
invite.wsName = GeneralUtils::UTF8ToUTF16(entity.GetCharacter()->GetName());
|
||||
invite.Send(invitee->GetSystemAddress());
|
||||
}
|
||||
}
|
||||
|
||||
void ServerTradeInvite::Serialize(RakNet::BitStream& bitStream) const {
|
||||
bitStream.Write(bNeedInvitePopUp);
|
||||
bitStream.Write(i64Requestor);
|
||||
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, wsName);
|
||||
}
|
||||
|
||||
bool ServerTradeInvite::Deserialize(RakNet::BitStream& bitStream) {
|
||||
VALIDATE_READ(bitStream.Read(bNeedInvitePopUp));
|
||||
VALIDATE_READ(bitStream.Read(i64Requestor));
|
||||
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, wsName));
|
||||
return true;
|
||||
}
|
||||
|
||||
void ServerTradeInitialReply::Serialize(RakNet::BitStream& bitStream) const {
|
||||
bitStream.Write(i64Invitee);
|
||||
bitStream.Write(resultType);
|
||||
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, wsName);
|
||||
}
|
||||
|
||||
bool ServerTradeInitialReply::Deserialize(RakNet::BitStream& bitStream) {
|
||||
VALIDATE_READ(bitStream.Read(i64Invitee));
|
||||
VALIDATE_READ(bitStream.Read(resultType));
|
||||
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, wsName));
|
||||
return true;
|
||||
}
|
||||
|
||||
void ServerTradeFinalReply::Serialize(RakNet::BitStream& bitStream) const {
|
||||
bitStream.Write(bResult);
|
||||
bitStream.Write(i64Invitee);
|
||||
BitStreamUtils::WriteLengthPrefixed<uint32_t>(bitStream, wsName);
|
||||
}
|
||||
|
||||
bool ServerTradeFinalReply::Deserialize(RakNet::BitStream& bitStream) {
|
||||
VALIDATE_READ(bitStream.Read(bResult));
|
||||
VALIDATE_READ(bitStream.Read(i64Invitee));
|
||||
VALIDATE_READ(BitStreamUtils::ReadLengthPrefixed<uint32_t>(bitStream, wsName));
|
||||
return true;
|
||||
}
|
||||
|
||||
void ClientTradeUpdate::Serialize(RakNet::BitStream& bitStream) const {
|
||||
bitStream.Write(i64Currency);
|
||||
WriteEntries(bitStream, inventoryMap);
|
||||
}
|
||||
|
||||
bool ClientTradeUpdate::Deserialize(RakNet::BitStream& bitStream) {
|
||||
VALIDATE_READ(bitStream.Read(i64Currency));
|
||||
VALIDATE_READ(ReadEntries(bitStream, inventoryMap));
|
||||
return true;
|
||||
}
|
||||
|
||||
void ClientTradeUpdate::Handle(Entity& entity, const SystemAddress& sysAddr) {
|
||||
LOG("Trade update from (%llu) -> (%llu), (%i)", entity.GetObjectID(), i64Currency, static_cast<uint32_t>(inventoryMap.size()));
|
||||
|
||||
std::vector<TradeItem> items{};
|
||||
|
||||
for (const auto& entry : inventoryMap) {
|
||||
const auto unknown1 = entry.subkey.value_or(0);
|
||||
const auto unknown2 = entry.count.value_or(0);
|
||||
const auto unknown3 = entry.inventoryType.value_or(0);
|
||||
|
||||
items.push_back({ entry.key, entry.templateID, unknown2 });
|
||||
|
||||
LOG("Trade item from (%llu) -> (%llu)/(%llu), (%i), (%llu), (%i), (%i)", entity.GetObjectID(), entry.key, entry.itemID, entry.templateID, unknown1, unknown2, unknown3);
|
||||
}
|
||||
|
||||
const auto& trade = TradingManager::Instance()->GetPlayerTrade(entity.GetObjectID());
|
||||
|
||||
if (trade == nullptr) return;
|
||||
|
||||
trade->SetCoins(entity.GetObjectID(), i64Currency);
|
||||
trade->SetItems(entity.GetObjectID(), items);
|
||||
trade->SendUpdateToOther(entity.GetObjectID());
|
||||
}
|
||||
|
||||
void ServerTradeUpdate::Serialize(RakNet::BitStream& bitStream) const {
|
||||
bitStream.Write(bAboutToPerform);
|
||||
bitStream.Write(i64Currency);
|
||||
WriteEntries(bitStream, inventoryMap);
|
||||
}
|
||||
|
||||
bool ServerTradeUpdate::Deserialize(RakNet::BitStream& bitStream) {
|
||||
VALIDATE_READ(bitStream.Read(bAboutToPerform));
|
||||
VALIDATE_READ(bitStream.Read(i64Currency));
|
||||
VALIDATE_READ(ReadEntries(bitStream, inventoryMap));
|
||||
return true;
|
||||
}
|
||||
|
||||
void ClientTradeCancel::Serialize(RakNet::BitStream& bitStream) const {}
|
||||
|
||||
bool ClientTradeCancel::Deserialize(RakNet::BitStream& bitStream) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void ClientTradeCancel::Handle(Entity& entity, const SystemAddress& sysAddr) {
|
||||
const auto& trade = TradingManager::Instance()->GetPlayerTrade(entity.GetObjectID());
|
||||
|
||||
if (trade == nullptr) return;
|
||||
|
||||
LOG("Trade canceled from (%llu)", entity.GetObjectID());
|
||||
|
||||
TradingManager::Instance()->CancelTrade(entity.GetObjectID(), trade->GetTradeId());
|
||||
}
|
||||
|
||||
void ClientTradeAccept::Serialize(RakNet::BitStream& bitStream) const {
|
||||
bitStream.Write(bFirst);
|
||||
}
|
||||
|
||||
bool ClientTradeAccept::Deserialize(RakNet::BitStream& bitStream) {
|
||||
VALIDATE_READ(bitStream.Read(bFirst));
|
||||
return true;
|
||||
}
|
||||
|
||||
void ClientTradeAccept::Handle(Entity& entity, const SystemAddress& sysAddr) {
|
||||
LOG("Trade accepted from (%llu) -> (%d)", entity.GetObjectID(), bFirst);
|
||||
|
||||
const auto& trade = TradingManager::Instance()->GetPlayerTrade(entity.GetObjectID());
|
||||
|
||||
if (trade == nullptr) return;
|
||||
|
||||
trade->SetAccepted(entity.GetObjectID(), bFirst);
|
||||
}
|
||||
|
||||
void ServerTradeCancel::Serialize(RakNet::BitStream& bitStream) const {}
|
||||
|
||||
bool ServerTradeCancel::Deserialize(RakNet::BitStream& bitStream) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void ServerTradeAccept::Serialize(RakNet::BitStream& bitStream) const {
|
||||
bitStream.Write(bFirst);
|
||||
}
|
||||
|
||||
bool ServerTradeAccept::Deserialize(RakNet::BitStream& bitStream) {
|
||||
VALIDATE_READ(bitStream.Read(bFirst));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
143
dGame/dGameMessages/TradeMessages.h
Normal file
143
dGame/dGameMessages/TradeMessages.h
Normal file
@@ -0,0 +1,143 @@
|
||||
#ifndef TRADEMESSAGES_H
|
||||
#define TRADEMESSAGES_H
|
||||
|
||||
#include "GameMessages.h"
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
// Game messages for player to player trading.
|
||||
// Field names follow the client (legouniverse.exe 1.10.64); fields are listed in wire order.
|
||||
// The server -> client messages are sent with Send (UNASSIGNED_SYSTEM_ADDRESS broadcasts), as before.
|
||||
namespace GameMessages {
|
||||
// One entry of a trade offer: the object ID it is keyed by, then the client's inventory item layout.
|
||||
// Optional fields are written as a flag bit followed by the value when the flag is set.
|
||||
struct TradeItemEntry {
|
||||
void Serialize(RakNet::BitStream& bitStream) const;
|
||||
bool Deserialize(RakNet::BitStream& bitStream);
|
||||
bool operator==(const TradeItemEntry&) const = default;
|
||||
|
||||
LWOOBJID key{}; // the map key, the item's object ID
|
||||
LWOOBJID itemID{};
|
||||
LOT templateID{};
|
||||
std::optional<LWOOBJID> subkey{};
|
||||
std::optional<uint32_t> count{};
|
||||
std::optional<uint16_t> slot{};
|
||||
std::optional<uint32_t> inventoryType{};
|
||||
|
||||
// Optional config (LDF) block, kept as bytes: u32 uncompressed size, compressed bit, then either the raw
|
||||
// data (uncompressed size bytes) or a u32 compressed size and the compressed data.
|
||||
struct Config {
|
||||
uint32_t size{};
|
||||
bool compressed{};
|
||||
uint32_t compressedSize{};
|
||||
std::vector<uint8_t> data{};
|
||||
bool operator==(const Config&) const = default;
|
||||
};
|
||||
std::optional<Config> config{};
|
||||
|
||||
bool unknownFlag{}; // trailing bit, not used by DLU
|
||||
};
|
||||
|
||||
// Client -> server.
|
||||
struct ClientTradeRequest : public NetGameMsg {
|
||||
ClientTradeRequest() : NetGameMsg(MessageType::Game::CLIENT_TRADE_REQUEST) {}
|
||||
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
|
||||
|
||||
bool bNeedInvitePopUp{};
|
||||
LWOOBJID i64Invitee{};
|
||||
};
|
||||
|
||||
// Server -> client.
|
||||
struct ServerTradeInvite : public NetGameMsg {
|
||||
ServerTradeInvite() : NetGameMsg(MessageType::Game::SERVER_TRADE_INVITE) {}
|
||||
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||
|
||||
bool bNeedInvitePopUp{};
|
||||
LWOOBJID i64Requestor{};
|
||||
std::u16string wsName{}; // u32 length prefixed
|
||||
};
|
||||
|
||||
// Server -> client.
|
||||
struct ServerTradeInitialReply : public NetGameMsg {
|
||||
ServerTradeInitialReply() : NetGameMsg(MessageType::Game::SERVER_TRADE_INITIAL_REPLY) {}
|
||||
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||
|
||||
LWOOBJID i64Invitee{};
|
||||
int32_t resultType{};
|
||||
std::u16string wsName{}; // u32 length prefixed
|
||||
};
|
||||
|
||||
// Server -> client.
|
||||
struct ServerTradeFinalReply : public NetGameMsg {
|
||||
ServerTradeFinalReply() : NetGameMsg(MessageType::Game::SERVER_TRADE_FINAL_REPLY) {}
|
||||
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||
|
||||
bool bResult{};
|
||||
LWOOBJID i64Invitee{};
|
||||
std::u16string wsName{}; // u32 length prefixed
|
||||
};
|
||||
|
||||
// Client -> server.
|
||||
struct ClientTradeUpdate : public NetGameMsg {
|
||||
ClientTradeUpdate() : NetGameMsg(MessageType::Game::CLIENT_TRADE_UPDATE) {}
|
||||
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
|
||||
|
||||
uint64_t i64Currency{};
|
||||
std::vector<TradeItemEntry> inventoryMap{}; // u32 count, then the entries
|
||||
};
|
||||
|
||||
// Server -> client.
|
||||
struct ServerTradeUpdate : public NetGameMsg {
|
||||
ServerTradeUpdate() : NetGameMsg(MessageType::Game::SERVER_TRADE_UPDATE) {}
|
||||
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||
|
||||
bool bAboutToPerform{};
|
||||
uint64_t i64Currency{};
|
||||
std::vector<TradeItemEntry> inventoryMap{}; // u32 count, then the entries
|
||||
};
|
||||
|
||||
// Client -> server. No payload.
|
||||
struct ClientTradeCancel : public NetGameMsg {
|
||||
ClientTradeCancel() : NetGameMsg(MessageType::Game::CLIENT_TRADE_CANCEL) {}
|
||||
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
|
||||
};
|
||||
|
||||
// Client -> server.
|
||||
struct ClientTradeAccept : public NetGameMsg {
|
||||
ClientTradeAccept() : NetGameMsg(MessageType::Game::CLIENT_TRADE_ACCEPT) {}
|
||||
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
|
||||
|
||||
bool bFirst{};
|
||||
};
|
||||
|
||||
// Server -> client. No payload.
|
||||
struct ServerTradeCancel : public NetGameMsg {
|
||||
ServerTradeCancel() : NetGameMsg(MessageType::Game::SERVER_TRADE_CANCEL) {}
|
||||
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||
};
|
||||
|
||||
// Server -> client.
|
||||
struct ServerTradeAccept : public NetGameMsg {
|
||||
ServerTradeAccept() : NetGameMsg(MessageType::Game::SERVER_TRADE_ACCEPT) {}
|
||||
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||
|
||||
bool bFirst{};
|
||||
};
|
||||
};
|
||||
|
||||
#endif // TRADEMESSAGES_H
|
||||
252
dGame/dGameMessages/VendorMessages.cpp
Normal file
252
dGame/dGameMessages/VendorMessages.cpp
Normal file
@@ -0,0 +1,252 @@
|
||||
#include "VendorMessages.h"
|
||||
|
||||
#include "AchievementVendorComponent.h"
|
||||
#include "BitStreamUtils.h"
|
||||
#include "CharacterComponent.h"
|
||||
#include "DonationVendorComponent.h"
|
||||
#include "eInventoryType.h"
|
||||
#include "eReplicaComponentType.h"
|
||||
#include "eVendorTransactionResult.h"
|
||||
#include "Entity.h"
|
||||
#include "EntityManager.h"
|
||||
#include "Game.h"
|
||||
#include "InventoryComponent.h"
|
||||
#include "Item.h"
|
||||
#include "PropertyVendorComponent.h"
|
||||
#include "User.h"
|
||||
#include "UserManager.h"
|
||||
#include "VendorComponent.h"
|
||||
|
||||
namespace {
|
||||
// The player that sent a message, found from the sender's address as the old handlers did.
|
||||
Entity* GetSendingPlayer(const SystemAddress& sysAddr) {
|
||||
User* user = UserManager::Instance()->GetUser(sysAddr);
|
||||
if (!user) return nullptr;
|
||||
return Game::entityManager->GetEntity(user->GetLoggedInChar());
|
||||
}
|
||||
}
|
||||
|
||||
namespace GameMessages {
|
||||
void VendorOpenWindow::Serialize(RakNet::BitStream& bitStream) const {}
|
||||
|
||||
bool VendorOpenWindow::Deserialize(RakNet::BitStream& bitStream) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void RequestVendorStatusUpdate::Serialize(RakNet::BitStream& bitStream) const {}
|
||||
|
||||
bool RequestVendorStatusUpdate::Deserialize(RakNet::BitStream& bitStream) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void RequestVendorStatusUpdate::Handle(Entity& entity, const SystemAddress& sysAddr) {
|
||||
// Only the VENDOR component answers, as before (not achievement or donation vendors).
|
||||
auto* vendor = static_cast<VendorComponent*>(entity.GetComponent(eReplicaComponentType::VENDOR));
|
||||
if (vendor) vendor->SendStatusUpdate(sysAddr, true);
|
||||
}
|
||||
|
||||
void VendorStatusUpdate::Serialize(RakNet::BitStream& bitStream) const {
|
||||
bitStream.Write(bUpdateOnly);
|
||||
bitStream.Write<uint32_t>(inventoryList.size());
|
||||
for (const auto& entry : inventoryList) {
|
||||
bitStream.Write(entry.lot);
|
||||
bitStream.Write(entry.sortPriority);
|
||||
}
|
||||
}
|
||||
|
||||
bool VendorStatusUpdate::Deserialize(RakNet::BitStream& bitStream) {
|
||||
VALIDATE_READ(bitStream.Read(bUpdateOnly));
|
||||
uint32_t count{};
|
||||
VALIDATE_READ(bitStream.Read(count));
|
||||
// Each entry is 8 bytes; refuse counts the stream cannot hold before allocating.
|
||||
if (static_cast<uint64_t>(count) * 64 > bitStream.GetNumberOfUnreadBits()) return false;
|
||||
inventoryList.resize(count);
|
||||
for (auto& entry : inventoryList) {
|
||||
VALIDATE_READ(bitStream.Read(entry.lot));
|
||||
VALIDATE_READ(bitStream.Read(entry.sortPriority));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void VendorTransactionResult::Serialize(RakNet::BitStream& bitStream) const {
|
||||
bitStream.Write(iResult);
|
||||
}
|
||||
|
||||
bool VendorTransactionResult::Deserialize(RakNet::BitStream& bitStream) {
|
||||
VALIDATE_READ(bitStream.Read(iResult));
|
||||
return true;
|
||||
}
|
||||
|
||||
void BuyFromVendor::Serialize(RakNet::BitStream& bitStream) const {
|
||||
bitStream.Write(confirmed);
|
||||
BitStreamUtils::WriteOptional(bitStream, count, 1);
|
||||
bitStream.Write(item);
|
||||
}
|
||||
|
||||
bool BuyFromVendor::Deserialize(RakNet::BitStream& bitStream) {
|
||||
VALIDATE_READ(bitStream.Read(confirmed));
|
||||
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, count, 1));
|
||||
VALIDATE_READ(bitStream.Read(item));
|
||||
return true;
|
||||
}
|
||||
|
||||
void BuyFromVendor::Handle(Entity& entity, const SystemAddress& sysAddr) {
|
||||
Entity* player = GetSendingPlayer(sysAddr);
|
||||
if (!player) return;
|
||||
|
||||
// handle buying normal items
|
||||
auto* vendorComponent = entity.GetComponent<VendorComponent>();
|
||||
if (vendorComponent) {
|
||||
vendorComponent->Buy(player, item, count);
|
||||
return;
|
||||
}
|
||||
|
||||
// handle buying achievement items
|
||||
auto* achievementVendorComponent = entity.GetComponent<AchievementVendorComponent>();
|
||||
if (achievementVendorComponent) {
|
||||
achievementVendorComponent->Buy(player, item, count);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle buying properties
|
||||
auto* propertyVendorComponent = entity.GetComponent<PropertyVendorComponent>();
|
||||
if (propertyVendorComponent) {
|
||||
propertyVendorComponent->OnBuyFromVendor(player, confirmed, item, count);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void SellToVendor::Serialize(RakNet::BitStream& bitStream) const {
|
||||
BitStreamUtils::WriteOptional(bitStream, count, 1);
|
||||
bitStream.Write(itemObjID);
|
||||
}
|
||||
|
||||
bool SellToVendor::Deserialize(RakNet::BitStream& bitStream) {
|
||||
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, count, 1));
|
||||
VALIDATE_READ(bitStream.Read(itemObjID));
|
||||
return true;
|
||||
}
|
||||
|
||||
void SellToVendor::Handle(Entity& entity, const SystemAddress& sysAddr) {
|
||||
Entity* player = GetSendingPlayer(sysAddr);
|
||||
if (!player) return;
|
||||
|
||||
auto* vendor = static_cast<VendorComponent*>(entity.GetComponent(eReplicaComponentType::VENDOR));
|
||||
if (!vendor) return;
|
||||
|
||||
vendor->SellToVendor(*player, sysAddr, itemObjID, count);
|
||||
}
|
||||
|
||||
void BuybackFromVendor::Serialize(RakNet::BitStream& bitStream) const {
|
||||
bitStream.Write(confirmed);
|
||||
BitStreamUtils::WriteOptional(bitStream, count, 1);
|
||||
bitStream.Write(item);
|
||||
}
|
||||
|
||||
bool BuybackFromVendor::Deserialize(RakNet::BitStream& bitStream) {
|
||||
VALIDATE_READ(bitStream.Read(confirmed));
|
||||
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, count, 1));
|
||||
VALIDATE_READ(bitStream.Read(item));
|
||||
return true;
|
||||
}
|
||||
|
||||
void BuybackFromVendor::Handle(Entity& entity, const SystemAddress& sysAddr) {
|
||||
//if (!confirmed) return; they always built in this confirmed garbage... but never used it?
|
||||
|
||||
Entity* player = GetSendingPlayer(sysAddr);
|
||||
if (!player) return;
|
||||
|
||||
auto* vendor = static_cast<VendorComponent*>(entity.GetComponent(eReplicaComponentType::VENDOR));
|
||||
if (!vendor) return;
|
||||
|
||||
vendor->BuybackFromVendor(*player, sysAddr, item, count);
|
||||
}
|
||||
|
||||
void AddDonationItem::Serialize(RakNet::BitStream& bitStream) const {
|
||||
BitStreamUtils::WriteOptional(bitStream, count, 1u);
|
||||
bitStream.Write(itemObjID);
|
||||
}
|
||||
|
||||
bool AddDonationItem::Deserialize(RakNet::BitStream& bitStream) {
|
||||
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, count, 1u));
|
||||
VALIDATE_READ(bitStream.Read(itemObjID));
|
||||
return true;
|
||||
}
|
||||
|
||||
void AddDonationItem::Handle(Entity& entity, const SystemAddress& sysAddr) {
|
||||
if (!itemObjID) return;
|
||||
|
||||
auto* donationVendorComponent = entity.GetComponent<DonationVendorComponent>();
|
||||
if (!donationVendorComponent) return;
|
||||
donationVendorComponent->AddDonationItem(sysAddr, itemObjID, count);
|
||||
}
|
||||
|
||||
void RemoveDonationItem::Serialize(RakNet::BitStream& bitStream) const {
|
||||
bitStream.Write(confirmed);
|
||||
BitStreamUtils::WriteOptional(bitStream, count, 1u);
|
||||
bitStream.Write(itemObjID);
|
||||
}
|
||||
|
||||
bool RemoveDonationItem::Deserialize(RakNet::BitStream& bitStream) {
|
||||
VALIDATE_READ(bitStream.Read(confirmed));
|
||||
VALIDATE_READ(BitStreamUtils::ReadOptional(bitStream, count, 1u));
|
||||
VALIDATE_READ(bitStream.Read(itemObjID));
|
||||
return true;
|
||||
}
|
||||
|
||||
void RemoveDonationItem::Handle(Entity& entity, const SystemAddress& sysAddr) {
|
||||
if (!itemObjID) return;
|
||||
|
||||
Entity* player = GetSendingPlayer(sysAddr);
|
||||
if (!player) return;
|
||||
|
||||
auto* inventoryComponent = player->GetComponent<InventoryComponent>();
|
||||
if (!inventoryComponent) return;
|
||||
|
||||
Item* item = inventoryComponent->FindItemById(itemObjID);
|
||||
if (!item) return;
|
||||
if (item->GetCount() < count) return;
|
||||
inventoryComponent->MoveItemToInventory(item, eInventoryType::BRICKS, count, true, false, true);
|
||||
}
|
||||
|
||||
void ConfirmDonationOnPlayer::Serialize(RakNet::BitStream& bitStream) const {
|
||||
bitStream.Write(vendorID);
|
||||
}
|
||||
|
||||
bool ConfirmDonationOnPlayer::Deserialize(RakNet::BitStream& bitStream) {
|
||||
// The old handler read nothing; keep accepting the message even if vendorID is missing.
|
||||
if (!bitStream.Read(vendorID)) vendorID = LWOOBJID_EMPTY;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConfirmDonationOnPlayer::Handle(Entity& entity, const SystemAddress& sysAddr) {
|
||||
auto* characterComponent = entity.GetComponent<CharacterComponent>();
|
||||
if (!characterComponent || !characterComponent->GetCurrentInteracting()) return;
|
||||
|
||||
auto* donationEntity = Game::entityManager->GetEntity(characterComponent->GetCurrentInteracting());
|
||||
if (!donationEntity) return;
|
||||
auto* donationVendorComponent = donationEntity->GetComponent<DonationVendorComponent>();
|
||||
if (!donationVendorComponent) return;
|
||||
donationVendorComponent->ConfirmDonation(entity);
|
||||
}
|
||||
|
||||
void CancelDonationOnPlayer::Serialize(RakNet::BitStream& bitStream) const {}
|
||||
|
||||
bool CancelDonationOnPlayer::Deserialize(RakNet::BitStream& bitStream) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void CancelDonationOnPlayer::Handle(Entity& entity, const SystemAddress& sysAddr) {
|
||||
auto* inventoryComponent = entity.GetComponent<InventoryComponent>();
|
||||
if (!inventoryComponent) return;
|
||||
auto* inventory = inventoryComponent->GetInventory(eInventoryType::DONATION);
|
||||
if (!inventory) return;
|
||||
auto items = inventory->GetItems();
|
||||
for (auto& [itemID, item] : items) {
|
||||
inventoryComponent->MoveItemToInventory(item, eInventoryType::BRICKS, item->GetCount(), false, false, true);
|
||||
}
|
||||
auto* characterComponent = entity.GetComponent<CharacterComponent>();
|
||||
if (!characterComponent) return;
|
||||
characterComponent->SetCurrentInteracting(LWOOBJID_EMPTY);
|
||||
}
|
||||
}
|
||||
132
dGame/dGameMessages/VendorMessages.h
Normal file
132
dGame/dGameMessages/VendorMessages.h
Normal file
@@ -0,0 +1,132 @@
|
||||
#ifndef VENDORMESSAGES_H
|
||||
#define VENDORMESSAGES_H
|
||||
|
||||
#include "GameMessages.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
enum class eVendorTransactionResult : uint32_t;
|
||||
|
||||
// Game messages for vendors (buy, sell, buyback, the vendor window and its stock) and donation vendors.
|
||||
// Field names follow the client (legouniverse.exe 1.10.64); fields are listed in wire order.
|
||||
namespace GameMessages {
|
||||
// Server -> client. Sent with SendToClient: the old function never broadcast.
|
||||
struct VendorOpenWindow : public NetGameMsg {
|
||||
VendorOpenWindow() : NetGameMsg(MessageType::Game::VENDOR_OPEN_WINDOW) {}
|
||||
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||
};
|
||||
|
||||
// Client -> server. Asks the vendor (the target) to resend its stock. No payload.
|
||||
struct RequestVendorStatusUpdate : public NetGameMsg {
|
||||
RequestVendorStatusUpdate() : NetGameMsg(MessageType::Game::REQUEST_VENDOR_STATUS_UPDATE) {}
|
||||
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
|
||||
};
|
||||
|
||||
// Server -> client. The vendor's stock. UNASSIGNED_SYSTEM_ADDRESS broadcasts.
|
||||
struct VendorStatusUpdate : public NetGameMsg {
|
||||
VendorStatusUpdate() : NetGameMsg(MessageType::Game::VENDOR_STATUS_UPDATE) {}
|
||||
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||
|
||||
struct Entry {
|
||||
LOT lot{};
|
||||
int32_t sortPriority{};
|
||||
bool operator==(const Entry&) const = default;
|
||||
};
|
||||
|
||||
bool bUpdateOnly{};
|
||||
std::vector<Entry> inventoryList{}; // u32 count, then the entries
|
||||
};
|
||||
|
||||
// Server -> client. Sent with SendToClient: the old function never broadcast.
|
||||
struct VendorTransactionResult : public NetGameMsg {
|
||||
VendorTransactionResult() : NetGameMsg(MessageType::Game::VENDOR_TRANSACTION_RESULT) {}
|
||||
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||
|
||||
eVendorTransactionResult iResult{};
|
||||
};
|
||||
|
||||
// Client -> server. Target is the vendor.
|
||||
struct BuyFromVendor : public NetGameMsg {
|
||||
BuyFromVendor() : NetGameMsg(MessageType::Game::BUY_FROM_VENDOR) {}
|
||||
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
|
||||
|
||||
bool confirmed{};
|
||||
int32_t count{ 1 }; // optional
|
||||
LOT item{};
|
||||
};
|
||||
|
||||
// Client -> server. Target is the vendor.
|
||||
struct SellToVendor : public NetGameMsg {
|
||||
SellToVendor() : NetGameMsg(MessageType::Game::SELL_TO_VENDOR) {}
|
||||
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
|
||||
|
||||
int32_t count{ 1 }; // optional
|
||||
LWOOBJID itemObjID{};
|
||||
};
|
||||
|
||||
// Client -> server. Target is the vendor.
|
||||
struct BuybackFromVendor : public NetGameMsg {
|
||||
BuybackFromVendor() : NetGameMsg(MessageType::Game::BUYBACK_FROM_VENDOR) {}
|
||||
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
|
||||
|
||||
bool confirmed{};
|
||||
int32_t count{ 1 }; // optional
|
||||
LWOOBJID item{};
|
||||
};
|
||||
|
||||
// Client -> server. Target is the donation vendor.
|
||||
struct AddDonationItem : public NetGameMsg {
|
||||
AddDonationItem() : NetGameMsg(MessageType::Game::ADD_DONATION_ITEM) {}
|
||||
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
|
||||
|
||||
uint32_t count{ 1 }; // optional
|
||||
LWOOBJID itemObjID{};
|
||||
};
|
||||
|
||||
// Client -> server.
|
||||
struct RemoveDonationItem : public NetGameMsg {
|
||||
RemoveDonationItem() : NetGameMsg(MessageType::Game::REMOVE_DONATION_ITEM) {}
|
||||
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
|
||||
|
||||
bool confirmed{};
|
||||
uint32_t count{ 1 }; // optional
|
||||
LWOOBJID itemObjID{};
|
||||
};
|
||||
|
||||
// Client -> server. Target is the player. The vendor comes from the player's current interaction,
|
||||
// not from vendorID.
|
||||
struct ConfirmDonationOnPlayer : public NetGameMsg {
|
||||
ConfirmDonationOnPlayer() : NetGameMsg(MessageType::Game::CONFIRM_DONATION_ON_PLAYER) {}
|
||||
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
|
||||
|
||||
// Not read by DLU: the old handler ignored the payload, so a message without it is still accepted.
|
||||
LWOOBJID vendorID{};
|
||||
};
|
||||
|
||||
// Client -> server. Target is the player. No payload.
|
||||
struct CancelDonationOnPlayer : public NetGameMsg {
|
||||
CancelDonationOnPlayer() : NetGameMsg(MessageType::Game::CANCEL_DONATION_ON_PLAYER) {}
|
||||
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
|
||||
};
|
||||
};
|
||||
|
||||
#endif // VENDORMESSAGES_H
|
||||
@@ -10,6 +10,7 @@ SET(DGAMEMESSAGES_TESTS
|
||||
"PropertyMessagesTests.cpp"
|
||||
"RacingMessagesTests.cpp"
|
||||
"UnSmashTests.cpp"
|
||||
"VendorMessagesTests.cpp"
|
||||
"LegacyGameMessageTests.cpp"
|
||||
"GameMessageDecoderTests.cpp"
|
||||
"HotPropertyMessageTests.cpp")
|
||||
|
||||
@@ -0,0 +1,328 @@
|
||||
#ifndef VENDORMESSAGESLEGACY_H
|
||||
#define VENDORMESSAGESLEGACY_H
|
||||
|
||||
// FROZEN ORACLE - DO NOT EDIT.
|
||||
// Verbatim copies of the hand written GameMessages::Send* functions that VendorMessages.h and TradeMessages.h
|
||||
// replaced (dGame/dGameMessages/GameMessages.cpp, branched from origin/main 129199e4). Only the namespace changed,
|
||||
// with one exception: SendVendorStatusUpdate takes the vendor's stock as a parameter instead of reading it from
|
||||
// the entity's VendorComponent (a VendorComponent cannot be stocked without CDClient data in the tests); the
|
||||
// rest of its body is unchanged.
|
||||
// The Read* functions are the read sequences of the replaced GameMessages::Handle* functions, verbatim up to
|
||||
// the point where the handler starts using what it read.
|
||||
|
||||
#include "BitStreamUtils.h"
|
||||
#include "dCommonVars.h"
|
||||
#include "dServer.h"
|
||||
#include "Entity.h"
|
||||
#include "eVendorTransactionResult.h"
|
||||
#include "Game.h"
|
||||
#include "MessageType/Client.h"
|
||||
#include "MessageType/Game.h"
|
||||
#include "ServiceType.h"
|
||||
#include "TradingManager.h"
|
||||
#include "VendorComponent.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace LegacyGameMessages {
|
||||
inline void SendVendorOpenWindow(Entity* entity, const SystemAddress& sysAddr) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(entity->GetObjectID());
|
||||
bitStream.Write(MessageType::Game::VENDOR_OPEN_WINDOW);
|
||||
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendVendorStatusUpdate(Entity* entity, const SystemAddress& sysAddr, const std::vector<SoldItem>& vendorItems, bool bUpdateOnly = false) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(entity->GetObjectID());
|
||||
bitStream.Write(MessageType::Game::VENDOR_STATUS_UPDATE);
|
||||
|
||||
bitStream.Write(bUpdateOnly);
|
||||
bitStream.Write<uint32_t>(vendorItems.size());
|
||||
|
||||
|
||||
for (const auto& item : vendorItems) {
|
||||
bitStream.Write(item.lot);
|
||||
bitStream.Write(item.sortPriority);
|
||||
}
|
||||
|
||||
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendVendorTransactionResult(Entity* entity, const SystemAddress& sysAddr, eVendorTransactionResult result) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
|
||||
|
||||
bitStream.Write(entity->GetObjectID());
|
||||
bitStream.Write(MessageType::Game::VENDOR_TRANSACTION_RESULT);
|
||||
bitStream.Write(result);
|
||||
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendServerTradeInvite(LWOOBJID objectId, bool bNeedInvitePopUp, LWOOBJID i64Requestor, std::u16string wsName, const SystemAddress& sysAddr) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(objectId);
|
||||
bitStream.Write(MessageType::Game::SERVER_TRADE_INVITE);
|
||||
|
||||
bitStream.Write(bNeedInvitePopUp);
|
||||
bitStream.Write(i64Requestor);
|
||||
bitStream.Write<uint32_t>(wsName.size());
|
||||
for (const auto character : wsName) {
|
||||
bitStream.Write(character);
|
||||
}
|
||||
|
||||
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendServerTradeInitialReply(LWOOBJID objectId, LWOOBJID i64Invitee, int32_t resultType, std::u16string wsName, const SystemAddress& sysAddr) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(objectId);
|
||||
bitStream.Write(MessageType::Game::SERVER_TRADE_INITIAL_REPLY);
|
||||
|
||||
bitStream.Write(i64Invitee);
|
||||
bitStream.Write(resultType);
|
||||
bitStream.Write<uint32_t>(wsName.size());
|
||||
for (const auto character : wsName) {
|
||||
bitStream.Write(character);
|
||||
}
|
||||
|
||||
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendServerTradeFinalReply(LWOOBJID objectId, bool bResult, LWOOBJID i64Invitee, std::u16string wsName, const SystemAddress& sysAddr) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(objectId);
|
||||
bitStream.Write(MessageType::Game::SERVER_TRADE_FINAL_REPLY);
|
||||
|
||||
bitStream.Write(bResult);
|
||||
bitStream.Write(i64Invitee);
|
||||
bitStream.Write<uint32_t>(wsName.size());
|
||||
for (const auto character : wsName) {
|
||||
bitStream.Write(character);
|
||||
}
|
||||
|
||||
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendServerTradeAccept(LWOOBJID objectId, bool bFirst, const SystemAddress& sysAddr) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(objectId);
|
||||
bitStream.Write(MessageType::Game::SERVER_TRADE_ACCEPT);
|
||||
|
||||
bitStream.Write(bFirst);
|
||||
|
||||
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendServerTradeCancel(LWOOBJID objectId, const SystemAddress& sysAddr) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(objectId);
|
||||
bitStream.Write(MessageType::Game::SERVER_TRADE_CANCEL);
|
||||
|
||||
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendServerTradeUpdate(LWOOBJID objectId, uint64_t coins, const std::vector<TradeItem>& items, const SystemAddress& sysAddr) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(objectId);
|
||||
bitStream.Write(MessageType::Game::SERVER_TRADE_UPDATE);
|
||||
|
||||
bitStream.Write(false);
|
||||
bitStream.Write(coins);
|
||||
bitStream.Write<uint32_t>(items.size());
|
||||
|
||||
for (const auto& item : items) {
|
||||
bitStream.Write(item.itemId);
|
||||
bitStream.Write(item.itemId);
|
||||
|
||||
bitStream.Write(item.itemLot);
|
||||
bitStream.Write0();
|
||||
bitStream.Write1();
|
||||
bitStream.Write(item.itemCount);
|
||||
bitStream.Write0();
|
||||
bitStream.Write0();
|
||||
bitStream.Write0();
|
||||
bitStream.Write0();
|
||||
}
|
||||
|
||||
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
// GameMessages::HandleBuyFromVendor
|
||||
struct LegacyBuyFromVendor { bool bConfirmed{}; int count{}; LOT item{}; };
|
||||
inline LegacyBuyFromVendor ReadBuyFromVendor(RakNet::BitStream& inStream) {
|
||||
bool bConfirmed{}; // This doesn't appear to do anything. Further research is needed.
|
||||
bool countIsDefault{};
|
||||
int count = 1;
|
||||
LOT item;
|
||||
|
||||
inStream.Read(bConfirmed);
|
||||
inStream.Read(countIsDefault);
|
||||
if (countIsDefault) inStream.Read(count);
|
||||
inStream.Read(item);
|
||||
return { bConfirmed, count, item };
|
||||
}
|
||||
|
||||
// GameMessages::HandleSellToVendor
|
||||
struct LegacySellToVendor { int count{}; LWOOBJID iObjID{}; };
|
||||
inline LegacySellToVendor ReadSellToVendor(RakNet::BitStream& inStream) {
|
||||
bool countIsDefault{};
|
||||
int count = 1;
|
||||
LWOOBJID iObjID;
|
||||
|
||||
inStream.Read(countIsDefault);
|
||||
if (countIsDefault) inStream.Read(count);
|
||||
inStream.Read(iObjID);
|
||||
return { count, iObjID };
|
||||
}
|
||||
|
||||
// GameMessages::HandleBuybackFromVendor
|
||||
struct LegacyBuybackFromVendor { bool confirmed{}; int count{}; LWOOBJID iObjID{}; };
|
||||
inline LegacyBuybackFromVendor ReadBuybackFromVendor(RakNet::BitStream& inStream) {
|
||||
bool confirmed = false;
|
||||
bool countIsDefault{};
|
||||
int count = 1;
|
||||
LWOOBJID iObjID;
|
||||
|
||||
inStream.Read(confirmed);
|
||||
inStream.Read(countIsDefault);
|
||||
if (countIsDefault) inStream.Read(count);
|
||||
inStream.Read(iObjID);
|
||||
return { confirmed, count, iObjID };
|
||||
}
|
||||
|
||||
// GameMessages::HandleAddDonationItem
|
||||
struct LegacyAddDonationItem { uint32_t count{}; LWOOBJID itemId{}; };
|
||||
inline LegacyAddDonationItem ReadAddDonationItem(RakNet::BitStream& inStream) {
|
||||
uint32_t count = 1;
|
||||
bool hasCount = false;
|
||||
inStream.Read(hasCount);
|
||||
if (hasCount) inStream.Read(count);
|
||||
LWOOBJID itemId = LWOOBJID_EMPTY;
|
||||
inStream.Read(itemId);
|
||||
return { count, itemId };
|
||||
}
|
||||
|
||||
// GameMessages::HandleRemoveDonationItem
|
||||
struct LegacyRemoveDonationItem { bool confirmed{}; uint32_t count{}; LWOOBJID itemId{}; };
|
||||
inline LegacyRemoveDonationItem ReadRemoveDonationItem(RakNet::BitStream& inStream) {
|
||||
bool confirmed = false;
|
||||
inStream.Read(confirmed);
|
||||
uint32_t count = 1;
|
||||
bool hasCount = false;
|
||||
inStream.Read(hasCount);
|
||||
if (hasCount) inStream.Read(count);
|
||||
LWOOBJID itemId = LWOOBJID_EMPTY;
|
||||
inStream.Read(itemId);
|
||||
return { confirmed, count, itemId };
|
||||
}
|
||||
|
||||
// GameMessages::HandleClientTradeRequest
|
||||
struct LegacyClientTradeRequest { bool bNeedInvitePopUp{}; LWOOBJID i64Invitee{}; };
|
||||
inline LegacyClientTradeRequest ReadClientTradeRequest(RakNet::BitStream& inStream) {
|
||||
bool bNeedInvitePopUp = inStream.ReadBit();
|
||||
LWOOBJID i64Invitee;
|
||||
|
||||
inStream.Read(i64Invitee);
|
||||
return { bNeedInvitePopUp, i64Invitee };
|
||||
}
|
||||
|
||||
// GameMessages::HandleClientTradeAccept
|
||||
inline bool ReadClientTradeAccept(RakNet::BitStream& inStream) {
|
||||
bool bFirst = inStream.ReadBit();
|
||||
return bFirst;
|
||||
}
|
||||
|
||||
// GameMessages::HandleClientTradeUpdate. The per item log values are returned alongside the TradeItems.
|
||||
struct LegacyTradeItemLog { LWOOBJID itemId{}; LWOOBJID itemId2{}; LOT lot{}; LWOOBJID unknown1{}; uint32_t unknown2{}; uint16_t slot{}; uint32_t unknown3{}; bool unknown4{}; };
|
||||
struct LegacyClientTradeUpdate { bool rejected{}; uint64_t currency{}; std::vector<TradeItem> items{}; std::vector<LegacyTradeItemLog> logs{}; };
|
||||
inline LegacyClientTradeUpdate ReadClientTradeUpdate(RakNet::BitStream& inStream) {
|
||||
LegacyClientTradeUpdate result;
|
||||
uint64_t currency;
|
||||
uint32_t itemCount;
|
||||
|
||||
inStream.Read(currency);
|
||||
inStream.Read(itemCount);
|
||||
if (itemCount > MAX_MESSAGE_LENGTH) { result.rejected = true; return result; }
|
||||
|
||||
std::vector<TradeItem> items{};
|
||||
|
||||
for (size_t i = 0; i < itemCount; i++) {
|
||||
LWOOBJID itemId;
|
||||
LWOOBJID itemId2;
|
||||
|
||||
inStream.Read(itemId);
|
||||
inStream.Read(itemId2);
|
||||
|
||||
LOT lot = 0;
|
||||
LWOOBJID unknown1 = 0;
|
||||
uint32_t unknown2 = 0;
|
||||
uint16_t slot = 0;
|
||||
uint32_t unknown3 = 0;
|
||||
uint32_t ldfSize = 0;
|
||||
bool unknown4;
|
||||
|
||||
inStream.Read(lot);
|
||||
if (inStream.ReadBit()) {
|
||||
inStream.Read(unknown1);
|
||||
}
|
||||
if (inStream.ReadBit()) {
|
||||
inStream.Read(unknown2);
|
||||
}
|
||||
if (inStream.ReadBit()) {
|
||||
inStream.Read(slot);
|
||||
}
|
||||
if (inStream.ReadBit()) {
|
||||
inStream.Read(unknown3);
|
||||
}
|
||||
if (inStream.ReadBit()) // No
|
||||
{
|
||||
inStream.Read(ldfSize);
|
||||
bool compressed = inStream.ReadBit();
|
||||
if (compressed) {
|
||||
uint32_t ldfCompressedSize = 0;
|
||||
inStream.Read(ldfCompressedSize);
|
||||
inStream.IgnoreBytes(ldfCompressedSize);
|
||||
} else {
|
||||
inStream.IgnoreBytes(ldfSize);
|
||||
}
|
||||
}
|
||||
unknown4 = inStream.ReadBit();
|
||||
|
||||
items.push_back({ itemId, lot, unknown2 });
|
||||
result.logs.push_back({ itemId, itemId2, lot, unknown1, unknown2, slot, unknown3, unknown4 });
|
||||
}
|
||||
result.currency = currency;
|
||||
result.items = items;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // VENDORMESSAGESLEGACY_H
|
||||
383
tests/dGameTests/dGameMessagesTests/VendorMessagesTests.cpp
Normal file
383
tests/dGameTests/dGameMessagesTests/VendorMessagesTests.cpp
Normal file
@@ -0,0 +1,383 @@
|
||||
#include "VendorMessages.h"
|
||||
#include "TradeMessages.h"
|
||||
#include "GameDependencies.h"
|
||||
#include "GameMessageTestUtils.h"
|
||||
#include "Legacy/VendorMessagesLegacy.h"
|
||||
|
||||
#include "eVendorTransactionResult.h"
|
||||
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using namespace GameMessageTestUtils;
|
||||
|
||||
namespace {
|
||||
const std::vector<int32_t> g_Ints = { 0, 1, -1, 5, 1727, std::numeric_limits<int32_t>::max(), std::numeric_limits<int32_t>::min() };
|
||||
const std::vector<std::u16string> g_Names = { u"", u"A", u"Mythran", u"é中文", std::u16string(300, u'x') };
|
||||
|
||||
PacketBytes Payload(const GameMessages::NetGameMsg& msg) {
|
||||
RakNet::BitStream bitStream;
|
||||
msg.Serialize(bitStream);
|
||||
return FromBitStream(bitStream);
|
||||
}
|
||||
|
||||
// Serializes msg, then reads it with the legacy read sequence; both must consume exactly the same bits.
|
||||
template<typename Result>
|
||||
Result ReadWithLegacy(const GameMessages::NetGameMsg& msg, const std::function<Result(RakNet::BitStream&)>& read) {
|
||||
RakNet::BitStream wire;
|
||||
msg.Serialize(wire);
|
||||
RakNet::BitStream legacyStream(wire.GetData(), wire.GetNumberOfBytesUsed(), false);
|
||||
auto result = read(legacyStream);
|
||||
EXPECT_EQ(legacyStream.GetReadOffset(), wire.GetNumberOfBitsUsed());
|
||||
return result;
|
||||
}
|
||||
|
||||
// The entry DLU writes for a traded item.
|
||||
GameMessages::TradeItemEntry EntryFor(const TradeItem& item) {
|
||||
GameMessages::TradeItemEntry entry;
|
||||
entry.key = item.itemId;
|
||||
entry.itemID = item.itemId;
|
||||
entry.templateID = item.itemLot;
|
||||
entry.count = item.itemCount;
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
class VendorMessagesTests : public GameDependenciesTest {
|
||||
protected:
|
||||
void SetUp() override { SetUpDependencies(); }
|
||||
void TearDown() override { TearDownDependencies(); }
|
||||
};
|
||||
|
||||
TEST_F(VendorMessagesTests, VendorOpenWindowMatchesLegacy) {
|
||||
for (const auto target : g_Targets) {
|
||||
Entity entity(target, info);
|
||||
GameMessages::VendorOpenWindow msg;
|
||||
msg.target = target;
|
||||
ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendVendorOpenWindow(&entity, a); }, msg, SendMode::SendToClient);
|
||||
RoundTrip(msg);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(VendorMessagesTests, VendorStatusUpdateMatchesLegacy) {
|
||||
std::vector<SoldItem> many;
|
||||
for (int32_t i = 0; i < 300; i++) many.emplace_back(1000 + i, i);
|
||||
const std::vector<std::vector<SoldItem>> stocks = { {}, { SoldItem(1727, 0) }, { SoldItem(-1, -1), SoldItem(0, std::numeric_limits<int32_t>::max()), SoldItem(16253, 2) }, many };
|
||||
for (const auto target : g_Targets) {
|
||||
Entity entity(target, info);
|
||||
for (const auto& stock : stocks) {
|
||||
for (const bool bUpdateOnly : { false, true }) {
|
||||
GameMessages::VendorStatusUpdate msg;
|
||||
msg.target = target;
|
||||
msg.bUpdateOnly = bUpdateOnly;
|
||||
for (const auto& item : stock) msg.inventoryList.push_back({ item.lot, item.sortPriority });
|
||||
ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendVendorStatusUpdate(&entity, a, stock, bUpdateOnly); }, msg);
|
||||
const auto copy = RoundTrip(msg);
|
||||
EXPECT_EQ(copy.bUpdateOnly, bUpdateOnly);
|
||||
EXPECT_EQ(copy.inventoryList, msg.inventoryList);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(VendorMessagesTests, VendorTransactionResultMatchesLegacy) {
|
||||
for (const auto target : g_Targets) {
|
||||
Entity entity(target, info);
|
||||
for (const auto result : { eVendorTransactionResult::SELL_SUCCESS, eVendorTransactionResult::SELL_FAIL, eVendorTransactionResult::PURCHASE_SUCCESS, eVendorTransactionResult::PURCHASE_FAIL, eVendorTransactionResult::DONATION_FAIL, eVendorTransactionResult::DONATION_FULL, static_cast<eVendorTransactionResult>(0xFFFFFFFF) }) {
|
||||
GameMessages::VendorTransactionResult msg;
|
||||
msg.target = target;
|
||||
msg.iResult = result;
|
||||
ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendVendorTransactionResult(&entity, a, result); }, msg, SendMode::SendToClient);
|
||||
EXPECT_EQ(RoundTrip(msg).iResult, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(VendorMessagesTests, TradeRepliesMatchLegacy) {
|
||||
for (const auto target : g_Targets) {
|
||||
for (const auto other : g_Targets) {
|
||||
for (const auto& name : g_Names) {
|
||||
for (const bool flag : { false, true }) {
|
||||
GameMessages::ServerTradeInvite invite;
|
||||
invite.target = target;
|
||||
invite.bNeedInvitePopUp = flag;
|
||||
invite.i64Requestor = other;
|
||||
invite.wsName = name;
|
||||
ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendServerTradeInvite(target, flag, other, name, a); }, invite);
|
||||
const auto inviteCopy = RoundTrip(invite);
|
||||
EXPECT_EQ(inviteCopy.bNeedInvitePopUp, flag);
|
||||
EXPECT_EQ(inviteCopy.i64Requestor, other);
|
||||
EXPECT_EQ(inviteCopy.wsName, name);
|
||||
|
||||
GameMessages::ServerTradeFinalReply finalReply;
|
||||
finalReply.target = target;
|
||||
finalReply.bResult = flag;
|
||||
finalReply.i64Invitee = other;
|
||||
finalReply.wsName = name;
|
||||
ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendServerTradeFinalReply(target, flag, other, name, a); }, finalReply);
|
||||
const auto finalCopy = RoundTrip(finalReply);
|
||||
EXPECT_EQ(finalCopy.bResult, flag);
|
||||
EXPECT_EQ(finalCopy.i64Invitee, other);
|
||||
EXPECT_EQ(finalCopy.wsName, name);
|
||||
}
|
||||
for (const auto resultType : g_Ints) {
|
||||
GameMessages::ServerTradeInitialReply initialReply;
|
||||
initialReply.target = target;
|
||||
initialReply.i64Invitee = other;
|
||||
initialReply.resultType = resultType;
|
||||
initialReply.wsName = name;
|
||||
ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendServerTradeInitialReply(target, other, resultType, name, a); }, initialReply);
|
||||
const auto initialCopy = RoundTrip(initialReply);
|
||||
EXPECT_EQ(initialCopy.i64Invitee, other);
|
||||
EXPECT_EQ(initialCopy.resultType, resultType);
|
||||
EXPECT_EQ(initialCopy.wsName, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(VendorMessagesTests, TradeAcceptAndCancelMatchLegacy) {
|
||||
for (const auto target : g_Targets) {
|
||||
for (const bool bFirst : { false, true }) {
|
||||
GameMessages::ServerTradeAccept accept;
|
||||
accept.target = target;
|
||||
accept.bFirst = bFirst;
|
||||
ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendServerTradeAccept(target, bFirst, a); }, accept);
|
||||
EXPECT_EQ(RoundTrip(accept).bFirst, bFirst);
|
||||
}
|
||||
GameMessages::ServerTradeCancel cancel;
|
||||
cancel.target = target;
|
||||
ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendServerTradeCancel(target, a); }, cancel);
|
||||
RoundTrip(cancel);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(VendorMessagesTests, ServerTradeUpdateMatchesLegacy) {
|
||||
std::vector<TradeItem> many;
|
||||
for (uint32_t i = 0; i < 50; i++) many.push_back({ 0x1000000000000000LL + i, static_cast<LOT>(i), i });
|
||||
const std::vector<std::vector<TradeItem>> itemSets = {
|
||||
{},
|
||||
{ { 0x0102030405060708LL, 1727, 1 } },
|
||||
{ { LWOOBJID_EMPTY, LOT_NULL, 0 }, { 0x1000000000000001LL, 0, std::numeric_limits<uint32_t>::max() } },
|
||||
many,
|
||||
};
|
||||
for (const auto target : g_Targets) {
|
||||
for (const uint64_t coins : { uint64_t{ 0 }, uint64_t{ 250 }, std::numeric_limits<uint64_t>::max() }) {
|
||||
for (const auto& items : itemSets) {
|
||||
GameMessages::ServerTradeUpdate msg;
|
||||
msg.target = target;
|
||||
msg.i64Currency = coins;
|
||||
for (const auto& item : items) msg.inventoryMap.push_back(EntryFor(item));
|
||||
ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendServerTradeUpdate(target, coins, items, a); }, msg);
|
||||
const auto copy = RoundTrip(msg);
|
||||
EXPECT_EQ(copy.i64Currency, coins);
|
||||
EXPECT_EQ(copy.inventoryMap, msg.inventoryMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(VendorMessagesTests, VendorInboundReadsLikeLegacy) {
|
||||
for (const auto id : g_Targets) {
|
||||
for (const auto count : g_Ints) {
|
||||
for (const bool confirmed : { false, true }) {
|
||||
GameMessages::BuyFromVendor buy;
|
||||
buy.confirmed = confirmed;
|
||||
buy.count = count;
|
||||
buy.item = static_cast<LOT>(id);
|
||||
const auto legacyBuy = ReadWithLegacy<LegacyGameMessages::LegacyBuyFromVendor>(buy, LegacyGameMessages::ReadBuyFromVendor);
|
||||
const auto buyCopy = RoundTrip(buy);
|
||||
EXPECT_EQ(buyCopy.confirmed, legacyBuy.bConfirmed);
|
||||
EXPECT_EQ(buyCopy.count, legacyBuy.count);
|
||||
EXPECT_EQ(buyCopy.item, legacyBuy.item);
|
||||
ExpectTruncatedFails(buy);
|
||||
|
||||
GameMessages::BuybackFromVendor buyback;
|
||||
buyback.confirmed = confirmed;
|
||||
buyback.count = count;
|
||||
buyback.item = id;
|
||||
const auto legacyBuyback = ReadWithLegacy<LegacyGameMessages::LegacyBuybackFromVendor>(buyback, LegacyGameMessages::ReadBuybackFromVendor);
|
||||
const auto buybackCopy = RoundTrip(buyback);
|
||||
EXPECT_EQ(buybackCopy.confirmed, legacyBuyback.confirmed);
|
||||
EXPECT_EQ(buybackCopy.count, legacyBuyback.count);
|
||||
EXPECT_EQ(buybackCopy.item, legacyBuyback.iObjID);
|
||||
ExpectTruncatedFails(buyback);
|
||||
|
||||
GameMessages::RemoveDonationItem remove;
|
||||
remove.confirmed = confirmed;
|
||||
remove.count = static_cast<uint32_t>(count);
|
||||
remove.itemObjID = id;
|
||||
const auto legacyRemove = ReadWithLegacy<LegacyGameMessages::LegacyRemoveDonationItem>(remove, LegacyGameMessages::ReadRemoveDonationItem);
|
||||
const auto removeCopy = RoundTrip(remove);
|
||||
EXPECT_EQ(removeCopy.confirmed, legacyRemove.confirmed);
|
||||
EXPECT_EQ(removeCopy.count, legacyRemove.count);
|
||||
EXPECT_EQ(removeCopy.itemObjID, legacyRemove.itemId);
|
||||
ExpectTruncatedFails(remove);
|
||||
}
|
||||
|
||||
GameMessages::SellToVendor sell;
|
||||
sell.count = count;
|
||||
sell.itemObjID = id;
|
||||
const auto legacySell = ReadWithLegacy<LegacyGameMessages::LegacySellToVendor>(sell, LegacyGameMessages::ReadSellToVendor);
|
||||
const auto sellCopy = RoundTrip(sell);
|
||||
EXPECT_EQ(sellCopy.count, legacySell.count);
|
||||
EXPECT_EQ(sellCopy.itemObjID, legacySell.iObjID);
|
||||
ExpectTruncatedFails(sell);
|
||||
|
||||
GameMessages::AddDonationItem add;
|
||||
add.count = static_cast<uint32_t>(count);
|
||||
add.itemObjID = id;
|
||||
const auto legacyAdd = ReadWithLegacy<LegacyGameMessages::LegacyAddDonationItem>(add, LegacyGameMessages::ReadAddDonationItem);
|
||||
const auto addCopy = RoundTrip(add);
|
||||
EXPECT_EQ(addCopy.count, legacyAdd.count);
|
||||
EXPECT_EQ(addCopy.itemObjID, legacyAdd.itemId);
|
||||
ExpectTruncatedFails(add);
|
||||
}
|
||||
|
||||
// The old handler read nothing, so ConfirmDonationOnPlayer is accepted with or without its vendorID.
|
||||
GameMessages::ConfirmDonationOnPlayer confirm;
|
||||
confirm.vendorID = id;
|
||||
EXPECT_EQ(RoundTrip(confirm).vendorID, id);
|
||||
RakNet::BitStream empty;
|
||||
GameMessages::ConfirmDonationOnPlayer fromEmpty;
|
||||
EXPECT_TRUE(fromEmpty.Deserialize(empty));
|
||||
}
|
||||
|
||||
// Messages without a payload.
|
||||
RakNet::BitStream empty;
|
||||
EXPECT_TRUE(GameMessages::RequestVendorStatusUpdate().Deserialize(empty));
|
||||
EXPECT_TRUE(GameMessages::CancelDonationOnPlayer().Deserialize(empty));
|
||||
EXPECT_TRUE(GameMessages::ClientTradeCancel().Deserialize(empty));
|
||||
}
|
||||
|
||||
TEST_F(VendorMessagesTests, TradeInboundReadsLikeLegacy) {
|
||||
for (const auto id : g_Targets) {
|
||||
for (const bool flag : { false, true }) {
|
||||
GameMessages::ClientTradeRequest request;
|
||||
request.bNeedInvitePopUp = flag;
|
||||
request.i64Invitee = id;
|
||||
const auto legacyRequest = ReadWithLegacy<LegacyGameMessages::LegacyClientTradeRequest>(request, LegacyGameMessages::ReadClientTradeRequest);
|
||||
const auto requestCopy = RoundTrip(request);
|
||||
EXPECT_EQ(requestCopy.bNeedInvitePopUp, legacyRequest.bNeedInvitePopUp);
|
||||
EXPECT_EQ(requestCopy.i64Invitee, legacyRequest.i64Invitee);
|
||||
ExpectTruncatedFails(request);
|
||||
|
||||
GameMessages::ClientTradeAccept accept;
|
||||
accept.bFirst = flag;
|
||||
EXPECT_EQ(ReadWithLegacy<bool>(accept, LegacyGameMessages::ReadClientTradeAccept), flag);
|
||||
EXPECT_EQ(RoundTrip(accept).bFirst, flag);
|
||||
ExpectTruncatedFails(accept);
|
||||
}
|
||||
}
|
||||
|
||||
// Every optional field of an entry, set and unset, with raw and compressed config blocks.
|
||||
std::vector<GameMessages::TradeItemEntry> entries;
|
||||
for (uint32_t mask = 0; mask < 64; mask++) {
|
||||
GameMessages::TradeItemEntry entry;
|
||||
entry.key = 0x0102030405060708LL + mask;
|
||||
entry.itemID = 0x1000000000000001LL + mask;
|
||||
entry.templateID = 1727 + static_cast<LOT>(mask);
|
||||
if (mask & 1) entry.subkey = 0x2000000000000002LL;
|
||||
if (mask & 2) entry.count = 7 + mask;
|
||||
if (mask & 4) entry.slot = static_cast<uint16_t>(3 + mask);
|
||||
if (mask & 8) entry.inventoryType = 5;
|
||||
if (mask & 16) {
|
||||
GameMessages::TradeItemEntry::Config config;
|
||||
config.compressed = (mask & 32) != 0;
|
||||
config.data = { 0xde, 0xad, 0xbe, 0xef, static_cast<uint8_t>(mask) };
|
||||
config.size = config.compressed ? 40 : static_cast<uint32_t>(config.data.size());
|
||||
config.compressedSize = config.compressed ? static_cast<uint32_t>(config.data.size()) : 0;
|
||||
entry.config = config;
|
||||
}
|
||||
entry.unknownFlag = (mask & 32) != 0;
|
||||
entries.push_back(entry);
|
||||
}
|
||||
const std::vector<std::vector<GameMessages::TradeItemEntry>> entrySets = { {}, { entries[0] }, { entries[3], entries[63] }, entries };
|
||||
|
||||
for (const uint64_t currency : { uint64_t{ 0 }, uint64_t{ 99 }, std::numeric_limits<uint64_t>::max() }) {
|
||||
for (const auto& set : entrySets) {
|
||||
GameMessages::ClientTradeUpdate update;
|
||||
update.i64Currency = currency;
|
||||
update.inventoryMap = set;
|
||||
const auto legacy = ReadWithLegacy<LegacyGameMessages::LegacyClientTradeUpdate>(update, LegacyGameMessages::ReadClientTradeUpdate);
|
||||
const auto copy = RoundTrip(update);
|
||||
ASSERT_FALSE(legacy.rejected);
|
||||
EXPECT_EQ(copy.i64Currency, legacy.currency);
|
||||
ASSERT_EQ(copy.inventoryMap.size(), legacy.items.size());
|
||||
for (size_t i = 0; i < legacy.items.size(); i++) {
|
||||
const auto& entry = copy.inventoryMap[i];
|
||||
// What the handler hands to the trade.
|
||||
EXPECT_EQ(entry.key, legacy.items[i].itemId);
|
||||
EXPECT_EQ(entry.templateID, legacy.items[i].itemLot);
|
||||
EXPECT_EQ(entry.count.value_or(0), legacy.items[i].itemCount);
|
||||
// What it logs.
|
||||
EXPECT_EQ(entry.itemID, legacy.logs[i].itemId2);
|
||||
EXPECT_EQ(entry.subkey.value_or(0), legacy.logs[i].unknown1);
|
||||
EXPECT_EQ(entry.slot.value_or(0), legacy.logs[i].slot);
|
||||
EXPECT_EQ(entry.inventoryType.value_or(0), legacy.logs[i].unknown3);
|
||||
EXPECT_EQ(entry.unknownFlag, legacy.logs[i].unknown4);
|
||||
}
|
||||
if (set.size() <= 2) ExpectTruncatedFails(update);
|
||||
}
|
||||
}
|
||||
|
||||
// The old handler ignored updates claiming more than MAX_MESSAGE_LENGTH items; the struct refuses them.
|
||||
RakNet::BitStream huge;
|
||||
huge.Write<uint64_t>(1);
|
||||
huge.Write<uint32_t>(MAX_MESSAGE_LENGTH + 1);
|
||||
RakNet::BitStream legacyHuge(huge.GetData(), huge.GetNumberOfBytesUsed(), false);
|
||||
EXPECT_TRUE(LegacyGameMessages::ReadClientTradeUpdate(legacyHuge).rejected);
|
||||
GameMessages::ClientTradeUpdate hugeUpdate;
|
||||
EXPECT_FALSE(hugeUpdate.Deserialize(huge));
|
||||
}
|
||||
|
||||
// Independent of the legacy code: hand computed payloads (RakNet writes MSB first).
|
||||
TEST_F(VendorMessagesTests, GoldenBytes) {
|
||||
GameMessages::BuyFromVendor buy;
|
||||
buy.confirmed = true;
|
||||
buy.item = 0x11;
|
||||
EXPECT_PACKET_EQ(FromHex("84 40 00 00 00", 34), Payload(buy));
|
||||
buy.confirmed = false;
|
||||
buy.count = 5;
|
||||
EXPECT_PACKET_EQ(FromHex("41 40 00 00 04 40 00 00 00", 66), Payload(buy));
|
||||
|
||||
GameMessages::SellToVendor sell;
|
||||
sell.itemObjID = 0x33;
|
||||
EXPECT_PACKET_EQ(FromHex("19 80 00 00 00 00 00 00 00", 65), Payload(sell));
|
||||
|
||||
GameMessages::VendorStatusUpdate status;
|
||||
status.bUpdateOnly = true;
|
||||
status.inventoryList = { { 1727, 0 }, { 3, -1 } };
|
||||
EXPECT_PACKET_EQ(FromHex("81 00 00 00 5f 83 00 00 00 00 00 00 01 80 00 00 7f ff ff ff 80", 161), Payload(status));
|
||||
|
||||
GameMessages::VendorTransactionResult result;
|
||||
result.iResult = eVendorTransactionResult::PURCHASE_FAIL;
|
||||
EXPECT_PACKET_EQ(FromHex("03 00 00 00"), Payload(result));
|
||||
|
||||
GameMessages::ServerTradeInvite invite;
|
||||
invite.i64Requestor = 0x11;
|
||||
invite.wsName = u"A";
|
||||
EXPECT_PACKET_EQ(FromHex("08 80 00 00 00 00 00 00 00 80 00 00 20 80 00", 113), Payload(invite));
|
||||
|
||||
GameMessages::ServerTradeUpdate update;
|
||||
update.i64Currency = 250;
|
||||
update.inventoryMap.push_back(EntryFor({ 0x22, 1727, 3 }));
|
||||
EXPECT_PACKET_EQ(FromHex("7d 00 00 00 00 00 00 00 00 80 00 00 11 00 00 00 00 00 00 00 11 00 00 00 00 00 00 00 5f 83 00 00 20 60 00 00 00", 295), Payload(update));
|
||||
|
||||
GameMessages::ServerTradeAccept accept;
|
||||
accept.bFirst = true;
|
||||
EXPECT_PACKET_EQ(FromHex("80", 1), Payload(accept));
|
||||
}
|
||||
|
||||
// The whole packet as Send puts it on the wire, including the header, target and message ID.
|
||||
TEST_F(VendorMessagesTests, GoldenPacket) {
|
||||
GameMessages::VendorOpenWindow open;
|
||||
open.target = 0x0102030405060708LL;
|
||||
// 53 = packet ID, 0005 = CLIENT, 0000000c = GAME_MSG, pad, target, u16 369.
|
||||
EXPECT_PACKET_EQ(FromHex("53 05 00 0c 00 00 00 00 08 07 06 05 04 03 02 01 71 01"), StructPacket(open));
|
||||
}
|
||||
Reference in New Issue
Block a user