refactor(chat): decompose into modular architecture, add GM commands, fix protocol

- Extract ChatPanel monolith into 15+ focused modules under ui/chat/
  (ChatInput, ChatTabManager, ChatTabCompleter, ChatMarkupParser,
  ChatMarkupRenderer, ChatCommandRegistry, ChatBubbleManager,
  ChatSettings, MacroEvaluator, GameStateAdapter, InputModifierAdapter)
- Split 2700-line chat_panel_commands.cpp into 11 command modules
- Add GM command handling: 190-command data table, dot-prefix interception,
  tab-completion, /gmhelp with category filter
- Fix ChatType enum to match WoW wire protocol (SAY=0x01 not 0x00);
  values 0x00-0x1B shared across Vanilla/TBC/WotLK
- Fix BG_SYSTEM_* values from 82-84 (UB in bitmask shifts) to 0x24-0x26
- Fix infinite Enter key loop after teleport (disable TOGGLE_CHAT repeat,
  add 2-frame input cooldown)
- Add tests: chat_markup_parser, chat_tab_completer, gm_commands,
  macro_evaluator

Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
This commit is contained in:
Pavel Okhlopkov 2026-04-12 14:59:56 +03:00
parent 09c4a9a04a
commit 42f1bb98ea
54 changed files with 7363 additions and 3856 deletions

View file

@ -570,48 +570,52 @@ public:
};
/**
* Chat message types
* Chat message types wire values shared across Vanilla 1.12, TBC 2.4.3, and WotLK 3.3.5a.
* Core types (0x000x1B) are identical in all expansions.
* WotLK adds: ACHIEVEMENT(0x30), GUILD_ACHIEVEMENT(0x31), PARTY_LEADER(0x33).
*/
enum class ChatType : uint8_t {
SAY = 0,
PARTY = 1,
RAID = 2,
GUILD = 3,
OFFICER = 4,
YELL = 5,
WHISPER = 6,
WHISPER_INFORM = 7,
EMOTE = 8,
TEXT_EMOTE = 9,
SYSTEM = 10,
MONSTER_SAY = 11,
MONSTER_YELL = 12,
MONSTER_EMOTE = 13,
CHANNEL = 14,
CHANNEL_JOIN = 15,
CHANNEL_LEAVE = 16,
CHANNEL_LIST = 17,
CHANNEL_NOTICE = 18,
CHANNEL_NOTICE_USER = 19,
AFK = 20,
DND = 21,
IGNORED = 22,
SKILL = 23,
LOOT = 24,
BATTLEGROUND = 25,
BATTLEGROUND_LEADER = 26,
RAID_LEADER = 27,
RAID_WARNING = 28,
ACHIEVEMENT = 29,
GUILD_ACHIEVEMENT = 30,
MONSTER_WHISPER = 42,
RAID_BOSS_WHISPER = 43,
RAID_BOSS_EMOTE = 44,
MONSTER_PARTY = 50,
// BG/Arena system messages (WoW 3.3.5a — no sender, treated as SYSTEM in display)
BG_SYSTEM_NEUTRAL = 82,
BG_SYSTEM_ALLIANCE = 83,
BG_SYSTEM_HORDE = 84
SYSTEM = 0x00,
SAY = 0x01,
PARTY = 0x02,
RAID = 0x03,
GUILD = 0x04,
OFFICER = 0x05,
YELL = 0x06,
WHISPER = 0x07,
WHISPER_FOREIGN = 0x08,
WHISPER_INFORM = 0x09,
EMOTE = 0x0A,
TEXT_EMOTE = 0x0B,
MONSTER_SAY = 0x0C,
MONSTER_PARTY = 0x0D,
MONSTER_YELL = 0x0E,
MONSTER_WHISPER = 0x0F,
MONSTER_EMOTE = 0x10,
CHANNEL = 0x11,
CHANNEL_JOIN = 0x12,
CHANNEL_LEAVE = 0x13,
CHANNEL_LIST = 0x14,
CHANNEL_NOTICE = 0x15,
CHANNEL_NOTICE_USER = 0x16,
AFK = 0x17,
DND = 0x18,
IGNORED = 0x19,
SKILL = 0x1A,
LOOT = 0x1B,
// 0x240x26: BG system messages
BG_SYSTEM_NEUTRAL = 0x24,
BG_SYSTEM_ALLIANCE = 0x25,
BG_SYSTEM_HORDE = 0x26,
RAID_LEADER = 0x27,
RAID_WARNING = 0x28,
RAID_BOSS_EMOTE = 0x29,
RAID_BOSS_WHISPER = 0x2A,
BATTLEGROUND = 0x2C,
BATTLEGROUND_LEADER = 0x2D,
ACHIEVEMENT = 0x30,
GUILD_ACHIEVEMENT = 0x31,
PARTY_LEADER = 0x33,
};
/**

View file

@ -0,0 +1,37 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <string>
#include <unordered_map>
namespace wowee {
namespace ui {
/**
* /castsequence persistent state shared across all macros using the same spell list.
*
* Extracted from chat_panel_commands.cpp static global (Phase 1.5 of chat_panel_ref.md).
* Keyed by the normalized (lowercase, comma-joined) spell sequence string.
*/
class CastSequenceTracker {
public:
struct State {
size_t index = 0;
float lastPressSec = 0.0f;
uint64_t lastTargetGuid = 0;
bool lastInCombat = false;
};
/** Get (or create) the state for a given sequence key. */
State& get(const std::string& seqKey) { return states_[seqKey]; }
/** Reset all tracked sequences. */
void clear() { states_.clear(); }
private:
std::unordered_map<std::string, State> states_;
};
} // namespace ui
} // namespace wowee

View file

@ -0,0 +1,46 @@
#pragma once
#include "ui/ui_services.hpp"
#include <cstdint>
#include <string>
#include <vector>
namespace wowee {
namespace game { class GameHandler; }
namespace ui {
/**
* Manages 3D-projected chat bubbles above entities.
*
* Extracted from ChatPanel (Phase 1.4 of chat_panel_ref.md).
* Owns bubble lifecycle: add, update (tick), render (ImGui overlay).
*/
class ChatBubbleManager {
public:
/** Add or replace a bubble for the given entity. */
void addBubble(uint64_t senderGuid, const std::string& message, bool isYell);
/** Render and tick all active bubbles (projects to screen via camera). */
void render(game::GameHandler& gameHandler, const UIServices& services);
/** Register the chat-bubble callback on GameHandler (call once per session). */
void setupCallback(game::GameHandler& gameHandler);
bool empty() const { return bubbles_.empty(); }
private:
struct ChatBubble {
uint64_t senderGuid = 0;
std::string message;
float timeRemaining = 0.0f;
float totalDuration = 0.0f;
bool isYell = false;
};
std::vector<ChatBubble> bubbles_;
bool callbackSet_ = false;
static constexpr size_t kMaxBubbles = 10;
};
} // namespace ui
} // namespace wowee

View file

