mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-06 00:53:52 +00:00
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:
parent
09c4a9a04a
commit
42f1bb98ea
54 changed files with 7363 additions and 3856 deletions
252
src/ui/chat/commands/target_commands.cpp
Normal file
252
src/ui/chat/commands/target_commands.cpp
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
// Target commands: /target, /cleartarget, /targetenemy, /targetfriend,
|
||||
// /targetlasttarget, /targetlastenemy, /targetlastfriend,
|
||||
// /focus, /clearfocus
|
||||
// Moved from ChatPanel::sendChatMessage() if/else chain (Phase 3).
|
||||
#include "ui/chat/i_chat_command.hpp"
|
||||
#include "ui/chat_panel.hpp"
|
||||
#include "game/game_handler.hpp"
|
||||
#include "game/entity.hpp"
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
|
||||
namespace wowee { namespace ui {
|
||||
|
||||
// Forward declaration of evaluateMacroConditionals (still in chat_panel_commands.cpp)
|
||||
std::string evaluateMacroConditionals(const std::string& rawArg,
|
||||
game::GameHandler& gameHandler,
|
||||
uint64_t& targetOverride);
|
||||
|
||||
namespace {
|
||||
|
||||
// Trim leading/trailing whitespace.
|
||||
inline void trimInPlace(std::string& s) {
|
||||
while (!s.empty() && s.front() == ' ') s.erase(s.begin());
|
||||
while (!s.empty() && s.back() == ' ') s.pop_back();
|
||||
}
|
||||
|
||||
// Search nearby visible entities by name (case-insensitive prefix match).
|
||||
// Returns the GUID of the nearest matching unit, or 0 if none found.
|
||||
uint64_t findNearestByName(game::GameHandler& gh, const std::string& targetArgLower) {
|
||||
uint64_t bestGuid = 0;
|
||||
float bestDist = std::numeric_limits<float>::max();
|
||||
const auto& pmi = gh.getMovementInfo();
|
||||
for (const auto& [guid, entity] : gh.getEntityManager().getEntities()) {
|
||||
if (!entity || entity->getType() == game::ObjectType::OBJECT) continue;
|
||||
std::string name;
|
||||
if (entity->getType() == game::ObjectType::PLAYER ||
|
||||
entity->getType() == game::ObjectType::UNIT) {
|
||||
auto unit = std::static_pointer_cast<game::Unit>(entity);
|
||||
name = unit->getName();
|
||||
}
|
||||
if (name.empty()) continue;
|
||||
std::string nameLower = name;
|
||||
for (char& c : nameLower) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
if (nameLower.find(targetArgLower) == 0) {
|
||||
float dx = entity->getX() - pmi.x;
|
||||
float dy = entity->getY() - pmi.y;
|
||||
float dz = entity->getZ() - pmi.z;
|
||||
float dist = dx*dx + dy*dy + dz*dz;
|
||||
if (dist < bestDist) {
|
||||
bestDist = dist;
|
||||
bestGuid = guid;
|
||||
}
|
||||
}
|
||||
}
|
||||
return bestGuid;
|
||||
}
|
||||
|
||||
} // anon namespace
|
||||
|
||||
// --- /target ---
|
||||
class TargetCommand : public IChatCommand {
|
||||
public:
|
||||
ChatCommandResult execute(ChatCommandContext& ctx) override {
|
||||
if (ctx.args.empty()) return {false, false};
|
||||
|
||||
std::string targetArg = ctx.args;
|
||||
|
||||
// Evaluate conditionals if present
|
||||
uint64_t targetCmdOverride = static_cast<uint64_t>(-1);
|
||||
if (!targetArg.empty() && targetArg.front() == '[') {
|
||||
targetArg = evaluateMacroConditionals(targetArg, ctx.gameHandler, targetCmdOverride);
|
||||
if (targetArg.empty() && targetCmdOverride == static_cast<uint64_t>(-1)) return {};
|
||||
trimInPlace(targetArg);
|
||||
}
|
||||
|
||||
// If conditionals resolved to a specific GUID, target it directly
|
||||
if (targetCmdOverride != static_cast<uint64_t>(-1) && targetCmdOverride != 0) {
|
||||
ctx.gameHandler.setTarget(targetCmdOverride);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (targetArg.empty()) return {};
|
||||
|
||||
std::string targetArgLower = targetArg;
|
||||
for (char& c : targetArgLower) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
|
||||
uint64_t bestGuid = findNearestByName(ctx.gameHandler, targetArgLower);
|
||||
if (bestGuid) {
|
||||
ctx.gameHandler.setTarget(bestGuid);
|
||||
} else {
|
||||
game::MessageChatData sysMsg;
|
||||
sysMsg.type = game::ChatType::SYSTEM;
|
||||
sysMsg.language = game::ChatLanguage::UNIVERSAL;
|
||||
sysMsg.message = "No target matching '" + targetArg + "' found.";
|
||||
ctx.gameHandler.addLocalChatMessage(sysMsg);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
std::vector<std::string> aliases() const override { return {"target"}; }
|
||||
std::string helpText() const override { return "Target unit by name"; }
|
||||
};
|
||||
|
||||
// --- /cleartarget ---
|
||||
class ClearTargetCommand : public IChatCommand {
|
||||
public:
|
||||
ChatCommandResult execute(ChatCommandContext& ctx) override {
|
||||
bool condPass = true;
|
||||
if (!ctx.args.empty()) {
|
||||
std::string ctArg = ctx.args;
|
||||
trimInPlace(ctArg);
|
||||
if (!ctArg.empty() && ctArg.front() == '[') {
|
||||
uint64_t ctOver = static_cast<uint64_t>(-1);
|
||||
std::string res = evaluateMacroConditionals(ctArg, ctx.gameHandler, ctOver);
|
||||
condPass = !(res.empty() && ctOver == static_cast<uint64_t>(-1));
|
||||
}
|
||||
}
|
||||
if (condPass) ctx.gameHandler.clearTarget();
|
||||
return {};
|
||||
}
|
||||
std::vector<std::string> aliases() const override { return {"cleartarget"}; }
|
||||
std::string helpText() const override { return "Clear current target"; }
|
||||
};
|
||||
|
||||
// --- /targetenemy ---
|
||||
class TargetEnemyCommand : public IChatCommand {
|
||||
public:
|
||||
ChatCommandResult execute(ChatCommandContext& ctx) override {
|
||||
ctx.gameHandler.targetEnemy(false);
|
||||
return {};
|
||||
}
|
||||
std::vector<std::string> aliases() const override { return {"targetenemy"}; }
|
||||
std::string helpText() const override { return "Cycle to next enemy"; }
|
||||
};
|
||||
|
||||
// --- /targetfriend ---
|
||||
class TargetFriendCommand : public IChatCommand {
|
||||
public:
|
||||
ChatCommandResult execute(ChatCommandContext& ctx) override {
|
||||
ctx.gameHandler.targetFriend(false);
|
||||
return {};
|
||||
}
|
||||
std::vector<std::string> aliases() const override { return {"targetfriend"}; }
|
||||
std::string helpText() const override { return "Cycle to next friendly unit"; }
|
||||
};
|
||||
|
||||
// --- /targetlasttarget, /targetlast ---
|
||||
class TargetLastTargetCommand : public IChatCommand {
|
||||
public:
|
||||
ChatCommandResult execute(ChatCommandContext& ctx) override {
|
||||
ctx.gameHandler.targetLastTarget();
|
||||
return {};
|
||||
}
|
||||
std::vector<std::string> aliases() const override { return {"targetlasttarget", "targetlast"}; }
|
||||
std::string helpText() const override { return "Target previous target"; }
|
||||
};
|
||||
|
||||
// --- /targetlastenemy ---
|
||||
class TargetLastEnemyCommand : public IChatCommand {
|
||||
public:
|
||||
ChatCommandResult execute(ChatCommandContext& ctx) override {
|
||||
ctx.gameHandler.targetEnemy(true);
|
||||
return {};
|
||||
}
|
||||
std::vector<std::string> aliases() const override { return {"targetlastenemy"}; }
|
||||
std::string helpText() const override { return "Cycle to previous enemy"; }
|
||||
};
|
||||
|
||||
// --- /targetlastfriend ---
|
||||
class TargetLastFriendCommand : public IChatCommand {
|
||||
public:
|
||||
ChatCommandResult execute(ChatCommandContext& ctx) override {
|
||||
ctx.gameHandler.targetFriend(true);
|
||||
return {};
|
||||
}
|
||||
std::vector<std::string> aliases() const override { return {"targetlastfriend"}; }
|
||||
std::string helpText() const override { return "Cycle to previous friendly unit"; }
|
||||
};
|
||||
|
||||
// --- /focus ---
|
||||
class FocusCommand : public IChatCommand {
|
||||
public:
|
||||
ChatCommandResult execute(ChatCommandContext& ctx) override {
|
||||
if (!ctx.args.empty()) {
|
||||
std::string focusArg = ctx.args;
|
||||
|
||||
// Evaluate conditionals if present
|
||||
uint64_t focusCmdOverride = static_cast<uint64_t>(-1);
|
||||
if (!focusArg.empty() && focusArg.front() == '[') {
|
||||
focusArg = evaluateMacroConditionals(focusArg, ctx.gameHandler, focusCmdOverride);
|
||||
if (focusArg.empty() && focusCmdOverride == static_cast<uint64_t>(-1)) return {};
|
||||
trimInPlace(focusArg);
|
||||
}
|
||||
|
||||
if (focusCmdOverride != static_cast<uint64_t>(-1) && focusCmdOverride != 0) {
|
||||
ctx.gameHandler.setFocus(focusCmdOverride);
|
||||
} else if (!focusArg.empty()) {
|
||||
std::string focusArgLower = focusArg;
|
||||
for (char& c : focusArgLower) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
uint64_t bestGuid = findNearestByName(ctx.gameHandler, focusArgLower);
|
||||
if (bestGuid) {
|
||||
ctx.gameHandler.setFocus(bestGuid);
|
||||
} else {
|
||||
game::MessageChatData msg;
|
||||
msg.type = game::ChatType::SYSTEM;
|
||||
msg.language = game::ChatLanguage::UNIVERSAL;
|
||||
msg.message = "No unit matching '" + focusArg + "' found.";
|
||||
ctx.gameHandler.addLocalChatMessage(msg);
|
||||
}
|
||||
}
|
||||
} else if (ctx.gameHandler.hasTarget()) {
|
||||
ctx.gameHandler.setFocus(ctx.gameHandler.getTargetGuid());
|
||||
} else {
|
||||
game::MessageChatData msg;
|
||||
msg.type = game::ChatType::SYSTEM;
|
||||
msg.language = game::ChatLanguage::UNIVERSAL;
|
||||
msg.message = "You must target a unit to set as focus.";
|
||||
ctx.gameHandler.addLocalChatMessage(msg);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
std::vector<std::string> aliases() const override { return {"focus"}; }
|
||||
std::string helpText() const override { return "Set focus target"; }
|
||||
};
|
||||
|
||||
// --- /clearfocus ---
|
||||
class ClearFocusCommand : public IChatCommand {
|
||||
public:
|
||||
ChatCommandResult execute(ChatCommandContext& ctx) override {
|
||||
ctx.gameHandler.clearFocus();
|
||||
return {};
|
||||
}
|
||||
std::vector<std::string> aliases() const override { return {"clearfocus"}; }
|
||||
std::string helpText() const override { return "Clear focus target"; }
|
||||
};
|
||||
|
||||
// --- Registration ---
|
||||
void registerTargetCommands(ChatCommandRegistry& reg) {
|
||||
reg.registerCommand(std::make_unique<TargetCommand>());
|
||||
reg.registerCommand(std::make_unique<ClearTargetCommand>());
|
||||
reg.registerCommand(std::make_unique<TargetEnemyCommand>());
|
||||
reg.registerCommand(std::make_unique<TargetFriendCommand>());
|
||||
reg.registerCommand(std::make_unique<TargetLastTargetCommand>());
|
||||
reg.registerCommand(std::make_unique<TargetLastEnemyCommand>());
|
||||
reg.registerCommand(std::make_unique<TargetLastFriendCommand>());
|
||||
reg.registerCommand(std::make_unique<FocusCommand>());
|
||||
reg.registerCommand(std::make_unique<ClearFocusCommand>());
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace wowee
|
||||
Loading…
Add table
Add a link
Reference in a new issue