Skip to content

Commit d366fec

Browse files
committed
fix(attributes): restore the previous target after a macro click-cast
The `macro` verb selected the clicked unit and left it targeted (Clique's click-heal minus the restore). Snapshot the current selection GUID, retarget the clicked unit, run the macro, then restore the prior target via FUN_TARGET_BY_GUID (which validates the GUID and drops a stale one), or ClearTarget when there was no prior target. The macro's actions dispatch synchronously while the clicked unit is selected, so restoring afterward doesn't affect them.
1 parent c6d99cf commit d366fec

2 files changed

Lines changed: 33 additions & 9 deletions

File tree

docs/API.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4039,7 +4039,7 @@ modifier/button-qualified, same precedence as `type`):
40394039
| `focus` | — | Sets the ClassicAPI focus to the `unit`. |
40404040
| `spell` | `spell` | Casts the `spell` on the `unit` via [`C_Spell.CastAtUnit`](#c_spellcastatunitspellidorname-unit) — the unit's GUID goes straight to the cast dispatcher (no target juggling), and ground-target spells land at the unit's feet. |
40414041
| `item` | `item`, or `bag`+`slot` | Uses an item on the `unit`. `item` may be a name / itemID / link (used via `C_Item.UseItemByName`, unit as the target) or a `"bag slot"` string like `"0 1"` (used via `UseContainerItem`). The deprecated `bag`+`slot` attributes are used when `item` is unset. |
4042-
| `macro` | `macrotext` or `macro` | Runs the macro text. Prefers an addon `RunMacro` (e.g. SuperCleveRoidMacros — named macros + extended conditionals); otherwise runs the text natively, line by line, through the stock `ChatEdit_ParseText`. |
4042+
| `macro` | `macrotext` or `macro` | Runs the macro text. To give the macro a unit, the handler selects the clicked `unit`, runs the macro, then restores the target from before the click. It prefers an addon `RunMacro` (for example, SuperCleveRoidMacros — named macros and extended conditionals). If no addon `RunMacro` exists, it runs the text natively, line by line, through the stock `ChatEdit_ParseText`. |
40434043
| `stopcasting` | — | Stops the current cast. |
40444044
| `menu` / `togglemenu` | — | Pops the standard unit dropdown at the cursor (whisper / inspect / trade / invite / …, the same menu `PlayerFrame` / `TargetFrame` / `PartyMemberFrame` show). |
40454045

src/frame/Attributes.cpp

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -650,12 +650,25 @@ bool DispatchVerb(void *L, int fi, const char *prefix, const char *suffix,
650650
// conditional, and unlike the `spell` verb (which feeds a GUID straight
651651
// to the cast dispatcher) we can't inject a target into arbitrary macro
652652
// text — so a plain `/cast Flash Heal` would hit the current target, not
653-
// the clicked unit. Emulate the classic Clique / pfUIevan click-heal:
654-
// target the clicked unit first, then run the macro. Like pfUIevan, the
655-
// previous target is NOT restored (the click doubles as a target swap).
656-
if (unit)
653+
// the clicked unit. Emulate the classic Clique click-heal: snapshot the
654+
// current target, retarget the clicked unit, run the macro, then restore
655+
// the previous target so the click doesn't leave the player retargeted.
656+
// The macro's actions (/cast etc.) dispatch against the clicked unit
657+
// synchronously while it's selected, so restoring the target afterward
658+
// doesn't affect them.
659+
uint64_t prevTarget = 0;
660+
const bool swapped = unit != nullptr;
661+
if (swapped) {
662+
prevTarget =
663+
(static_cast<uint64_t>(*reinterpret_cast<volatile uint32_t *>(
664+
Offsets::VAR_CURRENT_SELECTION_GUID_HI))
665+
<< 32) |
666+
*reinterpret_cast<volatile uint32_t *>(
667+
Offsets::VAR_CURRENT_SELECTION_GUID_LO);
657668
Game::Lua::CallGlobalString(L, "TargetUnit", unit);
669+
}
658670
char macro[512];
671+
bool handled = false;
659672
// `macrotext` is raw macro text (the modern default). Run it as text
660673
// through the stock chat parser so each line dispatches via
661674
// SlashCmdList — that routes /cast etc. through any addon slash hooks
@@ -664,17 +677,28 @@ bool DispatchVerb(void *L, int fi, const char *prefix, const char *suffix,
664677
// would silently resolve nothing.
665678
if (ReadModAttr(L, fi, prefix, "macrotext", suffix, macro, sizeof macro)) {
666679
RunMacroTextC(L, macro);
667-
return true;
680+
handled = true;
668681
}
669682
// Deprecated `macro` form: a saved-macro name/index. Prefer an
670683
// addon-provided RunMacro (SuperCleveRoidMacros, pfUI, …) to run the
671684
// named macro's body; fall back to the stock parser when none exists.
672-
if (ReadModAttr(L, fi, prefix, "macro", suffix, macro, sizeof macro)) {
685+
else if (ReadModAttr(L, fi, prefix, "macro", suffix, macro, sizeof macro)) {
673686
if (!Game::Lua::CallGlobalString(L, "RunMacro", macro))
674687
RunMacroTextC(L, macro);
675-
return true;
688+
handled = true;
676689
}
677-
return false;
690+
// Restore the target the click swapped away from. `FUN_TARGET_BY_GUID`
691+
// validates the GUID resolves to a live unit and bails otherwise, so a
692+
// target that despawned mid-macro is dropped cleanly rather than
693+
// committed; a zero prior target means "no target" → ClearTarget.
694+
if (swapped) {
695+
if (prevTarget)
696+
reinterpret_cast<void(__fastcall *)(const uint64_t *)>(
697+
Offsets::FUN_TARGET_BY_GUID)(&prevTarget);
698+
else
699+
Game::Lua::CallGlobal(L, "ClearTarget");
700+
}
701+
return handled;
678702
}
679703
if (Ascii::EqualCI(verb, "stop") || Ascii::EqualCI(verb, "stopcasting")) {
680704
Game::Lua::CallGlobal(L, "SpellStopCasting");

0 commit comments

Comments
 (0)