@ -0,0 +1,50 @@
// ChatCommandRegistry — command registration + dispatch.
// Replaces the 500-line if/else chain in sendChatMessage() (Phase 3.1).
#pragma once
#include "ui/chat/i_chat_command.hpp"
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
namespace wowee {
namespace ui {
/**
* Registry of all slash commands.
*
* dispatch() looks up the command by alias and calls execute().
* getCompletions() provides tab-completion for /command prefixes.
*/
class ChatCommandRegistry {
public:
/** Register a command (takes ownership). All aliases are mapped. */
void registerCommand(std::unique_ptr<IChatCommand> cmd);
/**
* Dispatch a slash command.
* @param cmdLower lowercase command name (e.g. "cast", "whisper")
* @param ctx context with args, gameHandler, services, etc.
* @return result indicating if handled and whether to clear input
*/
ChatCommandResult dispatch(const std::string& cmdLower, ChatCommandContext& ctx);
/** Get all command aliases matching a prefix (for tab completion). */
std::vector<std::string> getCompletions(const std::string& prefix) const;
/** Get help entries: (alias, helpText) for all registered commands. */
std::vector<std::pair<std::string, std::string>> getHelpEntries() const;
/** Check if a command alias is registered. */
bool hasCommand(const std::string& alias) const;
private:
// alias → raw pointer (non-owning, commands_ owns the objects)
std::unordered_map<std::string, IChatCommand*> commandMap_;
// Ownership of all registered commands
std::vector<std::unique_ptr<IChatCommand>> commands_;
};
} // namespace ui
} // namespace wowee

View file

@ -0,0 +1,37 @@
// Forward declarations for the chat subsystem.
// Include this instead of the full headers when only pointers/references are needed.
// Extracted in Phase 6.6 of chat_panel_ref.md.
#pragma once
namespace wowee {
namespace game { class GameHandler; }
namespace ui {
class ChatPanel;
class InventoryScreen;
class SpellbookScreen;
class QuestLogScreen;
// Chat subsystem types (under include/ui/chat/)
class ChatSettings;
class ChatInput;
class ChatTabManager;
class ChatBubbleManager;
class ChatMarkupParser;
class ChatMarkupRenderer;
class ChatCommandRegistry;
class ChatTabCompleter;
class CastSequenceTracker;
class MacroEvaluator;
class IGameState;
class IModifierState;
class IChatCommand;
struct ChatCommandContext;
struct ChatCommandResult;
struct UIServices;
} // namespace ui
} // namespace wowee

View file

@ -0,0 +1,95 @@
#pragma once
#include <cstring>
#include <string>
#include <vector>
namespace wowee {
namespace ui {
/**
* Chat input state: buffer, whisper target, sent-history, focus management.
*
* Extracted from ChatPanel (Phase 1.2 of chat_panel_ref.md).
* No UI or network dependencies pure state management.
*/
class ChatInput {
public:
// ---- Buffer access ----
char* getBuffer() { return buffer_; }
const char* getBuffer() const { return buffer_; }
size_t getBufferSize() const { return sizeof(buffer_); }
char* getWhisperBuffer() { return whisperBuffer_; }
const char* getWhisperBuffer() const { return whisperBuffer_; }
size_t getWhisperBufferSize() const { return sizeof(whisperBuffer_); }
void clear() { buffer_[0] = '\0'; }
bool isEmpty() const { return buffer_[0] == '\0'; }
std::string getText() const { return std::string(buffer_); }
void setText(const std::string& text) {
strncpy(buffer_, text.c_str(), sizeof(buffer_) - 1);
buffer_[sizeof(buffer_) - 1] = '\0';
}
// ---- Whisper target ----
std::string getWhisperTarget() const { return std::string(whisperBuffer_); }
void setWhisperTarget(const std::string& name) {
strncpy(whisperBuffer_, name.c_str(), sizeof(whisperBuffer_) - 1);
whisperBuffer_[sizeof(whisperBuffer_) - 1] = '\0';
}
// ---- Sent-message history (Up/Down arrow recall) ----
void pushToHistory(const std::string& msg);
std::string historyUp();
std::string historyDown();
void resetHistoryIndex() { historyIdx_ = -1; }
int getHistoryIndex() const { return historyIdx_; }
const std::vector<std::string>& getSentHistory() const { return sentHistory_; }
// ---- Focus state ----
bool isActive() const { return active_; }
void setActive(bool v) { active_ = v; }
bool shouldFocus() const { return focusRequested_; }
void requestFocus() { focusRequested_ = true; }
void clearFocusRequest() { focusRequested_ = false; }
bool shouldMoveCursorToEnd() const { return moveCursorToEnd_; }
void requestMoveCursorToEnd() { moveCursorToEnd_ = true; }
void clearMoveCursorToEnd() { moveCursorToEnd_ = false; }
// ---- Chat type selection ----
int getSelectedChatType() const { return selectedChatType_; }
void setSelectedChatType(int t) { selectedChatType_ = t; }
int getLastChatType() const { return lastChatType_; }
void setLastChatType(int t) { lastChatType_ = t; }
int getSelectedChannelIdx() const { return selectedChannelIdx_; }
void setSelectedChannelIdx(int i) { selectedChannelIdx_ = i; }
// ---- Link insertion (shift-click) ----
void insertLink(const std::string& link);
// ---- Slash key activation ----
void activateSlashInput();
private:
char buffer_[512] = "";
char whisperBuffer_[256] = "";
bool active_ = false;
bool focusRequested_ = false;
bool moveCursorToEnd_ = false;
// Chat type dropdown state
int selectedChatType_ = 0; // 0=SAY .. 10=CHANNEL
int lastChatType_ = 0;
int selectedChannelIdx_ = 0;
// Sent-message history
std::vector<std::string> sentHistory_;
int historyIdx_ = -1;
static constexpr int kMaxHistory = 50;
};
} // namespace ui
} // namespace wowee

View file

@ -0,0 +1,55 @@
#pragma once
#include <imgui.h>
#include <cstdint>
#include <string>
#include <vector>
namespace wowee {
namespace ui {
/**
* Segment types produced by ChatMarkupParser.
*
* Each segment represents a contiguous piece of a chat message
* after WoW markup (|c...|r, |Hitem:...|h[...]|h, URLs) has been decoded.
*/
enum class SegmentType {
Text, // Plain text (render with base message color)
ColoredText, // Text with explicit |cAARRGGBB color
ItemLink, // |Hitem:ID:...|h[Name]|h
SpellLink, // |Hspell:ID:...|h[Name]|h
QuestLink, // |Hquest:ID:LEVEL|h[Name]|h
AchievementLink, // |Hachievement:ID:...|h[Name]|h
Url, // https://... URL
};
/**
* A single parsed segment of a chat message.
*/
struct ChatSegment {
SegmentType type = SegmentType::Text;
std::string text; // display text (or URL)
ImVec4 color = ImVec4(1, 1, 1, 1); // explicit color (for ColoredText / links)
uint32_t id = 0; // itemId / spellId / questId / achievementId
uint32_t extra = 0; // quest level (for QuestLink)
std::string rawLink; // full original markup for shift-click insertion
};
/**
* Parses raw WoW-markup text into a flat list of typed segments.
*
* Extracted from ChatPanel::render() inline lambdas (Phase 2.1 of chat_panel_ref.md).
* Pure logic no ImGui calls, no game-state access. Fully unit-testable.
*/
class ChatMarkupParser {
public:
/** Parse a raw chat message string into ordered segments. */
std::vector<ChatSegment> parse(const std::string& rawMessage) const;
/** Parse |cAARRGGBB color code at given position. */
static ImVec4 parseWowColor(const std::string& text, size_t pos);
};
} // namespace ui
} // namespace wowee

View file

@ -0,0 +1,62 @@
#pragma once
#include "ui/chat/chat_markup_parser.hpp"
#include "ui/ui_services.hpp"
#include <vulkan/vulkan.h>
#include <functional>
#include <cstdint>
#include <string>
#include <vector>
namespace wowee {
namespace game { class GameHandler; }
namespace pipeline { class AssetManager; }
namespace ui {
class InventoryScreen;
class SpellbookScreen;
class QuestLogScreen;
/**
* Context needed by the renderer to display links, tooltips, and icons.
*/
struct MarkupRenderContext {
game::GameHandler* gameHandler = nullptr;
InventoryScreen* inventory = nullptr;
SpellbookScreen* spellbook = nullptr;
QuestLogScreen* questLog = nullptr;
pipeline::AssetManager* assetMgr = nullptr;
// Spell icon callback — same as ChatPanel::getSpellIcon
std::function<VkDescriptorSet(uint32_t, pipeline::AssetManager*)> getSpellIcon;
// Chat input buffer for shift-click link insertion
char* chatInputBuffer = nullptr;
size_t chatInputBufSize = 0;
bool* moveCursorToEnd = nullptr;
};
/**
* Renders parsed ChatSegments via ImGui.
*
* Extracted from ChatPanel::render() inline lambdas (Phase 2.2 of chat_panel_ref.md).
* Handles: colored text, item/spell/quest/achievement link tooltips+icons,
* URL click-to-open, shift-click link insertion.
*/
class ChatMarkupRenderer {
public:
/** Render a list of segments with ImGui. baseColor is the message-type color. */
void render(const std::vector<ChatSegment>& segments,
const ImVec4& baseColor,
const MarkupRenderContext& ctx) const;
/**
* Render a full item tooltip for the given item entry.
* Extracted from the renderItemLinkTooltip inline lambda.
*/
static void renderItemTooltip(uint32_t itemEntry,
game::GameHandler& gameHandler,
InventoryScreen& inventoryScreen,
pipeline::AssetManager* assetMgr);
};
} // namespace ui
} // namespace wowee

View file

@ -0,0 +1,40 @@
#pragma once
#include <functional>
// Forward declaration for ImGui (avoid pulling full imgui header)
struct ImGuiContext;
namespace wowee {
namespace ui {
/**
* Chat appearance and auto-join settings.
*
* Extracted from ChatPanel (Phase 1.1 of chat_panel_ref.md).
* Pure data + settings UI; no dependency on GameHandler or network.
*/
struct ChatSettings {
// Appearance
bool showTimestamps = false;
int fontSize = 1; // 0=small, 1=medium, 2=large
// Auto-join channels
bool autoJoinGeneral = true;
bool autoJoinTrade = true;
bool autoJoinLocalDefense = true;
bool autoJoinLFG = true;
bool autoJoinLocal = true;
// Window state
bool windowLocked = true;
/** Reset all chat settings to defaults. */
void restoreDefaults();
/** Render the "Chat" tab inside the Settings window. */
void renderSettingsTab(std::function<void()> saveSettingsFn);
};
} // namespace ui
} // namespace wowee

View file

@ -0,0 +1,53 @@
// ChatTabCompleter — cycling tab-completion state machine.
// Extracted from scattered vars in ChatPanel (Phase 5.1).
#pragma once
#include <string>
#include <vector>
namespace wowee {
namespace ui {
/**
* Stateful tab-completion engine.
*
* The caller gathers candidates and calls startCompletion() or cycle().
* The completer stores the prefix, sorted matches, and current index.
*/
class ChatTabCompleter {
public:
/**
* Start a new completion session with the given candidates.
* Resets the index to 0.
*/
void startCompletion(const std::string& prefix, std::vector<std::string> candidates);
/**
* Cycle to the next match. Returns true if there are matches.
* If the prefix changed, the caller should call startCompletion() instead.
*/
bool next();
/** Get the current match, or "" if no matches. */
std::string getCurrentMatch() const;
/** Get the number of matches in the current session. */
size_t matchCount() const { return matches_.size(); }
/** Check if a completion session is active. */
bool isActive() const { return matchIdx_ >= 0; }
/** Get the current prefix. */
const std::string& getPrefix() const { return prefix_; }
/** Reset the completer (e.g. on text change or arrow key). */
void reset();
private:
std::string prefix_;
std::vector<std::string> matches_;
int matchIdx_ = -1;
};
} // namespace ui
} // namespace wowee

View file

@ -0,0 +1,54 @@
#pragma once
#include "game/world_packets.hpp"
#include <imgui.h>
#include <cstdint>
#include <deque>
#include <string>
#include <vector>
namespace wowee {
namespace ui {
/**
* Chat tab definitions, unread tracking, type colors, and type names.
*
* Extracted from ChatPanel (Phase 1.3 of chat_panel_ref.md).
* Owns the tab configuration, unread badge counts, and message filtering.
*/
class ChatTabManager {
public:
ChatTabManager();
// ---- Tab access ----
int getTabCount() const { return static_cast<int>(tabs_.size()); }
const std::string& getTabName(int idx) const { return tabs_[idx].name; }
uint64_t getTabTypeMask(int idx) const { return tabs_[idx].typeMask; }
// ---- Unread tracking ----
int getUnreadCount(int idx) const;
void clearUnread(int idx);
/** Scan new messages since last call and increment unread counters for non-active tabs. */
void updateUnread(const std::deque<game::MessageChatData>& history, int activeTab);
// ---- Message filtering ----
bool shouldShowMessage(const game::MessageChatData& msg, int tabIndex) const;
// ---- Chat type helpers (static, no state needed) ----
static const char* getChatTypeName(game::ChatType type);
static ImVec4 getChatTypeColor(game::ChatType type);
private:
struct ChatTab {
std::string name;
uint64_t typeMask;
};
std::vector<ChatTab> tabs_;
std::vector<int> unread_;
size_t seenCount_ = 0;
void initTabs();
};
} // namespace ui
} // namespace wowee

View file

@ -0,0 +1,43 @@
#pragma once
#include "game/world_packets.hpp"
#include <algorithm>
#include <cctype>
#include <string>
namespace wowee {
// Forward declaration
namespace game { class GameHandler; }
namespace ui {
namespace chat_utils {
/** Create a system-type chat message (used 15+ times throughout commands). */
inline game::MessageChatData makeSystemMessage(const std::string& text) {
game::MessageChatData msg;
msg.type = game::ChatType::SYSTEM;
msg.language = game::ChatLanguage::UNIVERSAL;
msg.message = text;
return msg;
}
/** Trim leading/trailing whitespace from a string. */
inline std::string trim(const std::string& s) {
size_t first = s.find_first_not_of(" \t\r\n");
if (first == std::string::npos) return "";
size_t last = s.find_last_not_of(" \t\r\n");
return s.substr(first, last - first + 1);
}
/** Convert string to lowercase (returns copy). */
inline std::string toLower(std::string s) {
std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) {
return static_cast<char>(std::tolower(c));
});
return s;
}
} // namespace chat_utils
} // namespace ui
} // namespace wowee

View file

