mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-02 07:43:51 +00:00
Add spellbook, fix WMO floor clipping, and polish UI/visuals
- Add spellbook screen (P key) with Spell.dbc name lookup and action bar assignment - Default Attack and Hearthstone spells available in single player - Fix WMO floor clipping (gryphon roost) by tightening ceiling rejection threshold - Darken ocean water, increase wave motion and opacity - Add M2 model distance fade-in to prevent pop-in - Reposition chat window, add slash/enter key focus - Remove debug key commands (keep only F1 perf HUD, N minimap) - Performance: return chat history by const ref, use deque for O(1) pop_front
This commit is contained in:
parent
c49bb58e47
commit
4bc5064515
17 changed files with 486 additions and 431 deletions
|
|
@ -8,8 +8,6 @@
|
|||
#include "pipeline/dbc_loader.hpp"
|
||||
#include "core/logger.hpp"
|
||||
#include <imgui.h>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <cmath>
|
||||
#include <unordered_set>
|
||||
|
||||
|
|
@ -86,6 +84,9 @@ void GameScreen::render(game::GameHandler& gameHandler) {
|
|||
renderGossipWindow(gameHandler);
|
||||
renderVendorWindow(gameHandler);
|
||||
|
||||
// Spellbook (P key toggle handled inside)
|
||||
spellbookScreen.render(gameHandler, core::Application::getInstance().getAssetManager());
|
||||
|
||||
// Inventory (B key toggle handled inside)
|
||||
inventoryScreen.render(gameHandler.getInventory());
|
||||
|
||||
|
|
@ -200,9 +201,9 @@ void GameScreen::renderEntityList(game::GameHandler& gameHandler) {
|
|||
|
||||
// GUID
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
std::stringstream guidStr;
|
||||
guidStr << "0x" << std::hex << std::uppercase << std::setfill('0') << std::setw(16) << guid;
|
||||
ImGui::Text("%s", guidStr.str().c_str());
|
||||
char guidStr[24];
|
||||
snprintf(guidStr, sizeof(guidStr), "0x%016llX", (unsigned long long)guid);
|
||||
ImGui::Text("%s", guidStr);
|
||||
|
||||
// Type
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
|
|
@ -258,9 +259,16 @@ void GameScreen::renderEntityList(game::GameHandler& gameHandler) {
|
|||
}
|
||||
|
||||
void GameScreen::renderChatWindow(game::GameHandler& gameHandler) {
|
||||
ImGui::SetNextWindowSize(ImVec2(600, 300), ImGuiCond_FirstUseEver);
|
||||
ImGui::SetNextWindowPos(ImVec2(520, 390), ImGuiCond_FirstUseEver);
|
||||
ImGui::Begin("Chat", nullptr, ImGuiWindowFlags_NoCollapse);
|
||||
auto* window = core::Application::getInstance().getWindow();
|
||||
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
|
||||
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
|
||||
float chatW = std::min(500.0f, screenW * 0.4f);
|
||||
float chatH = 220.0f;
|
||||
float chatX = 8.0f;
|
||||
float chatY = screenH - chatH - 80.0f; // Above action bar
|
||||
ImGui::SetNextWindowSize(ImVec2(chatW, chatH), ImGuiCond_Always);
|
||||
ImGui::SetNextWindowPos(ImVec2(chatX, chatY), ImGuiCond_Always);
|
||||
ImGui::Begin("Chat", nullptr, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove);
|
||||
|
||||
// Chat history
|
||||
const auto& chatHistory = gameHandler.getChatHistory();
|
||||
|
|
@ -271,21 +279,13 @@ void GameScreen::renderChatWindow(game::GameHandler& gameHandler) {
|
|||
ImVec4 color = getChatTypeColor(msg.type);
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, color);
|
||||
|
||||
std::stringstream ss;
|
||||
|
||||
if (msg.type == game::ChatType::TEXT_EMOTE) {
|
||||
ss << "You " << msg.message;
|
||||
ImGui::TextWrapped("You %s", msg.message.c_str());
|
||||
} else if (!msg.senderName.empty()) {
|
||||
ImGui::TextWrapped("[%s] %s: %s", getChatTypeName(msg.type), msg.senderName.c_str(), msg.message.c_str());
|
||||
} else {
|
||||
ss << "[" << getChatTypeName(msg.type) << "] ";
|
||||
|
||||
if (!msg.senderName.empty()) {
|
||||
ss << msg.senderName << ": ";
|
||||
}
|
||||
|
||||
ss << msg.message;
|
||||
ImGui::TextWrapped("[%s] %s", getChatTypeName(msg.type), msg.message.c_str());
|
||||
}
|
||||
|
||||
ImGui::TextWrapped("%s", ss.str().c_str());
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
|
|
@ -379,6 +379,18 @@ void GameScreen::processTargetInput(game::GameHandler& gameHandler) {
|
|||
}
|
||||
}
|
||||
|
||||
// Slash key: focus chat input
|
||||
if (!io.WantCaptureKeyboard && input.isKeyJustPressed(SDL_SCANCODE_SLASH)) {
|
||||
refocusChatInput = true;
|
||||
chatInputBuffer[0] = '/';
|
||||
chatInputBuffer[1] = '\0';
|
||||
}
|
||||
|
||||
// Enter key: focus chat input (empty)
|
||||
if (!io.WantCaptureKeyboard && input.isKeyJustPressed(SDL_SCANCODE_RETURN)) {
|
||||
refocusChatInput = true;
|
||||
}
|
||||
|
||||
// Left-click targeting (when mouse not captured by UI)
|
||||
if (!io.WantCaptureMouse && input.isMouseButtonJustPressed(SDL_BUTTON_LEFT)) {
|
||||
auto* renderer = core::Application::getInstance().getRenderer();
|
||||
|
|
|
|||
195
src/ui/spellbook_screen.cpp
Normal file
195
src/ui/spellbook_screen.cpp
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
#include "ui/spellbook_screen.hpp"
|
||||
#include "core/input.hpp"
|
||||
#include "core/application.hpp"
|
||||
#include "pipeline/asset_manager.hpp"
|
||||
#include "pipeline/dbc_loader.hpp"
|
||||
#include "core/logger.hpp"
|
||||
#include <algorithm>
|
||||
|
||||
namespace wowee { namespace ui {
|
||||
|
||||
void SpellbookScreen::loadSpellDBC(pipeline::AssetManager* assetManager) {
|
||||
if (dbcLoadAttempted) return;
|
||||
dbcLoadAttempted = true;
|
||||
|
||||
if (!assetManager || !assetManager->isInitialized()) return;
|
||||
|
||||
auto dbc = assetManager->loadDBC("Spell.dbc");
|
||||
if (!dbc || !dbc->isLoaded()) {
|
||||
LOG_WARNING("Spellbook: Could not load Spell.dbc");
|
||||
return;
|
||||
}
|
||||
|
||||
// WoW 3.3.5a Spell.dbc: field 0 = SpellID, field 136 = SpellName_enUS
|
||||
// Validate field count to determine name field index
|
||||
uint32_t fieldCount = dbc->getFieldCount();
|
||||
uint32_t nameField = 136;
|
||||
|
||||
if (fieldCount < 137) {
|
||||
LOG_WARNING("Spellbook: Spell.dbc has ", fieldCount, " fields, expected 234+");
|
||||
// Try a heuristic: for smaller DBCs, name might be elsewhere
|
||||
if (fieldCount > 10) {
|
||||
nameField = fieldCount > 140 ? 136 : 1;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t count = dbc->getRecordCount();
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
uint32_t spellId = dbc->getUInt32(i, 0);
|
||||
std::string name = dbc->getString(i, nameField);
|
||||
if (!name.empty() && spellId > 0) {
|
||||
spellNames[spellId] = name;
|
||||
}
|
||||
}
|
||||
|
||||
dbcLoaded = true;
|
||||
LOG_INFO("Spellbook: Loaded ", spellNames.size(), " spell names from Spell.dbc");
|
||||
}
|
||||
|
||||
std::string SpellbookScreen::getSpellName(uint32_t spellId) const {
|
||||
auto it = spellNames.find(spellId);
|
||||
if (it != spellNames.end()) {
|
||||
return it->second;
|
||||
}
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "Spell #%u", spellId);
|
||||
return buf;
|
||||
}
|
||||
|
||||
void SpellbookScreen::render(game::GameHandler& gameHandler, pipeline::AssetManager* assetManager) {
|
||||
// P key toggle (edge-triggered)
|
||||
bool uiWantsKeyboard = ImGui::GetIO().WantCaptureKeyboard;
|
||||
bool pDown = !uiWantsKeyboard && core::Input::getInstance().isKeyPressed(SDL_SCANCODE_P);
|
||||
if (pDown && !pKeyWasDown) {
|
||||
open = !open;
|
||||
}
|
||||
pKeyWasDown = pDown;
|
||||
|
||||
if (!open) return;
|
||||
|
||||
// Lazy-load Spell.dbc on first open
|
||||
if (!dbcLoadAttempted) {
|
||||
loadSpellDBC(assetManager);
|
||||
}
|
||||
|
||||
auto* window = core::Application::getInstance().getWindow();
|
||||
float screenW = window ? static_cast<float>(window->getWidth()) : 1280.0f;
|
||||
float screenH = window ? static_cast<float>(window->getHeight()) : 720.0f;
|
||||
|
||||
float bookW = 340.0f;
|
||||
float bookH = std::min(500.0f, screenH - 120.0f);
|
||||
float bookX = screenW - bookW - 10.0f;
|
||||
float bookY = 80.0f;
|
||||
|
||||
ImGui::SetNextWindowPos(ImVec2(bookX, bookY), ImGuiCond_FirstUseEver);
|
||||
ImGui::SetNextWindowSize(ImVec2(bookW, bookH), ImGuiCond_FirstUseEver);
|
||||
|
||||
bool windowOpen = open;
|
||||
if (ImGui::Begin("Spellbook", &windowOpen)) {
|
||||
const auto& spells = gameHandler.getKnownSpells();
|
||||
|
||||
if (spells.empty()) {
|
||||
ImGui::TextDisabled("No spells known.");
|
||||
} else {
|
||||
ImGui::Text("%zu spells known", spells.size());
|
||||
ImGui::Separator();
|
||||
|
||||
// Action bar assignment mode indicator
|
||||
if (assigningSlot >= 0) {
|
||||
ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.3f, 1.0f),
|
||||
"Click a spell to assign to slot %d", assigningSlot + 1);
|
||||
if (ImGui::SmallButton("Cancel")) {
|
||||
assigningSlot = -1;
|
||||
}
|
||||
ImGui::Separator();
|
||||
}
|
||||
|
||||
// Spell list
|
||||
ImGui::BeginChild("SpellList", ImVec2(0, -60), true);
|
||||
|
||||
for (uint32_t spellId : spells) {
|
||||
ImGui::PushID(static_cast<int>(spellId));
|
||||
|
||||
std::string name = getSpellName(spellId);
|
||||
float cd = gameHandler.getSpellCooldown(spellId);
|
||||
bool onCooldown = cd > 0.0f;
|
||||
|
||||
// Color based on state
|
||||
if (onCooldown) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.5f, 0.5f, 0.5f, 1.0f));
|
||||
}
|
||||
|
||||
// Spell entry - clickable
|
||||
char label[256];
|
||||
if (onCooldown) {
|
||||
snprintf(label, sizeof(label), "%s (%.1fs)", name.c_str(), cd);
|
||||
} else {
|
||||
snprintf(label, sizeof(label), "%s", name.c_str());
|
||||
}
|
||||
|
||||
if (ImGui::Selectable(label, false, ImGuiSelectableFlags_AllowDoubleClick)) {
|
||||
if (assigningSlot >= 0) {
|
||||
// Assign to action bar slot
|
||||
gameHandler.setActionBarSlot(assigningSlot,
|
||||
game::ActionBarSlot::SPELL, spellId);
|
||||
assigningSlot = -1;
|
||||
} else if (ImGui::IsMouseDoubleClicked(0)) {
|
||||
// Double-click to cast
|
||||
uint64_t target = gameHandler.hasTarget() ? gameHandler.getTargetGuid() : 0;
|
||||
gameHandler.castSpell(spellId, target);
|
||||
}
|
||||
}
|
||||
|
||||
// Tooltip with spell ID
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::Text("%s", name.c_str());
|
||||
ImGui::TextDisabled("Spell ID: %u", spellId);
|
||||
if (!onCooldown) {
|
||||
ImGui::TextDisabled("Double-click to cast");
|
||||
ImGui::TextDisabled("Use action bar buttons below to assign");
|
||||
}
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
if (onCooldown) {
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
|
||||
// Action bar quick-assign buttons
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Assign to:");
|
||||
ImGui::SameLine();
|
||||
static const char* slotLabels[] = {"1","2","3","4","5","6","7","8","9","0","-","="};
|
||||
for (int i = 0; i < 12; ++i) {
|
||||
if (i > 0) ImGui::SameLine(0, 2);
|
||||
ImGui::PushID(100 + i);
|
||||
bool isAssigning = (assigningSlot == i);
|
||||
if (isAssigning) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.8f, 0.6f, 0.2f, 1.0f));
|
||||
}
|
||||
if (ImGui::SmallButton(slotLabels[i])) {
|
||||
assigningSlot = isAssigning ? -1 : i;
|
||||
}
|
||||
if (isAssigning) {
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
if (!windowOpen) {
|
||||
open = false;
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace wowee::ui
|
||||
Loading…
Add table
Add a link
Reference in a new issue