From ae3e57ac3b903837c9f80f6862a3106c1b2b42df Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 18 Mar 2026 02:30:35 -0700 Subject: [PATCH] feat: add /cancelform, /cancelshapeshift, /cancelaura slash commands These are standard WoW macro commands: - /cancelform / /cancelshapeshift: exits current shapeshift form by cancelling the first permanent aura (flag 0x20) on the player - /cancelaura : cancels a specific player buff by spell name or numeric ID (e.g. /cancelaura Stealth, /cancelaura #1784) Also expand the Tab-autocomplete command list to include /cancelaura, /cancelform, /cancelshapeshift, /dismount, /sit, /stand, /startattack, /stopcasting, /target, and other commands that were previously missing. --- src/ui/game_screen.cpp | 62 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index 1b32c7f2..55dd1a49 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -2594,16 +2594,19 @@ void GameScreen::renderChatWindow(game::GameHandler& gameHandler) { for (auto& ch : lowerWord) ch = static_cast(std::tolower(static_cast(ch))); static const std::vector kCmds = { - "/afk", "/away", "/cast", "/chathelp", "/clear", + "/afk", "/away", "/cancelaura", "/cancelform", "/cancelshapeshift", + "/cast", "/chathelp", "/clear", "/dance", "/do", "/dnd", "/e", "/emote", - "/cl", "/combatlog", "/equip", "/follow", "/g", "/guild", "/guildinfo", + "/cl", "/combatlog", "/dismount", "/equip", "/follow", + "/g", "/guild", "/guildinfo", "/gmticket", "/grouploot", "/i", "/instance", "/invite", "/j", "/join", "/kick", "/l", "/leave", "/local", "/me", "/p", "/party", "/r", "/raid", "/raidwarning", "/random", "/reply", "/roll", - "/s", "/say", "/setloot", "/shout", - "/stopattack", "/stopfollow", "/t", "/time", + "/s", "/say", "/setloot", "/shout", "/sit", "/stand", + "/startattack", "/stopattack", "/stopfollow", "/stopcasting", + "/t", "/target", "/time", "/trade", "/uninvite", "/use", "/w", "/whisper", "/who", "/wts", "/wtb", "/y", "/yell", "/zone" }; @@ -5619,6 +5622,57 @@ void GameScreen::sendChatMessage(game::GameHandler& gameHandler) { return; } + // /cancelform / /cancelshapeshift — leave current shapeshift/stance + if (cmdLower == "cancelform" || cmdLower == "cancelshapeshift") { + // Cancel the first permanent shapeshift aura the player has + for (const auto& aura : gameHandler.getPlayerAuras()) { + if (aura.spellId == 0) continue; + // Permanent shapeshift auras have the permanent flag (0x20) set + if (aura.flags & 0x20) { + gameHandler.cancelAura(aura.spellId); + break; + } + } + chatInputBuffer[0] = '\0'; + return; + } + + // /cancelaura — cancel a specific buff by name or ID + if (cmdLower == "cancelaura" && spacePos != std::string::npos) { + std::string auraArg = command.substr(spacePos + 1); + while (!auraArg.empty() && auraArg.front() == ' ') auraArg.erase(auraArg.begin()); + while (!auraArg.empty() && auraArg.back() == ' ') auraArg.pop_back(); + // Try numeric ID first + { + std::string numStr = auraArg; + if (!numStr.empty() && numStr.front() == '#') numStr.erase(numStr.begin()); + bool isNum = !numStr.empty() && + std::all_of(numStr.begin(), numStr.end(), + [](unsigned char c){ return std::isdigit(c); }); + if (isNum) { + uint32_t spellId = 0; + try { spellId = static_cast(std::stoul(numStr)); } catch (...) {} + if (spellId) gameHandler.cancelAura(spellId); + chatInputBuffer[0] = '\0'; + return; + } + } + // Name match against player auras + std::string argLow = auraArg; + for (char& c : argLow) c = static_cast(std::tolower(static_cast(c))); + for (const auto& aura : gameHandler.getPlayerAuras()) { + if (aura.spellId == 0) continue; + std::string sn = gameHandler.getSpellName(aura.spellId); + for (char& c : sn) c = static_cast(std::tolower(static_cast(c))); + if (sn == argLow) { + gameHandler.cancelAura(aura.spellId); + break; + } + } + chatInputBuffer[0] = '\0'; + return; + } + // /sit command if (cmdLower == "sit") { gameHandler.setStandState(1); // 1 = sit