mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
feat: implement WoW macro conditional evaluator for /cast
Adds evaluateMacroConditionals() which parses the [cond1,cond2] Spell; [cond3] Spell2; Default syntax and returns the first matching alternative. Supported conditions: - mod:shift/ctrl/alt, nomod — keyboard modifier state - target=player/focus/target, @player/@focus/@target — target override - help / harm (noharm / nohelp) — target faction check - dead / nodead — target health check - exists / noexists — target presence check - combat / nocombat — player combat state - noform / nostance / form:0 — shapeshift/stance state - Unknown conditions are permissive (true) to avoid false negatives. /cast now resolves conditionals before spell lookup and routes castSpell() to the [target=X] override GUID when specified. isHostileFaction() exposed as isHostileFactionPublic() for UI use.
This commit is contained in:
parent
ed3bca3d17
commit
30513d0f06
2 changed files with 186 additions and 2 deletions
|
|
@ -1888,6 +1888,7 @@ public:
|
||||||
|
|
||||||
bool isMounted() const { return currentMountDisplayId_ != 0; }
|
bool isMounted() const { return currentMountDisplayId_ != 0; }
|
||||||
bool isHostileAttacker(uint64_t guid) const { return hostileAttackers_.count(guid) > 0; }
|
bool isHostileAttacker(uint64_t guid) const { return hostileAttackers_.count(guid) > 0; }
|
||||||
|
bool isHostileFactionPublic(uint32_t factionTemplateId) const { return isHostileFaction(factionTemplateId); }
|
||||||
float getServerRunSpeed() const { return serverRunSpeed_; }
|
float getServerRunSpeed() const { return serverRunSpeed_; }
|
||||||
float getServerWalkSpeed() const { return serverWalkSpeed_; }
|
float getServerWalkSpeed() const { return serverWalkSpeed_; }
|
||||||
float getServerSwimSpeed() const { return serverSwimSpeed_; }
|
float getServerSwimSpeed() const { return serverSwimSpeed_; }
|
||||||
|
|
|
||||||
|
|
@ -259,6 +259,9 @@ bool GameScreen::shouldShowMessage(const game::MessageChatData& msg, int tabInde
|
||||||
// Forward declaration — defined near sendChatMessage below
|
// Forward declaration — defined near sendChatMessage below
|
||||||
static std::string firstMacroCommand(const std::string& macroText);
|
static std::string firstMacroCommand(const std::string& macroText);
|
||||||
static std::vector<std::string> allMacroCommands(const std::string& macroText);
|
static std::vector<std::string> allMacroCommands(const std::string& macroText);
|
||||||
|
static std::string evaluateMacroConditionals(const std::string& rawArg,
|
||||||
|
game::GameHandler& gameHandler,
|
||||||
|
uint64_t& targetOverride);
|
||||||
|
|
||||||
void GameScreen::render(game::GameHandler& gameHandler) {
|
void GameScreen::render(game::GameHandler& gameHandler) {
|
||||||
// Set up chat bubble callback (once)
|
// Set up chat bubble callback (once)
|
||||||
|
|
@ -5277,6 +5280,170 @@ static std::vector<std::string> allMacroCommands(const std::string& macroText) {
|
||||||
return cmds;
|
return cmds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// WoW macro conditional evaluator
|
||||||
|
// Parses: [cond1,cond2] Spell1; [cond3] Spell2; DefaultSpell
|
||||||
|
// Returns the first matching alternative's argument, or "" if none matches.
|
||||||
|
// targetOverride is set to a specific GUID if [target=X] was in the conditions,
|
||||||
|
// or left as UINT64_MAX to mean "use the normal target".
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
static std::string evaluateMacroConditionals(const std::string& rawArg,
|
||||||
|
game::GameHandler& gameHandler,
|
||||||
|
uint64_t& targetOverride) {
|
||||||
|
targetOverride = static_cast<uint64_t>(-1);
|
||||||
|
|
||||||
|
auto& input = core::Input::getInstance();
|
||||||
|
|
||||||
|
const bool shiftHeld = input.isKeyPressed(SDL_SCANCODE_LSHIFT) ||
|
||||||
|
input.isKeyPressed(SDL_SCANCODE_RSHIFT);
|
||||||
|
const bool ctrlHeld = input.isKeyPressed(SDL_SCANCODE_LCTRL) ||
|
||||||
|
input.isKeyPressed(SDL_SCANCODE_RCTRL);
|
||||||
|
const bool altHeld = input.isKeyPressed(SDL_SCANCODE_LALT) ||
|
||||||
|
input.isKeyPressed(SDL_SCANCODE_RALT);
|
||||||
|
const bool anyMod = shiftHeld || ctrlHeld || altHeld;
|
||||||
|
|
||||||
|
// Split rawArg on ';' → alternatives
|
||||||
|
std::vector<std::string> alts;
|
||||||
|
{
|
||||||
|
std::string cur;
|
||||||
|
for (char c : rawArg) {
|
||||||
|
if (c == ';') { alts.push_back(cur); cur.clear(); }
|
||||||
|
else cur += c;
|
||||||
|
}
|
||||||
|
alts.push_back(cur);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluate a single comma-separated condition token.
|
||||||
|
// tgt is updated if a target= or @ specifier is found.
|
||||||
|
auto evalCond = [&](const std::string& raw, uint64_t& tgt) -> bool {
|
||||||
|
std::string c = raw;
|
||||||
|
// trim
|
||||||
|
size_t s = c.find_first_not_of(" \t"); if (s) 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: @player, @focus, @mouseover (mouseover → skip, no tracking)
|
||||||
|
if (!c.empty() && c[0] == '@') {
|
||||||
|
std::string spec = c.substr(1);
|
||||||
|
if (spec == "player") tgt = gameHandler.getPlayerGuid();
|
||||||
|
else if (spec == "focus") tgt = gameHandler.getFocusGuid();
|
||||||
|
else if (spec == "target") tgt = gameHandler.getTargetGuid();
|
||||||
|
// mouseover: no tracking yet — treat as "use current target"
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// target=X specifiers
|
||||||
|
if (c.rfind("target=", 0) == 0) {
|
||||||
|
std::string spec = c.substr(7);
|
||||||
|
if (spec == "player") tgt = gameHandler.getPlayerGuid();
|
||||||
|
else if (spec == "focus") tgt = gameHandler.getFocusGuid();
|
||||||
|
else if (spec == "target") tgt = gameHandler.getTargetGuid();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mod / nomod
|
||||||
|
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 / nocombat
|
||||||
|
if (c == "combat") return gameHandler.isInCombat();
|
||||||
|
if (c == "nocombat") return !gameHandler.isInCombat();
|
||||||
|
|
||||||
|
// Helper to get the effective target entity
|
||||||
|
auto effTarget = [&]() -> std::shared_ptr<game::Entity> {
|
||||||
|
if (tgt != static_cast<uint64_t>(-1) && tgt != 0)
|
||||||
|
return gameHandler.getEntityManager().getEntity(tgt);
|
||||||
|
return gameHandler.getTarget();
|
||||||
|
};
|
||||||
|
|
||||||
|
// exists / noexists
|
||||||
|
if (c == "exists") return effTarget() != nullptr;
|
||||||
|
if (c == "noexists") return effTarget() == nullptr;
|
||||||
|
|
||||||
|
// dead / nodead
|
||||||
|
if (c == "dead") {
|
||||||
|
auto t = effTarget();
|
||||||
|
auto u = t ? std::dynamic_pointer_cast<game::Unit>(t) : nullptr;
|
||||||
|
return u && u->getHealth() == 0;
|
||||||
|
}
|
||||||
|
if (c == "nodead") {
|
||||||
|
auto t = effTarget();
|
||||||
|
auto u = t ? std::dynamic_pointer_cast<game::Unit>(t) : nullptr;
|
||||||
|
return u && u->getHealth() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// help (friendly) / harm (hostile) and their no- variants
|
||||||
|
auto unitHostile = [&](const std::shared_ptr<game::Entity>& t) -> bool {
|
||||||
|
if (!t) return false;
|
||||||
|
auto u = std::dynamic_pointer_cast<game::Unit>(t);
|
||||||
|
return u && gameHandler.isHostileFactionPublic(u->getFactionTemplate());
|
||||||
|
};
|
||||||
|
if (c == "harm" || c == "nohelp") { return unitHostile(effTarget()); }
|
||||||
|
if (c == "help" || c == "noharm") { return !unitHostile(effTarget()); }
|
||||||
|
|
||||||
|
// noform / nostance — player is NOT in a shapeshift/stance
|
||||||
|
if (c == "noform" || c == "nostance") {
|
||||||
|
for (const auto& a : gameHandler.getPlayerAuras())
|
||||||
|
if (!a.isEmpty() && a.maxDurationMs == -1) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// form:0 same as noform
|
||||||
|
if (c == "form:0" || c == "stance:0") {
|
||||||
|
for (const auto& a : gameHandler.getPlayerAuras())
|
||||||
|
if (!a.isEmpty() && a.maxDurationMs == -1) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unknown → permissive (don't block)
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
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 (!evalCond(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 {};
|
||||||
|
}
|
||||||
|
|
||||||
// Execute all non-comment lines of a macro body in sequence.
|
// Execute all non-comment lines of a macro body in sequence.
|
||||||
// In WoW, every line executes per click; the server enforces spell-cast limits.
|
// In WoW, every line executes per click; the server enforces spell-cast limits.
|
||||||
void GameScreen::executeMacroText(game::GameHandler& gameHandler, const std::string& macroText) {
|
void GameScreen::executeMacroText(game::GameHandler& gameHandler, const std::string& macroText) {
|
||||||
|
|
@ -6175,6 +6342,18 @@ void GameScreen::sendChatMessage(game::GameHandler& gameHandler) {
|
||||||
while (!spellArg.empty() && spellArg.front() == ' ') spellArg.erase(spellArg.begin());
|
while (!spellArg.empty() && spellArg.front() == ' ') spellArg.erase(spellArg.begin());
|
||||||
while (!spellArg.empty() && spellArg.back() == ' ') spellArg.pop_back();
|
while (!spellArg.empty() && spellArg.back() == ' ') spellArg.pop_back();
|
||||||
|
|
||||||
|
// Evaluate WoW macro conditionals: /cast [mod:shift] Greater Heal; Flash Heal
|
||||||
|
uint64_t castTargetOverride = static_cast<uint64_t>(-1);
|
||||||
|
if (!spellArg.empty() && spellArg.front() == '[') {
|
||||||
|
spellArg = evaluateMacroConditionals(spellArg, gameHandler, castTargetOverride);
|
||||||
|
if (spellArg.empty()) {
|
||||||
|
chatInputBuffer[0] = '\0';
|
||||||
|
return; // No conditional matched — skip cast
|
||||||
|
}
|
||||||
|
while (!spellArg.empty() && spellArg.front() == ' ') spellArg.erase(spellArg.begin());
|
||||||
|
while (!spellArg.empty() && spellArg.back() == ' ') spellArg.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
// Support numeric spell ID: /cast 133 or /cast #133
|
// Support numeric spell ID: /cast 133 or /cast #133
|
||||||
{
|
{
|
||||||
std::string numStr = spellArg;
|
std::string numStr = spellArg;
|
||||||
|
|
@ -6186,7 +6365,9 @@ void GameScreen::sendChatMessage(game::GameHandler& gameHandler) {
|
||||||
uint32_t spellId = 0;
|
uint32_t spellId = 0;
|
||||||
try { spellId = static_cast<uint32_t>(std::stoul(numStr)); } catch (...) {}
|
try { spellId = static_cast<uint32_t>(std::stoul(numStr)); } catch (...) {}
|
||||||
if (spellId != 0) {
|
if (spellId != 0) {
|
||||||
uint64_t targetGuid = gameHandler.hasTarget() ? gameHandler.getTargetGuid() : 0;
|
uint64_t targetGuid = (castTargetOverride != static_cast<uint64_t>(-1))
|
||||||
|
? castTargetOverride
|
||||||
|
: (gameHandler.hasTarget() ? gameHandler.getTargetGuid() : 0);
|
||||||
gameHandler.castSpell(spellId, targetGuid);
|
gameHandler.castSpell(spellId, targetGuid);
|
||||||
}
|
}
|
||||||
chatInputBuffer[0] = '\0';
|
chatInputBuffer[0] = '\0';
|
||||||
|
|
@ -6246,7 +6427,9 @@ void GameScreen::sendChatMessage(game::GameHandler& gameHandler) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bestSpellId) {
|
if (bestSpellId) {
|
||||||
uint64_t targetGuid = gameHandler.hasTarget() ? gameHandler.getTargetGuid() : 0;
|
uint64_t targetGuid = (castTargetOverride != static_cast<uint64_t>(-1))
|
||||||
|
? castTargetOverride
|
||||||
|
: (gameHandler.hasTarget() ? gameHandler.getTargetGuid() : 0);
|
||||||
gameHandler.castSpell(bestSpellId, targetGuid);
|
gameHandler.castSpell(bestSpellId, targetGuid);
|
||||||
} else {
|
} else {
|
||||||
game::MessageChatData sysMsg;
|
game::MessageChatData sysMsg;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue