mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-26 08:50:13 +00:00
Extends ItemQuality enum with ARTIFACT (6) and HEIRLOOM (7) to match WotLK 3.3.5a quality values, with light gold color (e6cc80) and display name support in inventory UI and tooltips.
352 lines
11 KiB
C++
352 lines
11 KiB
C++
#include "game/inventory.hpp"
|
|
#include "core/logger.hpp"
|
|
|
|
namespace wowee {
|
|
namespace game {
|
|
|
|
static const ItemSlot EMPTY_SLOT{};
|
|
|
|
Inventory::Inventory() = default;
|
|
|
|
const ItemSlot& Inventory::getBackpackSlot(int index) const {
|
|
if (index < 0 || index >= BACKPACK_SLOTS) return EMPTY_SLOT;
|
|
return backpack[index];
|
|
}
|
|
|
|
bool Inventory::setBackpackSlot(int index, const ItemDef& item) {
|
|
if (index < 0 || index >= BACKPACK_SLOTS) return false;
|
|
backpack[index].item = item;
|
|
return true;
|
|
}
|
|
|
|
bool Inventory::clearBackpackSlot(int index) {
|
|
if (index < 0 || index >= BACKPACK_SLOTS) return false;
|
|
backpack[index].item = ItemDef{};
|
|
return true;
|
|
}
|
|
|
|
const ItemSlot& Inventory::getEquipSlot(EquipSlot slot) const {
|
|
int idx = static_cast<int>(slot);
|
|
if (idx < 0 || idx >= NUM_EQUIP_SLOTS) return EMPTY_SLOT;
|
|
return equipment[idx];
|
|
}
|
|
|
|
bool Inventory::setEquipSlot(EquipSlot slot, const ItemDef& item) {
|
|
int idx = static_cast<int>(slot);
|
|
if (idx < 0 || idx >= NUM_EQUIP_SLOTS) return false;
|
|
equipment[idx].item = item;
|
|
return true;
|
|
}
|
|
|
|
bool Inventory::clearEquipSlot(EquipSlot slot) {
|
|
int idx = static_cast<int>(slot);
|
|
if (idx < 0 || idx >= NUM_EQUIP_SLOTS) return false;
|
|
equipment[idx].item = ItemDef{};
|
|
return true;
|
|
}
|
|
|
|
int Inventory::getBagSize(int bagIndex) const {
|
|
if (bagIndex < 0 || bagIndex >= NUM_BAG_SLOTS) return 0;
|
|
return bags[bagIndex].size;
|
|
}
|
|
|
|
void Inventory::setBagSize(int bagIndex, int size) {
|
|
if (bagIndex < 0 || bagIndex >= NUM_BAG_SLOTS) return;
|
|
bags[bagIndex].size = std::min(size, MAX_BAG_SIZE);
|
|
}
|
|
|
|
const ItemSlot& Inventory::getBagSlot(int bagIndex, int slotIndex) const {
|
|
if (bagIndex < 0 || bagIndex >= NUM_BAG_SLOTS) return EMPTY_SLOT;
|
|
if (slotIndex < 0 || slotIndex >= bags[bagIndex].size) return EMPTY_SLOT;
|
|
return bags[bagIndex].slots[slotIndex];
|
|
}
|
|
|
|
bool Inventory::setBagSlot(int bagIndex, int slotIndex, const ItemDef& item) {
|
|
if (bagIndex < 0 || bagIndex >= NUM_BAG_SLOTS) return false;
|
|
if (slotIndex < 0 || slotIndex >= bags[bagIndex].size) return false;
|
|
bags[bagIndex].slots[slotIndex].item = item;
|
|
return true;
|
|
}
|
|
|
|
bool Inventory::clearBagSlot(int bagIndex, int slotIndex) {
|
|
if (bagIndex < 0 || bagIndex >= NUM_BAG_SLOTS) return false;
|
|
if (slotIndex < 0 || slotIndex >= bags[bagIndex].size) return false;
|
|
bags[bagIndex].slots[slotIndex].item = ItemDef{};
|
|
return true;
|
|
}
|
|
|
|
const ItemSlot& Inventory::getBankSlot(int index) const {
|
|
if (index < 0 || index >= BANK_SLOTS) return EMPTY_SLOT;
|
|
return bankSlots_[index];
|
|
}
|
|
|
|
bool Inventory::setBankSlot(int index, const ItemDef& item) {
|
|
if (index < 0 || index >= BANK_SLOTS) return false;
|
|
bankSlots_[index].item = item;
|
|
return true;
|
|
}
|
|
|
|
bool Inventory::clearBankSlot(int index) {
|
|
if (index < 0 || index >= BANK_SLOTS) return false;
|
|
bankSlots_[index].item = ItemDef{};
|
|
return true;
|
|
}
|
|
|
|
const ItemSlot& Inventory::getBankBagSlot(int bagIndex, int slotIndex) const {
|
|
if (bagIndex < 0 || bagIndex >= BANK_BAG_SLOTS) return EMPTY_SLOT;
|
|
if (slotIndex < 0 || slotIndex >= bankBags_[bagIndex].size) return EMPTY_SLOT;
|
|
return bankBags_[bagIndex].slots[slotIndex];
|
|
}
|
|
|
|
bool Inventory::setBankBagSlot(int bagIndex, int slotIndex, const ItemDef& item) {
|
|
if (bagIndex < 0 || bagIndex >= BANK_BAG_SLOTS) return false;
|
|
if (slotIndex < 0 || slotIndex >= bankBags_[bagIndex].size) return false;
|
|
bankBags_[bagIndex].slots[slotIndex].item = item;
|
|
return true;
|
|
}
|
|
|
|
bool Inventory::clearBankBagSlot(int bagIndex, int slotIndex) {
|
|
if (bagIndex < 0 || bagIndex >= BANK_BAG_SLOTS) return false;
|
|
if (slotIndex < 0 || slotIndex >= bankBags_[bagIndex].size) return false;
|
|
bankBags_[bagIndex].slots[slotIndex].item = ItemDef{};
|
|
return true;
|
|
}
|
|
|
|
int Inventory::getBankBagSize(int bagIndex) const {
|
|
if (bagIndex < 0 || bagIndex >= BANK_BAG_SLOTS) return 0;
|
|
return bankBags_[bagIndex].size;
|
|
}
|
|
|
|
void Inventory::setBankBagSize(int bagIndex, int size) {
|
|
if (bagIndex < 0 || bagIndex >= BANK_BAG_SLOTS) return;
|
|
bankBags_[bagIndex].size = std::min(size, MAX_BAG_SIZE);
|
|
}
|
|
|
|
const ItemSlot& Inventory::getBankBagItem(int bagIndex) const {
|
|
static const ItemSlot EMPTY_SLOT;
|
|
if (bagIndex < 0 || bagIndex >= BANK_BAG_SLOTS) return EMPTY_SLOT;
|
|
return bankBags_[bagIndex].bagItem;
|
|
}
|
|
|
|
void Inventory::setBankBagItem(int bagIndex, const ItemDef& item) {
|
|
if (bagIndex < 0 || bagIndex >= BANK_BAG_SLOTS) return;
|
|
bankBags_[bagIndex].bagItem.item = item;
|
|
}
|
|
|
|
void Inventory::swapBagContents(int bagA, int bagB) {
|
|
if (bagA < 0 || bagA >= NUM_BAG_SLOTS || bagB < 0 || bagB >= NUM_BAG_SLOTS) return;
|
|
if (bagA == bagB) return;
|
|
std::swap(bags[bagA], bags[bagB]);
|
|
}
|
|
|
|
int Inventory::findFreeBackpackSlot() const {
|
|
for (int i = 0; i < BACKPACK_SLOTS; i++) {
|
|
if (backpack[i].empty()) return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
bool Inventory::addItem(const ItemDef& item) {
|
|
// Try stacking first
|
|
if (item.maxStack > 1) {
|
|
for (int i = 0; i < BACKPACK_SLOTS; i++) {
|
|
if (!backpack[i].empty() &&
|
|
backpack[i].item.itemId == item.itemId &&
|
|
backpack[i].item.stackCount < backpack[i].item.maxStack) {
|
|
uint32_t space = backpack[i].item.maxStack - backpack[i].item.stackCount;
|
|
uint32_t toAdd = std::min(space, item.stackCount);
|
|
backpack[i].item.stackCount += toAdd;
|
|
if (toAdd >= item.stackCount) return true;
|
|
// Remaining needs a new slot — fall through
|
|
}
|
|
}
|
|
}
|
|
|
|
int slot = findFreeBackpackSlot();
|
|
if (slot < 0) return false;
|
|
backpack[slot].item = item;
|
|
return true;
|
|
}
|
|
|
|
void Inventory::populateTestItems() {
|
|
// Equipment
|
|
{
|
|
ItemDef sword;
|
|
sword.itemId = 25;
|
|
sword.name = "Worn Shortsword";
|
|
sword.quality = ItemQuality::COMMON;
|
|
sword.inventoryType = 21; // Main Hand
|
|
sword.strength = 1;
|
|
sword.displayInfoId = 1542; // Sword_1H_Short_A_02.m2
|
|
sword.subclassName = "Sword";
|
|
setEquipSlot(EquipSlot::MAIN_HAND, sword);
|
|
}
|
|
{
|
|
ItemDef shield;
|
|
shield.itemId = 2129;
|
|
shield.name = "Large Round Shield";
|
|
shield.quality = ItemQuality::COMMON;
|
|
shield.inventoryType = 14; // Off Hand (Shield)
|
|
shield.armor = 18;
|
|
shield.stamina = 1;
|
|
shield.displayInfoId = 18662; // Shield_Round_A_01.m2
|
|
shield.subclassName = "Shield";
|
|
setEquipSlot(EquipSlot::OFF_HAND, shield);
|
|
}
|
|
// Shirt/pants/boots in backpack (character model renders bare geosets)
|
|
{
|
|
ItemDef shirt;
|
|
shirt.itemId = 38;
|
|
shirt.name = "Recruit's Shirt";
|
|
shirt.quality = ItemQuality::COMMON;
|
|
shirt.inventoryType = 4; // Shirt
|
|
shirt.displayInfoId = 2163;
|
|
addItem(shirt);
|
|
}
|
|
{
|
|
ItemDef pants;
|
|
pants.itemId = 39;
|
|
pants.name = "Recruit's Pants";
|
|
pants.quality = ItemQuality::COMMON;
|
|
pants.inventoryType = 7; // Legs
|
|
pants.armor = 4;
|
|
pants.displayInfoId = 1883;
|
|
addItem(pants);
|
|
}
|
|
{
|
|
ItemDef boots;
|
|
boots.itemId = 40;
|
|
boots.name = "Recruit's Boots";
|
|
boots.quality = ItemQuality::COMMON;
|
|
boots.inventoryType = 8; // Feet
|
|
boots.armor = 3;
|
|
boots.displayInfoId = 2166;
|
|
addItem(boots);
|
|
}
|
|
|
|
// Backpack items
|
|
{
|
|
ItemDef potion;
|
|
potion.itemId = 118;
|
|
potion.name = "Minor Healing Potion";
|
|
potion.quality = ItemQuality::COMMON;
|
|
potion.stackCount = 3;
|
|
potion.maxStack = 5;
|
|
addItem(potion);
|
|
}
|
|
{
|
|
ItemDef hearthstone;
|
|
hearthstone.itemId = 6948;
|
|
hearthstone.name = "Hearthstone";
|
|
hearthstone.quality = ItemQuality::COMMON;
|
|
addItem(hearthstone);
|
|
}
|
|
{
|
|
ItemDef leather;
|
|
leather.itemId = 2318;
|
|
leather.name = "Light Leather";
|
|
leather.quality = ItemQuality::COMMON;
|
|
leather.stackCount = 5;
|
|
leather.maxStack = 20;
|
|
addItem(leather);
|
|
}
|
|
{
|
|
ItemDef cloth;
|
|
cloth.itemId = 2589;
|
|
cloth.name = "Linen Cloth";
|
|
cloth.quality = ItemQuality::COMMON;
|
|
cloth.stackCount = 8;
|
|
cloth.maxStack = 20;
|
|
addItem(cloth);
|
|
}
|
|
{
|
|
ItemDef quest;
|
|
quest.itemId = 50000;
|
|
quest.name = "Kobold Candle";
|
|
quest.quality = ItemQuality::COMMON;
|
|
quest.stackCount = 4;
|
|
quest.maxStack = 10;
|
|
addItem(quest);
|
|
}
|
|
{
|
|
ItemDef ring;
|
|
ring.itemId = 11287;
|
|
ring.name = "Verdant Ring";
|
|
ring.quality = ItemQuality::UNCOMMON;
|
|
ring.inventoryType = 11; // Ring
|
|
ring.stamina = 3;
|
|
ring.spirit = 2;
|
|
addItem(ring);
|
|
}
|
|
{
|
|
ItemDef cloak;
|
|
cloak.itemId = 2570;
|
|
cloak.name = "Linen Cloak";
|
|
cloak.quality = ItemQuality::UNCOMMON;
|
|
cloak.inventoryType = 16; // Back
|
|
cloak.armor = 10;
|
|
cloak.agility = 1;
|
|
cloak.displayInfoId = 15055;
|
|
addItem(cloak);
|
|
}
|
|
{
|
|
ItemDef rareAxe;
|
|
rareAxe.itemId = 15268;
|
|
rareAxe.name = "Stoneslayer";
|
|
rareAxe.quality = ItemQuality::RARE;
|
|
rareAxe.inventoryType = 17; // Two-Hand
|
|
rareAxe.strength = 8;
|
|
rareAxe.stamina = 7;
|
|
rareAxe.subclassName = "Axe";
|
|
rareAxe.displayInfoId = 782; // Axe_2H_Battle_B_01.m2
|
|
addItem(rareAxe);
|
|
}
|
|
|
|
LOG_INFO("Inventory: populated test items (2 equipped, 11 backpack)");
|
|
}
|
|
|
|
const char* getQualityName(ItemQuality quality) {
|
|
switch (quality) {
|
|
case ItemQuality::POOR: return "Poor";
|
|
case ItemQuality::COMMON: return "Common";
|
|
case ItemQuality::UNCOMMON: return "Uncommon";
|
|
case ItemQuality::RARE: return "Rare";
|
|
case ItemQuality::EPIC: return "Epic";
|
|
case ItemQuality::LEGENDARY: return "Legendary";
|
|
case ItemQuality::ARTIFACT: return "Artifact";
|
|
case ItemQuality::HEIRLOOM: return "Heirloom";
|
|
default: return "Unknown";
|
|
}
|
|
}
|
|
|
|
const char* getEquipSlotName(EquipSlot slot) {
|
|
switch (slot) {
|
|
case EquipSlot::HEAD: return "Head";
|
|
case EquipSlot::NECK: return "Neck";
|
|
case EquipSlot::SHOULDERS: return "Shoulders";
|
|
case EquipSlot::SHIRT: return "Shirt";
|
|
case EquipSlot::CHEST: return "Chest";
|
|
case EquipSlot::WAIST: return "Waist";
|
|
case EquipSlot::LEGS: return "Legs";
|
|
case EquipSlot::FEET: return "Feet";
|
|
case EquipSlot::WRISTS: return "Wrists";
|
|
case EquipSlot::HANDS: return "Hands";
|
|
case EquipSlot::RING1: return "Ring 1";
|
|
case EquipSlot::RING2: return "Ring 2";
|
|
case EquipSlot::TRINKET1: return "Trinket 1";
|
|
case EquipSlot::TRINKET2: return "Trinket 2";
|
|
case EquipSlot::BACK: return "Back";
|
|
case EquipSlot::MAIN_HAND: return "Main Hand";
|
|
case EquipSlot::OFF_HAND: return "Off Hand";
|
|
case EquipSlot::RANGED: return "Ranged";
|
|
case EquipSlot::TABARD: return "Tabard";
|
|
case EquipSlot::BAG1: return "Bag 1";
|
|
case EquipSlot::BAG2: return "Bag 2";
|
|
case EquipSlot::BAG3: return "Bag 3";
|
|
case EquipSlot::BAG4: return "Bag 4";
|
|
default: return "Unknown";
|
|
}
|
|
}
|
|
|
|
} // namespace game
|
|
} // namespace wowee
|