Skip to content

Commit eaf31cf

Browse files
author
mergetest
committed
Merge branch 'pr-217'
# Conflicts: # CHANGELOG.md
2 parents 497de6b + cd4f649 commit eaf31cf

4 files changed

Lines changed: 104 additions & 9 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
### Bug Fixes
66

7+
* (Click Casting) **Fixed all binds being dead in the first arena/dungeon of a fresh session until a reload.** At the first login of a session, the per-spec profile auto-switch could run before the game had your specialization ready — and instead of waiting, it fell back to spec 1 and switched your click-casting to the wrong spec's profile for the rest of the session. The check now recognises when spec data isn't ready yet and re-runs automatically as soon as it arrives (and again at loading screens and arena preparation as a safety net); it is also deferred instead of skipped when it lands during combat. (by Krathe)
78
* (Pinned Frames) Fixed a leftover pinned-frame handle (an empty "drag to move" box, often orange) that could stay stuck on screen and survive reloads. The confirmed trigger was switching to a profile with fewer pinned sets than the previous one — the extra set's frame was left behind with no way to hide it. The cleanup now enforces the full rule at every login, profile switch and party/raid change: any pinned frame that the active profile doesn't define, has disabled, or that belongs to the other mode is hidden — so a stuck box is cleaned up no matter how it got stranded. (by Krathe)
89

910
### Improvements

ClickCasting/Bindings.lua

Lines changed: 67 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2408,13 +2408,55 @@ end
24082408

24092409
-- Process all bindings and build unified macro map
24102410
-- Returns: { [keyString] = { macroText = "...", templateBinding = binding } }
2411+
-- Re-resolve click-casting state once cold-start data becomes available. Two
2412+
-- things can be built before GetSpecialization() resolves at the first login
2413+
-- of a session, and both used to stay wrong all day until a /reload:
2414+
-- * loadoutCheckUnresolved (CheckLoadoutProfileSwitch): the spec→profile
2415+
-- auto-switch could not run (or, before the guard, ran with the `or 1`
2416+
-- spec fallback and switched to the WRONG spec's profile) — the field
2417+
-- report: "none of my binds work in my first arena of the day"
2418+
-- * macroMapUnresolved (BuildUnifiedMacroMap): loadSpec-scoped bindings
2419+
-- were dropped from the map (legacy/import-only config; belt)
2420+
-- Debounced; each resolver clears its own flag on success and self-defers in
2421+
-- combat (pendingLoadoutCheck / needsBindingRefresh). No-op in steady state,
2422+
-- so the extra triggers cost nothing.
2423+
function CC:ResolveProvisionalMap(reason)
2424+
if not (self.macroMapUnresolved or self.loadoutCheckUnresolved) then return end
2425+
if not (self.db and self.db.enabled) then return end
2426+
if self.provisionalResolveTimer then self.provisionalResolveTimer:Cancel() end
2427+
self.provisionalResolveTimer = C_Timer.NewTimer(0.5, function()
2428+
CC.provisionalResolveTimer = nil
2429+
if CC.loadoutCheckUnresolved then
2430+
DF:Debug("CLICK", "Re-running deferred loadout profile check (%s)", tostring(reason))
2431+
CC:CheckLoadoutProfileSwitch()
2432+
end
2433+
if CC.macroMapUnresolved then
2434+
DF:Debug("CLICK", "Rebuilding provisional binding map (%s)", tostring(reason))
2435+
CC:ApplyBindings()
2436+
end
2437+
end)
2438+
end
2439+
24112440
function CC:BuildUnifiedMacroMap()
24122441
local macroMap = {}
2413-
2442+
2443+
-- Cold-start guard: ShouldBindingLoad drops every loadSpec-scoped binding
2444+
-- while GetSpecialization() is still nil (first login of a session, before
2445+
-- spec data resolves). The map is built once and cached, so a map built in
2446+
-- that window silently loses those bindings — and an all-spec-scoped setup
2447+
-- comes up EMPTY, which downstream disables clicks on our frames entirely
2448+
-- ("none of my binds work in my first arena of the day until I reload").
2449+
-- Record the condition on the module; the resolve watchers (SPELLS_CHANGED /
2450+
-- PLAYER_SPECIALIZATION_CHANGED / arena prep) rebuild when data arrives, and
2451+
-- ApplyBindingsToFrameUnified refuses to wipe a frame off a suspect map.
2452+
local specKnown = GetSpecialization() ~= nil
2453+
local anySpecScoped = false
2454+
24142455
-- Group all bindings by their key string
24152456
local keyGroups = {}
24162457
for i, binding in ipairs(self.db.bindings) do
24172458
if binding.enabled ~= false then
2459+
if binding.loadSpec then anySpecScoped = true end
24182460
local keyString = self:GetBindingKeyString(binding)
24192461
if keyString then
24202462
if not keyGroups[keyString] then
@@ -2424,6 +2466,11 @@ function CC:BuildUnifiedMacroMap()
24242466
end
24252467
end
24262468
end
2469+
2470+
self.macroMapUnresolved = (anySpecScoped and not specKnown) or nil
2471+
if self.macroMapUnresolved then
2472+
DF:DebugWarn("CLICK", "BuildUnifiedMacroMap: spec unknown with spec-scoped bindings — map is provisional")
2473+
end
24272474

24282475
-- Build macro for each key group
24292476
for keyString, group in pairs(keyGroups) do
@@ -2529,17 +2576,16 @@ function CC:ApplyBindingsToFrameUnified(frame, skipKeyboardUpdate)
25292576
frameName, debugstack(2, 1, 0) or "unknown")
25302577
end
25312578

2532-
-- Clear existing bindings first
2533-
self:ClearBindingsFromFrame(frame)
2534-
25352579
-- Build unified macro map if not already built
25362580
if not self.unifiedMacroMap then
25372581
self.unifiedMacroMap = self:BuildUnifiedMacroMap()
25382582
-- Refresh keyboard bindings on all frames since map was just built
25392583
self:RefreshKeyboardBindings()
25402584
end
2541-
2542-
-- Check if this frame has ANY bindings that apply to it
2585+
2586+
-- Check if this frame has ANY bindings that apply to it — BEFORE the
2587+
-- destructive clear below, so a provisional (cold-start) map can bail out
2588+
-- without touching the frame's existing state.
25432589
local hasAnyBindings = false
25442590
local isDandersFrame = frame.dfIsDandersFrame == true
25452591
local isBlizzardFrame = frame.dfIsBlizzardFrame == true
@@ -2565,6 +2611,16 @@ function CC:ApplyBindingsToFrameUnified(frame, skipKeyboardUpdate)
25652611

25662612
-- If no bindings apply to this frame
25672613
if not hasAnyBindings then
2614+
-- Provisional map (spec not yet resolved at build time): the emptiness
2615+
-- is almost certainly the cold-start drop, not the user's config. Do
2616+
-- NOT clear/disable anything — leave the frame exactly as it is; the
2617+
-- resolve watchers rebuild and re-apply once spec data arrives.
2618+
if self.macroMapUnresolved then
2619+
DF:Debug("CLICK", "ApplyBindings %s deferred — provisional map (spec unresolved)", frameName)
2620+
return
2621+
end
2622+
-- Genuinely no bindings for this frame: clean it up.
2623+
self:ClearBindingsFromFrame(frame)
25682624
if isDandersFrame then
25692625
-- For DandersFrames, completely disable clicks when no bindings apply
25702626
-- Our own frames get type1/type2 set in InitializeHeaderChild as a safety net
@@ -2579,7 +2635,11 @@ function CC:ApplyBindingsToFrameUnified(frame, skipKeyboardUpdate)
25792635
end
25802636
return
25812637
end
2582-
2638+
2639+
-- Clear existing bindings first (moved below the applicability check so a
2640+
-- provisional-map bailout above never strips a frame's working state)
2641+
self:ClearBindingsFromFrame(frame)
2642+
25832643
-- Register for clicks based on castOnDown option
25842644
if frame.RegisterForClicks then
25852645
local castOnDown = self.profile and self.profile.options and self.profile.options.castOnDown

ClickCasting/Events.lua

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ function CC:RegisterEvents()
2929
-- Nameplate events for click-casting on nameplates
3030
eventFrame:RegisterEvent("NAME_PLATE_UNIT_ADDED")
3131
eventFrame:RegisterEvent("NAME_PLATE_UNIT_REMOVED")
32+
33+
-- Spell data arrival — resolves a provisional (cold-start) binding map
34+
eventFrame:RegisterEvent("SPELLS_CHANGED")
3235

3336
eventFrame:SetScript("OnEvent", function(_, event, ...)
3437
if event == "PLAYER_REGEN_ENABLED" then
@@ -39,6 +42,12 @@ function CC:RegisterEvents()
3942
elseif event == "PLAYER_SPECIALIZATION_CHANGED" or event == "ACTIVE_PLAYER_SPECIALIZATION_CHANGED" then
4043
-- Spec changed - check for profile switch
4144
CC:OnSpecChanged()
45+
-- Cold-start resolve: a map built before GetSpecialization()
46+
-- resolved dropped every spec-scoped binding — rebuild it now
47+
CC:ResolveProvisionalMap("spec-resolved")
48+
elseif event == "SPELLS_CHANGED" then
49+
-- Spell data arrived/changed — no-op unless the map is provisional
50+
CC:ResolveProvisionalMap("spells-changed")
4251
elseif event == "TRAIT_CONFIG_UPDATED" or event == "TRAIT_CONFIG_CREATED" or event == "ACTIVE_COMBAT_CONFIG_CHANGED" then
4352
-- Loadout/talent changed - check for profile switch and reapply bindings (with debounce)
4453
if not InCombatLockdown() then
@@ -96,6 +105,12 @@ function CC:RegisterEvents()
96105
-- so a broken hover-bind state never survives a zone change
97106
CC:RunBindingRepair("zone-in", true)
98107

108+
-- Cold-start resolve: if the login build ran before spec data
109+
-- was available, the map is provisional — rebuild it on the
110+
-- first loading screen so it is correct BEFORE the first
111+
-- arena/dungeon of the session, not only after a /reload
112+
CC:ResolveProvisionalMap("zone-in")
113+
99114
-- Check for loadout-based profile on initial load
100115
C_Timer.After(1, function()
101116
if not InCombatLockdown() then
@@ -105,6 +120,8 @@ function CC:RegisterEvents()
105120
end)
106121
elseif event == "ARENA_PREP_OPPONENT_SPECIALIZATIONS" then
107122
-- Arena frames should now exist
123+
-- Belt: never enter an arena on a provisional (cold-start) map
124+
CC:ResolveProvisionalMap("arena-prep")
108125
CC:OnArenaPrep()
109126
elseif event == "INSTANCE_ENCOUNTER_ENGAGE_UNIT" then
110127
-- Boss frames should now exist

ClickCasting/Profiles.lua

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -492,10 +492,27 @@ end
492492
-- Check and auto-switch profile based on current loadout
493493
function CC:CheckLoadoutProfileSwitch()
494494
if InCombatLockdown() then
495-
-- Will be called again when combat ends
495+
-- Defer, don't drop: entering an arena/dungeon starts combat quickly,
496+
-- and silently losing the check leaves the previous spec's profile
497+
-- active for the whole match. OnCombatEnd drains pendingLoadoutCheck.
498+
self.pendingLoadoutCheck = true
496499
return
497500
end
498-
501+
502+
-- Cold-start guard: at the first login of a session this can run BEFORE
503+
-- GetSpecialization() resolves. GetCurrentSpec()'s `or 1` fallback would
504+
-- then MASK the missing data and switch to spec 1's profile — the wrong
505+
-- profile for anyone whose actual spec is 2+ ("none of my binds work in
506+
-- my first arena of the day until I reload"). Record the unresolved state
507+
-- and let the resolve watchers (spec/spell events, loading screens, arena
508+
-- prep) re-run this check once real data arrives.
509+
if not GetSpecialization() then
510+
self.loadoutCheckUnresolved = true
511+
DF:Debug("CLICK", "CheckLoadoutProfileSwitch: spec data not ready — deferred to resolve watchers")
512+
return
513+
end
514+
self.loadoutCheckUnresolved = nil
515+
499516
local specIndex = GetCurrentSpec()
500517
local loadoutID = GetCurrentLoadoutConfigID()
501518
local assignedProfile, isSpecific = self:GetProfileForLoadout(specIndex, loadoutID)

0 commit comments

Comments
 (0)