feat: add /stopmacro support and low durability warning for equipped items

- /stopmacro [conditions] halts remaining macro commands; supports all existing
  macro conditionals ([combat], [nocombat], [mod:shift], etc.) via the sentinel
  action trick on evaluateMacroConditionals
- macroStopped_ flag in GameScreen; executeMacroText resets and checks it after
  each command so /stopmacro mid-macro skips all subsequent lines
- Emit a "X is about to break!" UI error + system chat when an equipped item's
  durability drops below 20% via SMSG_UPDATE_OBJECT field delta; warning fires
  once per threshold crossing (prevDur >= maxDur/5, newDur < maxDur/5)
This commit is contained in:
Kelsi 2026-03-18 04:14:44 -07:00
parent d7c377292e
commit 09b0bea981
3 changed files with 52 additions and 0 deletions

View file

@ -5564,12 +5564,16 @@ static std::string evaluateMacroConditionals(const std::string& rawArg,
// Execute all non-comment lines of a macro body in sequence.
// In WoW, every line executes per click; the server enforces spell-cast limits.
// /stopmacro (with optional conditionals) halts the remaining commands early.
void GameScreen::executeMacroText(game::GameHandler& gameHandler, const std::string& macroText) {
macroStopped_ = false;
for (const auto& cmd : allMacroCommands(macroText)) {
strncpy(chatInputBuffer, cmd.c_str(), sizeof(chatInputBuffer) - 1);
chatInputBuffer[sizeof(chatInputBuffer) - 1] = '\0';
sendChatMessage(gameHandler);
if (macroStopped_) break;
}
macroStopped_ = false;
}
// /castsequence persistent state — shared across all macros using the same spell list.
@ -5633,6 +5637,29 @@ void GameScreen::sendChatMessage(game::GameHandler& gameHandler) {
return;
}
// /stopmacro [conditions]
// Halts execution of the current macro (remaining lines are skipped).
// With a condition block, only stops if the conditions evaluate to true.
// /stopmacro → always stops
// /stopmacro [combat] → stops only while in combat
// /stopmacro [nocombat] → stops only when not in combat
if (cmdLower == "stopmacro") {
bool shouldStop = true;
if (spacePos != std::string::npos) {
std::string condArg = command.substr(spacePos + 1);
while (!condArg.empty() && condArg.front() == ' ') condArg.erase(condArg.begin());
if (!condArg.empty() && condArg.front() == '[') {
// Append a sentinel action so evaluateMacroConditionals can signal a match.
uint64_t tgtOver = static_cast<uint64_t>(-1);
std::string hit = evaluateMacroConditionals(condArg + " __stop__", gameHandler, tgtOver);
shouldStop = !hit.empty();
}
}
if (shouldStop) macroStopped_ = true;
chatInputBuffer[0] = '\0';
return;
}
// /invite command
if (cmdLower == "invite" && spacePos != std::string::npos) {
std::string targetName = command.substr(spacePos + 1);