@ -0,0 +1,63 @@
// GameStateAdapter — wraps GameHandler + Renderer to implement IGameState.
// Phase 4.2 of chat_panel_ref.md.
#pragma once
#include "ui/chat/i_game_state.hpp"
namespace wowee {
namespace game { class GameHandler; }
namespace rendering { class Renderer; }
namespace ui {
/**
* Concrete adapter from GameHandler + Renderer IGameState.
* Flatten complex entity/aura queries into the simple IGameState interface.
*/
class GameStateAdapter : public IGameState {
public:
GameStateAdapter(game::GameHandler& gameHandler, rendering::Renderer* renderer);
// --- GUIDs ---
uint64_t getPlayerGuid() const override;
uint64_t getTargetGuid() const override;
uint64_t getFocusGuid() const override;
uint64_t getPetGuid() const override;
uint64_t getMouseoverGuid() const override;
// --- Player state ---
bool isInCombat() const override;
bool isMounted() const override;
bool isSwimming() const override;
bool isFlying() const override;
bool isCasting() const override;
bool isChanneling() const override;
bool isStealthed() const override;
bool hasPet() const override;
bool isInGroup() const override;
bool isInRaid() const override;
bool isIndoors() const override;
// --- Numeric ---
uint8_t getActiveTalentSpec() const override;
uint32_t getVehicleId() const override;
uint32_t getCurrentCastSpellId() const override;
// --- Spell/aura ---
std::string getSpellName(uint32_t spellId) const override;
bool hasAuraByName(uint64_t targetGuid, const std::string& spellName,
bool wantDebuff) const override;
bool hasFormAura() const override;
// --- Entity queries ---
bool entityExists(uint64_t guid) const override;
bool entityIsDead(uint64_t guid) const override;
bool entityIsHostile(uint64_t guid) const override;
private:
game::GameHandler& gameHandler_;
rendering::Renderer* renderer_;
};
} // namespace ui
} // namespace wowee

View file

