mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
fix: escape newlines in macro cfg persistence; execute all macro lines
- Macro text is now escaped (\\n, \\\\) on save and unescaped on load, fixing multiline macros silently truncating after the first line in the character config file. - executeMacroText() runs every non-comment line of a macro body in sequence (WoW behaviour), replacing the firstMacroCommand() approach that only fired the first actionable line. The server still enforces one spell-cast per click; non-cast commands (target, equip, pet, etc.) now all execute correctly in the same macro activation.
This commit is contained in:
parent
c676d99fc2
commit
ed3bca3d17
3 changed files with 60 additions and 23 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<uint32_t>(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_"
|
||||
|
|
|
|||
|
|
@ -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<std::string> 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<std::string> allMacroCommands(const std::string& macroText) {
|
||||
std::vector<std::string> 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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue