2026-02-02 12:24:50 -08:00
|
|
|
#pragma once
|
|
|
|
|
|
|
|
|
|
#include "game/inventory.hpp"
|
2026-02-06 14:24:38 -08:00
|
|
|
#include "game/character.hpp"
|
2026-02-06 13:47:03 -08:00
|
|
|
#include "game/world_packets.hpp"
|
2026-02-22 03:32:08 -08:00
|
|
|
#include <vulkan/vulkan.h>
|
2026-02-02 12:24:50 -08:00
|
|
|
#include <imgui.h>
|
2026-02-13 22:51:49 -08:00
|
|
|
#include <array>
|
2026-02-06 13:47:03 -08:00
|
|
|
#include <functional>
|
2026-02-06 14:24:38 -08:00
|
|
|
#include <memory>
|
|
|
|
|
#include <unordered_map>
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
namespace wowee {
|
2026-02-06 14:24:38 -08:00
|
|
|
namespace pipeline { class AssetManager; }
|
|
|
|
|
namespace rendering { class CharacterPreview; class CharacterRenderer; }
|
2026-02-06 13:47:03 -08:00
|
|
|
namespace game { class GameHandler; }
|
2026-02-02 12:24:50 -08:00
|
|
|
namespace ui {
|
|
|
|
|
|
|
|
|
|
class InventoryScreen {
|
|
|
|
|
public:
|
2026-02-06 14:24:38 -08:00
|
|
|
~InventoryScreen();
|
|
|
|
|
|
2026-02-06 13:47:03 -08:00
|
|
|
/// Render bags window (B key). Positioned at bottom of screen.
|
2026-02-05 14:01:26 -08:00
|
|
|
void render(game::Inventory& inventory, uint64_t moneyCopper);
|
2026-02-06 13:47:03 -08:00
|
|
|
|
|
|
|
|
/// Render character screen (C key). Standalone equipment window.
|
2026-02-06 14:24:38 -08:00
|
|
|
void renderCharacterScreen(game::GameHandler& gameHandler);
|
2026-02-06 13:47:03 -08:00
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
bool isOpen() const { return open; }
|
|
|
|
|
void toggle() { open = !open; }
|
|
|
|
|
void setOpen(bool o) { open = o; }
|
|
|
|
|
|
2026-02-13 22:51:49 -08:00
|
|
|
// Separate bag window controls
|
|
|
|
|
void toggleBackpack();
|
|
|
|
|
void toggleBag(int idx);
|
|
|
|
|
void openAllBags();
|
|
|
|
|
void closeAllBags();
|
|
|
|
|
void setSeparateBags(bool sep) { separateBags_ = sep; }
|
|
|
|
|
bool isSeparateBags() const { return separateBags_; }
|
2026-02-19 01:50:50 -08:00
|
|
|
void toggleCompactBags() { compactBags_ = !compactBags_; }
|
|
|
|
|
bool isCompactBags() const { return compactBags_; }
|
2026-03-17 08:18:46 -07:00
|
|
|
void setShowKeyring(bool show) { showKeyring_ = show; }
|
|
|
|
|
bool isShowKeyring() const { return showKeyring_; }
|
2026-02-19 01:50:50 -08:00
|
|
|
bool isBackpackOpen() const { return backpackOpen_; }
|
|
|
|
|
bool isBagOpen(int idx) const { return idx >= 0 && idx < 4 ? bagOpen_[idx] : false; }
|
2026-02-13 22:51:49 -08:00
|
|
|
|
2026-02-06 13:47:03 -08:00
|
|
|
bool isCharacterOpen() const { return characterOpen; }
|
|
|
|
|
void toggleCharacter() { characterOpen = !characterOpen; }
|
|
|
|
|
void setCharacterOpen(bool o) { characterOpen = o; }
|
|
|
|
|
|
|
|
|
|
/// Enable vendor mode: right-clicking bag items sells them.
|
|
|
|
|
void setVendorMode(bool enabled, game::GameHandler* handler) {
|
|
|
|
|
vendorMode_ = enabled;
|
|
|
|
|
gameHandler_ = handler;
|
|
|
|
|
}
|
2026-02-06 18:34:45 -08:00
|
|
|
void setGameHandler(game::GameHandler* handler) { gameHandler_ = handler; }
|
2026-02-06 13:47:03 -08:00
|
|
|
|
2026-02-06 14:24:38 -08:00
|
|
|
/// Set asset manager for icon/model loading
|
|
|
|
|
void setAssetManager(pipeline::AssetManager* am) { assetManager_ = am; }
|
|
|
|
|
|
|
|
|
|
/// Store player appearance for character preview
|
|
|
|
|
void setPlayerAppearance(game::Race race, game::Gender gender,
|
|
|
|
|
uint8_t skin, uint8_t face,
|
|
|
|
|
uint8_t hairStyle, uint8_t hairColor,
|
|
|
|
|
uint8_t facialHair);
|
|
|
|
|
|
|
|
|
|
/// Mark the character preview as needing equipment update
|
|
|
|
|
void markPreviewDirty() { previewDirty_ = true; }
|
|
|
|
|
|
|
|
|
|
/// Update the preview animation (call each frame)
|
|
|
|
|
void updatePreview(float deltaTime);
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
/// Returns true if equipment changed since last call, and clears the flag.
|
|
|
|
|
bool consumeEquipmentDirty() { bool d = equipmentDirty; equipmentDirty = false; return d; }
|
2026-02-05 14:55:42 -08:00
|
|
|
/// Returns true if any inventory slot changed since last call, and clears the flag.
|
|
|
|
|
bool consumeInventoryDirty() { bool d = inventoryDirty; inventoryDirty = false; return d; }
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
bool open = false;
|
2026-02-06 13:47:03 -08:00
|
|
|
bool characterOpen = false;
|
2026-02-02 12:24:50 -08:00
|
|
|
bool bKeyWasDown = false;
|
2026-02-13 22:51:49 -08:00
|
|
|
bool separateBags_ = true;
|
2026-02-19 01:50:50 -08:00
|
|
|
bool compactBags_ = false;
|
2026-03-17 08:18:46 -07:00
|
|
|
bool showKeyring_ = true;
|
2026-02-13 22:51:49 -08:00
|
|
|
bool backpackOpen_ = false;
|
|
|
|
|
std::array<bool, 4> bagOpen_{};
|
2026-02-06 13:47:03 -08:00
|
|
|
bool cKeyWasDown = false;
|
2026-02-02 12:24:50 -08:00
|
|
|
bool equipmentDirty = false;
|
2026-02-05 14:55:42 -08:00
|
|
|
bool inventoryDirty = false;
|
2026-02-02 12:24:50 -08:00
|
|
|
|
2026-02-06 13:47:03 -08:00
|
|
|
// Vendor sell mode
|
|
|
|
|
bool vendorMode_ = false;
|
|
|
|
|
game::GameHandler* gameHandler_ = nullptr;
|
|
|
|
|
|
2026-02-06 14:24:38 -08:00
|
|
|
// Asset manager for icons and preview
|
|
|
|
|
pipeline::AssetManager* assetManager_ = nullptr;
|
|
|
|
|
|
|
|
|
|
// Item icon cache: displayInfoId -> GL texture
|
2026-02-22 03:32:08 -08:00
|
|
|
std::unordered_map<uint32_t, VkDescriptorSet> iconCache_;
|
2026-02-06 15:41:29 -08:00
|
|
|
public:
|
2026-02-22 03:32:08 -08:00
|
|
|
VkDescriptorSet getItemIcon(uint32_t displayInfoId);
|
2026-03-12 05:20:44 -07:00
|
|
|
void renderItemTooltip(const game::ItemQueryResponseData& info, const game::Inventory* inventory = nullptr);
|
2026-02-06 15:41:29 -08:00
|
|
|
private:
|
2026-02-06 14:24:38 -08:00
|
|
|
|
|
|
|
|
// Character model preview
|
|
|
|
|
std::unique_ptr<rendering::CharacterPreview> charPreview_;
|
|
|
|
|
bool previewInitialized_ = false;
|
|
|
|
|
bool previewDirty_ = false;
|
|
|
|
|
|
|
|
|
|
// Stored player appearance for preview
|
|
|
|
|
game::Race playerRace_ = game::Race::HUMAN;
|
|
|
|
|
game::Gender playerGender_ = game::Gender::MALE;
|
|
|
|
|
uint8_t playerSkin_ = 0;
|
|
|
|
|
uint8_t playerFace_ = 0;
|
|
|
|
|
uint8_t playerHairStyle_ = 0;
|
|
|
|
|
uint8_t playerHairColor_ = 0;
|
|
|
|
|
uint8_t playerFacialHair_ = 0;
|
|
|
|
|
|
|
|
|
|
void initPreview();
|
|
|
|
|
void updatePreviewEquipment(game::Inventory& inventory);
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
// Drag-and-drop held item state
|
|
|
|
|
bool holdingItem = false;
|
|
|
|
|
game::ItemDef heldItem;
|
2026-02-26 13:38:29 -08:00
|
|
|
enum class HeldSource { NONE, BACKPACK, BAG, EQUIPMENT, BANK, BANK_BAG, BANK_BAG_EQUIP };
|
2026-02-02 12:24:50 -08:00
|
|
|
HeldSource heldSource = HeldSource::NONE;
|
|
|
|
|
int heldBackpackIndex = -1;
|
2026-02-17 01:00:04 -08:00
|
|
|
int heldBagIndex = -1;
|
|
|
|
|
int heldBagSlotIndex = -1;
|
2026-02-26 13:38:29 -08:00
|
|
|
int heldBankIndex = -1;
|
|
|
|
|
int heldBankBagIndex = -1;
|
|
|
|
|
int heldBankBagSlotIndex = -1;
|
2026-02-02 12:24:50 -08:00
|
|
|
game::EquipSlot heldEquipSlot = game::EquipSlot::NUM_SLOTS;
|
|
|
|
|
|
2026-02-26 00:59:07 -08:00
|
|
|
// Slot rendering with interaction support
|
|
|
|
|
enum class SlotKind { BACKPACK, EQUIPMENT };
|
|
|
|
|
|
|
|
|
|
// Click-and-hold pickup tracking
|
|
|
|
|
bool pickupPending_ = false;
|
|
|
|
|
float pickupPressTime_ = 0.0f;
|
|
|
|
|
SlotKind pickupSlotKind_ = SlotKind::BACKPACK;
|
|
|
|
|
int pickupBackpackIndex_ = -1;
|
|
|
|
|
int pickupBagIndex_ = -1;
|
|
|
|
|
int pickupBagSlotIndex_ = -1;
|
|
|
|
|
game::EquipSlot pickupEquipSlot_ = game::EquipSlot::NUM_SLOTS;
|
2026-02-26 13:38:29 -08:00
|
|
|
static constexpr float kPickupHoldThreshold = 0.10f; // seconds
|
2026-02-26 00:59:07 -08:00
|
|
|
|
2026-02-13 22:51:49 -08:00
|
|
|
void renderSeparateBags(game::Inventory& inventory, uint64_t moneyCopper);
|
|
|
|
|
void renderAggregateBags(game::Inventory& inventory, uint64_t moneyCopper);
|
|
|
|
|
void renderBagWindow(const char* title, bool& isOpen, game::Inventory& inventory,
|
|
|
|
|
int bagIndex, float defaultX, float defaultY, uint64_t moneyCopper);
|
2026-02-02 12:24:50 -08:00
|
|
|
void renderEquipmentPanel(game::Inventory& inventory);
|
2026-02-19 01:50:50 -08:00
|
|
|
void renderBackpackPanel(game::Inventory& inventory, bool collapseEmptySections = false);
|
2026-03-10 23:08:15 -07:00
|
|
|
void renderStatsPanel(game::Inventory& inventory, uint32_t playerLevel, int32_t serverArmor = 0,
|
2026-03-13 08:35:18 -07:00
|
|
|
const int32_t* serverStats = nullptr, const int32_t* serverResists = nullptr,
|
|
|
|
|
const game::GameHandler* gh = nullptr);
|
2026-03-09 14:52:13 -07:00
|
|
|
void renderReputationPanel(game::GameHandler& gameHandler);
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
void renderItemSlot(game::Inventory& inventory, const game::ItemSlot& slot,
|
|
|
|
|
float size, const char* label,
|
|
|
|
|
SlotKind kind, int backpackIndex,
|
2026-02-17 01:00:04 -08:00
|
|
|
game::EquipSlot equipSlot,
|
|
|
|
|
int bagIndex = -1, int bagSlotIndex = -1);
|
2026-02-19 01:50:50 -08:00
|
|
|
void renderItemTooltip(const game::ItemDef& item, const game::Inventory* inventory = nullptr);
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
// Held item helpers
|
|
|
|
|
void pickupFromBackpack(game::Inventory& inv, int index);
|
2026-02-17 01:00:04 -08:00
|
|
|
void pickupFromBag(game::Inventory& inv, int bagIndex, int slotIndex);
|
2026-02-02 12:24:50 -08:00
|
|
|
void pickupFromEquipment(game::Inventory& inv, game::EquipSlot slot);
|
|
|
|
|
void placeInBackpack(game::Inventory& inv, int index);
|
2026-02-17 01:00:04 -08:00
|
|
|
void placeInBag(game::Inventory& inv, int bagIndex, int slotIndex);
|
2026-02-02 12:24:50 -08:00
|
|
|
void placeInEquipment(game::Inventory& inv, game::EquipSlot slot);
|
|
|
|
|
void cancelPickup(game::Inventory& inv);
|
|
|
|
|
game::EquipSlot getEquipSlotForType(uint8_t inventoryType, game::Inventory& inv);
|
|
|
|
|
void renderHeldItem();
|
2026-02-19 01:50:50 -08:00
|
|
|
bool bagHasAnyItems(const game::Inventory& inventory, int bagIndex) const;
|
2026-02-02 12:24:50 -08:00
|
|
|
|
2026-03-11 23:32:43 -07:00
|
|
|
// Drop confirmation (drag-outside-window destroy)
|
Fix camera orbit, deselect, chat formatting, loot/vendor bugs, critter hostility, and character screen
Smooth idle camera orbit without jump at loop boundary, click empty space to
deselect target, auto-target when attacked, fix critter hostility so neutral
factions aren't flagged red, add armor/stats to item templates, fix loot
iterator invalidation, show item template names as fallback, position drop
confirmation at cursor, remove [SYSTEM] chat prefix, show NPC names in monster
say/yell, and prevent auto-login on character select screen.
2026-02-06 16:40:44 -08:00
|
|
|
bool dropConfirmOpen_ = false;
|
|
|
|
|
int dropBackpackIndex_ = -1;
|
|
|
|
|
std::string dropItemName_;
|
|
|
|
|
|
2026-03-11 23:32:43 -07:00
|
|
|
// Destroy confirmation (Shift+right-click destroy)
|
|
|
|
|
bool destroyConfirmOpen_ = false;
|
|
|
|
|
uint8_t destroyBag_ = 0xFF;
|
|
|
|
|
uint8_t destroySlot_ = 0;
|
|
|
|
|
uint8_t destroyCount_ = 1;
|
|
|
|
|
std::string destroyItemName_;
|
|
|
|
|
|
2026-03-11 21:09:42 -07:00
|
|
|
// Pending chat item link from shift-click
|
|
|
|
|
std::string pendingChatItemLink_;
|
|
|
|
|
|
2026-02-06 16:04:25 -08:00
|
|
|
public:
|
2026-02-02 12:24:50 -08:00
|
|
|
static ImVec4 getQualityColor(game::ItemQuality quality);
|
2026-02-06 19:17:35 -08:00
|
|
|
|
|
|
|
|
/// Returns true if the user is currently holding an item (pickup cursor).
|
|
|
|
|
bool isHoldingItem() const { return holdingItem; }
|
|
|
|
|
/// Returns the item being held (only valid when isHoldingItem() is true).
|
|
|
|
|
const game::ItemDef& getHeldItem() const { return heldItem; }
|
2026-02-20 17:41:19 -08:00
|
|
|
/// Begin pickup from an equipment slot (e.g., bag bar slot) into held cursor.
|
|
|
|
|
bool beginPickupFromEquipSlot(game::Inventory& inv, game::EquipSlot slot);
|
2026-02-06 19:17:35 -08:00
|
|
|
/// Cancel the pickup, returning the item to its original slot.
|
|
|
|
|
void returnHeldItem(game::Inventory& inv) { cancelPickup(inv); }
|
2026-02-20 17:41:19 -08:00
|
|
|
/// Drop the currently held item into a specific equipment slot.
|
|
|
|
|
/// Returns true if the drop was accepted and consumed.
|
|
|
|
|
bool dropHeldItemToEquipSlot(game::Inventory& inv, game::EquipSlot slot);
|
2026-03-11 21:09:42 -07:00
|
|
|
/// Returns a WoW item link string if the user shift-clicked a bag item, then clears it.
|
|
|
|
|
std::string getAndClearPendingChatLink() {
|
|
|
|
|
std::string out = std::move(pendingChatItemLink_);
|
|
|
|
|
pendingChatItemLink_.clear();
|
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-25 13:54:47 -08:00
|
|
|
/// Drop the currently held item into a bank slot via CMSG_SWAP_ITEM.
|
|
|
|
|
void dropIntoBankSlot(game::GameHandler& gh, uint8_t dstBag, uint8_t dstSlot);
|
2026-02-26 13:38:29 -08:00
|
|
|
/// Pick up an item from main bank slot (click-and-hold from bank window).
|
|
|
|
|
void pickupFromBank(game::Inventory& inv, int bankIndex);
|
|
|
|
|
/// Pick up an item from a bank bag slot (click-and-hold from bank window).
|
|
|
|
|
void pickupFromBankBag(game::Inventory& inv, int bagIndex, int slotIndex);
|
|
|
|
|
/// Pick up a bag from a bank bag equip slot (click-and-hold from bank window).
|
|
|
|
|
void pickupFromBankBagEquip(game::Inventory& inv, int bagIndex);
|
2026-02-02 12:24:50 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
} // namespace ui
|
|
|
|
|
} // namespace wowee
|