@ -0,0 +1,272 @@
// GM command metadata — names, security levels, syntax, help text.
// Sourced from AzerothCore GM commands wiki.
// Used for tab-completion and .gmhelp display; the server handles execution.
#pragma once
#include <array>
#include <string_view>
#include <cstdint>
namespace wowee {
namespace ui {
struct GmCommandEntry {
std::string_view name; // e.g. "gm on"
uint8_t security; // 0=player, 1=mod, 2=gm, 3=admin, 4=console
std::string_view syntax; // e.g. ".gm [on/off]"
std::string_view help; // short description
};
// Curated list of the most useful GM commands for a client emulator.
// The full AzerothCore list has 500+ commands — this table covers the
// ones players/GMs actually type regularly, organized by category.
inline constexpr std::array kGmCommands = {
// ── GM mode & info ──────────────────────────────────────
GmCommandEntry{"gm", 1, ".gm [on/off]", "Toggle GM mode or show state"},
GmCommandEntry{"gm on", 1, ".gm on", "Enable GM mode"},
GmCommandEntry{"gm off", 1, ".gm off", "Disable GM mode"},
GmCommandEntry{"gm fly", 2, ".gm fly [on/off]", "Toggle fly mode"},
GmCommandEntry{"gm visible", 2, ".gm visible [on/off]", "Toggle GM visibility"},
GmCommandEntry{"gm chat", 2, ".gm chat [on/off]", "Toggle GM chat badge"},
GmCommandEntry{"gm ingame", 0, ".gm ingame", "List online GMs"},
GmCommandEntry{"gm list", 3, ".gm list", "List all GM accounts"},
// ── Teleportation ───────────────────────────────────────
GmCommandEntry{"tele", 1, ".tele #location", "Teleport to location"},
GmCommandEntry{"tele group", 2, ".tele group #location", "Teleport group to location"},
GmCommandEntry{"tele name", 2, ".tele name $player #location", "Teleport player to location"},
GmCommandEntry{"go xyz", 1, ".go xyz #x #y [#z [#map [#o]]]", "Teleport to coordinates"},
GmCommandEntry{"go creature", 1, ".go creature #guid", "Teleport to creature by GUID"},
GmCommandEntry{"go creature id", 1, ".go creature id #entry", "Teleport to creature by entry"},
GmCommandEntry{"go gameobject", 1, ".go gameobject #guid", "Teleport to gameobject"},
GmCommandEntry{"go graveyard", 1, ".go graveyard #id", "Teleport to graveyard"},
GmCommandEntry{"go taxinode", 1, ".go taxinode #id", "Teleport to taxinode"},
GmCommandEntry{"go trigger", 1, ".go trigger #id", "Teleport to areatrigger"},
GmCommandEntry{"go zonexy", 1, ".go zonexy #x #y [#zone]", "Teleport to zone coordinates"},
GmCommandEntry{"appear", 1, ".appear $player", "Teleport to player"},
GmCommandEntry{"summon", 2, ".summon $player", "Summon player to you"},
GmCommandEntry{"groupsummon", 2, ".groupsummon $player", "Summon player and group"},
GmCommandEntry{"recall", 2, ".recall [$player]", "Return to pre-teleport location"},
GmCommandEntry{"unstuck", 2, ".unstuck $player [inn/graveyard]", "Unstuck player"},
GmCommandEntry{"gps", 1, ".gps", "Show current position info"},
// ── Character & level ───────────────────────────────────
GmCommandEntry{"levelup", 2, ".levelup [$player] [#levels]", "Increase player level"},
GmCommandEntry{"character level", 3, ".character level [$player] [#lvl]", "Set character level"},
GmCommandEntry{"character rename", 2, ".character rename [$name]", "Flag character for rename"},
GmCommandEntry{"character changefaction", 2, ".character changefaction $name", "Flag for faction change"},
GmCommandEntry{"character changerace", 2, ".character changerace $name", "Flag for race change"},
GmCommandEntry{"character customize", 2, ".character customize [$name]", "Flag for customization"},
GmCommandEntry{"character reputation", 2, ".character reputation [$name]", "Show reputation info"},
GmCommandEntry{"character titles", 2, ".character titles [$name]", "Show known titles"},
GmCommandEntry{"pinfo", 2, ".pinfo [$player]", "Show player account info"},
GmCommandEntry{"guid", 2, ".guid", "Show target GUID"},
// ── Items & inventory ───────────────────────────────────
GmCommandEntry{"additem", 2, ".additem #id [#count]", "Add item to inventory"},
GmCommandEntry{"additem set", 2, ".additem set #setid", "Add item set to inventory"},
// ── Spells & auras ──────────────────────────────────────
GmCommandEntry{"learn", 2, ".learn #spell [all]", "Learn a spell"},
GmCommandEntry{"unlearn", 2, ".unlearn #spell [all]", "Unlearn a spell"},
GmCommandEntry{"learn all my class", 2, ".learn all my class", "Learn all class spells"},
GmCommandEntry{"learn all my spells", 2, ".learn all my spells", "Learn all available spells"},
GmCommandEntry{"learn all my talents", 2, ".learn all my talents", "Learn all talents"},
GmCommandEntry{"learn all crafts", 2, ".learn all crafts", "Learn all professions"},
GmCommandEntry{"learn all lang", 2, ".learn all lang", "Learn all languages"},
GmCommandEntry{"learn all recipes", 2, ".learn all recipes [$prof]", "Learn all recipes"},
GmCommandEntry{"cast", 2, ".cast #spell [triggered]", "Cast spell on target"},
GmCommandEntry{"cast self", 2, ".cast self #spell", "Cast spell on self"},
GmCommandEntry{"aura", 2, ".aura #spell", "Add aura to target"},
GmCommandEntry{"unaura", 2, ".unaura #spell", "Remove aura from target"},
GmCommandEntry{"cooldown", 2, ".cooldown [#spell]", "Remove cooldowns"},
GmCommandEntry{"maxskill", 2, ".maxskill", "Max all skills for target"},
GmCommandEntry{"setskill", 2, ".setskill #skill #level [#max]", "Set skill level"},
// ── Modify stats ────────────────────────────────────────
GmCommandEntry{"modify money", 2, ".modify money #amount", "Add/remove money"},
GmCommandEntry{"modify hp", 2, ".modify hp #value", "Set current HP"},
GmCommandEntry{"modify mana", 2, ".modify mana #value", "Set current mana"},
GmCommandEntry{"modify energy", 2, ".modify energy #value", "Set current energy"},
GmCommandEntry{"modify speed all", 2, ".modify speed all #rate", "Set all movement speeds"},
GmCommandEntry{"modify speed fly", 2, ".modify speed fly #rate", "Set fly speed"},
GmCommandEntry{"modify speed walk", 2, ".modify speed walk #rate", "Set walk speed"},
GmCommandEntry{"modify speed swim", 2, ".modify speed swim #rate", "Set swim speed"},
GmCommandEntry{"modify mount", 2, ".modify mount #id #speed", "Display as mounted"},
GmCommandEntry{"modify scale", 2, ".modify scale #rate", "Set model scale"},
GmCommandEntry{"modify honor", 2, ".modify honor #amount", "Add honor points"},
GmCommandEntry{"modify reputation", 2, ".modify reputation #faction #val", "Set faction reputation"},
GmCommandEntry{"modify talentpoints", 2, ".modify talentpoints #amount", "Set talent points"},
GmCommandEntry{"modify gender", 2, ".modify gender male/female", "Change gender"},
// ── Cheats ──────────────────────────────────────────────
GmCommandEntry{"cheat god", 2, ".cheat god [on/off]", "Toggle god mode"},
GmCommandEntry{"cheat casttime", 2, ".cheat casttime [on/off]", "Toggle no cast time"},
GmCommandEntry{"cheat cooldown", 2, ".cheat cooldown [on/off]", "Toggle no cooldowns"},
GmCommandEntry{"cheat power", 2, ".cheat power [on/off]", "Toggle no mana cost"},
GmCommandEntry{"cheat explore", 2, ".cheat explore #flag", "Reveal/hide all maps"},
GmCommandEntry{"cheat taxi", 2, ".cheat taxi on/off", "Toggle all taxi routes"},
GmCommandEntry{"cheat waterwalk", 2, ".cheat waterwalk on/off", "Toggle waterwalk"},
GmCommandEntry{"cheat status", 2, ".cheat status", "Show active cheats"},
// ── NPC ─────────────────────────────────────────────────
GmCommandEntry{"npc add", 3, ".npc add #entry", "Spawn creature"},
GmCommandEntry{"npc delete", 3, ".npc delete [#guid]", "Delete creature"},
GmCommandEntry{"npc info", 1, ".npc info", "Show NPC details"},
GmCommandEntry{"npc guid", 1, ".npc guid", "Show NPC GUID"},
GmCommandEntry{"npc near", 2, ".npc near [#dist]", "List nearby NPCs"},
GmCommandEntry{"npc say", 2, ".npc say $message", "Make NPC say text"},
GmCommandEntry{"npc yell", 2, ".npc yell $message", "Make NPC yell text"},
GmCommandEntry{"npc move", 3, ".npc move [#guid]", "Move NPC to your position"},
GmCommandEntry{"npc set level", 3, ".npc set level #level", "Set NPC level"},
GmCommandEntry{"npc set model", 3, ".npc set model #displayid", "Set NPC model"},
GmCommandEntry{"npc tame", 2, ".npc tame", "Tame selected creature"},
// ── Game objects ────────────────────────────────────────
GmCommandEntry{"gobject add", 3, ".gobject add #entry", "Spawn gameobject"},
GmCommandEntry{"gobject delete", 3, ".gobject delete #guid", "Delete gameobject"},
GmCommandEntry{"gobject info", 1, ".gobject info [#entry]", "Show gameobject info"},
GmCommandEntry{"gobject near", 3, ".gobject near [#dist]", "List nearby objects"},
GmCommandEntry{"gobject move", 3, ".gobject move #guid [#x #y #z]", "Move gameobject"},
GmCommandEntry{"gobject target", 1, ".gobject target [#id]", "Find nearest gameobject"},
GmCommandEntry{"gobject activate", 2, ".gobject activate #guid", "Activate object (door/button)"},
// ── Combat & death ──────────────────────────────────────
GmCommandEntry{"revive", 2, ".revive", "Revive selected/self"},
GmCommandEntry{"die", 2, ".die", "Kill selected/self"},
GmCommandEntry{"damage", 2, ".damage #amount [#school [#spell]]", "Deal damage to target"},
GmCommandEntry{"combatstop", 2, ".combatstop [$player]", "Stop combat for target"},
GmCommandEntry{"freeze", 2, ".freeze [$player]", "Freeze player"},
GmCommandEntry{"unfreeze", 2, ".unfreeze [$player]", "Unfreeze player"},
GmCommandEntry{"dismount", 0, ".dismount", "Dismount if mounted"},
GmCommandEntry{"respawn", 2, ".respawn", "Respawn nearby creatures/GOs"},
GmCommandEntry{"respawn all", 2, ".respawn all", "Respawn all nearby"},
// ── Quests ──────────────────────────────────────────────
GmCommandEntry{"quest add", 2, ".quest add #id", "Add quest to log"},
GmCommandEntry{"quest complete", 2, ".quest complete #id", "Complete quest objectives"},
GmCommandEntry{"quest remove", 2, ".quest remove #id", "Remove quest from log"},
GmCommandEntry{"quest reward", 2, ".quest reward #id", "Grant quest reward"},
GmCommandEntry{"quest status", 2, ".quest status #id [$name]", "Show quest status"},
// ── Honor & arena ───────────────────────────────────────
GmCommandEntry{"honor add", 2, ".honor add #amount", "Add honor points"},
GmCommandEntry{"honor update", 2, ".honor update", "Force honor field update"},
GmCommandEntry{"achievement add", 2, ".achievement add #id", "Add achievement to target"},
// ── Group & guild ───────────────────────────────────────
GmCommandEntry{"group list", 2, ".group list [$player]", "List group members"},
GmCommandEntry{"group revive", 2, ".group revive $player", "Revive group members"},
GmCommandEntry{"group disband", 2, ".group disband [$player]", "Disband player's group"},
GmCommandEntry{"guild create", 2, ".guild create $leader \"$name\"", "Create guild"},
GmCommandEntry{"guild delete", 2, ".guild delete \"$name\"", "Delete guild"},
GmCommandEntry{"guild invite", 2, ".guild invite $player \"$guild\"", "Add player to guild"},
GmCommandEntry{"guild info", 2, ".guild info", "Show guild info"},
// ── Lookup & search ─────────────────────────────────────
GmCommandEntry{"lookup item", 1, ".lookup item $name", "Search item by name"},
GmCommandEntry{"lookup spell", 1, ".lookup spell $name", "Search spell by name"},
GmCommandEntry{"lookup spell id", 1, ".lookup spell id #id", "Look up spell by ID"},
GmCommandEntry{"lookup creature", 1, ".lookup creature $name", "Search creature by name"},
GmCommandEntry{"lookup quest", 1, ".lookup quest $name", "Search quest by name"},
GmCommandEntry{"lookup gobject", 1, ".lookup gobject $name", "Search gameobject by name"},
GmCommandEntry{"lookup area", 1, ".lookup area $name", "Search area by name"},
GmCommandEntry{"lookup taxinode", 1, ".lookup taxinode $name", "Search taxinode by name"},
GmCommandEntry{"lookup teleport", 1, ".lookup teleport $name", "Search teleport by name"},
GmCommandEntry{"lookup faction", 1, ".lookup faction $name", "Search faction by name"},
GmCommandEntry{"lookup title", 1, ".lookup title $name", "Search title by name"},
GmCommandEntry{"lookup event", 1, ".lookup event $name", "Search event by name"},
GmCommandEntry{"lookup map", 1, ".lookup map $name", "Search map by name"},
GmCommandEntry{"lookup skill", 1, ".lookup skill $name", "Search skill by name"},
// ── Titles ──────────────────────────────────────────────
GmCommandEntry{"titles add", 2, ".titles add #id", "Add title to target"},
GmCommandEntry{"titles remove", 2, ".titles remove #id", "Remove title from target"},
GmCommandEntry{"titles current", 2, ".titles current #id", "Set current title"},
// ── Morph & display ─────────────────────────────────────
GmCommandEntry{"morph", 1, ".morph #displayid", "Change your model"},
GmCommandEntry{"morph target", 1, ".morph target #displayid", "Change target model"},
GmCommandEntry{"morph mount", 1, ".morph mount #displayid", "Change mount model"},
GmCommandEntry{"morph reset", 1, ".morph reset", "Reset target model"},
// ── Debug & info ────────────────────────────────────────
GmCommandEntry{"debug anim", 3, ".debug anim", "Debug animation"},
GmCommandEntry{"debug arena", 3, ".debug arena", "Toggle arena debug"},
GmCommandEntry{"debug bg", 3, ".debug bg", "Toggle BG debug"},
GmCommandEntry{"debug los", 3, ".debug los", "Show line of sight info"},
GmCommandEntry{"debug loot", 2, ".debug loot $type $id [#count]", "Simulate loot generation"},
GmCommandEntry{"list auras", 1, ".list auras", "List auras on target"},
GmCommandEntry{"list creature", 1, ".list creature #id [#max]", "List creature spawns"},
GmCommandEntry{"list item", 1, ".list item #id [#max]", "List item locations"},
// ── Server & system ─────────────────────────────────────
GmCommandEntry{"announce", 2, ".announce $message", "Broadcast to all players"},
GmCommandEntry{"gmannounce", 2, ".gmannounce $message", "Broadcast to online GMs"},
GmCommandEntry{"notify", 2, ".notify $message", "On-screen broadcast"},
GmCommandEntry{"server info", 0, ".server info", "Show server version/players"},
GmCommandEntry{"server motd", 0, ".server motd", "Show message of the day"},
GmCommandEntry{"commands", 0, ".commands", "List available commands"},
GmCommandEntry{"help", 0, ".help [$cmd]", "Show command help"},
GmCommandEntry{"save", 0, ".save", "Save your character"},
GmCommandEntry{"saveall", 2, ".saveall", "Save all characters"},
// ── Account & bans ──────────────────────────────────────
GmCommandEntry{"account", 0, ".account", "Show account info"},
GmCommandEntry{"account set gmlevel", 4, ".account set gmlevel $acct #lvl", "Set GM security level"},
GmCommandEntry{"ban account", 2, ".ban account $name $time $reason", "Ban account"},
GmCommandEntry{"ban character", 2, ".ban character $name $time $reason", "Ban character"},
GmCommandEntry{"ban ip", 2, ".ban ip $ip $time $reason", "Ban IP address"},
GmCommandEntry{"unban account", 3, ".unban account $name", "Unban account"},
GmCommandEntry{"unban character", 3, ".unban character $name", "Unban character"},
GmCommandEntry{"unban ip", 3, ".unban ip $ip", "Unban IP address"},
GmCommandEntry{"kick", 2, ".kick [$player] [$reason]", "Kick player from world"},
GmCommandEntry{"mute", 2, ".mute $player $minutes [$reason]", "Mute player chat"},
GmCommandEntry{"unmute", 2, ".unmute [$player]", "Unmute player"},
// ── Misc ────────────────────────────────────────────────
GmCommandEntry{"distance", 3, ".distance", "Distance to selected target"},
GmCommandEntry{"wchange", 3, ".wchange #type #grade", "Change weather"},
GmCommandEntry{"mailbox", 1, ".mailbox", "Open mailbox"},
GmCommandEntry{"played", 0, ".played", "Show time played"},
GmCommandEntry{"gear repair", 2, ".gear repair", "Repair all gear"},
GmCommandEntry{"gear stats", 0, ".gear stats", "Show avg item level"},
GmCommandEntry{"reset talents", 3, ".reset talents [$player]", "Reset talents"},
GmCommandEntry{"reset spells", 3, ".reset spells [$player]", "Reset spells"},
GmCommandEntry{"pet create", 2, ".pet create", "Create pet from target"},
GmCommandEntry{"pet learn", 2, ".pet learn #spell", "Teach spell to pet"},
// ── Waypoints ───────────────────────────────────────────
GmCommandEntry{"wp add", 3, ".wp add", "Add waypoint at your position"},
GmCommandEntry{"wp show", 3, ".wp show on/off", "Toggle waypoint display"},
GmCommandEntry{"wp load", 3, ".wp load #pathid", "Load path for creature"},
GmCommandEntry{"wp unload", 3, ".wp unload", "Unload creature path"},
// ── Instance ────────────────────────────────────────────
GmCommandEntry{"instance listbinds", 1, ".instance listbinds", "Show instance binds"},
GmCommandEntry{"instance unbind", 2, ".instance unbind <map|all>", "Clear instance binds"},
GmCommandEntry{"instance stats", 1, ".instance stats", "Show instance stats"},
// ── Events ──────────────────────────────────────────────
GmCommandEntry{"event activelist", 2, ".event activelist", "Show active events"},
GmCommandEntry{"event start", 2, ".event start #id", "Start event"},
GmCommandEntry{"event stop", 2, ".event stop #id", "Stop event"},
GmCommandEntry{"event info", 2, ".event info #id", "Show event info"},
// ── Reload (common) ─────────────────────────────────────
GmCommandEntry{"reload all", 3, ".reload all", "Reload all tables"},
GmCommandEntry{"reload creature_template", 3, ".reload creature_template #entry", "Reload creature template"},
GmCommandEntry{"reload quest_template", 3, ".reload quest_template", "Reload quest templates"},
GmCommandEntry{"reload config", 3, ".reload config", "Reload server config"},
GmCommandEntry{"reload game_tele", 3, ".reload game_tele", "Reload teleport locations"},
// ── Ticket ──────────────────────────────────────────────
GmCommandEntry{"ticket list", 2, ".ticket list", "List open GM tickets"},
GmCommandEntry{"ticket close", 2, ".ticket close #id", "Close ticket"},
GmCommandEntry{"ticket delete", 3, ".ticket delete #id", "Delete ticket permanently"},
GmCommandEntry{"ticket viewid", 2, ".ticket viewid #id", "View ticket details"},
};
} // namespace ui
} // namespace wowee

View file

@ -0,0 +1,57 @@
// IChatCommand — interface for all slash commands.
// Phase 3.1 of chat_panel_ref.md.
#pragma once
#include <string>
#include <vector>
namespace wowee {
// Forward declarations
namespace game { class GameHandler; }
namespace ui { struct UIServices; class ChatPanel; }
namespace ui {
/**
* Context passed to every command's execute() method.
* Provides everything a command needs without coupling to ChatPanel.
*/
struct ChatCommandContext {
game::GameHandler& gameHandler;
UIServices& services;
ChatPanel& panel; // for input buffer access, macro state
std::string args; // everything after "/cmd "
std::string fullCommand; // the original command name (lowercase)
};
/**
* Result returned by a command to tell the dispatcher what to do next.
*/
struct ChatCommandResult {
bool handled = true; // false → command not recognized, fall through
bool clearInput = true; // clear the input buffer after execution
};
/**
* Interface for all chat slash commands.
*
* Adding a new command = create a class implementing this interface,
* register it in ChatCommandRegistry. Zero edits to existing code. (OCP)
*/
class IChatCommand {
public:
virtual ~IChatCommand() = default;
/** Execute the command. */
virtual ChatCommandResult execute(ChatCommandContext& ctx) = 0;
/** Return all aliases for this command (e.g. {"w", "whisper", "tell", "t"}). */
virtual std::vector<std::string> aliases() const = 0;
/** Optional help text shown by /help. */
virtual std::string helpText() const { return ""; }
};
} // namespace ui
} // namespace wowee

