mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
feat: implement client-side macro text storage and execution
Macros in WoW are client-side — the server sends only a macro index via SMSG_ACTION_BUTTONS, never the text. This commit adds local storage and a UI so macro slots are actually usable. - GameHandler: getMacroText/setMacroText accessors backed by macros_ map; text is persisted to the character .cfg file as macro_N_text= entries - Action bar left-click: MACRO slot executes first line of macro text as a chat/slash command (same path as /cast, /use, etc.) - Context menu: "Execute" and "Edit" items for MACRO slots; "Edit" opens a multiline modal editor (320×80 px, up to 255 chars) with Save/Cancel - Tooltip: shows macro text body below the index; hints "right-click to Edit" when no text is set yet
This commit is contained in:
parent
1588c1029a
commit
2c86fb4fa6
4 changed files with 96 additions and 0 deletions
|
|
@ -885,6 +885,10 @@ public:
|
||||||
const std::array<ActionBarSlot, ACTION_BAR_SLOTS>& getActionBar() const { return actionBar; }
|
const std::array<ActionBarSlot, ACTION_BAR_SLOTS>& getActionBar() const { return actionBar; }
|
||||||
void setActionBarSlot(int slot, ActionBarSlot::Type type, uint32_t id);
|
void setActionBarSlot(int slot, ActionBarSlot::Type type, uint32_t id);
|
||||||
|
|
||||||
|
// Client-side macro text storage (server sends only macro index; text is stored locally)
|
||||||
|
const std::string& getMacroText(uint32_t macroId) const;
|
||||||
|
void setMacroText(uint32_t macroId, const std::string& text);
|
||||||
|
|
||||||
void saveCharacterConfig();
|
void saveCharacterConfig();
|
||||||
void loadCharacterConfig();
|
void loadCharacterConfig();
|
||||||
static std::string getCharacterConfigDir();
|
static std::string getCharacterConfigDir();
|
||||||
|
|
@ -2759,6 +2763,7 @@ private:
|
||||||
|
|
||||||
float castTimeTotal = 0.0f;
|
float castTimeTotal = 0.0f;
|
||||||
std::array<ActionBarSlot, ACTION_BAR_SLOTS> actionBar{};
|
std::array<ActionBarSlot, ACTION_BAR_SLOTS> actionBar{};
|
||||||
|
std::unordered_map<uint32_t, std::string> macros_; // client-side macro text (persisted in char config)
|
||||||
std::vector<AuraSlot> playerAuras;
|
std::vector<AuraSlot> playerAuras;
|
||||||
std::vector<AuraSlot> targetAuras;
|
std::vector<AuraSlot> targetAuras;
|
||||||
std::unordered_map<uint64_t, std::vector<AuraSlot>> unitAurasCache_; // per-unit aura cache
|
std::unordered_map<uint64_t, std::vector<AuraSlot>> unitAurasCache_; // per-unit aura cache
|
||||||
|
|
|
||||||
|
|
@ -201,6 +201,10 @@ private:
|
||||||
// Keybinding customization
|
// Keybinding customization
|
||||||
int pendingRebindAction = -1; // -1 = not rebinding, otherwise action index
|
int pendingRebindAction = -1; // -1 = not rebinding, otherwise action index
|
||||||
bool awaitingKeyPress = false;
|
bool awaitingKeyPress = false;
|
||||||
|
// Macro editor popup state
|
||||||
|
uint32_t macroEditorId_ = 0; // macro index being edited
|
||||||
|
bool macroEditorOpen_ = false; // deferred OpenPopup flag
|
||||||
|
char macroEditorBuf_[256] = {}; // edit buffer
|
||||||
bool pendingUseOriginalSoundtrack = true;
|
bool pendingUseOriginalSoundtrack = true;
|
||||||
bool pendingShowActionBar2 = true; // Show second action bar above main bar
|
bool pendingShowActionBar2 = true; // Show second action bar above main bar
|
||||||
float pendingActionBarScale = 1.0f; // Multiplier for action bar slot size (0.5–1.5)
|
float pendingActionBarScale = 1.0f; // Multiplier for action bar slot size (0.5–1.5)
|
||||||
|
|
|
||||||
|
|
@ -23468,6 +23468,21 @@ std::string GameHandler::getCharacterConfigDir() {
|
||||||
return dir;
|
return dir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const std::string EMPTY_MACRO_TEXT;
|
||||||
|
|
||||||
|
const std::string& GameHandler::getMacroText(uint32_t macroId) const {
|
||||||
|
auto it = macros_.find(macroId);
|
||||||
|
return (it != macros_.end()) ? it->second : EMPTY_MACRO_TEXT;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameHandler::setMacroText(uint32_t macroId, const std::string& text) {
|
||||||
|
if (text.empty())
|
||||||
|
macros_.erase(macroId);
|
||||||
|
else
|
||||||
|
macros_[macroId] = text;
|
||||||
|
saveCharacterConfig();
|
||||||
|
}
|
||||||
|
|
||||||
void GameHandler::saveCharacterConfig() {
|
void GameHandler::saveCharacterConfig() {
|
||||||
const Character* ch = getActiveCharacter();
|
const Character* ch = getActiveCharacter();
|
||||||
if (!ch || ch->name.empty()) return;
|
if (!ch || ch->name.empty()) return;
|
||||||
|
|
@ -23494,6 +23509,12 @@ void GameHandler::saveCharacterConfig() {
|
||||||
out << "action_bar_" << i << "_id=" << actionBar[i].id << "\n";
|
out << "action_bar_" << i << "_id=" << actionBar[i].id << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save client-side macro text
|
||||||
|
for (const auto& [id, text] : macros_) {
|
||||||
|
if (!text.empty())
|
||||||
|
out << "macro_" << id << "_text=" << text << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
// Save quest log
|
// Save quest log
|
||||||
out << "quest_log_count=" << questLog_.size() << "\n";
|
out << "quest_log_count=" << questLog_.size() << "\n";
|
||||||
for (size_t i = 0; i < questLog_.size(); i++) {
|
for (size_t i = 0; i < questLog_.size(); i++) {
|
||||||
|
|
@ -23534,6 +23555,15 @@ void GameHandler::loadCharacterConfig() {
|
||||||
try { savedGender = std::stoi(val); } catch (...) {}
|
try { savedGender = std::stoi(val); } catch (...) {}
|
||||||
} else if (key == "use_female_model") {
|
} else if (key == "use_female_model") {
|
||||||
try { savedUseFemaleModel = std::stoi(val); } catch (...) {}
|
try { savedUseFemaleModel = std::stoi(val); } catch (...) {}
|
||||||
|
} else if (key.rfind("macro_", 0) == 0) {
|
||||||
|
// Parse macro_N_text
|
||||||
|
size_t firstUnder = 6; // length of "macro_"
|
||||||
|
size_t secondUnder = key.find('_', firstUnder);
|
||||||
|
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;
|
||||||
} else if (key.rfind("action_bar_", 0) == 0) {
|
} else if (key.rfind("action_bar_", 0) == 0) {
|
||||||
// Parse action_bar_N_type or action_bar_N_id
|
// Parse action_bar_N_type or action_bar_N_id
|
||||||
size_t firstUnderscore = 11; // length of "action_bar_"
|
size_t firstUnderscore = 11; // length of "action_bar_"
|
||||||
|
|
|
||||||
|
|
@ -7483,6 +7483,16 @@ void GameScreen::renderActionBar(game::GameHandler& gameHandler) {
|
||||||
gameHandler.castSpell(slot.id, target);
|
gameHandler.castSpell(slot.id, target);
|
||||||
} else if (slot.type == game::ActionBarSlot::ITEM && slot.id != 0) {
|
} else if (slot.type == game::ActionBarSlot::ITEM && slot.id != 0) {
|
||||||
gameHandler.useItemById(slot.id);
|
gameHandler.useItemById(slot.id);
|
||||||
|
} else if (slot.type == game::ActionBarSlot::MACRO) {
|
||||||
|
const std::string& text = gameHandler.getMacroText(slot.id);
|
||||||
|
if (!text.empty()) {
|
||||||
|
// Execute first line of macro as a chat command
|
||||||
|
size_t nl = text.find('\n');
|
||||||
|
std::string firstLine = (nl != std::string::npos) ? text.substr(0, nl) : text;
|
||||||
|
strncpy(chatInputBuffer, firstLine.c_str(), sizeof(chatInputBuffer) - 1);
|
||||||
|
chatInputBuffer[sizeof(chatInputBuffer) - 1] = '\0';
|
||||||
|
sendChatMessage(gameHandler);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -7513,6 +7523,24 @@ void GameScreen::renderActionBar(game::GameHandler& gameHandler) {
|
||||||
}
|
}
|
||||||
} else if (slot.type == game::ActionBarSlot::MACRO) {
|
} else if (slot.type == game::ActionBarSlot::MACRO) {
|
||||||
ImGui::TextDisabled("Macro #%u", slot.id);
|
ImGui::TextDisabled("Macro #%u", slot.id);
|
||||||
|
ImGui::Separator();
|
||||||
|
if (ImGui::MenuItem("Execute")) {
|
||||||
|
const std::string& text = gameHandler.getMacroText(slot.id);
|
||||||
|
if (!text.empty()) {
|
||||||
|
size_t nl = text.find('\n');
|
||||||
|
std::string firstLine = (nl != std::string::npos) ? text.substr(0, nl) : text;
|
||||||
|
strncpy(chatInputBuffer, firstLine.c_str(), sizeof(chatInputBuffer) - 1);
|
||||||
|
chatInputBuffer[sizeof(chatInputBuffer) - 1] = '\0';
|
||||||
|
sendChatMessage(gameHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("Edit")) {
|
||||||
|
const std::string& txt = gameHandler.getMacroText(slot.id);
|
||||||
|
strncpy(macroEditorBuf_, txt.c_str(), sizeof(macroEditorBuf_) - 1);
|
||||||
|
macroEditorBuf_[sizeof(macroEditorBuf_) - 1] = '\0';
|
||||||
|
macroEditorId_ = slot.id;
|
||||||
|
macroEditorOpen_ = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
if (ImGui::MenuItem("Clear Slot")) {
|
if (ImGui::MenuItem("Clear Slot")) {
|
||||||
|
|
@ -7574,6 +7602,13 @@ void GameScreen::renderActionBar(game::GameHandler& gameHandler) {
|
||||||
} else if (slot.type == game::ActionBarSlot::MACRO) {
|
} else if (slot.type == game::ActionBarSlot::MACRO) {
|
||||||
ImGui::BeginTooltip();
|
ImGui::BeginTooltip();
|
||||||
ImGui::Text("Macro #%u", slot.id);
|
ImGui::Text("Macro #%u", slot.id);
|
||||||
|
const std::string& macroText = gameHandler.getMacroText(slot.id);
|
||||||
|
if (!macroText.empty()) {
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::TextUnformatted(macroText.c_str());
|
||||||
|
} else {
|
||||||
|
ImGui::TextDisabled("(no text — right-click to Edit)");
|
||||||
|
}
|
||||||
ImGui::EndTooltip();
|
ImGui::EndTooltip();
|
||||||
} else if (slot.type == game::ActionBarSlot::ITEM) {
|
} else if (slot.type == game::ActionBarSlot::ITEM) {
|
||||||
ImGui::BeginTooltip();
|
ImGui::BeginTooltip();
|
||||||
|
|
@ -7786,6 +7821,28 @@ void GameScreen::renderActionBar(game::GameHandler& gameHandler) {
|
||||||
if (i > 0) ImGui::SameLine(0, spacing);
|
if (i > 0) ImGui::SameLine(0, spacing);
|
||||||
renderBarSlot(i, keyLabels1[i]);
|
renderBarSlot(i, keyLabels1[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Macro editor modal — opened by "Edit" in action bar context menus
|
||||||
|
if (macroEditorOpen_) {
|
||||||
|
ImGui::OpenPopup("Edit Macro###MacroEdit");
|
||||||
|
macroEditorOpen_ = false;
|
||||||
|
}
|
||||||
|
if (ImGui::BeginPopupModal("Edit Macro###MacroEdit", nullptr,
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse)) {
|
||||||
|
ImGui::Text("Macro #%u (first line executes on click)", macroEditorId_);
|
||||||
|
ImGui::SetNextItemWidth(320.0f);
|
||||||
|
ImGui::InputTextMultiline("##MacroText", macroEditorBuf_, sizeof(macroEditorBuf_),
|
||||||
|
ImVec2(320.0f, 80.0f));
|
||||||
|
if (ImGui::Button("Save")) {
|
||||||
|
gameHandler.setMacroText(macroEditorId_, std::string(macroEditorBuf_));
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Cancel")) {
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue