mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-11-14 07:47:13 +00:00
fix(vendor): dropping the oldest buyback item moves the rest up, as live
Live's 28th sale to a full buyback removed the oldest item, then sent MoveItemInInventory (inventory 11, VENDOR_BUYBACK) for each of the 26 items left, to slots 0 to 25 in order, before the sold item was added after them. The server left the hole, so the sold item took the dropped item's slot and the buyback page was no longer in sale order. The buyback inventory is not saved and nothing changes hands; only slots move. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
27
dGame/dComponents/Vendor/BaseVendorComponent.cpp
vendored
27
dGame/dComponents/Vendor/BaseVendorComponent.cpp
vendored
@@ -15,6 +15,7 @@
|
||||
#include "UserManager.h"
|
||||
#include "CheatDetection.h"
|
||||
#include "VendorMessages.h"
|
||||
#include "InventoryMessages.h"
|
||||
#include "Item.h"
|
||||
#include "EconomyLedger.h"
|
||||
|
||||
@@ -355,10 +356,36 @@ void BaseVendorComponent::MakeRoomInBuyback(InventoryComponent& inventory, const
|
||||
// added. Each item gets a new, higher object ID when it is sold, so the oldest is the one with the lowest ID.
|
||||
// The ledger counted these items as gone when they were sold
|
||||
EconomyLedger::ScopedItemTransfer alreadyCounted;
|
||||
bool dropped = false;
|
||||
while (buyback->GetItems().size() >= BUYBACK_SIZE) {
|
||||
const auto items = buyback->GetItems().size();
|
||||
const auto oldest = std::ranges::min_element(buyback->GetItems(), {}, [](const auto& entry) { return entry.first; });
|
||||
oldest->second->SetCount(0, false, false, false, eLootSourceType::VENDOR);
|
||||
if (buyback->GetItems().size() >= items) break; // not removed (an empty stack)
|
||||
dropped = true;
|
||||
}
|
||||
if (dropped) CompactBuyback(inventory);
|
||||
}
|
||||
|
||||
void BaseVendorComponent::CompactBuyback(InventoryComponent& inventory) {
|
||||
auto* const buyback = inventory.GetInventory(eInventoryType::VENDOR_BUYBACK);
|
||||
if (!buyback) return;
|
||||
|
||||
// Live moved what was left up, in order, before the sold item went in after it: the 2014 capture of a 28th sale
|
||||
// has MoveItemInInventory (inventory 11) for each of the 26 items left, to slots 0 to 25, then the sold item added
|
||||
const auto& player = *inventory.GetParent();
|
||||
const auto slots = buyback->GetSlots();
|
||||
uint32_t slot = 0;
|
||||
for (auto* const item : slots | std::views::values) {
|
||||
if (item->GetSlot() != slot) {
|
||||
item->SetSlot(slot);
|
||||
GameMessages::MoveItemInInventory move;
|
||||
move.target = player.GetObjectID();
|
||||
move.iObjID = item->GetId();
|
||||
move.inventoryType = eInventoryType::VENDOR_BUYBACK;
|
||||
move.slot = static_cast<int32_t>(slot);
|
||||
move.SendToClient(player.GetSystemAddress());
|
||||
}
|
||||
slot++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +75,9 @@ public:
|
||||
// a slot of their own.
|
||||
static void MakeRoomInBuyback(InventoryComponent& inventory, const Item& sold, uint32_t count);
|
||||
|
||||
// Moves the buyback items up to slots 0, 1, ... in their order, telling the player of each one that moved
|
||||
static void CompactBuyback(InventoryComponent& inventory);
|
||||
|
||||
private:
|
||||
void HandleMrReeCameras();
|
||||
bool SetupItem(LOT item);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "Item.h"
|
||||
#include "VendorComponent.h"
|
||||
#include "eReplicaComponentType.h"
|
||||
#include "InventoryMessages.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
@@ -77,3 +78,50 @@ TEST_F(VendorBuybackTests, RoomLeftOnAStackKeepsEverything) {
|
||||
VendorComponent::MakeRoomInBuyback(*inventory, *sold, 6);
|
||||
EXPECT_EQ(buyback->GetItems().size(), VendorComponent::BUYBACK_SIZE - 1);
|
||||
}
|
||||
|
||||
TEST_F(VendorBuybackTests, DroppingTheOldestMovesTheRestUp) {
|
||||
const auto oldest = nextId;
|
||||
for (size_t i = 0; i < VendorComponent::BUYBACK_SIZE; i++) Give(buyback, 3001);
|
||||
auto* const sold = Give(inventory->GetInventory(eInventoryType::ITEMS), 3001);
|
||||
auto* const server = static_cast<dServerMock*>(Game::server);
|
||||
server->ClearSentPackets();
|
||||
|
||||
VendorComponent::MakeRoomInBuyback(*inventory, *sold, 1);
|
||||
|
||||
// The 26 left in slots 0 to 25 in their order, the sold item's slot free after them
|
||||
for (LWOOBJID id = oldest + 1; id < oldest + static_cast<LWOOBJID>(VendorComponent::BUYBACK_SIZE); id++) {
|
||||
EXPECT_EQ(buyback->FindItemById(id)->GetSlot(), static_cast<uint32_t>(id - oldest - 1));
|
||||
}
|
||||
EXPECT_EQ(buyback->FindEmptySlot(), static_cast<int32_t>(VendorComponent::BUYBACK_SIZE - 1));
|
||||
|
||||
// A MoveItemInInventory for each, in the buyback inventory, as live sent them
|
||||
std::vector<std::pair<LWOOBJID, int32_t>> moves;
|
||||
for (const auto& packet : server->GetSentPackets()) {
|
||||
RakNet::BitStream bitStream(const_cast<uint8_t*>(packet.bytes.data()), packet.bytes.size(), false);
|
||||
LWOOBJID target{};
|
||||
MessageType::Game id{};
|
||||
if (!GameMessages::NetGameMsg::ReadPacketHeader(bitStream, target, id) || id != MessageType::Game::MOVE_ITEM_IN_INVENTORY) continue;
|
||||
GameMessages::MoveItemInInventory move;
|
||||
ASSERT_TRUE(move.Deserialize(bitStream));
|
||||
EXPECT_EQ(target, entity->GetObjectID());
|
||||
EXPECT_EQ(move.inventoryType, eInventoryType::VENDOR_BUYBACK);
|
||||
EXPECT_EQ(move.destInvType, eInventoryType::INVALID);
|
||||
EXPECT_EQ(move.responseCode, 0);
|
||||
moves.emplace_back(move.iObjID, move.slot);
|
||||
}
|
||||
ASSERT_EQ(moves.size(), VendorComponent::BUYBACK_SIZE - 1);
|
||||
for (size_t i = 0; i < moves.size(); i++) {
|
||||
EXPECT_EQ(moves[i].first, oldest + 1 + static_cast<LWOOBJID>(i));
|
||||
EXPECT_EQ(moves[i].second, static_cast<int32_t>(i));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(VendorBuybackTests, NothingMovesWhenNothingIsDropped) {
|
||||
Give(buyback, 3001);
|
||||
auto* const sold = Give(inventory->GetInventory(eInventoryType::ITEMS), 3001);
|
||||
auto* const server = static_cast<dServerMock*>(Game::server);
|
||||
server->ClearSentPackets();
|
||||
|
||||
VendorComponent::MakeRoomInBuyback(*inventory, *sold, 1);
|
||||
EXPECT_TRUE(server->GetSentPackets().empty());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user