View file

@ -0,0 +1,63 @@
// IGameState — abstract interface for game state queries used by macro evaluation.
// Allows unit testing with mock state. Phase 4.1 of chat_panel_ref.md.
#pragma once
#include <cstdint>
#include <string>
namespace wowee {
namespace ui {
/**
* Read-only view of game state for macro conditional evaluation.
*
* All entity/aura queries are flattened to simple types so callers
* don't need to depend on game::Entity, game::Unit, etc.
*/
class IGameState {
public:
virtual ~IGameState() = default;
// --- GUIDs ---
virtual uint64_t getPlayerGuid() const = 0;
virtual uint64_t getTargetGuid() const = 0;
virtual uint64_t getFocusGuid() const = 0;
virtual uint64_t getPetGuid() const = 0;
virtual uint64_t getMouseoverGuid() const = 0;
// --- Player state booleans ---
virtual bool isInCombat() const = 0;
virtual bool isMounted() const = 0;
virtual bool isSwimming() const = 0;
virtual bool isFlying() const = 0;
virtual bool isCasting() const = 0;
virtual bool isChanneling() const = 0;
virtual bool isStealthed() const = 0;
virtual bool hasPet() const = 0;
virtual bool isInGroup() const = 0;
virtual bool isInRaid() const = 0;
virtual bool isIndoors() const = 0;
// --- Numeric state ---
virtual uint8_t getActiveTalentSpec() const = 0; // 0-based index
virtual uint32_t getVehicleId() const = 0;
virtual uint32_t getCurrentCastSpellId() const = 0;
// --- Spell/aura queries ---
virtual std::string getSpellName(uint32_t spellId) const = 0;
/** Check if target (or player if guid==playerGuid) has a buff/debuff by name. */
virtual bool hasAuraByName(uint64_t targetGuid, const std::string& spellName,
bool wantDebuff) const = 0;
/** Check if player has a form/stance aura (permanent aura, maxDurationMs == -1). */
virtual bool hasFormAura() const = 0;
// --- Entity queries (flattened, no Entity* exposure) ---
virtual bool entityExists(uint64_t guid) const = 0;
virtual bool entityIsDead(uint64_t guid) const = 0;
virtual bool entityIsHostile(uint64_t guid) const = 0;
};
} // namespace ui
} // namespace wowee

View file

@ -0,0 +1,21 @@
// IModifierState — abstract interface for keyboard modifier queries.
// Allows unit testing macro conditionals without real input system. Phase 4.1.
#pragma once
namespace wowee {
namespace ui {
/**
* Read-only view of keyboard modifier state for macro conditional evaluation.
*/
class IModifierState {
public:
virtual ~IModifierState() = default;
virtual bool isShiftHeld() const = 0;
virtual bool isCtrlHeld() const = 0;
virtual bool isAltHeld() const = 0;
};
} // namespace ui
} // namespace wowee

View file

@ -0,0 +1,22 @@
// InputModifierAdapter — wraps core::Input to implement IModifierState.
// Phase 4.3 of chat_panel_ref.md.
#pragma once
#include "ui/chat/i_modifier_state.hpp"
namespace wowee {
namespace ui {
/**
* Concrete adapter from core::Input IModifierState.
* Reads real keyboard state from SDL.
*/
class InputModifierAdapter : public IModifierState {
public:
bool isShiftHeld() const override;
bool isCtrlHeld() const override;
bool isAltHeld() const override;
};
} // namespace ui
} // namespace wowee

View file

@ -0,0 +1,50 @@
// MacroEvaluator — WoW macro conditional parser and evaluator.
// Extracted from evaluateMacroConditionals() in chat_panel_commands.cpp.
// Phase 4.4 of chat_panel_ref.md.
#pragma once
#include <cstdint>
#include <string>
namespace wowee {
namespace ui {
class IGameState;
class IModifierState;
/**
* Evaluates WoW-style macro conditional expressions.
*
* Syntax: [cond1,cond2] Spell1; [cond3] Spell2; DefaultSpell
*
* The first alternative whose conditions all evaluate true is returned.
* If no conditions match, returns "".
*
* @p targetOverride is set to a specific GUID if [target=X] or [@X]
* was in the matching conditions, or left as UINT64_MAX for "use normal target".
*/
class MacroEvaluator {
public:
MacroEvaluator(IGameState& gameState, IModifierState& modState);
/**
* Evaluate a macro conditional string.
* @param rawArg The conditional text (e.g. "[combat] Spell1; Spell2")
* @param targetOverride Output: set to target GUID if specified, or -1
* @return The matched argument text, or "" if nothing matched
*/
std::string evaluate(const std::string& rawArg, uint64_t& targetOverride) const;
private:
/** Evaluate a single condition token (e.g. "combat", "mod:shift", "@focus"). */
bool evalCondition(const std::string& cond, uint64_t& tgt) const;
/** Resolve effective target GUID (follows @/target= overrides). */
uint64_t resolveEffectiveTarget(uint64_t tgt) const;
IGameState& gameState_;
IModifierState& modState_;
};
} // namespace ui
} // namespace wowee

View file

@ -2,6 +2,15 @@
#include "game/game_handler.hpp"
#include "ui/ui_services.hpp"
#include "ui/chat/chat_settings.hpp"
#include "ui/chat/chat_input.hpp"
#include "ui/chat/chat_tab_manager.hpp"
#include "ui/chat/chat_bubble_manager.hpp"
#include "ui/chat/cast_sequence_tracker.hpp"
#include "ui/chat/chat_markup_parser.hpp"
#include "ui/chat/chat_markup_renderer.hpp"
#include "ui/chat/chat_command_registry.hpp"
#include "ui/chat/chat_tab_completer.hpp"
#include <vulkan/vulkan.h>
#include <imgui.h>
#include <string>
@ -69,9 +78,6 @@ public:
/** Execute a macro body (one line per 'click'). */
void executeMacroText(game::GameHandler& gameHandler,
InventoryScreen& inventoryScreen,
SpellbookScreen& spellbookScreen,
QuestLogScreen& questLogScreen,
const std::string& macroText);
// ---- Slash-command side-effects ----
@ -90,25 +96,31 @@ public:
/** Return accumulated slash-command flags and reset them. */
SlashCommands consumeSlashCommands();
// ---- Chat settings (read/written by GameScreen save/load & settings tab) ----
// ---- Chat settings (delegated to ChatSettings) ----
bool chatShowTimestamps = false;
int chatFontSize = 1; // 0=small, 1=medium, 2=large
bool chatAutoJoinGeneral = true;
bool chatAutoJoinTrade = true;
bool chatAutoJoinLocalDefense = true;
bool chatAutoJoinLFG = true;
bool chatAutoJoinLocal = true;
ChatSettings settings;
int activeChatTab = 0;
// Legacy accessors — forward to settings struct for external code
// (GameScreen save/load reads these directly)
bool& chatShowTimestamps = settings.showTimestamps;
int& chatFontSize = settings.fontSize;
bool& chatAutoJoinGeneral = settings.autoJoinGeneral;
bool& chatAutoJoinTrade = settings.autoJoinTrade;
bool& chatAutoJoinLocalDefense = settings.autoJoinLocalDefense;
bool& chatAutoJoinLFG = settings.autoJoinLFG;
bool& chatAutoJoinLocal = settings.autoJoinLocal;
/** Spell icon lookup callback — set by GameScreen each frame before render(). */
std::function<VkDescriptorSet(uint32_t, pipeline::AssetManager*)> getSpellIcon;
/** Render the "Chat" tab inside the Settings window. */
void renderSettingsTab(std::function<void()> saveSettingsFn);
/** Render the "Chat" tab inside the Settings window (delegates to settings). */
void renderSettingsTab(std::function<void()> saveSettingsFn) {
settings.renderSettingsTab(std::move(saveSettingsFn));
}
/** Reset all chat settings to defaults. */
void restoreDefaults();
/** Reset all chat settings to defaults (delegates to settings). */
void restoreDefaults() { settings.restoreDefaults(); }
// UIServices injection (Phase B singleton breaking)
void setServices(const UIServices& services) { services_ = services; }
@ -116,14 +128,31 @@ public:
/** Replace $g/$G and $n/$N gender/name placeholders in quest/chat text. */
std::string replaceGenderPlaceholders(const std::string& text, game::GameHandler& gameHandler);
// ---- Accessors for command system (Phase 3) ----
char* getChatInputBuffer() { return chatInputBuffer_; }
size_t getChatInputBufferSize() const { return sizeof(chatInputBuffer_); }
char* getWhisperTargetBuffer() { return whisperTargetBuffer_; }
size_t getWhisperTargetBufferSize() const { return sizeof(whisperTargetBuffer_); }
int getSelectedChatType() const { return selectedChatType_; }
void setSelectedChatType(int t) { selectedChatType_ = t; }
int getSelectedChannelIdx() const { return selectedChannelIdx_; }
bool& macroStopped() { return macroStopped_; }
CastSequenceTracker& getCastSeqTracker() { return castSeqTracker_; }
SlashCommands& getSlashCmds() { return slashCmds_; }
UIServices& getServices() { return services_; }
ChatCommandRegistry& getCommandRegistry() { return commandRegistry_; }
private:
// Injected UI services (Phase B singleton breaking)
UIServices services_;
// ---- Chat input state ----
// NOTE: These will migrate to ChatInput in Phase 6 (slim ChatPanel).
// ChatInput class is ready at include/ui/chat/chat_input.hpp.
char chatInputBuffer_[512] = "";
char whisperTargetBuffer_[256] = "";
bool chatInputActive_ = false;
int chatInputCooldown_ = 0; // frames to suppress re-activation after send
int selectedChatType_ = 0; // 0=SAY .. 10=CHANNEL
int lastChatType_ = 0;
int selectedChannelIdx_ = 0;
@ -137,55 +166,44 @@ private:
// Macro stop flag
bool macroStopped_ = false;
// Tab-completion state
std::string chatTabPrefix_;
std::vector<std::string> chatTabMatches_;
int chatTabMatchIdx_ = -1;
// /castsequence state (delegated to CastSequenceTracker, Phase 1.5)
CastSequenceTracker castSeqTracker_;
// Command registry (Phase 3 — replaces if/else chain)
ChatCommandRegistry commandRegistry_;
void registerAllCommands();
// Markup parser + renderer (Phase 2)
ChatMarkupParser markupParser_;
ChatMarkupRenderer markupRenderer_;
// Tab-completion (Phase 5 — delegated to ChatTabCompleter)
ChatTabCompleter tabCompleter_;
// Mention notification
size_t chatMentionSeenCount_ = 0;
// ---- Chat tabs ----
struct ChatTab {
std::string name;
uint64_t typeMask;
};
std::vector<ChatTab> chatTabs_;
std::vector<int> chatTabUnread_;
size_t chatTabSeenCount_ = 0;
void initChatTabs();
bool shouldShowMessage(const game::MessageChatData& msg, int tabIndex) const;
// ---- Chat tabs (delegated to ChatTabManager) ----
ChatTabManager tabManager_;
// ---- Chat window visual state ----
bool chatScrolledUp_ = false;
bool chatForceScrollToBottom_ = false;
bool chatWindowLocked_ = true;
// windowLocked is in settings.windowLocked (kept in sync via reference)
bool& chatWindowLocked_ = settings.windowLocked;
ImVec2 chatWindowPos_ = ImVec2(0.0f, 0.0f);
bool chatWindowPosInit_ = false;
// ---- Chat bubbles ----
struct ChatBubble {
uint64_t senderGuid = 0;
std::string message;
float timeRemaining = 0.0f;
float totalDuration = 0.0f;
bool isYell = false;
};
std::vector<ChatBubble> chatBubbles_;
bool chatBubbleCallbackSet_ = false;
// ---- Chat bubbles (delegated to ChatBubbleManager) ----
ChatBubbleManager bubbleManager_;
// ---- Whisper toast state (populated in render, rendered by GameScreen/ToastManager) ----
// Whisper scanning lives here because it's tightly coupled to chat history iteration.
size_t whisperSeenCount_ = 0;
// ---- Helpers ----
void sendChatMessage(game::GameHandler& gameHandler,
InventoryScreen& inventoryScreen,
SpellbookScreen& spellbookScreen,
QuestLogScreen& questLogScreen);
const char* getChatTypeName(game::ChatType type) const;
ImVec4 getChatTypeColor(game::ChatType type) const;
void sendChatMessage(game::GameHandler& gameHandler);
// getChatTypeName / getChatTypeColor now static in ChatTabManager
// Cached game handler for input callback (set each frame in render)
game::GameHandler* cachedGameHandler_ = nullptr;