mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-17 17:43:52 +00:00
Add separate draggable bag windows, fix dismount and player equipment
Bags are now individual draggable ImGui windows (backpack + each equipped bag) with per-bag toggle from the bag bar. B key opens/closes all. A settings toggle under Gameplay lets users switch back to the original aggregate single-window mode. Window width adapts to bag item name length. Fix dismount by clearing local mount state immediately (optimistic) instead of waiting for server confirmation, and allow buff bar right-click dismount regardless of the aura's buff flag. Fix other players appearing naked by queuing them for auto-inspect when the visible item field layout hasn't been detected yet.
This commit is contained in:
parent
89ccb0720a
commit
85864ab05b
5 changed files with 264 additions and 62 deletions
|
|
@ -82,6 +82,7 @@ private:
|
||||||
int pendingUiOpacity = 65;
|
int pendingUiOpacity = 65;
|
||||||
bool pendingMinimapRotate = false;
|
bool pendingMinimapRotate = false;
|
||||||
bool pendingMinimapSquare = false;
|
bool pendingMinimapSquare = false;
|
||||||
|
bool pendingSeparateBags = true;
|
||||||
|
|
||||||
// UI element transparency (0.0 = fully transparent, 1.0 = fully opaque)
|
// UI element transparency (0.0 = fully transparent, 1.0 = fully opaque)
|
||||||
float uiOpacity_ = 0.65f;
|
float uiOpacity_ = 0.65f;
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
#include "game/world_packets.hpp"
|
#include "game/world_packets.hpp"
|
||||||
#include <GL/glew.h>
|
#include <GL/glew.h>
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
#include <array>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
@ -29,6 +30,14 @@ public:
|
||||||
void toggle() { open = !open; }
|
void toggle() { open = !open; }
|
||||||
void setOpen(bool o) { open = o; }
|
void setOpen(bool o) { open = o; }
|
||||||
|
|
||||||
|
// 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_; }
|
||||||
|
|
||||||
bool isCharacterOpen() const { return characterOpen; }
|
bool isCharacterOpen() const { return characterOpen; }
|
||||||
void toggleCharacter() { characterOpen = !characterOpen; }
|
void toggleCharacter() { characterOpen = !characterOpen; }
|
||||||
void setCharacterOpen(bool o) { characterOpen = o; }
|
void setCharacterOpen(bool o) { characterOpen = o; }
|
||||||
|
|
@ -64,6 +73,9 @@ private:
|
||||||
bool open = false;
|
bool open = false;
|
||||||
bool characterOpen = false;
|
bool characterOpen = false;
|
||||||
bool bKeyWasDown = false;
|
bool bKeyWasDown = false;
|
||||||
|
bool separateBags_ = true;
|
||||||
|
bool backpackOpen_ = false;
|
||||||
|
std::array<bool, 4> bagOpen_{};
|
||||||
bool cKeyWasDown = false;
|
bool cKeyWasDown = false;
|
||||||
bool equipmentDirty = false;
|
bool equipmentDirty = false;
|
||||||
bool inventoryDirty = false;
|
bool inventoryDirty = false;
|
||||||
|
|
@ -106,6 +118,10 @@ private:
|
||||||
int heldBackpackIndex = -1;
|
int heldBackpackIndex = -1;
|
||||||
game::EquipSlot heldEquipSlot = game::EquipSlot::NUM_SLOTS;
|
game::EquipSlot heldEquipSlot = game::EquipSlot::NUM_SLOTS;
|
||||||
|
|
||||||
|
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);
|
||||||
void renderEquipmentPanel(game::Inventory& inventory);
|
void renderEquipmentPanel(game::Inventory& inventory);
|
||||||
void renderBackpackPanel(game::Inventory& inventory);
|
void renderBackpackPanel(game::Inventory& inventory);
|
||||||
void renderStatsPanel(game::Inventory& inventory, uint32_t playerLevel);
|
void renderStatsPanel(game::Inventory& inventory, uint32_t playerLevel);
|
||||||
|
|
|
||||||
|
|
@ -5539,7 +5539,13 @@ void GameHandler::maybeDetectVisibleItemLayout() {
|
||||||
|
|
||||||
void GameHandler::updateOtherPlayerVisibleItems(uint64_t guid, const std::map<uint16_t, uint32_t>& fields) {
|
void GameHandler::updateOtherPlayerVisibleItems(uint64_t guid, const std::map<uint16_t, uint32_t>& fields) {
|
||||||
if (guid == 0 || guid == playerGuid) return;
|
if (guid == 0 || guid == playerGuid) return;
|
||||||
if (visibleItemEntryBase_ < 0 || visibleItemStride_ <= 0) return;
|
if (visibleItemEntryBase_ < 0 || visibleItemStride_ <= 0) {
|
||||||
|
// Layout not detected yet — queue this player for inspect as fallback.
|
||||||
|
if (socket && state == WorldState::IN_WORLD) {
|
||||||
|
pendingAutoInspect_.insert(guid);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
std::array<uint32_t, 19> newEntries{};
|
std::array<uint32_t, 19> newEntries{};
|
||||||
for (int s = 0; s < 19; s++) {
|
for (int s = 0; s < 19; s++) {
|
||||||
|
|
@ -5713,18 +5719,16 @@ void GameHandler::handleAttackStop(network::Packet& packet) {
|
||||||
|
|
||||||
void GameHandler::dismount() {
|
void GameHandler::dismount() {
|
||||||
if (!socket) return;
|
if (!socket) return;
|
||||||
if (!isMounted()) {
|
// Clear local mount state immediately (optimistic dismount).
|
||||||
// Local/server desync guard: clear visual mount even when server says unmounted.
|
// Server will confirm via SMSG_UPDATE_OBJECT with mountDisplayId=0.
|
||||||
|
if (currentMountDisplayId_ != 0 || taxiMountActive_) {
|
||||||
if (mountCallback_) {
|
if (mountCallback_) {
|
||||||
mountCallback_(0);
|
mountCallback_(0);
|
||||||
}
|
}
|
||||||
currentMountDisplayId_ = 0;
|
currentMountDisplayId_ = 0;
|
||||||
taxiMountActive_ = false;
|
taxiMountActive_ = false;
|
||||||
taxiMountDisplayId_ = 0;
|
taxiMountDisplayId_ = 0;
|
||||||
onTaxiFlight_ = false;
|
LOG_INFO("Dismount: cleared local mount state");
|
||||||
taxiActivatePending_ = false;
|
|
||||||
taxiClientActive_ = false;
|
|
||||||
LOG_INFO("Dismount desync recovery: force-cleared local mount state");
|
|
||||||
}
|
}
|
||||||
network::Packet pkt(wireOpcode(Opcode::CMSG_CANCEL_MOUNT_AURA));
|
network::Packet pkt(wireOpcode(Opcode::CMSG_CANCEL_MOUNT_AURA));
|
||||||
socket->send(pkt);
|
socket->send(pkt);
|
||||||
|
|
|
||||||
|
|
@ -2860,8 +2860,10 @@ void GameScreen::renderBagBar(game::GameHandler& gameHandler) {
|
||||||
ImVec2(0, 0), ImVec2(1, 1),
|
ImVec2(0, 0), ImVec2(1, 1),
|
||||||
ImVec4(0.1f, 0.1f, 0.1f, 0.9f),
|
ImVec4(0.1f, 0.1f, 0.1f, 0.9f),
|
||||||
ImVec4(1, 1, 1, 1))) {
|
ImVec4(1, 1, 1, 1))) {
|
||||||
// TODO: Open specific bag
|
if (inventoryScreen.isSeparateBags())
|
||||||
inventoryScreen.toggle();
|
inventoryScreen.toggleBag(i);
|
||||||
|
else
|
||||||
|
inventoryScreen.toggle();
|
||||||
}
|
}
|
||||||
if (ImGui::IsItemHovered()) {
|
if (ImGui::IsItemHovered()) {
|
||||||
ImGui::SetTooltip("%s", bagItem.item.name.c_str());
|
ImGui::SetTooltip("%s", bagItem.item.name.c_str());
|
||||||
|
|
@ -2870,7 +2872,7 @@ void GameScreen::renderBagBar(game::GameHandler& gameHandler) {
|
||||||
// Empty bag slot
|
// Empty bag slot
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.15f, 0.15f, 0.15f, 0.8f));
|
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.15f, 0.15f, 0.15f, 0.8f));
|
||||||
if (ImGui::Button("##empty", ImVec2(slotSize, slotSize))) {
|
if (ImGui::Button("##empty", ImVec2(slotSize, slotSize))) {
|
||||||
// Empty slot - maybe show equipment to find a bag?
|
// Empty slot - no bag equipped
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor();
|
ImGui::PopStyleColor();
|
||||||
if (ImGui::IsItemHovered()) {
|
if (ImGui::IsItemHovered()) {
|
||||||
|
|
@ -2902,11 +2904,17 @@ void GameScreen::renderBagBar(game::GameHandler& gameHandler) {
|
||||||
ImVec2(0, 0), ImVec2(1, 1),
|
ImVec2(0, 0), ImVec2(1, 1),
|
||||||
ImVec4(0.1f, 0.1f, 0.1f, 0.9f),
|
ImVec4(0.1f, 0.1f, 0.1f, 0.9f),
|
||||||
ImVec4(1, 1, 1, 1))) {
|
ImVec4(1, 1, 1, 1))) {
|
||||||
inventoryScreen.toggle();
|
if (inventoryScreen.isSeparateBags())
|
||||||
|
inventoryScreen.toggleBackpack();
|
||||||
|
else
|
||||||
|
inventoryScreen.toggle();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (ImGui::Button("B", ImVec2(slotSize, slotSize))) {
|
if (ImGui::Button("B", ImVec2(slotSize, slotSize))) {
|
||||||
inventoryScreen.toggle();
|
if (inventoryScreen.isSeparateBags())
|
||||||
|
inventoryScreen.toggleBackpack();
|
||||||
|
else
|
||||||
|
inventoryScreen.toggle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ImGui::IsItemHovered()) {
|
if (ImGui::IsItemHovered()) {
|
||||||
|
|
@ -3398,10 +3406,10 @@ void GameScreen::renderBuffBar(game::GameHandler& gameHandler) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Right-click to cancel buffs / dismount
|
// Right-click to cancel buffs / dismount
|
||||||
if (ImGui::IsItemClicked(ImGuiMouseButton_Right) && isBuff) {
|
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
|
||||||
if (gameHandler.isMounted()) {
|
if (gameHandler.isMounted()) {
|
||||||
gameHandler.dismount();
|
gameHandler.dismount();
|
||||||
} else {
|
} else if (isBuff) {
|
||||||
gameHandler.cancelAura(aura.spellId);
|
gameHandler.cancelAura(aura.spellId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4981,6 +4989,14 @@ void GameScreen::renderSettingsWindow() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui::Spacing();
|
||||||
|
ImGui::Text("Bags");
|
||||||
|
ImGui::Separator();
|
||||||
|
if (ImGui::Checkbox("Separate Bag Windows", &pendingSeparateBags)) {
|
||||||
|
inventoryScreen.setSeparateBags(pendingSeparateBags);
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
|
|
@ -4991,6 +5007,8 @@ void GameScreen::renderSettingsWindow() {
|
||||||
pendingUiOpacity = 65;
|
pendingUiOpacity = 65;
|
||||||
pendingMinimapRotate = false;
|
pendingMinimapRotate = false;
|
||||||
pendingMinimapSquare = false;
|
pendingMinimapSquare = false;
|
||||||
|
pendingSeparateBags = true;
|
||||||
|
inventoryScreen.setSeparateBags(true);
|
||||||
uiOpacity_ = 0.65f;
|
uiOpacity_ = 0.65f;
|
||||||
minimapRotate_ = false;
|
minimapRotate_ = false;
|
||||||
minimapSquare_ = false;
|
minimapSquare_ = false;
|
||||||
|
|
@ -5438,6 +5456,7 @@ void GameScreen::saveSettings() {
|
||||||
out << "ui_opacity=" << pendingUiOpacity << "\n";
|
out << "ui_opacity=" << pendingUiOpacity << "\n";
|
||||||
out << "minimap_rotate=" << (pendingMinimapRotate ? 1 : 0) << "\n";
|
out << "minimap_rotate=" << (pendingMinimapRotate ? 1 : 0) << "\n";
|
||||||
out << "minimap_square=" << (pendingMinimapSquare ? 1 : 0) << "\n";
|
out << "minimap_square=" << (pendingMinimapSquare ? 1 : 0) << "\n";
|
||||||
|
out << "separate_bags=" << (pendingSeparateBags ? 1 : 0) << "\n";
|
||||||
|
|
||||||
// Audio
|
// Audio
|
||||||
out << "master_volume=" << pendingMasterVolume << "\n";
|
out << "master_volume=" << pendingMasterVolume << "\n";
|
||||||
|
|
@ -5487,6 +5506,9 @@ void GameScreen::loadSettings() {
|
||||||
int v = std::stoi(val);
|
int v = std::stoi(val);
|
||||||
minimapSquare_ = (v != 0);
|
minimapSquare_ = (v != 0);
|
||||||
pendingMinimapSquare = minimapSquare_;
|
pendingMinimapSquare = minimapSquare_;
|
||||||
|
} else if (key == "separate_bags") {
|
||||||
|
pendingSeparateBags = (std::stoi(val) != 0);
|
||||||
|
inventoryScreen.setSeparateBags(pendingSeparateBags);
|
||||||
}
|
}
|
||||||
// Audio
|
// Audio
|
||||||
else if (key == "master_volume") pendingMasterVolume = std::clamp(std::stoi(val), 0, 100);
|
else if (key == "master_volume") pendingMasterVolume = std::clamp(std::stoi(val), 0, 100);
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,9 @@
|
||||||
#include "core/logger.hpp"
|
#include "core/logger.hpp"
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
|
#include <algorithm>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|
||||||
namespace wowee {
|
namespace wowee {
|
||||||
|
|
@ -529,13 +531,30 @@ void InventoryScreen::renderHeldItem() {
|
||||||
// Bags window (B key) — bottom of screen, no equipment panel
|
// Bags window (B key) — bottom of screen, no equipment panel
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
|
void InventoryScreen::toggleBackpack() {
|
||||||
|
backpackOpen_ = !backpackOpen_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InventoryScreen::toggleBag(int idx) {
|
||||||
|
if (idx >= 0 && idx < 4)
|
||||||
|
bagOpen_[idx] = !bagOpen_[idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
void InventoryScreen::openAllBags() {
|
||||||
|
backpackOpen_ = true;
|
||||||
|
for (auto& b : bagOpen_) b = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InventoryScreen::closeAllBags() {
|
||||||
|
backpackOpen_ = false;
|
||||||
|
for (auto& b : bagOpen_) b = false;
|
||||||
|
}
|
||||||
|
|
||||||
void InventoryScreen::render(game::Inventory& inventory, uint64_t moneyCopper) {
|
void InventoryScreen::render(game::Inventory& inventory, uint64_t moneyCopper) {
|
||||||
// B key toggle (edge-triggered)
|
// B key toggle (edge-triggered)
|
||||||
bool uiWantsKeyboard = ImGui::GetIO().WantCaptureKeyboard;
|
bool uiWantsKeyboard = ImGui::GetIO().WantCaptureKeyboard;
|
||||||
bool bDown = !uiWantsKeyboard && core::Input::getInstance().isKeyPressed(SDL_SCANCODE_B);
|
bool bDown = !uiWantsKeyboard && core::Input::getInstance().isKeyPressed(SDL_SCANCODE_B);
|
||||||
if (bDown && !bKeyWasDown) {
|
bool bToggled = bDown && !bKeyWasDown;
|
||||||
open = !open;
|
|
||||||
}
|
|
||||||
bKeyWasDown = bDown;
|
bKeyWasDown = bDown;
|
||||||
|
|
||||||
// C key toggle for character screen (edge-triggered)
|
// C key toggle for character screen (edge-triggered)
|
||||||
|
|
@ -545,6 +564,18 @@ void InventoryScreen::render(game::Inventory& inventory, uint64_t moneyCopper) {
|
||||||
}
|
}
|
||||||
cKeyWasDown = cDown;
|
cKeyWasDown = cDown;
|
||||||
|
|
||||||
|
if (separateBags_) {
|
||||||
|
if (bToggled) {
|
||||||
|
bool anyOpen = backpackOpen_;
|
||||||
|
for (auto b : bagOpen_) anyOpen |= b;
|
||||||
|
if (anyOpen) closeAllBags();
|
||||||
|
else openAllBags();
|
||||||
|
}
|
||||||
|
open = backpackOpen_ || std::any_of(bagOpen_.begin(), bagOpen_.end(), [](bool b){ return b; });
|
||||||
|
} else {
|
||||||
|
if (bToggled) open = !open;
|
||||||
|
}
|
||||||
|
|
||||||
if (!open) {
|
if (!open) {
|
||||||
if (holdingItem) cancelPickup(inventory);
|
if (holdingItem) cancelPickup(inventory);
|
||||||
return;
|
return;
|
||||||
|
|
@ -560,53 +591,12 @@ void InventoryScreen::render(game::Inventory& inventory, uint64_t moneyCopper) {
|
||||||
cancelPickup(inventory);
|
cancelPickup(inventory);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGuiIO& io = ImGui::GetIO();
|
if (separateBags_) {
|
||||||
float screenW = io.DisplaySize.x;
|
renderSeparateBags(inventory, moneyCopper);
|
||||||
float screenH = io.DisplaySize.y;
|
} else {
|
||||||
|
renderAggregateBags(inventory, moneyCopper);
|
||||||
// Calculate bag window size
|
|
||||||
constexpr float slotSize = 40.0f;
|
|
||||||
constexpr int columns = 4;
|
|
||||||
int rows = (inventory.getBackpackSize() + columns - 1) / columns;
|
|
||||||
float bagContentH = rows * (slotSize + 4.0f) + 40.0f; // slots + header + money
|
|
||||||
|
|
||||||
// Check for extra bags and add space
|
|
||||||
for (int bag = 0; bag < game::Inventory::NUM_BAG_SLOTS; bag++) {
|
|
||||||
int bagSize = inventory.getBagSize(bag);
|
|
||||||
if (bagSize <= 0) continue;
|
|
||||||
int bagRows = (bagSize + columns - 1) / columns;
|
|
||||||
bagContentH += bagRows * (slotSize + 4.0f) + 30.0f; // slots + header
|
|
||||||
}
|
}
|
||||||
|
|
||||||
float windowW = columns * (slotSize + 4.0f) + 30.0f;
|
|
||||||
float windowH = bagContentH + 50.0f; // padding
|
|
||||||
|
|
||||||
// Position at bottom-right of screen
|
|
||||||
float posX = screenW - windowW - 10.0f;
|
|
||||||
float posY = screenH - windowH - 60.0f; // above action bar area
|
|
||||||
|
|
||||||
ImGui::SetNextWindowPos(ImVec2(posX, posY), ImGuiCond_Always);
|
|
||||||
ImGui::SetNextWindowSize(ImVec2(windowW, windowH), ImGuiCond_Always);
|
|
||||||
|
|
||||||
ImGuiWindowFlags flags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove;
|
|
||||||
if (!ImGui::Begin("Bags", &open, flags)) {
|
|
||||||
ImGui::End();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderBackpackPanel(inventory);
|
|
||||||
|
|
||||||
// Money display
|
|
||||||
ImGui::Spacing();
|
|
||||||
uint64_t gold = moneyCopper / 10000;
|
|
||||||
uint64_t silver = (moneyCopper / 100) % 100;
|
|
||||||
uint64_t copper = moneyCopper % 100;
|
|
||||||
ImGui::TextColored(ImVec4(1.0f, 0.84f, 0.0f, 1.0f), "%llug %llus %lluc",
|
|
||||||
static_cast<unsigned long long>(gold),
|
|
||||||
static_cast<unsigned long long>(silver),
|
|
||||||
static_cast<unsigned long long>(copper));
|
|
||||||
ImGui::End();
|
|
||||||
|
|
||||||
// Detect held item dropped outside inventory windows → drop confirmation
|
// Detect held item dropped outside inventory windows → drop confirmation
|
||||||
if (holdingItem && heldItem.itemId != 6948 && ImGui::IsMouseReleased(ImGuiMouseButton_Left) &&
|
if (holdingItem && heldItem.itemId != 6948 && ImGui::IsMouseReleased(ImGuiMouseButton_Left) &&
|
||||||
!ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow) &&
|
!ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow) &&
|
||||||
|
|
@ -646,6 +636,175 @@ void InventoryScreen::render(game::Inventory& inventory, uint64_t moneyCopper) {
|
||||||
renderHeldItem();
|
renderHeldItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Aggregate mode — original single-window bags
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void InventoryScreen::renderAggregateBags(game::Inventory& inventory, uint64_t moneyCopper) {
|
||||||
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
|
float screenW = io.DisplaySize.x;
|
||||||
|
float screenH = io.DisplaySize.y;
|
||||||
|
|
||||||
|
constexpr float slotSize = 40.0f;
|
||||||
|
constexpr int columns = 4;
|
||||||
|
int rows = (inventory.getBackpackSize() + columns - 1) / columns;
|
||||||
|
float bagContentH = rows * (slotSize + 4.0f) + 40.0f;
|
||||||
|
|
||||||
|
for (int bag = 0; bag < game::Inventory::NUM_BAG_SLOTS; bag++) {
|
||||||
|
int bagSize = inventory.getBagSize(bag);
|
||||||
|
if (bagSize <= 0) continue;
|
||||||
|
int bagRows = (bagSize + columns - 1) / columns;
|
||||||
|
bagContentH += bagRows * (slotSize + 4.0f) + 30.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float windowW = columns * (slotSize + 4.0f) + 30.0f;
|
||||||
|
float windowH = bagContentH + 50.0f;
|
||||||
|
|
||||||
|
float posX = screenW - windowW - 10.0f;
|
||||||
|
float posY = screenH - windowH - 60.0f;
|
||||||
|
|
||||||
|
ImGui::SetNextWindowPos(ImVec2(posX, posY), ImGuiCond_Always);
|
||||||
|
ImGui::SetNextWindowSize(ImVec2(windowW, windowH), ImGuiCond_Always);
|
||||||
|
|
||||||
|
ImGuiWindowFlags flags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove;
|
||||||
|
if (!ImGui::Begin("Bags", &open, flags)) {
|
||||||
|
ImGui::End();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderBackpackPanel(inventory);
|
||||||
|
|
||||||
|
ImGui::Spacing();
|
||||||
|
uint64_t gold = moneyCopper / 10000;
|
||||||
|
uint64_t silver = (moneyCopper / 100) % 100;
|
||||||
|
uint64_t copper = moneyCopper % 100;
|
||||||
|
ImGui::TextColored(ImVec4(1.0f, 0.84f, 0.0f, 1.0f), "%llug %llus %lluc",
|
||||||
|
static_cast<unsigned long long>(gold),
|
||||||
|
static_cast<unsigned long long>(silver),
|
||||||
|
static_cast<unsigned long long>(copper));
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Separate mode — individual draggable bag windows
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void InventoryScreen::renderSeparateBags(game::Inventory& inventory, uint64_t moneyCopper) {
|
||||||
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
|
float screenW = io.DisplaySize.x;
|
||||||
|
float screenH = io.DisplaySize.y;
|
||||||
|
|
||||||
|
constexpr float slotSize = 40.0f;
|
||||||
|
constexpr int columns = 4;
|
||||||
|
constexpr float baseWindowW = columns * (slotSize + 4.0f) + 30.0f;
|
||||||
|
|
||||||
|
// Backpack window (rightmost, bottom-right)
|
||||||
|
if (backpackOpen_) {
|
||||||
|
int bpRows = (inventory.getBackpackSize() + columns - 1) / columns;
|
||||||
|
float bpH = bpRows * (slotSize + 4.0f) + 80.0f; // header + money + padding
|
||||||
|
float defaultX = screenW - baseWindowW - 10.0f;
|
||||||
|
float defaultY = screenH - bpH - 60.0f;
|
||||||
|
renderBagWindow("Backpack", backpackOpen_, inventory, -1, defaultX, defaultY, moneyCopper);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extra bag windows (stacked to the left of backpack)
|
||||||
|
for (int bag = 0; bag < game::Inventory::NUM_BAG_SLOTS; bag++) {
|
||||||
|
if (!bagOpen_[bag]) continue;
|
||||||
|
int bagSize = inventory.getBagSize(bag);
|
||||||
|
if (bagSize <= 0) {
|
||||||
|
bagOpen_[bag] = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bagRows = (bagSize + columns - 1) / columns;
|
||||||
|
float bagH = bagRows * (slotSize + 4.0f) + 60.0f;
|
||||||
|
float defaultX = screenW - (baseWindowW + 10.0f) * (bag + 2) - 10.0f;
|
||||||
|
float defaultY = screenH - bagH - 60.0f;
|
||||||
|
|
||||||
|
// Build title from equipped bag item name
|
||||||
|
char title[64];
|
||||||
|
game::EquipSlot bagSlot = static_cast<game::EquipSlot>(static_cast<int>(game::EquipSlot::BAG1) + bag);
|
||||||
|
const auto& bagItem = inventory.getEquipSlot(bagSlot);
|
||||||
|
if (!bagItem.empty() && !bagItem.item.name.empty()) {
|
||||||
|
snprintf(title, sizeof(title), "%s##bag%d", bagItem.item.name.c_str(), bag);
|
||||||
|
} else {
|
||||||
|
snprintf(title, sizeof(title), "Bag %d##bag%d", bag + 1, bag);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderBagWindow(title, bagOpen_[bag], inventory, bag, defaultX, defaultY, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update open state based on individual windows
|
||||||
|
open = backpackOpen_ || std::any_of(bagOpen_.begin(), bagOpen_.end(), [](bool b){ return b; });
|
||||||
|
}
|
||||||
|
|
||||||
|
void InventoryScreen::renderBagWindow(const char* title, bool& isOpen,
|
||||||
|
game::Inventory& inventory, int bagIndex,
|
||||||
|
float defaultX, float defaultY, uint64_t moneyCopper) {
|
||||||
|
constexpr float slotSize = 40.0f;
|
||||||
|
constexpr int columns = 4;
|
||||||
|
|
||||||
|
int numSlots = (bagIndex < 0) ? inventory.getBackpackSize() : inventory.getBagSize(bagIndex);
|
||||||
|
if (numSlots <= 0) return;
|
||||||
|
|
||||||
|
int rows = (numSlots + columns - 1) / columns;
|
||||||
|
float contentH = rows * (slotSize + 4.0f) + 10.0f;
|
||||||
|
if (bagIndex < 0) contentH += 25.0f; // money display for backpack
|
||||||
|
float gridW = columns * (slotSize + 4.0f) + 30.0f;
|
||||||
|
// Ensure window is wide enough for the title + close button
|
||||||
|
const char* displayTitle = title;
|
||||||
|
const char* hashPos = strstr(title, "##");
|
||||||
|
float titleW = ImGui::CalcTextSize(displayTitle, hashPos).x + 50.0f; // close button + padding
|
||||||
|
float windowW = std::max(gridW, titleW);
|
||||||
|
float windowH = contentH + 40.0f; // title bar + padding
|
||||||
|
|
||||||
|
ImGui::SetNextWindowPos(ImVec2(defaultX, defaultY), ImGuiCond_FirstUseEver);
|
||||||
|
ImGui::SetNextWindowSize(ImVec2(windowW, windowH), ImGuiCond_Always);
|
||||||
|
|
||||||
|
ImGuiWindowFlags flags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize;
|
||||||
|
if (!ImGui::Begin(title, &isOpen, flags)) {
|
||||||
|
ImGui::End();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render item slots in 4-column grid
|
||||||
|
for (int i = 0; i < numSlots; i++) {
|
||||||
|
if (i % columns != 0) ImGui::SameLine();
|
||||||
|
|
||||||
|
const game::ItemSlot& slot = (bagIndex < 0)
|
||||||
|
? inventory.getBackpackSlot(i)
|
||||||
|
: inventory.getBagSlot(bagIndex, i);
|
||||||
|
|
||||||
|
char id[32];
|
||||||
|
if (bagIndex < 0) {
|
||||||
|
snprintf(id, sizeof(id), "##sbp_%d", i);
|
||||||
|
} else {
|
||||||
|
snprintf(id, sizeof(id), "##sb%d_%d", bagIndex, i);
|
||||||
|
}
|
||||||
|
ImGui::PushID(id);
|
||||||
|
|
||||||
|
// For backpack slots, pass actual backpack index for drag/drop
|
||||||
|
int bpIdx = (bagIndex < 0) ? i : -1;
|
||||||
|
renderItemSlot(inventory, slot, slotSize, nullptr,
|
||||||
|
SlotKind::BACKPACK, bpIdx, game::EquipSlot::NUM_SLOTS);
|
||||||
|
ImGui::PopID();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Money display at bottom of backpack
|
||||||
|
if (bagIndex < 0 && moneyCopper > 0) {
|
||||||
|
ImGui::Spacing();
|
||||||
|
uint64_t gold = moneyCopper / 10000;
|
||||||
|
uint64_t silver = (moneyCopper / 100) % 100;
|
||||||
|
uint64_t copper = moneyCopper % 100;
|
||||||
|
ImGui::TextColored(ImVec4(1.0f, 0.84f, 0.0f, 1.0f), "%llug %llus %lluc",
|
||||||
|
static_cast<unsigned long long>(gold),
|
||||||
|
static_cast<unsigned long long>(silver),
|
||||||
|
static_cast<unsigned long long>(copper));
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Character screen (C key) — equipment + model preview + stats
|
// Character screen (C key) — equipment + model preview + stats
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue