diff --git a/Bartender4.toc b/Bartender4.toc index 90c257f..13da278 100644 --- a/Bartender4.toc +++ b/Bartender4.toc @@ -5,9 +5,9 @@ ## X-Curse-Repository-ID: wow/bartender4/mainline ## Title: Bartender4 -## Notes: Simple and Advanced combined - Bartender4 ActionBar AddOn +## Notes: Simple and Advanced combined - Bartender4 ActionBar AddOn (CoA fork: [aura:Name] / [form:Name] paging) -## Version: 4.4.2-2-g3b02ee4 +## Version: 4.4.2-2-g3b02ee4-coa1 ## Author: Nevcairiel ## X-Email: h.leppkes at gmail dot com @@ -55,6 +55,9 @@ ButtonBar.lua StateBar.lua ActionBar.lua +## CoA patches ## +CoAAuraConditionals.lua + ## Buttons ## ActionButton.lua PetButton.lua diff --git a/CoAAuraConditionals.lua b/CoAAuraConditionals.lua new file mode 100644 index 0000000..646fc60 --- /dev/null +++ b/CoAAuraConditionals.lua @@ -0,0 +1,111 @@ +--[[ + CoA Bartender4 patch — translate [aura:Name] / [form:Name] / + non-numeric [stance:Name] in paging conditionals into [stance:N]. + + Why: Blizzard's secure macro parser (SecureCmdOptionParse) only + accepts a numeric index for [stance:] / [form:] and has no [aura:] + keyword at all. Addons cannot register new conditionals into the + secure parser. So the only honest place to do this is *before* we + hand the string to RegisterStateDriver — we resolve form names to + indices via GetShapeshiftFormInfo() and rewrite the keyword. + + Scope: only shapeshift forms are resolvable this way. That covers + Druid, Warrior stances, Death Knight presences, Priest Shadowform, + and every CoA custom-class form (Venomancer, Wildkin, etc.) since + those all live in the shapeshift bar. Non-form auras (regular + buffs) cannot be expressed as a stance index and would need a + synthetic state-driver attribute — out of scope here. + + Usage in Bartender4 paging: + [aura:Beetle Form]6;1 -- equivalent to [stance:1]6;1 (if Beetle Form is form 1) + [form:Beetle Form]6;1 + [stance:Beetle Form]6;1 + [stance:Beetle Form/Wasp Form]6;1 -- slash-list works too + [noaura:Beetle Form]2 + + All three keywords (aura, form, stance) are accepted equivalently + when the value is a non-numeric form name. Numeric values pass + through unchanged. Unresolvable names are left as-is so the failure + is visible at conditional-parse time rather than silently swallowed. +]] + +local Bartender4 = _G.Bartender4 +if not Bartender4 then return end + +local KEYWORDS = { "nostance", "noform", "noaura", "stance", "form", "aura" } +local KEYWORD_OUT = { + stance = "stance", form = "stance", aura = "stance", + nostance = "nostance", noform = "nostance", noaura = "nostance", +} + +local function shapeshiftIndexByName(name) + if type(GetNumShapeshiftForms) ~= "function" or type(GetShapeshiftFormInfo) ~= "function" then + return nil + end + local n = GetNumShapeshiftForms() or 0 + local lname = name:lower() + for i = 1, n do + local _, fname = GetShapeshiftFormInfo(i) + if fname and fname:lower() == lname then + return i + end + end + return nil +end + +local function translateValueList(values) + local parts, anyTranslated = {}, false + for v in values:gmatch("[^/]+") do + local trimmed = v:match("^%s*(.-)%s*$") or v + if trimmed:match("^%d+$") or trimmed == "" then + parts[#parts + 1] = trimmed + else + local idx = shapeshiftIndexByName(trimmed) + if idx then + parts[#parts + 1] = tostring(idx) + anyTranslated = true + else + parts[#parts + 1] = trimmed + end + end + end + return table.concat(parts, "/"), anyTranslated +end + +function Bartender4:CoATranslateConditionals(s) + if type(s) ~= "string" or s == "" then return s end + for _, kw in ipairs(KEYWORDS) do + local outKw = KEYWORD_OUT[kw] + s = s:gsub(kw .. ":([^,%]]+)", function(value) + local newValue, didTranslate = translateValueList(value) + if didTranslate then + return outKw .. ":" .. newValue + end + return nil + end) + end + return s +end + +-- Re-apply paging when shapeshift form set changes (new form learned, +-- spec respec, etc.) so freshly-resolvable names take effect without +-- a /reload. Skipped during combat lockdown — RegisterStateDriver is +-- secure and the SetAttribute calls inside UpdateStates would taint. +local refresh = CreateFrame("Frame") +refresh:RegisterEvent("UPDATE_SHAPESHIFT_FORMS") +refresh:RegisterEvent("PLAYER_ENTERING_WORLD") +refresh:RegisterEvent("PLAYER_REGEN_ENABLED") +local pendingRefresh = false +refresh:SetScript("OnEvent", function(self, event) + if InCombatLockdown and InCombatLockdown() then + pendingRefresh = true + return + end + if event == "PLAYER_REGEN_ENABLED" and not pendingRefresh then return end + pendingRefresh = false + local mod = Bartender4 and Bartender4.GetModule and Bartender4:GetModule("ActionBars", true) + if not mod or not mod.actionbars then return end + for _, bar in pairs(mod.actionbars) do + if bar and bar.UpdateStates then bar:UpdateStates() end + end +end) diff --git a/README-CoA.md b/README-CoA.md new file mode 100644 index 0000000..74bcd23 --- /dev/null +++ b/README-CoA.md @@ -0,0 +1,100 @@ +CoA Bartender4 +============== + +This is the Children of Ascension fork of [Bartender4][upstream] by +Hendrik "Nevcairiel" Leppkes, vendored from Ascension's WotLK 3.3.5 +client (`/srv/add01/wow-ascension/Interface/AddOns/Bartender4`, +upstream version `4.4.2-2-g3b02ee4`). + +[upstream]: https://github.com/Nevcairiel/Bartender4 + +What we patched +--------------- + +A single feature: paging conditionals can refer to **shapeshift forms +by name**, not just by numeric stance index. + +``` +[aura:Beetle Form]6;1 -- equivalent to [stance:N]6;1 +[form:Beetle Form]6;1 +[stance:Beetle Form]6;1 -- non-numeric value also accepted + +[stance:Beetle Form/Wasp Form]9 -- slash-lists work +[noaura:Beetle Form]2 -- negation works +``` + +Three keywords are accepted equivalently when the value is a non-numeric +form name: `aura:`, `form:`, `stance:` (and their `no…:` negations). +All resolve via `GetShapeshiftFormInfo()` and get rewritten to +`[stance:N]` before the string reaches Blizzard's secure macro parser. + +This covers every Venomancer / Wildkin / etc. CoA custom-class form, +plus stock Druid / Warrior / DK / Priest forms — anything that lives +in the shapeshift bar. Non-form auras (regular buffs) cannot be +expressed as a stance index and are out of scope. + +Why upstream can't accept this patch as-is +------------------------------------------ + +`SecureCmdOptionParse` is hardcoded in the WoW client; addons cannot +register new conditional keywords into it. Our translation pass runs +*before* `RegisterStateDriver`, rewriting `[aura:Name]` to `[stance:N]` +at addon scope. That works on a private server with predictable form +sets, but upstream serves multiple expansions and clients where the +keyword `aura` may eventually mean something different (or be reserved +by Blizzard) — so it stays a fork patch. + +Files we touched +---------------- + +| File | Change | +|---|---| +| `Bartender4.toc` | Version `…-coa1`, loads `CoAAuraConditionals.lua`, Notes appended | +| `StateBar.lua` | 3-line guard before `RegisterStateDriver` calls our translator | +| `CoAAuraConditionals.lua` | New file — the translator + `UPDATE_SHAPESHIFT_FORMS` re-apply | +| `.gitattributes` | `* -text` so we don't normalise upstream CRLF (keeps merges clean) | + +Pulling future upstream releases +-------------------------------- + +Upstream lives at . The patch +is small and the touched files are stable, so future merges should be +mostly painless: + +```bash +git remote add upstream https://github.com/Nevcairiel/Bartender4.git +git fetch upstream +git merge upstream/master # or whatever upstream's default is +# resolve trivial conflicts in StateBar.lua + Bartender4.toc +luac -p StateBar.lua CoAAuraConditionals.lua +``` + +Deploying +--------- + +```bash +rsync -a --delete \ + --exclude=.git --exclude=.gitattributes --exclude='README-CoA.md' \ + /home/sub/repos/coa/coa-bartender/ \ + /srv/add01/wow-ascension/Interface/AddOns/Bartender4/ +``` + +Same for `wow-ptr`. After deploy the in-game version string reads +`4.4.2-2-g3b02ee4-coa1`, so you can confirm the patched copy is loaded. + +Verifying it works +------------------ + +In-game, run any of: + +``` +/run print(SecureCmdOptionParse("[stance:Beetle Form]yes;no")) +/run print(SecureCmdOptionParse("[aura:Beetle Form]yes;no")) +``` + +Both should print **`no`** even when in Beetle Form, because Blizzard's +parser still doesn't understand the names — but Bartender4's paging +**will** work, because the translator rewrites the string before +handing it to the state driver. To verify *that*, set up paging as +`[aura:Beetle Form]6;1` in Bar 1 → State Configuration → Custom, shift +into Beetle Form, and watch Bar 1 swap to page 6. diff --git a/StateBar.lua b/StateBar.lua index 4f4a6a2..cc3bca0 100644 --- a/StateBar.lua +++ b/StateBar.lua @@ -187,6 +187,10 @@ function StateBar:UpdateStates(returnOnly) UnregisterStateDriver(self, "page") self:SetAttribute("state-page", "0") + -- CoA: rewrite [aura:Name] / [form:Name] / [stance:Name] -> [stance:N] + if Bartender4.CoATranslateConditionals then + statedriver = Bartender4:CoATranslateConditionals(statedriver) + end RegisterStateDriver(self, "page", statedriver or "0") self:SetAttribute("_onstate-assist-help", [[