mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 10:53:44 +00:00
The buyback inventory grew by 9 slots whenever it was nearly full, so the vendor's buyback page kept resizing. Live kept 27 items: a 2014 capture of 29 sales shows that on the 28th, the server sent RemoveItemFromInventory (buyback inventory) for the first item sold, then added the new one. The buyback inventory now keeps its size. Before a sale needs a new buyback slot and the inventory holds 27 or more items, the oldest items (lowest object ID: each sale gives a new, higher ID) are removed. Sales that fit on an existing buyback stack remove nothing. The removal is not counted again by the economy ledger, which counted the items as gone when they were sold. Fixes issue 1129. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
345 lines
7.3 KiB
C++
345 lines
7.3 KiB
C++
#include "Inventory.h"
|
|
#include "GameMessages.h"
|
|
#include "InventoryMessages.h"
|
|
#include "Game.h"
|
|
#include "Item.h"
|
|
#include "InventoryComponent.h"
|
|
#include "eItemType.h"
|
|
#include "eReplicaComponentType.h"
|
|
#include "ObjectIDManager.h"
|
|
#include "eObjectBits.h"
|
|
|
|
#include "CDComponentsRegistryTable.h"
|
|
#include <ranges>
|
|
|
|
std::vector<LOT> Inventory::m_GameMasterRestrictedItems = {
|
|
1727, // GM Only - JetPack
|
|
2243, // GM Only - Hammer of Doom
|
|
3293, // GM Only - Flamethrower
|
|
3735, // GM Only - Large Jetpack
|
|
5873, // GM Only - Winged Helm of Speed
|
|
6407, // Gm Only - Hat of Pwnage
|
|
14442 // The jamesster jetpack
|
|
};
|
|
|
|
Inventory::Inventory(const eInventoryType type, const uint32_t size, const std::vector<Item*>& items, InventoryComponent* component) {
|
|
this->type = type;
|
|
this->size = size;
|
|
this->free = size;
|
|
this->component = component;
|
|
|
|
for (auto* item : items) {
|
|
AddManagedItem(item);
|
|
}
|
|
}
|
|
|
|
eInventoryType Inventory::GetType() const {
|
|
return type;
|
|
}
|
|
|
|
uint32_t Inventory::GetSize() const {
|
|
return size;
|
|
}
|
|
|
|
std::map<LWOOBJID, Item*>& Inventory::GetItems() {
|
|
return items;
|
|
}
|
|
|
|
std::map<uint32_t, Item*> Inventory::GetSlots() const {
|
|
std::map<uint32_t, Item*> slots;
|
|
|
|
for (const auto& pair : items) {
|
|
auto* item = pair.second;
|
|
|
|
slots.insert_or_assign(item->GetSlot(), item);
|
|
}
|
|
|
|
return slots;
|
|
}
|
|
|
|
InventoryComponent* Inventory::GetComponent() const {
|
|
return component;
|
|
}
|
|
|
|
uint32_t Inventory::GetLotCount(const LOT lot) const {
|
|
uint32_t count = 0;
|
|
|
|
for (const auto& pair : items) {
|
|
const auto* item = pair.second;
|
|
|
|
if (item->GetLot() == lot) {
|
|
count += item->GetCount();
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
void Inventory::SetSize(const uint32_t value) {
|
|
const auto delta = static_cast<int32_t>(value) - static_cast<int32_t>(size);
|
|
free = static_cast<uint32_t>(std::max(0, static_cast<int32_t>(free) + delta));
|
|
|
|
size = value;
|
|
|
|
GameMessages::SetInventorySize setSize;
|
|
setSize.target = component->GetParent()->GetObjectID();
|
|
setSize.inventoryType = type;
|
|
setSize.size = static_cast<int>(size);
|
|
setSize.SendToClient(component->GetParent()->GetSystemAddress());
|
|
}
|
|
|
|
int32_t Inventory::FindEmptySlot() {
|
|
if (free <= 6) // Up from 1
|
|
{
|
|
// The buyback inventory keeps its size: selling to a full one drops the oldest item (VendorComponent::SellToVendor)
|
|
if (type != ITEMS && type != VAULT_ITEMS && type != eInventoryType::VAULT_MODELS && type != eInventoryType::VENDOR_BUYBACK) {
|
|
uint32_t newSize = size;
|
|
|
|
if (type == MODELS) {
|
|
newSize = 240;
|
|
} else {
|
|
newSize += 10u;
|
|
}
|
|
|
|
if (newSize > GetSize()) {
|
|
SetSize(newSize);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (free == 0) {
|
|
return -1;
|
|
}
|
|
|
|
const auto slots = GetSlots();
|
|
|
|
for (auto i = 0u; i < size; ++i) {
|
|
if (slots.find(i) == slots.end()) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int32_t Inventory::GetEmptySlots() {
|
|
return free;
|
|
}
|
|
|
|
bool Inventory::IsSlotEmpty(int32_t slot) {
|
|
const auto slots = GetSlots();
|
|
|
|
const auto& index = slots.find(slot);
|
|
|
|
return index == slots.end();
|
|
}
|
|
|
|
Item* Inventory::FindItemById(const LWOOBJID id) const {
|
|
const auto& index = items.find(id);
|
|
|
|
if (index == items.end()) {
|
|
return nullptr;
|
|
}
|
|
|
|
return index->second;
|
|
}
|
|
|
|
Item* Inventory::FindItemByLot(const LOT lot, const bool ignoreEquipped, const bool ignoreBound) const {
|
|
Item* smallest = nullptr;
|
|
|
|
for (const auto& pair : items) {
|
|
auto* item = pair.second;
|
|
|
|
if (item->GetLot() != lot) {
|
|
continue;
|
|
}
|
|
|
|
if (ignoreEquipped && item->IsEquipped()) {
|
|
continue;
|
|
}
|
|
|
|
if (ignoreBound && item->GetBound()) {
|
|
continue;
|
|
}
|
|
|
|
if (smallest == nullptr) {
|
|
smallest = item;
|
|
|
|
continue;
|
|
}
|
|
|
|
if (smallest->GetCount() > item->GetCount()) {
|
|
smallest = item;
|
|
}
|
|
}
|
|
|
|
return smallest;
|
|
}
|
|
|
|
Item* Inventory::FindItemBySlot(const uint32_t slot) const {
|
|
const auto slots = GetSlots();
|
|
|
|
const auto index = slots.find(slot);
|
|
|
|
if (index == slots.end()) {
|
|
return nullptr;
|
|
}
|
|
|
|
return index->second;
|
|
}
|
|
|
|
Item* Inventory::FindItemBySubKey(LWOOBJID id) const {
|
|
for (const auto& item : items) {
|
|
if (item.second->GetSubKey() == id) {
|
|
return item.second;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void Inventory::AddManagedItem(Item* item) {
|
|
const auto id = item->GetId();
|
|
|
|
if (items.find(id) != items.end()) {
|
|
LOG("Attempting to add an item with an already present id (%llu)!", id);
|
|
|
|
return;
|
|
}
|
|
|
|
const auto slots = GetSlots();
|
|
|
|
const auto slot = item->GetSlot();
|
|
|
|
if (slots.find(slot) != slots.end()) {
|
|
LOG("Attempting to add an item with an already present slot (%i)!", slot);
|
|
|
|
return;
|
|
}
|
|
|
|
items.insert_or_assign(id, item);
|
|
|
|
free--;
|
|
}
|
|
|
|
void Inventory::RemoveManagedItem(Item* item) {
|
|
const auto id = item->GetId();
|
|
|
|
if (items.find(id) == items.end()) {
|
|
LOG("Attempting to remove an item with an invalid id (%llu), lot (%i)!", id, item->GetLot());
|
|
|
|
return;
|
|
}
|
|
|
|
items.erase(id);
|
|
|
|
free++;
|
|
}
|
|
|
|
eInventoryType Inventory::FindInventoryTypeForLot(const LOT lot) {
|
|
auto itemComponent = FindItemComponent(lot);
|
|
|
|
const auto itemType = static_cast<eItemType>(itemComponent.itemType);
|
|
|
|
switch (itemType) {
|
|
case eItemType::BRICK:
|
|
return BRICKS;
|
|
|
|
case eItemType::BEHAVIOR:
|
|
return BEHAVIORS;
|
|
|
|
case eItemType::PROPERTY:
|
|
return PROPERTY_DEEDS;
|
|
|
|
case eItemType::MODEL:
|
|
case eItemType::PET_INVENTORY_ITEM:
|
|
case eItemType::LOOT_MODEL:
|
|
case eItemType::VEHICLE:
|
|
case eItemType::MOUNT:
|
|
return MODELS;
|
|
|
|
case eItemType::HAT:
|
|
case eItemType::HAIR:
|
|
case eItemType::NECK:
|
|
case eItemType::LEFT_HAND:
|
|
case eItemType::RIGHT_HAND:
|
|
case eItemType::LEGS:
|
|
case eItemType::LEFT_TRINKET:
|
|
case eItemType::RIGHT_TRINKET:
|
|
case eItemType::COLLECTIBLE:
|
|
case eItemType::CONSUMABLE:
|
|
case eItemType::CHEST:
|
|
case eItemType::EGG:
|
|
case eItemType::PET_FOOD:
|
|
case eItemType::PACKAGE:
|
|
case eItemType::LUP_MODEL:
|
|
return ITEMS;
|
|
|
|
case eItemType::QUEST_OBJECT:
|
|
case eItemType::UNKNOWN:
|
|
default:
|
|
return QUEST;
|
|
}
|
|
}
|
|
|
|
const CDItemComponent& Inventory::FindItemComponent(const LOT lot) {
|
|
auto* registry = CDClientManager::GetTable<CDComponentsRegistryTable>();
|
|
|
|
auto* itemComponents = CDClientManager::GetTable<CDItemComponentTable>();
|
|
|
|
const auto componentId = registry->GetByIDAndType(lot, eReplicaComponentType::ITEM);
|
|
|
|
if (componentId == 0) {
|
|
LOG("Failed to find item component for (%i)!", lot);
|
|
|
|
return CDItemComponentTable::Default;
|
|
}
|
|
|
|
const auto& itemComponent = itemComponents->GetItemComponentByID(componentId);
|
|
|
|
return itemComponent;
|
|
}
|
|
|
|
bool Inventory::IsValidItem(const LOT lot) {
|
|
auto* registry = CDClientManager::GetTable<CDComponentsRegistryTable>();
|
|
|
|
const auto componentId = registry->GetByIDAndType(lot, eReplicaComponentType::ITEM);
|
|
|
|
return componentId != 0;
|
|
}
|
|
|
|
const std::vector<LOT>& Inventory::GetAllGMItems() {
|
|
return m_GameMasterRestrictedItems;
|
|
}
|
|
|
|
void Inventory::DeleteAllItems() {
|
|
while (!this->items.empty()) {
|
|
if (items.begin()->second) items.begin()->second->SetCount(0);
|
|
}
|
|
}
|
|
|
|
Inventory::~Inventory() {
|
|
for (auto item : items) {
|
|
delete item.second;
|
|
}
|
|
|
|
items.clear();
|
|
}
|
|
|
|
void Inventory::RegenerateItemIDs() {
|
|
std::map<LWOOBJID, Item*> newItems{};
|
|
for (auto* const item : items | std::views::values) {
|
|
if (item->GetParent() != LWOOBJID_EMPTY) continue; // temp items dont need new ids
|
|
const bool equipped = item->IsEquipped();
|
|
if (equipped) item->UnEquip();
|
|
const auto oldID = item->GetId();
|
|
const auto newID = item->GenerateID();
|
|
LOG("Updating item ID from %llu to %llu", oldID, newID);
|
|
newItems.insert_or_assign(newID, item);
|
|
if (equipped) item->Equip();
|
|
}
|
|
|
|
// We don't want to delete the item pointers, we're just moving from map to map
|
|
items = newItems;
|
|
}
|