mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 10:53:44 +00:00
refactor: inventory and item game messages as structs
Converts AddItemToInventoryClientSync, SetInventorySize,
RemoveItemFromInventory, ConsumeClientItem, UseItemResult,
UseItemRequirementsResponse, ResponseMoveItemBetweenInventoryTypes,
NotifyNotEnoughInvSpace, UpdateInventoryUi, MarkInventoryItemAsActive and
MoveInventoryBatch (11 old Send functions) and the received EquipInventory,
UnEquipInventory, RemoveItemFromInventory, MoveItemInInventory,
MoveItemBetweenInventoryTypes, RequestMoveItemBetweenInventoryTypes,
PushEquippedItemsState, PopEquippedItemsState, ClientItemConsumed,
UseNonEquipmentItem, SetConsumableItem, UpdateInventoryGroup and
UpdateInventoryGroupContents (13 handlers) to NetGameMsgs in
InventoryMessages.{h,cpp}. The received messages are registered in
GameMessageHandler's map and handled by InventoryComponent (new On* methods
holding the old handler logic verbatim); the old functions and switch cases
are deleted. AddItemToInventoryClientSync::SetItem fills the fields that
come from the Item.
No wire change and no change in recipients. Kept as DLU has always sent them
and documented: NotifyNotEnoughInvSpace goes out with the message ID of
VehicleNotifyFinishedRace, and RemoveItemFromInventory always sets the flag
of the fields DLU fills. The old SendMoveInventoryBatch was never called
and wrote one flag bit fewer than the client reads (it had no moveSubkey);
the struct follows the client's layout (1.10.64,
LWOInventoryComponent_Common::msgMoveInventoryBatch at 00ce1310) and has no
oracle. UnEquipInventory still ignores the trailing replacementObjectID the
client sends, as before.
Verified byte for byte against a frozen verbatim copy of the old functions
over an input grid (AddItemToInventoryClientSync with real Items, extra info
and bind flags), to one client and broadcast; received messages compared
with the old handlers' read sequences and every truncated payload rejected;
hand computed golden bytes; round trips; a deliberate width mutation made
the tests fail.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ SET(DGAMEMESSAGES_TESTS
|
||||
"GameMessageTests.cpp"
|
||||
"GameMsgSplitTests.cpp"
|
||||
"EffectsMessagesTests.cpp"
|
||||
"InventoryMessagesTests.cpp"
|
||||
"MissionMessagesTests.cpp"
|
||||
"PetMessagesTests.cpp"
|
||||
"PropertyMessagesTests.cpp"
|
||||
|
||||
519
tests/dGameTests/dGameMessagesTests/InventoryMessagesTests.cpp
Normal file
519
tests/dGameTests/dGameMessagesTests/InventoryMessagesTests.cpp
Normal file
@@ -0,0 +1,519 @@
|
||||
#include "InventoryMessages.h"
|
||||
#include "GameDependencies.h"
|
||||
#include "GameMessageTestUtils.h"
|
||||
#include "Legacy/InventoryMessagesLegacy.h"
|
||||
|
||||
#include "CDComponentsRegistryTable.h"
|
||||
#include "CDItemComponentTable.h"
|
||||
#include "eReplicaComponentType.h"
|
||||
#include "Inventory.h"
|
||||
#include "Item.h"
|
||||
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using namespace GameMessageTestUtils;
|
||||
|
||||
namespace {
|
||||
const std::vector<int32_t> g_Ints = { 0, 1, -1, 1727, std::numeric_limits<int32_t>::max(), std::numeric_limits<int32_t>::min() };
|
||||
const std::vector<uint32_t> g_UInts = { 0, 1, 2, 999, std::numeric_limits<uint32_t>::max() };
|
||||
const std::vector<LWOOBJID> g_Ids = { LWOOBJID_EMPTY, 0x1000000000000001LL, -1 };
|
||||
const std::vector<eInventoryType> g_InventoryTypes = { eInventoryType::ITEMS, eInventoryType::VAULT_ITEMS, eInventoryType::MODELS, eInventoryType::INVALID };
|
||||
const std::vector<std::u16string> g_WStrings = { u"", u"a", u"ma=1:6416+1:8092", u"été ☃", 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;
|
||||
}
|
||||
|
||||
// Makes lot a valid item (ItemComponent componentID) with the given bind flags, without a CDClient database.
|
||||
void RegisterItemLot(const LOT lot, const uint32_t componentID, const bool isBOE, const bool isBOP) {
|
||||
auto& registry = CDClientManager::GetEntriesMutable<CDComponentsRegistryTable>();
|
||||
registry.insert_or_assign(static_cast<uint64_t>(lot), componentID);
|
||||
registry.insert_or_assign(static_cast<uint64_t>(eReplicaComponentType::ITEM) << 32 | static_cast<uint64_t>(lot), componentID);
|
||||
CDItemComponent component{};
|
||||
component.id = componentID;
|
||||
component.isBOE = isBOE;
|
||||
component.isBOP = isBOP;
|
||||
CDClientManager::GetEntriesMutable<CDItemComponentTable>().insert_or_assign(componentID, component);
|
||||
}
|
||||
}
|
||||
|
||||
class InventoryMessagesTests : public GameDependenciesTest {
|
||||
protected:
|
||||
void SetUp() override { SetUpDependencies(); }
|
||||
void TearDown() override { TearDownDependencies(); }
|
||||
};
|
||||
|
||||
TEST_F(InventoryMessagesTests, AddItemToInventoryClientSyncMatchesLegacy) {
|
||||
RegisterItemLot(1727, 90001, false, false);
|
||||
RegisterItemLot(6416, 90002, true, false);
|
||||
RegisterItemLot(8092, 90003, false, true);
|
||||
|
||||
LwoNameValue emptyConfig;
|
||||
LwoNameValue config;
|
||||
config.Insert(u"assemblyPartLOTs", std::u16string(u"1:6416+1:8092"));
|
||||
config.Insert<int32_t>(u"userModelID", 42);
|
||||
|
||||
uint32_t slot = 0;
|
||||
for (const auto inventoryType : { eInventoryType::ITEMS, eInventoryType::MODELS }) {
|
||||
Inventory inventory(inventoryType, 400, {}, nullptr);
|
||||
for (const LOT lot : { 1727, 6416, 8092 }) {
|
||||
for (const uint32_t count : { 0u, 1u, 7u }) {
|
||||
for (const bool bound : { false, true }) {
|
||||
for (const auto* itemConfig : { &emptyConfig, &config }) {
|
||||
// The inventory owns (and deletes) the item.
|
||||
auto* item = new Item(0x5000 + slot, lot, &inventory, slot, count, bound, *itemConfig, LWOOBJID_EMPTY, LWOOBJID_EMPTY, eLootSourceType::NONE);
|
||||
slot++;
|
||||
for (const auto target : g_Targets) {
|
||||
Entity entity(target, info);
|
||||
for (const LWOOBJID newID : { LWOOBJID_EMPTY, LWOOBJID{ 0x7777 } }) {
|
||||
for (const bool showFlyingLoot : { false, true }) {
|
||||
for (const int itemCount : { 1, 0, -1, 5 }) {
|
||||
for (const LWOOBJID subKey : { LWOOBJID_EMPTY, LWOOBJID{ 0x99 } }) {
|
||||
for (const auto lootSource : { eLootSourceType::NONE, eLootSourceType::QUICKBUILD }) {
|
||||
GameMessages::AddItemToInventoryClientSync msg;
|
||||
msg.target = target;
|
||||
msg.SetItem(*item);
|
||||
msg.eLootTypeSource = lootSource;
|
||||
msg.iSubkey = subKey;
|
||||
msg.itemCount = itemCount;
|
||||
msg.newObjID = newID;
|
||||
msg.showFlyingLoot = showFlyingLoot;
|
||||
ExpectSameAsLegacy([&](const SystemAddress& a) {
|
||||
LegacyGameMessages::SendAddItemToInventoryClientSync(&entity, a, item, newID, showFlyingLoot, itemCount, subKey, lootSource);
|
||||
}, msg, SendMode::SendToClient);
|
||||
const auto copy = RoundTrip(msg);
|
||||
EXPECT_EQ(copy.extraInfo, msg.extraInfo);
|
||||
EXPECT_EQ(copy.iObjTemplate, lot);
|
||||
EXPECT_EQ(copy.invType, inventoryType);
|
||||
EXPECT_EQ(copy.itemsTotal, count);
|
||||
EXPECT_EQ(copy.itemCount, itemCount);
|
||||
EXPECT_EQ(copy.iSubkey, subKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(InventoryMessagesTests, SmallMessagesMatchLegacy) {
|
||||
for (const auto target : g_Targets) {
|
||||
Entity entity(target, info);
|
||||
const auto toEntity = [&](const std::function<void()>& legacy, const GameMessages::NetGameMsg& msg) {
|
||||
// The legacy functions sent to entity->GetSystemAddress() only.
|
||||
const auto legacyPackets = Capture(legacy);
|
||||
const auto ours = Capture([&] { msg.SendToClient(entity.GetSystemAddress()); });
|
||||
ASSERT_EQ(legacyPackets.size(), 1);
|
||||
ASSERT_EQ(ours.size(), 1);
|
||||
EXPECT_PACKET_EQ(FromCapture(legacyPackets[0]), FromCapture(ours[0]));
|
||||
EXPECT_EQ(legacyPackets[0].sysAddr, ours[0].sysAddr);
|
||||
EXPECT_EQ(legacyPackets[0].broadcast, ours[0].broadcast);
|
||||
};
|
||||
|
||||
for (const auto type : g_InventoryTypes) {
|
||||
for (const auto size : g_Ints) {
|
||||
GameMessages::SetInventorySize msg;
|
||||
msg.target = target;
|
||||
msg.inventoryType = type;
|
||||
msg.size = size;
|
||||
toEntity([&] { LegacyGameMessages::SendSetInventorySize(&entity, type, size); }, msg);
|
||||
const auto copy = RoundTrip(msg);
|
||||
EXPECT_EQ(copy.inventoryType, type);
|
||||
EXPECT_EQ(copy.size, size);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto item : g_Ids) {
|
||||
for (const bool success : { false, true }) {
|
||||
GameMessages::ConsumeClientItem consume;
|
||||
consume.target = target;
|
||||
consume.bSuccess = success;
|
||||
consume.item = item;
|
||||
toEntity([&] { LegacyGameMessages::SendConsumeClientItem(&entity, success, item); }, consume);
|
||||
const auto copy = RoundTrip(consume);
|
||||
EXPECT_EQ(copy.bSuccess, success);
|
||||
EXPECT_EQ(copy.item, item);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto lot : g_Ints) {
|
||||
for (const bool success : { false, true }) {
|
||||
GameMessages::UseItemResult result;
|
||||
result.target = target;
|
||||
result.itemTemplateID = lot;
|
||||
result.useItemResult = success;
|
||||
toEntity([&] { LegacyGameMessages::SendUseItemResult(&entity, lot, success); }, result);
|
||||
const auto copy = RoundTrip(result);
|
||||
EXPECT_EQ(copy.itemTemplateID, lot);
|
||||
EXPECT_EQ(copy.useItemResult, success);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto response : { eUseItemResponse::NoImaginationForPet, eUseItemResponse::FailedPrecondition, static_cast<eUseItemResponse>(0) }) {
|
||||
GameMessages::UseItemRequirementsResponse msg;
|
||||
msg.target = target;
|
||||
msg.eUseResponse = response;
|
||||
ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendUseItemRequirementsResponse(target, a, response); }, msg, SendMode::SendToClient);
|
||||
EXPECT_EQ(RoundTrip(msg).eUseResponse, response);
|
||||
}
|
||||
|
||||
for (const auto dst : g_InventoryTypes) {
|
||||
for (const auto src : g_InventoryTypes) {
|
||||
for (const auto code : { eReponseMoveItemBetweenInventoryTypeCode::SUCCESS, eReponseMoveItemBetweenInventoryTypeCode::FAIL_GENERIC, eReponseMoveItemBetweenInventoryTypeCode::FAIL_INV_FULL, eReponseMoveItemBetweenInventoryTypeCode::FAIL_CANT_MOVE_THINKING_HAT }) {
|
||||
GameMessages::ResponseMoveItemBetweenInventoryTypes msg;
|
||||
msg.target = target;
|
||||
msg.inventoryTypeDestination = dst;
|
||||
msg.inventoryTypeSource = src;
|
||||
msg.response = code;
|
||||
ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendResponseMoveItemBetweenInventoryTypes(target, a, dst, src, code); }, msg, SendMode::SendToClient);
|
||||
const auto copy = RoundTrip(msg);
|
||||
EXPECT_EQ(copy.inventoryTypeDestination, dst);
|
||||
EXPECT_EQ(copy.inventoryTypeSource, src);
|
||||
EXPECT_EQ(copy.response, code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto slots : g_UInts) {
|
||||
for (const auto type : g_InventoryTypes) {
|
||||
GameMessages::NotifyNotEnoughInvSpace msg;
|
||||
msg.target = target;
|
||||
msg.freeSlotsNeeded = slots;
|
||||
msg.inventoryType = type;
|
||||
ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendNotifyNotEnoughInvSpace(target, slots, type, a); }, msg);
|
||||
const auto copy = RoundTrip(msg);
|
||||
EXPECT_EQ(copy.freeSlotsNeeded, slots);
|
||||
EXPECT_EQ(copy.inventoryType, type);
|
||||
}
|
||||
}
|
||||
|
||||
for (const bool active : { false, true }) {
|
||||
for (const auto type : { eUnequippableActiveType::INVALID, eUnequippableActiveType::PET, eUnequippableActiveType::MOUNT }) {
|
||||
for (const auto item : g_Ids) {
|
||||
GameMessages::MarkInventoryItemAsActive msg;
|
||||
msg.target = target;
|
||||
msg.bActive = active;
|
||||
msg.iType = type;
|
||||
msg.itemID = item;
|
||||
ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendMarkInventoryItemAsActive(target, active, type, item, a); }, msg);
|
||||
const auto copy = RoundTrip(msg);
|
||||
EXPECT_EQ(copy.bActive, active);
|
||||
EXPECT_EQ(copy.iType, type);
|
||||
EXPECT_EQ(copy.itemID, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GameMessages::UpdateInventoryUi updateUi;
|
||||
updateUi.target = target;
|
||||
ExpectSameAsLegacy([&](const SystemAddress& a) { LegacyGameMessages::SendUpdateInventoryUi(target, a); }, updateUi, SendMode::SendToClient);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(InventoryMessagesTests, RemoveItemFromInventoryMatchesLegacy) {
|
||||
for (const auto target : g_Targets) {
|
||||
Entity entity(target, info);
|
||||
for (const auto id : g_Ids) {
|
||||
for (const LOT lot : { LOT_NULL, LOT{ 1727 } }) {
|
||||
for (const auto type : g_InventoryTypes) {
|
||||
for (const auto stackCount : g_UInts) {
|
||||
for (const auto stackRemaining : g_UInts) {
|
||||
GameMessages::RemoveItemFromInventory msg;
|
||||
msg.target = target;
|
||||
msg.bConfirmed = true;
|
||||
msg.eInvType = type;
|
||||
msg.eLootTypeSource = LOOTTYPE_NONE;
|
||||
msg.iObjID = id;
|
||||
msg.iObjTemplate = lot;
|
||||
msg.iStackCount = stackCount;
|
||||
msg.iStackRemaining = stackRemaining;
|
||||
ExpectSameAsLegacy([&](const SystemAddress& a) {
|
||||
LegacyGameMessages::SendRemoveItemFromInventory(&entity, a, id, lot, type, stackCount, stackRemaining);
|
||||
}, msg, SendMode::SendToClient);
|
||||
|
||||
// What the server sends reads back the same with the old handler's read sequence.
|
||||
const auto legacy = ReadWithLegacy<LegacyGameMessages::LegacyRemoveItem>(msg, LegacyGameMessages::ReadRemoveItemFromInventory);
|
||||
const auto copy = RoundTrip(msg);
|
||||
EXPECT_EQ(copy.eInvType, legacy.eInvType);
|
||||
EXPECT_EQ(copy.iObjID, legacy.iObjID);
|
||||
EXPECT_EQ(copy.iObjTemplate, legacy.iObjTemplate);
|
||||
EXPECT_EQ(copy.iStackCount, legacy.iStackCount);
|
||||
EXPECT_EQ(copy.iStackRemaining, legacy.iStackRemaining);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(InventoryMessagesTests, InboundReadsLikeLegacy) {
|
||||
// RemoveItemFromInventory as the client sends it (every optional field both ways is covered by the
|
||||
// optional reads; the "always sent" flags only matter for what the server writes).
|
||||
for (const bool flag : { false, true }) {
|
||||
for (const auto& extraInfo : g_WStrings) {
|
||||
for (const auto id : g_Ids) {
|
||||
GameMessages::RemoveItemFromInventory msg;
|
||||
msg.bConfirmed = flag;
|
||||
msg.bDeleteItem = !flag;
|
||||
msg.bOutSuccess = flag;
|
||||
msg.extraInfo = extraInfo;
|
||||
msg.forceDeletion = flag;
|
||||
msg.iLootTypeSource = id;
|
||||
msg.iObjID = id;
|
||||
msg.iRequestingObjID = id;
|
||||
msg.iSubkey = id;
|
||||
msg.iTradeID = id;
|
||||
const auto legacy = ReadWithLegacy<LegacyGameMessages::LegacyRemoveItem>(msg, LegacyGameMessages::ReadRemoveItemFromInventory);
|
||||
const auto copy = RoundTrip(msg);
|
||||
EXPECT_EQ(copy.bConfirmed, legacy.bConfirmed);
|
||||
EXPECT_EQ(copy.bDeleteItem, legacy.bDeleteItem);
|
||||
EXPECT_EQ(copy.bOutSuccess, legacy.bOutSuccess);
|
||||
EXPECT_EQ(copy.extraInfo, legacy.extraInfo);
|
||||
EXPECT_EQ(copy.forceDeletion, legacy.forceDeletion);
|
||||
EXPECT_EQ(copy.iLootTypeSource, legacy.iLootTypeSource);
|
||||
EXPECT_EQ(copy.iRequestingObjID, legacy.iRequestingObjID);
|
||||
EXPECT_EQ(copy.iSubkey, legacy.iSubkey);
|
||||
EXPECT_EQ(copy.iTradeID, legacy.iTradeID);
|
||||
ExpectTruncatedFails(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
// The same message with every optional flag clear, as a client that relies on the defaults would send it.
|
||||
{
|
||||
RakNet::BitStream wire;
|
||||
wire.Write1(); // bConfirmed
|
||||
wire.Write1(); // bDeleteItem
|
||||
wire.Write0(); // bOutSuccess
|
||||
wire.Write0(); // eInvType
|
||||
wire.Write0(); // eLootTypeSource
|
||||
wire.Write<uint32_t>(0); // extraInfo
|
||||
wire.Write1(); // forceDeletion
|
||||
for (int i = 0; i < 8; i++) wire.Write0(); // the eight optional ids and counts
|
||||
RakNet::BitStream legacyStream(wire.GetData(), wire.GetNumberOfBytesUsed(), false);
|
||||
const auto legacy = LegacyGameMessages::ReadRemoveItemFromInventory(legacyStream);
|
||||
GameMessages::RemoveItemFromInventory msg;
|
||||
ASSERT_TRUE(msg.Deserialize(wire));
|
||||
EXPECT_EQ(msg.eInvType, legacy.eInvType);
|
||||
EXPECT_EQ(msg.eLootTypeSource, legacy.eLootTypeSource);
|
||||
EXPECT_EQ(msg.iObjTemplate, legacy.iObjTemplate);
|
||||
EXPECT_EQ(msg.iStackCount, legacy.iStackCount);
|
||||
EXPECT_EQ(msg.iStackRemaining, legacy.iStackRemaining);
|
||||
}
|
||||
|
||||
for (const auto id : g_Ids) {
|
||||
for (const bool flag : { false, true }) {
|
||||
GameMessages::EquipInventory equip;
|
||||
equip.bIgnoreCooldown = !flag;
|
||||
equip.bOutSuccess = flag;
|
||||
equip.itemToEquip = id;
|
||||
const auto legacyEquip = ReadWithLegacy<LegacyGameMessages::LegacyEquip>(equip, LegacyGameMessages::ReadEquipItem);
|
||||
EXPECT_EQ(RoundTrip(equip).itemToEquip, legacyEquip.objectID);
|
||||
ExpectTruncatedFails(equip);
|
||||
|
||||
GameMessages::UnEquipInventory unequip;
|
||||
unequip.bEvenIfDead = flag;
|
||||
unequip.bIgnoreCooldown = !flag;
|
||||
unequip.bOutSuccess = flag;
|
||||
unequip.itemToUnequip = id;
|
||||
const auto legacyUnequip = ReadWithLegacy<LegacyGameMessages::LegacyEquip>(unequip, LegacyGameMessages::ReadUnequipItem);
|
||||
EXPECT_EQ(RoundTrip(unequip).itemToUnequip, legacyUnequip.objectID);
|
||||
ExpectTruncatedFails(unequip);
|
||||
|
||||
for (const auto value : g_Ints) {
|
||||
GameMessages::MoveItemInInventory move;
|
||||
move.destInvType = flag ? value : static_cast<int32_t>(eInventoryType::INVALID);
|
||||
move.iObjID = id;
|
||||
move.inventoryType = value;
|
||||
move.responseCode = -value;
|
||||
move.slot = value / 2;
|
||||
const auto legacyMove = ReadWithLegacy<LegacyGameMessages::LegacyMoveItemInInventory>(move, LegacyGameMessages::ReadMoveItemInInventory);
|
||||
const auto moveCopy = RoundTrip(move);
|
||||
EXPECT_EQ(moveCopy.destInvType, legacyMove.destInvType);
|
||||
EXPECT_EQ(moveCopy.iObjID, legacyMove.iObjID);
|
||||
EXPECT_EQ(moveCopy.inventoryType, legacyMove.inventoryType);
|
||||
EXPECT_EQ(moveCopy.responseCode, legacyMove.responseCode);
|
||||
EXPECT_EQ(moveCopy.slot, legacyMove.slot);
|
||||
ExpectTruncatedFails(move);
|
||||
|
||||
GameMessages::RequestMoveItemBetweenInventoryTypes request;
|
||||
request.bAllowPartial = flag;
|
||||
request.destSlot = value;
|
||||
request.iStackCount = -value;
|
||||
request.invTypeDst = flag ? eInventoryType::VAULT_ITEMS : eInventoryType::ITEMS;
|
||||
request.invTypeSrc = flag ? eInventoryType::ITEMS : eInventoryType::MODELS;
|
||||
request.itemID = id;
|
||||
request.showFlyingLoot = !flag;
|
||||
request.subkey = id;
|
||||
request.itemLOT = value;
|
||||
const auto legacyRequest = ReadWithLegacy<LegacyGameMessages::LegacyRequestMove>(request, LegacyGameMessages::ReadRequestMoveItemBetweenInventoryTypes);
|
||||
const auto requestCopy = RoundTrip(request);
|
||||
EXPECT_EQ(requestCopy.bAllowPartial, legacyRequest.bAllowPartial);
|
||||
EXPECT_EQ(requestCopy.destSlot, legacyRequest.destSlot);
|
||||
EXPECT_EQ(requestCopy.iStackCount, legacyRequest.iStackCount);
|
||||
EXPECT_EQ(requestCopy.invTypeDst, legacyRequest.invTypeDst);
|
||||
EXPECT_EQ(requestCopy.invTypeSrc, legacyRequest.invTypeSrc);
|
||||
EXPECT_EQ(requestCopy.itemID, legacyRequest.itemID);
|
||||
EXPECT_EQ(requestCopy.showFlyingLoot, legacyRequest.showFlyingLoot);
|
||||
EXPECT_EQ(requestCopy.subkey, legacyRequest.subkey);
|
||||
if (value != LOT_NULL) EXPECT_EQ(requestCopy.itemLOT, legacyRequest.itemLOT); // unused; the old default was 0
|
||||
ExpectTruncatedFails(request);
|
||||
}
|
||||
|
||||
for (const auto count : g_UInts) {
|
||||
for (const LOT lot : { LOT_NULL, LOT{ 1727 } }) {
|
||||
GameMessages::MoveItemBetweenInventoryTypes move;
|
||||
move.inventoryTypeA = eInventoryType::MODELS;
|
||||
move.inventoryTypeB = flag ? eInventoryType::VAULT_MODELS : eInventoryType::TEMP_MODELS;
|
||||
move.objectID = id;
|
||||
move.showFlyingLoot = flag;
|
||||
move.stackCount = count;
|
||||
move.templateID = lot;
|
||||
const auto legacy = ReadWithLegacy<LegacyGameMessages::LegacyMoveItemBetweenInventoryTypes>(move, LegacyGameMessages::ReadMoveItemBetweenInventoryTypes);
|
||||
const auto copy = RoundTrip(move);
|
||||
EXPECT_EQ(copy.inventoryTypeA, legacy.inventoryTypeA);
|
||||
EXPECT_EQ(copy.inventoryTypeB, legacy.inventoryTypeB);
|
||||
EXPECT_EQ(copy.objectID, legacy.objectID);
|
||||
EXPECT_EQ(copy.showFlyingLoot, legacy.showFlyingLoot);
|
||||
EXPECT_EQ(copy.stackCount, legacy.stackCount);
|
||||
EXPECT_EQ(copy.templateID, legacy.templateID);
|
||||
ExpectTruncatedFails(move);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GameMessages::ClientItemConsumed consumed;
|
||||
consumed.item = id;
|
||||
EXPECT_EQ(ReadWithLegacy<LWOOBJID>(consumed, LegacyGameMessages::ReadSingle<LWOOBJID>), id);
|
||||
EXPECT_EQ(RoundTrip(consumed).item, id);
|
||||
ExpectTruncatedFails(consumed);
|
||||
|
||||
GameMessages::UseNonEquipmentItem use;
|
||||
use.itemToUse = id;
|
||||
EXPECT_EQ(ReadWithLegacy<LWOOBJID>(use, LegacyGameMessages::ReadSingle<LWOOBJID>), id);
|
||||
EXPECT_EQ(RoundTrip(use).itemToUse, id);
|
||||
ExpectTruncatedFails(use);
|
||||
}
|
||||
|
||||
for (const auto lot : g_Ints) {
|
||||
GameMessages::SetConsumableItem consumable;
|
||||
consumable.itemTemplateID = lot;
|
||||
EXPECT_EQ(ReadWithLegacy<LOT>(consumable, LegacyGameMessages::ReadSingle<LOT>), lot);
|
||||
EXPECT_EQ(RoundTrip(consumable).itemTemplateID, lot);
|
||||
ExpectTruncatedFails(consumable);
|
||||
}
|
||||
|
||||
for (const std::string action : std::vector<std::string>{ "ADD", "MODIFY", "REMOVE", "", "bogus" }) {
|
||||
for (const std::string& groupID : std::vector<std::string>{ "", "user_group1", std::string(200, 'g') }) {
|
||||
for (const auto& groupName : g_WStrings) {
|
||||
for (const auto type : { eInventoryType::ITEMS, eInventoryType::BRICKS, eInventoryType::MODELS }) {
|
||||
for (const bool locked : { false, true }) {
|
||||
GameMessages::UpdateInventoryGroup group;
|
||||
group.action = action;
|
||||
group.groupID = groupID;
|
||||
group.groupName = groupName;
|
||||
group.inventoryType = type;
|
||||
group.locked = locked;
|
||||
const auto legacy = ReadWithLegacy<LegacyGameMessages::LegacyGroupUpdate>(group, LegacyGameMessages::ReadUpdateInventoryGroup);
|
||||
ASSERT_TRUE(legacy.ok);
|
||||
const auto copy = RoundTrip(group);
|
||||
EXPECT_EQ(copy.action, legacy.action);
|
||||
EXPECT_EQ(copy.groupID, legacy.groupId);
|
||||
EXPECT_EQ(copy.groupName, legacy.groupName);
|
||||
EXPECT_EQ(copy.inventoryType, legacy.inventory);
|
||||
EXPECT_EQ(copy.locked, legacy.locked);
|
||||
ExpectTruncatedFails(group);
|
||||
}
|
||||
}
|
||||
for (const auto lot : g_Ints) {
|
||||
GameMessages::UpdateInventoryGroupContents contents;
|
||||
contents.action = action;
|
||||
contents.groupID = groupID;
|
||||
contents.inventoryType = eInventoryType::BRICKS;
|
||||
contents.lot = lot;
|
||||
const auto legacy = ReadWithLegacy<LegacyGameMessages::LegacyGroupUpdate>(contents, LegacyGameMessages::ReadUpdateInventoryGroupContents);
|
||||
ASSERT_TRUE(legacy.ok);
|
||||
const auto copy = RoundTrip(contents);
|
||||
EXPECT_EQ(copy.action, legacy.action);
|
||||
EXPECT_EQ(copy.groupID, legacy.groupId);
|
||||
EXPECT_EQ(copy.inventoryType, legacy.inventory);
|
||||
EXPECT_EQ(copy.lot, legacy.lot);
|
||||
ExpectTruncatedFails(contents);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A group name longer than the old handler allowed is rejected.
|
||||
RakNet::BitStream tooLong;
|
||||
tooLong.Write<uint32_t>(0);
|
||||
tooLong.Write<uint32_t>(0);
|
||||
tooLong.Write<uint32_t>(MAX_MESSAGE_LENGTH / 2 + 1);
|
||||
GameMessages::UpdateInventoryGroup group;
|
||||
EXPECT_FALSE(group.Deserialize(tooLong));
|
||||
}
|
||||
|
||||
// MoveInventoryBatch had no oracle (the old function was never called); it follows the client's layout
|
||||
// (legouniverse.exe 1.10.64, LWOInventoryComponent_Common::msgMoveInventoryBatch reads moveLOT and moveSubKey).
|
||||
TEST_F(InventoryMessagesTests, MoveInventoryBatchRoundTrips) {
|
||||
GameMessages::MoveInventoryBatch batch;
|
||||
EXPECT_PACKET_EQ(FromHex("00 00", 9), Payload(batch));
|
||||
batch.bAllowPartial = true;
|
||||
batch.count = 3;
|
||||
batch.dstBag = eInventoryType::VAULT_ITEMS;
|
||||
batch.moveLOT = 1727;
|
||||
batch.moveSubkey = 0x22;
|
||||
batch.showFlyingLoot = true;
|
||||
batch.srcBag = eInventoryType::MODELS;
|
||||
batch.startObjectID = 0x33;
|
||||
const auto copy = RoundTrip(batch);
|
||||
EXPECT_EQ(copy.count, 3);
|
||||
EXPECT_EQ(copy.dstBag, eInventoryType::VAULT_ITEMS);
|
||||
EXPECT_EQ(copy.moveLOT, 1727);
|
||||
EXPECT_EQ(copy.moveSubkey, 0x22);
|
||||
EXPECT_EQ(copy.srcBag, eInventoryType::MODELS);
|
||||
EXPECT_EQ(copy.startObjectID, 0x33);
|
||||
ExpectTruncatedFails(batch);
|
||||
}
|
||||
|
||||
// Independent of the legacy code: hand computed payloads (RakNet writes MSB first).
|
||||
TEST_F(InventoryMessagesTests, GoldenBytes) {
|
||||
GameMessages::EquipInventory equip;
|
||||
equip.bOutSuccess = true;
|
||||
equip.itemToEquip = 0x11;
|
||||
// 0, 1, then 11 00 00 00 00 00 00 00
|
||||
EXPECT_PACKET_EQ(FromHex("44 40 00 00 00 00 00 00 00", 66), Payload(equip));
|
||||
|
||||
GameMessages::ResponseMoveItemBetweenInventoryTypes response;
|
||||
response.inventoryTypeDestination = eInventoryType::VAULT_ITEMS; // 1
|
||||
response.response = eReponseMoveItemBetweenInventoryTypeCode::SUCCESS; // 0
|
||||
// 1 + 01 00 00 00, 0, 1 + 00 00 00 00
|
||||
EXPECT_PACKET_EQ(FromHex("80 80 00 00 20 00 00 00 00", 67), Payload(response));
|
||||
|
||||
GameMessages::SetInventorySize size;
|
||||
size.inventoryType = eInventoryType::ITEMS;
|
||||
size.size = 20;
|
||||
EXPECT_PACKET_EQ(FromHex("00 00 00 00 14 00 00 00"), Payload(size));
|
||||
}
|
||||
@@ -0,0 +1,464 @@
|
||||
#ifndef INVENTORYMESSAGESLEGACY_H
|
||||
#define INVENTORYMESSAGESLEGACY_H
|
||||
|
||||
// FROZEN ORACLE - DO NOT EDIT.
|
||||
// Verbatim copies of the hand written GameMessages functions that InventoryMessages.h replaced
|
||||
// (dGame/dGameMessages/GameMessages.cpp, branched from origin/main 129199e4). Only the namespace changed.
|
||||
// The Read* functions are the read sequences of the replaced GameMessages::Handle* functions.
|
||||
// SendMoveInventoryBatch is not here: nothing called it, and MoveInventoryBatch follows the client's layout.
|
||||
|
||||
#include "BitStreamUtils.h"
|
||||
#include "dCommonVars.h"
|
||||
#include "dServer.h"
|
||||
#include "eInventoryType.h"
|
||||
#include "eLootSourceType.h"
|
||||
#include "eReponseMoveItemBetweenInventoryTypeCode.h"
|
||||
#include "eUnequippableActiveType.h"
|
||||
#include "eUseItemResponse.h"
|
||||
#include "Entity.h"
|
||||
#include "Game.h"
|
||||
#include "GeneralUtils.h"
|
||||
#include "Inventory.h"
|
||||
#include "Item.h"
|
||||
#include "MessageType/Client.h"
|
||||
#include "MessageType/Game.h"
|
||||
#include "ServiceType.h"
|
||||
|
||||
#include <ranges>
|
||||
#include <string>
|
||||
|
||||
namespace LegacyGameMessages {
|
||||
inline void SendAddItemToInventoryClientSync(Entity* entity, const SystemAddress& sysAddr, Item* item, const LWOOBJID& objectID, bool showFlyingLoot, int itemCount, LWOOBJID subKey, eLootSourceType lootSourceType) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(entity->GetObjectID());
|
||||
bitStream.Write(MessageType::Game::ADD_ITEM_TO_INVENTORY_CLIENT_SYNC);
|
||||
bitStream.Write(item->GetBound());
|
||||
bitStream.Write(item->GetInfo().isBOE);
|
||||
bitStream.Write(item->GetInfo().isBOP);
|
||||
|
||||
bitStream.Write(lootSourceType != eLootSourceType::NONE); // Loot source
|
||||
if (lootSourceType != eLootSourceType::NONE) bitStream.Write(lootSourceType);
|
||||
std::u16string extraInfo;
|
||||
|
||||
const auto& config = item->GetConfig();
|
||||
|
||||
for (const auto& data : config.values | std::views::values) {
|
||||
extraInfo += GeneralUtils::ASCIIToUTF16(data->GetString()) + u",";
|
||||
}
|
||||
|
||||
if (extraInfo.length() > 0) extraInfo.pop_back(); // remove the last comma
|
||||
|
||||
bitStream.Write<uint32_t>(extraInfo.size());
|
||||
if (extraInfo.size() > 0) {
|
||||
for (uint32_t i = 0; i < extraInfo.size(); ++i) {
|
||||
bitStream.Write<uint16_t>(extraInfo[i]);
|
||||
}
|
||||
bitStream.Write<uint16_t>(0x00);
|
||||
}
|
||||
|
||||
bitStream.Write(item->GetLot());
|
||||
|
||||
bitStream.Write(subKey != LWOOBJID_EMPTY);
|
||||
if (subKey != LWOOBJID_EMPTY) bitStream.Write(subKey);
|
||||
|
||||
auto* inventory = item->GetInventory();
|
||||
const auto inventoryType = inventory->GetType();
|
||||
|
||||
bitStream.Write(inventoryType != eInventoryType::ITEMS);
|
||||
if (inventoryType != eInventoryType::ITEMS) bitStream.Write(inventoryType);
|
||||
|
||||
bitStream.Write(itemCount != 1);
|
||||
if (itemCount != 1) bitStream.Write(itemCount);
|
||||
|
||||
const auto count = item->GetCount();
|
||||
|
||||
bitStream.Write(count != 0); //items total
|
||||
if (count != 0) bitStream.Write(count);
|
||||
|
||||
bitStream.Write(objectID);
|
||||
bitStream.Write(0.0f);
|
||||
bitStream.Write(0.0f);
|
||||
bitStream.Write(0.0f);
|
||||
bitStream.Write(showFlyingLoot);
|
||||
bitStream.Write(item->GetSlot());
|
||||
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendSetInventorySize(Entity* entity, int invType, int size) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(entity->GetObjectID());
|
||||
bitStream.Write(MessageType::Game::SET_INVENTORY_SIZE);
|
||||
bitStream.Write(invType);
|
||||
bitStream.Write(size);
|
||||
|
||||
SystemAddress sysAddr = entity->GetSystemAddress();
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendRemoveItemFromInventory(Entity* entity, const SystemAddress& sysAddr, LWOOBJID objectID, LOT templateID, int inventoryType, uint32_t stackCount, uint32_t stackRemaining) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
// this is used for a lot more than just inventory trashing (trades, vendors, etc.) but for now since it's just used for that, that's all im going to implement
|
||||
bool bConfirmed = true;
|
||||
bool bDeleteItem = true;
|
||||
bool bOutSuccess = false;
|
||||
int eInvType = inventoryType;
|
||||
int eLootTypeSource = LOOTTYPE_NONE;
|
||||
bool forceDeletion = true;
|
||||
LWOOBJID iLootTypeSource = LWOOBJID_EMPTY;
|
||||
LWOOBJID iObjID = objectID;
|
||||
LOT iObjTemplate = templateID;
|
||||
LWOOBJID iRequestingObjID = LWOOBJID_EMPTY;
|
||||
uint32_t iStackCount = stackCount;
|
||||
uint32_t iStackRemaining = stackRemaining;
|
||||
LWOOBJID iSubkey = LWOOBJID_EMPTY;
|
||||
LWOOBJID iTradeID = LWOOBJID_EMPTY;
|
||||
|
||||
bitStream.Write(entity->GetObjectID());
|
||||
bitStream.Write(MessageType::Game::REMOVE_ITEM_FROM_INVENTORY);
|
||||
bitStream.Write(bConfirmed);
|
||||
bitStream.Write(bDeleteItem);
|
||||
bitStream.Write(bOutSuccess);
|
||||
bitStream.Write1();
|
||||
bitStream.Write(eInvType);
|
||||
bitStream.Write1();
|
||||
bitStream.Write(eLootTypeSource);
|
||||
bitStream.Write<uint32_t>(0); //extra info
|
||||
//bitStream.Write<uint16_t>(0); //extra info
|
||||
bitStream.Write(forceDeletion);
|
||||
bitStream.Write0();
|
||||
bitStream.Write1();
|
||||
bitStream.Write(iObjID);
|
||||
bitStream.Write1();
|
||||
bitStream.Write(iObjTemplate);
|
||||
bitStream.Write0();
|
||||
bitStream.Write1();
|
||||
bitStream.Write(iStackCount);
|
||||
bitStream.Write1();
|
||||
bitStream.Write(iStackRemaining);
|
||||
bitStream.Write0();
|
||||
bitStream.Write0();
|
||||
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendConsumeClientItem(Entity* entity, bool bSuccess, LWOOBJID item) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(entity->GetObjectID());
|
||||
bitStream.Write(MessageType::Game::CONSUME_CLIENT_ITEM);
|
||||
bitStream.Write(bSuccess);
|
||||
bitStream.Write(item);
|
||||
|
||||
SystemAddress sysAddr = entity->GetSystemAddress();
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendUseItemResult(Entity* entity, LOT templateID, bool useItemResult) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(entity->GetObjectID());
|
||||
bitStream.Write(MessageType::Game::USE_ITEM_RESULT);
|
||||
bitStream.Write(templateID);
|
||||
bitStream.Write(useItemResult);
|
||||
|
||||
SystemAddress sysAddr = entity->GetSystemAddress();
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendUseItemRequirementsResponse(LWOOBJID objectID, const SystemAddress& sysAddr, eUseItemResponse itemResponse) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(objectID);
|
||||
bitStream.Write(MessageType::Game::USE_ITEM_REQUIREMENTS_RESPONSE);
|
||||
|
||||
bitStream.Write(itemResponse);
|
||||
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendResponseMoveItemBetweenInventoryTypes(LWOOBJID objectId, const SystemAddress& sysAddr, eInventoryType inventoryTypeDestination, eInventoryType inventoryTypeSource, eReponseMoveItemBetweenInventoryTypeCode response) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(objectId);
|
||||
bitStream.Write(MessageType::Game::RESPONSE_MOVE_ITEM_BETWEEN_INVENTORY_TYPES);
|
||||
|
||||
bitStream.Write(inventoryTypeDestination != eInventoryType::ITEMS);
|
||||
if (inventoryTypeDestination != eInventoryType::ITEMS) bitStream.Write(inventoryTypeDestination);
|
||||
|
||||
bitStream.Write(inventoryTypeSource != eInventoryType::ITEMS);
|
||||
if (inventoryTypeSource != eInventoryType::ITEMS) bitStream.Write(inventoryTypeSource);
|
||||
|
||||
bitStream.Write(response != eReponseMoveItemBetweenInventoryTypeCode::FAIL_GENERIC);
|
||||
if (response != eReponseMoveItemBetweenInventoryTypeCode::FAIL_GENERIC) bitStream.Write(response);
|
||||
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendNotifyNotEnoughInvSpace(LWOOBJID objectId, uint32_t freeSlotsNeeded, eInventoryType inventoryType, const SystemAddress& sysAddr) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(objectId);
|
||||
bitStream.Write(MessageType::Game::VEHICLE_NOTIFY_FINISHED_RACE);
|
||||
|
||||
bitStream.Write(freeSlotsNeeded);
|
||||
bitStream.Write(inventoryType != 0);
|
||||
if (inventoryType != 0) bitStream.Write(inventoryType);
|
||||
|
||||
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
inline void SendUpdateInventoryUi(LWOOBJID objectId, const SystemAddress& sysAddr) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(objectId);
|
||||
bitStream.Write(MessageType::Game::UPDATE_INVENTORY_UI);
|
||||
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
|
||||
inline void SendMarkInventoryItemAsActive(LWOOBJID objectId, bool bActive, eUnequippableActiveType iType, LWOOBJID itemID, const SystemAddress& sysAddr) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(objectId);
|
||||
bitStream.Write(MessageType::Game::MARK_INVENTORY_ITEM_AS_ACTIVE);
|
||||
|
||||
bitStream.Write(bActive);
|
||||
|
||||
bitStream.Write(iType != eUnequippableActiveType::INVALID);
|
||||
if (iType != eUnequippableActiveType::INVALID) bitStream.Write(iType);
|
||||
|
||||
bitStream.Write(itemID != LWOOBJID_EMPTY);
|
||||
if (itemID != LWOOBJID_EMPTY) bitStream.Write(itemID);
|
||||
|
||||
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
|
||||
struct LegacyEquip { bool immediate{}; LWOOBJID objectID{}; };
|
||||
inline LegacyEquip ReadEquipItem(RakNet::BitStream& inStream) {
|
||||
bool immediate;
|
||||
LWOOBJID objectID;
|
||||
inStream.Read(immediate);
|
||||
inStream.Read(immediate); //twice?
|
||||
inStream.Read(objectID);
|
||||
return { immediate, objectID };
|
||||
}
|
||||
|
||||
inline LegacyEquip ReadUnequipItem(RakNet::BitStream& inStream) {
|
||||
bool immediate;
|
||||
LWOOBJID objectID;
|
||||
inStream.Read(immediate);
|
||||
inStream.Read(immediate);
|
||||
inStream.Read(immediate);
|
||||
inStream.Read(objectID);
|
||||
return { immediate, objectID };
|
||||
}
|
||||
|
||||
struct LegacyRemoveItem {
|
||||
bool bConfirmed; bool bDeleteItem; bool bOutSuccess; int eInvType; int eLootTypeSource; std::u16string extraInfo; bool forceDeletion;
|
||||
LWOOBJID iLootTypeSource; LWOOBJID iObjID; LOT iObjTemplate; LWOOBJID iRequestingObjID; uint32_t iStackCount; uint32_t iStackRemaining; LWOOBJID iSubkey; LWOOBJID iTradeID;
|
||||
};
|
||||
inline LegacyRemoveItem ReadRemoveItemFromInventory(RakNet::BitStream& inStream) {
|
||||
bool bConfirmed = false;
|
||||
bool bDeleteItem = true;
|
||||
bool bOutSuccess = false;
|
||||
bool eInvTypeIsDefault = false;
|
||||
int eInvType = INVENTORY_MAX;
|
||||
bool eLootTypeSourceIsDefault = false;
|
||||
int eLootTypeSource = LOOTTYPE_NONE;
|
||||
int32_t extraInfoLength = 0;
|
||||
std::u16string extraInfo;
|
||||
bool forceDeletion = true;
|
||||
bool iLootTypeSourceIsDefault = false;
|
||||
LWOOBJID iLootTypeSource = LWOOBJID_EMPTY;
|
||||
bool iObjIDIsDefault = false;
|
||||
LWOOBJID iObjID = LWOOBJID_EMPTY;
|
||||
bool iObjTemplateIsDefault = false;
|
||||
LOT iObjTemplate = LOT_NULL;
|
||||
bool iRequestingObjIDIsDefault = false;
|
||||
LWOOBJID iRequestingObjID = LWOOBJID_EMPTY;
|
||||
bool iStackCountIsDefault = false;
|
||||
uint32_t iStackCount = 1;
|
||||
bool iStackRemainingIsDefault = false;
|
||||
uint32_t iStackRemaining = 0;
|
||||
bool iSubkeyIsDefault = false;
|
||||
LWOOBJID iSubkey = LWOOBJID_EMPTY;
|
||||
bool iTradeIDIsDefault = false;
|
||||
LWOOBJID iTradeID = LWOOBJID_EMPTY;
|
||||
|
||||
inStream.Read(bConfirmed);
|
||||
inStream.Read(bDeleteItem);
|
||||
inStream.Read(bOutSuccess);
|
||||
inStream.Read(eInvTypeIsDefault);
|
||||
if (eInvTypeIsDefault) inStream.Read(eInvType);
|
||||
inStream.Read(eLootTypeSourceIsDefault);
|
||||
if (eLootTypeSourceIsDefault) inStream.Read(eLootTypeSource);
|
||||
inStream.Read(extraInfoLength);
|
||||
if (extraInfoLength > 0) {
|
||||
for (uint32_t i = 0; i < extraInfoLength; ++i) {
|
||||
uint16_t character;
|
||||
inStream.Read(character);
|
||||
extraInfo.push_back(character);
|
||||
}
|
||||
uint16_t nullTerm;
|
||||
inStream.Read(nullTerm);
|
||||
}
|
||||
inStream.Read(forceDeletion);
|
||||
inStream.Read(iLootTypeSourceIsDefault);
|
||||
if (iLootTypeSourceIsDefault) inStream.Read(iLootTypeSource);
|
||||
inStream.Read(iObjIDIsDefault);
|
||||
if (iObjIDIsDefault) inStream.Read(iObjID);
|
||||
inStream.Read(iObjTemplateIsDefault);
|
||||
if (iObjTemplateIsDefault) inStream.Read(iObjTemplate);
|
||||
inStream.Read(iRequestingObjIDIsDefault);
|
||||
if (iRequestingObjIDIsDefault) inStream.Read(iRequestingObjID);
|
||||
inStream.Read(iStackCountIsDefault);
|
||||
if (iStackCountIsDefault) inStream.Read(iStackCount);
|
||||
inStream.Read(iStackRemainingIsDefault);
|
||||
if (iStackRemainingIsDefault) inStream.Read(iStackRemaining);
|
||||
inStream.Read(iSubkeyIsDefault);
|
||||
if (iSubkeyIsDefault) inStream.Read(iSubkey);
|
||||
inStream.Read(iTradeIDIsDefault);
|
||||
if (iTradeIDIsDefault) inStream.Read(iTradeID);
|
||||
return { bConfirmed, bDeleteItem, bOutSuccess, eInvType, eLootTypeSource, extraInfo, forceDeletion, iLootTypeSource, iObjID, iObjTemplate, iRequestingObjID, iStackCount, iStackRemaining, iSubkey, iTradeID };
|
||||
}
|
||||
|
||||
struct LegacyMoveItemInInventory { int32_t destInvType; LWOOBJID iObjID; int inventoryType; int responseCode; int slot; };
|
||||
inline LegacyMoveItemInInventory ReadMoveItemInInventory(RakNet::BitStream& inStream) {
|
||||
bool destInvTypeIsDefault = false;
|
||||
int32_t destInvType = eInventoryType::INVALID;
|
||||
LWOOBJID iObjID;
|
||||
int inventoryType;
|
||||
int responseCode;
|
||||
int slot;
|
||||
inStream.Read(destInvTypeIsDefault);
|
||||
if (destInvTypeIsDefault) { inStream.Read(destInvType); }
|
||||
inStream.Read(iObjID);
|
||||
inStream.Read(inventoryType);
|
||||
inStream.Read(responseCode);
|
||||
inStream.Read(slot);
|
||||
return { destInvType, iObjID, inventoryType, responseCode, slot };
|
||||
}
|
||||
|
||||
struct LegacyMoveItemBetweenInventoryTypes { eInventoryType inventoryTypeA; eInventoryType inventoryTypeB; LWOOBJID objectID; bool showFlyingLoot; uint32_t stackCount; LOT templateID; };
|
||||
inline LegacyMoveItemBetweenInventoryTypes ReadMoveItemBetweenInventoryTypes(RakNet::BitStream& inStream) {
|
||||
eInventoryType inventoryTypeA;
|
||||
eInventoryType inventoryTypeB;
|
||||
LWOOBJID objectID;
|
||||
bool showFlyingLoot = true;
|
||||
bool stackCountIsDefault = false;
|
||||
uint32_t stackCount = 1;
|
||||
bool templateIDIsDefault = false;
|
||||
LOT templateID = LOT_NULL;
|
||||
|
||||
inStream.Read(inventoryTypeA);
|
||||
inStream.Read(inventoryTypeB);
|
||||
inStream.Read(objectID);
|
||||
inStream.Read(showFlyingLoot);
|
||||
inStream.Read(stackCountIsDefault);
|
||||
if (stackCountIsDefault) inStream.Read(stackCount);
|
||||
inStream.Read(templateIDIsDefault);
|
||||
if (templateIDIsDefault) inStream.Read(templateID);
|
||||
return { inventoryTypeA, inventoryTypeB, objectID, showFlyingLoot, stackCount, templateID };
|
||||
}
|
||||
|
||||
struct LegacyRequestMove { bool bAllowPartial; int32_t destSlot; int32_t iStackCount; eInventoryType invTypeDst; eInventoryType invTypeSrc; LWOOBJID itemID; bool showFlyingLoot; LWOOBJID subkey; LOT itemLOT; };
|
||||
inline LegacyRequestMove ReadRequestMoveItemBetweenInventoryTypes(RakNet::BitStream& inStream) {
|
||||
bool bAllowPartial{};
|
||||
int32_t destSlot = -1;
|
||||
int32_t iStackCount = 1;
|
||||
eInventoryType invTypeDst = ITEMS;
|
||||
eInventoryType invTypeSrc = ITEMS;
|
||||
LWOOBJID itemID = LWOOBJID_EMPTY;
|
||||
bool showFlyingLoot{};
|
||||
LWOOBJID subkey = LWOOBJID_EMPTY;
|
||||
LOT itemLOT = 0;
|
||||
|
||||
bAllowPartial = inStream.ReadBit();
|
||||
if (inStream.ReadBit()) inStream.Read(destSlot);
|
||||
if (inStream.ReadBit()) inStream.Read(iStackCount);
|
||||
if (inStream.ReadBit()) inStream.Read(invTypeDst);
|
||||
if (inStream.ReadBit()) inStream.Read(invTypeSrc);
|
||||
if (inStream.ReadBit()) inStream.Read(itemID);
|
||||
showFlyingLoot = inStream.ReadBit();
|
||||
if (inStream.ReadBit()) inStream.Read(subkey);
|
||||
if (inStream.ReadBit()) inStream.Read(itemLOT);
|
||||
return { bAllowPartial, destSlot, iStackCount, invTypeDst, invTypeSrc, itemID, showFlyingLoot, subkey, itemLOT };
|
||||
}
|
||||
|
||||
// HandleClientItemConsumed, HandleUseNonEquipmentItem and HandleSetConsumableItem each read one value.
|
||||
template<typename T>
|
||||
inline T ReadSingle(RakNet::BitStream& inStream) {
|
||||
T value;
|
||||
inStream.Read(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
// The two group handlers read into an InventoryComponent::GroupUpdate; its fields are locals here.
|
||||
struct LegacyGroupUpdate { bool ok; std::string action; std::string groupId; std::u16string groupName; eInventoryType inventory; bool locked; LOT lot; };
|
||||
inline LegacyGroupUpdate ReadUpdateInventoryGroup(RakNet::BitStream& inStream) {
|
||||
std::string action;
|
||||
std::u16string groupName;
|
||||
std::string groupId;
|
||||
eInventoryType inventory{};
|
||||
bool locked{}; // All groups are locked by default
|
||||
|
||||
uint32_t size{};
|
||||
if (!inStream.Read(size)) return {};
|
||||
if (size > MAX_MESSAGE_LENGTH) return {}; // Bounds check before resize
|
||||
action.resize(size);
|
||||
if (!inStream.Read(action.data(), size)) return {};
|
||||
|
||||
if (!inStream.Read(size)) return {};
|
||||
if (size > MAX_MESSAGE_LENGTH) return {}; // Bounds check before resize
|
||||
groupId.resize(size);
|
||||
if (!inStream.Read(groupId.data(), size)) return {};
|
||||
|
||||
if (!inStream.Read(size)) return {};
|
||||
if (size > MAX_MESSAGE_LENGTH / 2) return {}; // Bounds check: size * 2 would overflow or exceed limit
|
||||
groupName.resize(size);
|
||||
if (!inStream.Read(reinterpret_cast<char*>(groupName.data()), size * 2)) return {};
|
||||
|
||||
if (!inStream.Read(inventory)) return {};
|
||||
if (!inStream.Read(locked)) return {};
|
||||
return { true, action, groupId, groupName, inventory, locked, 0 };
|
||||
}
|
||||
|
||||
inline LegacyGroupUpdate ReadUpdateInventoryGroupContents(RakNet::BitStream& inStream) {
|
||||
std::string action;
|
||||
std::string groupId;
|
||||
eInventoryType inventory{};
|
||||
LOT lot{};
|
||||
|
||||
uint32_t size{};
|
||||
if (!inStream.Read(size)) return {};
|
||||
action.resize(size);
|
||||
if (!inStream.Read(action.data(), size)) return {};
|
||||
|
||||
if (!inStream.Read(size)) return {};
|
||||
groupId.resize(size);
|
||||
if (!inStream.Read(groupId.data(), size)) return {};
|
||||
|
||||
if (!inStream.Read(inventory)) return {};
|
||||
if (!inStream.Read(lot)) return {};
|
||||
return { true, action, groupId, u"", inventory, false, lot };
|
||||
}
|
||||
}
|
||||
|
||||
#endif // INVENTORYMESSAGESLEGACY_H
|
||||
Reference in New Issue
Block a user