diff --git a/include/ui/game_screen.hpp b/include/ui/game_screen.hpp index 26db5c83..0b958cb2 100644 --- a/include/ui/game_screen.hpp +++ b/include/ui/game_screen.hpp @@ -278,6 +278,7 @@ private: * Send chat message */ void sendChatMessage(game::GameHandler& gameHandler); + void executeMacroText(game::GameHandler& gameHandler, const std::string& macroText); /** * Get chat type name diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 9089d438..84010de9 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -23509,10 +23509,19 @@ void GameHandler::saveCharacterConfig() { out << "action_bar_" << i << "_id=" << actionBar[i].id << "\n"; } - // Save client-side macro text + // Save client-side macro text (escape newlines as \n literal) for (const auto& [id, text] : macros_) { - if (!text.empty()) - out << "macro_" << id << "_text=" << text << "\n"; + if (!text.empty()) { + std::string escaped; + escaped.reserve(text.size()); + for (char c : text) { + if (c == '\n') { escaped += "\\n"; } + else if (c == '\r') { /* skip CR */ } + else if (c == '\\') { escaped += "\\\\"; } + else { escaped += c; } + } + out << "macro_" << id << "_text=" << escaped << "\n"; + } } // Save quest log @@ -23562,8 +23571,21 @@ void GameHandler::loadCharacterConfig() { if (secondUnder == std::string::npos) continue; uint32_t macroId = 0; try { macroId = static_cast(std::stoul(key.substr(firstUnder, secondUnder - firstUnder))); } catch (...) { continue; } - if (key.substr(secondUnder + 1) == "text" && !val.empty()) - macros_[macroId] = val; + if (key.substr(secondUnder + 1) == "text" && !val.empty()) { + // Unescape \n and \\ sequences + std::string unescaped; + unescaped.reserve(val.size()); + for (size_t i = 0; i < val.size(); ++i) { + if (val[i] == '\\' && i + 1 < val.size()) { + if (val[i+1] == 'n') { unescaped += '\n'; ++i; } + else if (val[i+1] == '\\') { unescaped += '\\'; ++i; } + else { unescaped += val[i]; } + } else { + unescaped += val[i]; + } + } + macros_[macroId] = std::move(unescaped); + } } else if (key.rfind("action_bar_", 0) == 0) { // Parse action_bar_N_type or action_bar_N_id size_t firstUnderscore = 11; // length of "action_bar_" diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index 5af139af..dba74cda 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -258,6 +258,7 @@ bool GameScreen::shouldShowMessage(const game::MessageChatData& msg, int tabInde // Forward declaration — defined near sendChatMessage below static std::string firstMacroCommand(const std::string& macroText); +static std::vector allMacroCommands(const std::string& macroText); void GameScreen::render(game::GameHandler& gameHandler) { // Set up chat bubble callback (once) @@ -2843,12 +2844,7 @@ void GameScreen::processTargetInput(game::GameHandler& gameHandler) { } else if (bar[slotIdx].type == game::ActionBarSlot::ITEM && bar[slotIdx].id != 0) { gameHandler.useItemById(bar[slotIdx].id); } else if (bar[slotIdx].type == game::ActionBarSlot::MACRO) { - std::string cmd = firstMacroCommand(gameHandler.getMacroText(bar[slotIdx].id)); - if (!cmd.empty()) { - strncpy(chatInputBuffer, cmd.c_str(), sizeof(chatInputBuffer) - 1); - chatInputBuffer[sizeof(chatInputBuffer) - 1] = '\0'; - sendChatMessage(gameHandler); - } + executeMacroText(gameHandler, gameHandler.getMacroText(bar[slotIdx].id)); } } } @@ -5263,6 +5259,34 @@ static std::string firstMacroCommand(const std::string& macroText) { return {}; } +// Collect all non-comment, non-empty lines from a macro body. +static std::vector allMacroCommands(const std::string& macroText) { + std::vector cmds; + size_t pos = 0; + while (pos <= macroText.size()) { + size_t nl = macroText.find('\n', pos); + std::string line = (nl != std::string::npos) ? macroText.substr(pos, nl - pos) : macroText.substr(pos); + if (!line.empty() && line.back() == '\r') line.pop_back(); + size_t start = line.find_first_not_of(" \t"); + if (start != std::string::npos) line = line.substr(start); + if (!line.empty() && line.front() != '#') + cmds.push_back(std::move(line)); + if (nl == std::string::npos) break; + pos = nl + 1; + } + return cmds; +} + +// Execute all non-comment lines of a macro body in sequence. +// In WoW, every line executes per click; the server enforces spell-cast limits. +void GameScreen::executeMacroText(game::GameHandler& gameHandler, const std::string& macroText) { + for (const auto& cmd : allMacroCommands(macroText)) { + strncpy(chatInputBuffer, cmd.c_str(), sizeof(chatInputBuffer) - 1); + chatInputBuffer[sizeof(chatInputBuffer) - 1] = '\0'; + sendChatMessage(gameHandler); + } +} + void GameScreen::sendChatMessage(game::GameHandler& gameHandler) { if (strlen(chatInputBuffer) > 0) { std::string input(chatInputBuffer); @@ -7672,12 +7696,7 @@ void GameScreen::renderActionBar(game::GameHandler& gameHandler) { } else if (slot.type == game::ActionBarSlot::ITEM && slot.id != 0) { gameHandler.useItemById(slot.id); } else if (slot.type == game::ActionBarSlot::MACRO) { - std::string cmd = firstMacroCommand(gameHandler.getMacroText(slot.id)); - if (!cmd.empty()) { - strncpy(chatInputBuffer, cmd.c_str(), sizeof(chatInputBuffer) - 1); - chatInputBuffer[sizeof(chatInputBuffer) - 1] = '\0'; - sendChatMessage(gameHandler); - } + executeMacroText(gameHandler, gameHandler.getMacroText(slot.id)); } } @@ -7710,12 +7729,7 @@ void GameScreen::renderActionBar(game::GameHandler& gameHandler) { ImGui::TextDisabled("Macro #%u", slot.id); ImGui::Separator(); if (ImGui::MenuItem("Execute")) { - std::string cmd = firstMacroCommand(gameHandler.getMacroText(slot.id)); - if (!cmd.empty()) { - strncpy(chatInputBuffer, cmd.c_str(), sizeof(chatInputBuffer) - 1); - chatInputBuffer[sizeof(chatInputBuffer) - 1] = '\0'; - sendChatMessage(gameHandler); - } + executeMacroText(gameHandler, gameHandler.getMacroText(slot.id)); } if (ImGui::MenuItem("Edit")) { const std::string& txt = gameHandler.getMacroText(slot.id);