mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-11 11:33: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
223
src/ui/chat/macro_evaluator.cpp
Normal file
223
src/ui/chat/macro_evaluator.cpp
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
// MacroEvaluator — WoW macro conditional parser and evaluator.
|
||||
// Moved from evaluateMacroConditionals() in chat_panel_commands.cpp (Phase 4.4).
|
||||
#include "ui/chat/macro_evaluator.hpp"
|
||||
#include "ui/chat/i_game_state.hpp"
|
||||
#include "ui/chat/i_modifier_state.hpp"
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <vector>
|
||||
|
||||
namespace wowee { namespace ui {
|
||||
|
||||
MacroEvaluator::MacroEvaluator(IGameState& gameState, IModifierState& modState)
|
||||
: gameState_(gameState), modState_(modState) {}
|
||||
|
||||
uint64_t MacroEvaluator::resolveEffectiveTarget(uint64_t tgt) const {
|
||||
if (tgt != static_cast<uint64_t>(-1) && tgt != 0)
|
||||
return tgt;
|
||||
return gameState_.getTargetGuid();
|
||||
}
|
||||
|
||||
bool MacroEvaluator::evalCondition(const std::string& raw, uint64_t& tgt) const {
|
||||
// Trim
|
||||
std::string c = raw;
|
||||
size_t s = c.find_first_not_of(" \t");
|
||||
c = (s != std::string::npos) ? c.substr(s) : "";
|
||||
size_t e = c.find_last_not_of(" \t");
|
||||
if (e != std::string::npos) c.resize(e + 1);
|
||||
if (c.empty()) return true;
|
||||
|
||||
// --- @target specifiers ---
|
||||
if (c[0] == '@') {
|
||||
std::string spec = c.substr(1);
|
||||
if (spec == "player") tgt = gameState_.getPlayerGuid();
|
||||
else if (spec == "focus") tgt = gameState_.getFocusGuid();
|
||||
else if (spec == "target") tgt = gameState_.getTargetGuid();
|
||||
else if (spec == "pet") {
|
||||
uint64_t pg = gameState_.getPetGuid();
|
||||
if (pg != 0) tgt = pg; else return false;
|
||||
}
|
||||
else if (spec == "mouseover") {
|
||||
uint64_t mo = gameState_.getMouseoverGuid();
|
||||
if (mo != 0) tgt = mo; else return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- target=X specifiers ---
|
||||
if (c.rfind("target=", 0) == 0) {
|
||||
std::string spec = c.substr(7);
|
||||
if (spec == "player") tgt = gameState_.getPlayerGuid();
|
||||
else if (spec == "focus") tgt = gameState_.getFocusGuid();
|
||||
else if (spec == "target") tgt = gameState_.getTargetGuid();
|
||||
else if (spec == "pet") {
|
||||
uint64_t pg = gameState_.getPetGuid();
|
||||
if (pg != 0) tgt = pg; else return false;
|
||||
}
|
||||
else if (spec == "mouseover") {
|
||||
uint64_t mo = gameState_.getMouseoverGuid();
|
||||
if (mo != 0) tgt = mo; else return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- Modifier keys ---
|
||||
const bool shiftHeld = modState_.isShiftHeld();
|
||||
const bool ctrlHeld = modState_.isCtrlHeld();
|
||||
const bool altHeld = modState_.isAltHeld();
|
||||
const bool anyMod = shiftHeld || ctrlHeld || altHeld;
|
||||
|
||||
if (c == "nomod" || c == "mod:none") return !anyMod;
|
||||
if (c.rfind("mod:", 0) == 0) {
|
||||
std::string mods = c.substr(4);
|
||||
bool ok = true;
|
||||
if (mods.find("shift") != std::string::npos && !shiftHeld) ok = false;
|
||||
if (mods.find("ctrl") != std::string::npos && !ctrlHeld) ok = false;
|
||||
if (mods.find("alt") != std::string::npos && !altHeld) ok = false;
|
||||
return ok;
|
||||
}
|
||||
|
||||
// --- Combat ---
|
||||
if (c == "combat") return gameState_.isInCombat();
|
||||
if (c == "nocombat") return !gameState_.isInCombat();
|
||||
|
||||
// --- Effective target for exists/dead/help/harm ---
|
||||
uint64_t eff = resolveEffectiveTarget(tgt);
|
||||
|
||||
if (c == "exists") return gameState_.entityExists(eff);
|
||||
if (c == "noexists") return !gameState_.entityExists(eff);
|
||||
|
||||
if (c == "dead") return gameState_.entityIsDead(eff);
|
||||
if (c == "nodead") return !gameState_.entityIsDead(eff);
|
||||
|
||||
if (c == "harm" || c == "nohelp") return gameState_.entityIsHostile(eff);
|
||||
if (c == "help" || c == "noharm") return !gameState_.entityIsHostile(eff);
|
||||
|
||||
// --- Mounted / swimming / flying ---
|
||||
if (c == "mounted") return gameState_.isMounted();
|
||||
if (c == "nomounted") return !gameState_.isMounted();
|
||||
if (c == "swimming") return gameState_.isSwimming();
|
||||
if (c == "noswimming") return !gameState_.isSwimming();
|
||||
if (c == "flying") return gameState_.isFlying();
|
||||
if (c == "noflying") return !gameState_.isFlying();
|
||||
|
||||
// --- Channeling / casting ---
|
||||
if (c == "channeling") return gameState_.isCasting() && gameState_.isChanneling();
|
||||
if (c == "nochanneling") return !(gameState_.isCasting() && gameState_.isChanneling());
|
||||
if (c.rfind("channeling:", 0) == 0 && c.size() > 11) {
|
||||
if (!gameState_.isChanneling()) return false;
|
||||
std::string want = c.substr(11);
|
||||
for (char& ch : want) ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
|
||||
uint32_t castSpellId = gameState_.getCurrentCastSpellId();
|
||||
std::string sn = gameState_.getSpellName(castSpellId);
|
||||
for (char& ch : sn) ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
|
||||
return sn == want;
|
||||
}
|
||||
if (c == "casting") return gameState_.isCasting();
|
||||
if (c == "nocasting") return !gameState_.isCasting();
|
||||
|
||||
// --- Stealthed ---
|
||||
if (c == "stealthed") return gameState_.isStealthed();
|
||||
if (c == "nostealthed") return !gameState_.isStealthed();
|
||||
|
||||
// --- Pet ---
|
||||
if (c == "pet") return gameState_.hasPet();
|
||||
if (c == "nopet") return !gameState_.hasPet();
|
||||
|
||||
// --- Indoors / outdoors ---
|
||||
if (c == "indoors" || c == "nooutdoors") return gameState_.isIndoors();
|
||||
if (c == "outdoors" || c == "noindoors") return !gameState_.isIndoors();
|
||||
|
||||
// --- Group / raid ---
|
||||
if (c == "group" || c == "party") return gameState_.isInGroup();
|
||||
if (c == "nogroup") return !gameState_.isInGroup();
|
||||
if (c == "raid") return gameState_.isInRaid();
|
||||
if (c == "noraid") return !gameState_.isInRaid();
|
||||
|
||||
// --- Talent spec ---
|
||||
if (c.rfind("spec:", 0) == 0) {
|
||||
uint8_t wantSpec = 0;
|
||||
try { wantSpec = static_cast<uint8_t>(std::stoul(c.substr(5))); } catch (...) {}
|
||||
return wantSpec > 0 && gameState_.getActiveTalentSpec() == (wantSpec - 1);
|
||||
}
|
||||
|
||||
// --- Form / stance ---
|
||||
if (c == "noform" || c == "nostance" || c == "form:0" || c == "stance:0")
|
||||
return !gameState_.hasFormAura();
|
||||
|
||||
// --- Buff / debuff ---
|
||||
if (c.rfind("buff:", 0) == 0 && c.size() > 5)
|
||||
return gameState_.hasAuraByName(tgt, c.substr(5), false);
|
||||
if (c.rfind("nobuff:", 0) == 0 && c.size() > 7)
|
||||
return !gameState_.hasAuraByName(tgt, c.substr(7), false);
|
||||
if (c.rfind("debuff:", 0) == 0 && c.size() > 7)
|
||||
return gameState_.hasAuraByName(tgt, c.substr(7), true);
|
||||
if (c.rfind("nodebuff:", 0) == 0 && c.size() > 9)
|
||||
return !gameState_.hasAuraByName(tgt, c.substr(9), true);
|
||||
|
||||
// --- Vehicle ---
|
||||
if (c == "vehicle") return gameState_.getVehicleId() != 0;
|
||||
if (c == "novehicle") return gameState_.getVehicleId() == 0;
|
||||
|
||||
// Unknown → permissive (don't block)
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroEvaluator::evaluate(const std::string& rawArg,
|
||||
uint64_t& targetOverride) const {
|
||||
targetOverride = static_cast<uint64_t>(-1);
|
||||
|
||||
// Split rawArg on ';' → alternatives
|
||||
std::vector<std::string> alts;
|
||||
{
|
||||
std::string cur;
|
||||
for (char ch : rawArg) {
|
||||
if (ch == ';') { alts.push_back(cur); cur.clear(); }
|
||||
else cur += ch;
|
||||
}
|
||||
alts.push_back(cur);
|
||||
}
|
||||
|
||||
for (auto& alt : alts) {
|
||||
// Trim
|
||||
size_t fs = alt.find_first_not_of(" \t");
|
||||
if (fs == std::string::npos) continue;
|
||||
alt = alt.substr(fs);
|
||||
size_t ls = alt.find_last_not_of(" \t");
|
||||
if (ls != std::string::npos) alt.resize(ls + 1);
|
||||
|
||||
if (!alt.empty() && alt[0] == '[') {
|
||||
size_t close = alt.find(']');
|
||||
if (close == std::string::npos) continue;
|
||||
std::string condStr = alt.substr(1, close - 1);
|
||||
std::string argPart = alt.substr(close + 1);
|
||||
// Trim argPart
|
||||
size_t as = argPart.find_first_not_of(" \t");
|
||||
argPart = (as != std::string::npos) ? argPart.substr(as) : "";
|
||||
|
||||
// Evaluate comma-separated conditions
|
||||
uint64_t tgt = static_cast<uint64_t>(-1);
|
||||
bool pass = true;
|
||||
size_t cp = 0;
|
||||
while (pass) {
|
||||
size_t comma = condStr.find(',', cp);
|
||||
std::string tok = condStr.substr(cp,
|
||||
comma == std::string::npos ? std::string::npos : comma - cp);
|
||||
if (!evalCondition(tok, tgt)) { pass = false; break; }
|
||||
if (comma == std::string::npos) break;
|
||||
cp = comma + 1;
|
||||
}
|
||||
if (pass) {
|
||||
if (tgt != static_cast<uint64_t>(-1)) targetOverride = tgt;
|
||||
return argPart;
|
||||
}
|
||||
} else {
|
||||
// No condition block — default fallback always matches
|
||||
return alt;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace wowee
|
||||
Loading…
Add table
Add a link
Reference in a new issue