Skip to content

Commit 348c1e1

Browse files
author
mergetest
committed
Click casting: add Always Cast fallback option (bug #991)
Bindings whose macro was all conditional clauses did nothing when no clause matched — a global bind with the mouseover fallback could never show a ground-targeted spell's aiming circle while hovering nothing. New per-binding Advanced option appends a terminal unconditional clause (combat/mounted gating preserved) to both the frame and global macros, and enabling it also activates the key globally. Self-cast keeps precedence when both are enabled.
1 parent 9b0e398 commit 348c1e1

4 files changed

Lines changed: 62 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
* (Click Casting) Fixed keyboard and mouse-wheel click-cast binds being silently dropped mid-hover during combat — the safety check that removes hover binds could misread the cursor as off the frame while the frame's position was briefly unreadable, wiping the binds until the frame was re-hovered. Binds are now only removed when the cursor is provably off the frame. (by Krathe)
99
* (Click Casting) Fixed binds on keys from international keyboard layouts (such as æ, ø or å) not casting on the frame under the cursor.
1010

11+
### New Features
12+
13+
* (Click Casting) New "Always Cast" option in a binding's Advanced settings. When enabled, the key still casts with the spell's normal targeting if no other rule matches — so ground-targeted spells (like aimable Shaman totems) show their aiming circle when pressed while hovering nothing.
14+
1115
## [4.7.3]
1216

1317
### Bug Fixes

ClickCasting/Bindings.lua

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -847,7 +847,9 @@ function CC:BuildHovercastSetupScript()
847847
-- These fallbacks only work when NOT hovering a frame, so we need
848848
-- the key binding to be active globally, not just when hovering
849849
local fallback = binding.fallback or {}
850-
local hasFallbackThatNeedsGlobal = fallback.mouseover or fallback.target or fallback.selfCast
850+
-- alwaysCast needs the key active everywhere too — its whole point
851+
-- is casting while hovering nothing (bug #991)
852+
local hasFallbackThatNeedsGlobal = fallback.mouseover or fallback.target or fallback.selfCast or fallback.alwaysCast
851853

852854
-- Check for useGlobalBind flag (for items/macros that need to work everywhere)
853855
local hasGlobalBindFlag = binding.useGlobalBind == true
@@ -2031,7 +2033,18 @@ function CC:BuildMacroTextForBinding(binding, forGlobalBinding)
20312033
if hasSelf then
20322034
table.insert(parts, "[@player" .. combatStr .. mountedStr .. "] " .. spellName)
20332035
end
2034-
2036+
2037+
-- Always Cast: terminal unconditional clause — when no clause above
2038+
-- matches (nothing hovered / ineligible unit), cast with WoW's default
2039+
-- targeting so ground-targeted spells show their aiming circle
2040+
-- (bug #991). With Self enabled the [@player] clause above always
2041+
-- resolves first, so Self takes precedence. Combat/mounted gating
2042+
-- still applies.
2043+
if fallback.alwaysCast then
2044+
local conds = (combatStr .. mountedStr):sub(2) -- strip leading comma; "" when ungated
2045+
table.insert(parts, (conds ~= "" and ("[" .. conds .. "] ") or "") .. spellName)
2046+
end
2047+
20352048
-- If no fallbacks enabled, just cast normally (will use WoW's default targeting)
20362049
if #parts == 0 then
20372050
table.insert(parts, spellName)
@@ -2349,7 +2362,20 @@ function CC:BuildCombinedMacroForBindings(bindings, forGlobalBinding)
23492362
table.insert(parts, "[@player" .. combatStr .. "] " .. friendlySpell)
23502363
end
23512364
end
2352-
2365+
2366+
-- Always Cast (bug #991): terminal unconditional clause, mirroring the
2367+
-- single-binding builder. First contributing binding with the flag wins;
2368+
-- the self-cast clause above resolves first when enabled.
2369+
for _, b in ipairs({friendlyBinding, hostileBinding, anyBinding}) do
2370+
if b and b.fallback and b.fallback.alwaysCast and b.spellName then
2371+
local spell = GetLocalizedSpellName(b.spellId) or b.spellName
2372+
local combatCond = GetCombatCondition(b)
2373+
local combatStr = combatCond == "combat" and ",combat" or (combatCond == "nocombat" and ",nocombat" or "")
2374+
table.insert(parts, (combatStr ~= "" and ("[" .. combatStr:sub(2) .. "] ") or "") .. spell)
2375+
break
2376+
end
2377+
end
2378+
23532379
if #parts == 0 then return nil end
23542380

23552381
-- Check if any contributing binding has stopSpellTarget enabled

ClickCasting/Constants.lua

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ local DEFAULT_BINDING = {
303303
target = false,
304304
selfCast = false,
305305
stopSpellTarget = false,
306+
alwaysCast = false,
306307
},
307308
-- Load conditions
308309
loadSpec = nil, -- nil = all specs, or table of spec IDs
@@ -453,6 +454,10 @@ local FALLBACK_INFO = {
453454
name = "Self",
454455
desc = "Cast on yourself as a last resort if no other valid target is found.",
455456
},
457+
alwaysCast = {
458+
name = "Always Cast",
459+
desc = "If no rule above matches (hovering nothing, or an ineligible unit), cast anyway using the spell's normal targeting. Lets ground-targeted spells show their aiming circle when pressed in the open. If Self is also enabled, Self applies first.",
460+
},
456461
stopSpellTarget = {
457462
name = "Cancel Targeting",
458463
desc = "Adds /stopspelltarget to the macro which cancels the blue targeting hand after casting. Disable this for spells like Rescue that require a targeting phase to complete.",

ClickCasting/UI/BindingEditor.lua

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -656,7 +656,7 @@ function CC:CreateEditBindingPanel()
656656
-- ============================================================
657657

658658
local COLLAPSED_HEIGHT = 502
659-
local EXPANDED_HEIGHT = 685
659+
local EXPANDED_HEIGHT = 707
660660

661661
-- Advanced header/toggle button
662662
local advancedToggle = CreateFrame("Button", nil, panel, "BackdropTemplate")
@@ -689,7 +689,7 @@ function CC:CreateEditBindingPanel()
689689
-- Advanced content container (hidden by default)
690690
local advancedContent = CreateFrame("Frame", nil, panel)
691691
advancedContent:SetPoint("TOPLEFT", advancedToggle, "BOTTOMLEFT", 0, -8)
692-
advancedContent:SetSize(296, 140)
692+
advancedContent:SetSize(296, 162)
693693
advancedContent:Hide()
694694
panel.advancedContent = advancedContent
695695

@@ -737,16 +737,25 @@ function CC:CreateEditBindingPanel()
737737
end)
738738
panel.selfCB = selfCB
739739

740+
-- Always Cast checkbox (terminal unconditional fallback — bug #991)
741+
local alwaysCastCB = CreateCheckbox(advancedContent, FALLBACK_INFO.alwaysCast.name, FALLBACK_INFO.alwaysCast.desc)
742+
alwaysCastCB:SetPoint("TOPLEFT", 18, -104)
743+
alwaysCastCB:SetScript("OnClick", function(self)
744+
panel.pendingBinding.fallback = panel.pendingBinding.fallback or {}
745+
panel.pendingBinding.fallback.alwaysCast = self:GetChecked()
746+
end)
747+
panel.alwaysCastCB = alwaysCastCB
748+
740749
-- Macro Options section header
741750
local macroOptionsLabel = advancedContent:CreateFontString(nil, "OVERLAY", "DFFontNormal")
742-
macroOptionsLabel:SetPoint("TOPLEFT", 0, -108)
751+
macroOptionsLabel:SetPoint("TOPLEFT", 0, -130)
743752
macroOptionsLabel:SetText(L["Macro Options:"])
744753
macroOptionsLabel:SetTextColor(C_TEXT_DIM.r, C_TEXT_DIM.g, C_TEXT_DIM.b)
745754
panel.macroOptionsLabel = macroOptionsLabel
746755

747756
-- Cancel Targeting checkbox (stopSpellTarget)
748757
local stopSpellTargetCB = CreateCheckbox(advancedContent, FALLBACK_INFO.stopSpellTarget.name, FALLBACK_INFO.stopSpellTarget.desc)
749-
stopSpellTargetCB:SetPoint("TOPLEFT", 18, -128)
758+
stopSpellTargetCB:SetPoint("TOPLEFT", 18, -150)
750759
stopSpellTargetCB:SetScript("OnClick", function(self)
751760
panel.pendingBinding.fallback = panel.pendingBinding.fallback or {}
752761
panel.pendingBinding.fallback.stopSpellTarget = self:GetChecked()
@@ -755,15 +764,15 @@ function CC:CreateEditBindingPanel()
755764

756765
-- Target on cast checkbox (per-binding override of the global setting)
757766
local targetOnCastCB = CreateCheckbox(advancedContent, L["Target on cast"], L["Also make this unit your target when you click-cast on it. Overrides the global 'Target unit when click-casting' setting."])
758-
targetOnCastCB:SetPoint("TOPLEFT", 18, -150)
767+
targetOnCastCB:SetPoint("TOPLEFT", 18, -172)
759768
targetOnCastCB:SetScript("OnClick", function(self)
760769
panel.pendingBinding.targetOnCast = self:GetChecked()
761770
end)
762771
panel.targetOnCastCB = targetOnCastCB
763772

764773
-- Priority slider (inside advanced content)
765774
local priorityLabel = advancedContent:CreateFontString(nil, "OVERLAY", "DFFontNormal")
766-
priorityLabel:SetPoint("TOPLEFT", 0, -158)
775+
priorityLabel:SetPoint("TOPLEFT", 0, -180)
767776
priorityLabel:SetText(L["Priority:"])
768777
priorityLabel:SetTextColor(C_TEXT_DIM.r, C_TEXT_DIM.g, C_TEXT_DIM.b)
769778
panel.priorityLabel = priorityLabel
@@ -796,7 +805,7 @@ function CC:CreateEditBindingPanel()
796805
end,
797806
CC.ACCENT -- accentColor (ClickCasting green)
798807
)
799-
prioritySlider:SetPoint("TOPLEFT", 68, -155)
808+
prioritySlider:SetPoint("TOPLEFT", 68, -177)
800809

801810
-- Direction note (standard GUI label style). The slider's container is 50px
802811
-- tall (its bottom sits near the Delete/Cancel/Save row), so the note is placed
@@ -1182,7 +1191,7 @@ function CC:ShowEditBindingPanel(spellData, existingBinding, existingIndex)
11821191
-- Check if this binding has advanced options set (should auto-expand)
11831192
local fallback = panel.pendingBinding.fallback or { mouseover = false, target = false, selfCast = false }
11841193
local hasAdvancedOptions = fallback.mouseover or fallback.target or fallback.selfCast or fallback.stopSpellTarget
1185-
or panel.pendingBinding.targetOnCast ~= nil
1194+
or fallback.alwaysCast or panel.pendingBinding.targetOnCast ~= nil
11861195
local currentPriority = panel.pendingBinding.priority or 5
11871196
if currentPriority ~= 5 then
11881197
hasAdvancedOptions = true
@@ -1203,6 +1212,7 @@ function CC:ShowEditBindingPanel(spellData, existingBinding, existingIndex)
12031212
if panel.mouseoverCB then panel.mouseoverCB:Hide() end
12041213
if panel.targetFallbackCB then panel.targetFallbackCB:Hide() end
12051214
if panel.selfCB then panel.selfCB:Hide() end
1215+
if panel.alwaysCastCB then panel.alwaysCastCB:Hide() end
12061216
if panel.macroOptionsLabel then panel.macroOptionsLabel:Hide() end
12071217
if panel.stopSpellTargetCB then panel.stopSpellTargetCB:Hide() end
12081218
if panel.targetOnCastCB then panel.targetOnCastCB:Hide() end
@@ -1259,6 +1269,7 @@ function CC:ShowEditBindingPanel(spellData, existingBinding, existingIndex)
12591269
if panel.mouseoverCB then panel.mouseoverCB:Show() end
12601270
if panel.targetFallbackCB then panel.targetFallbackCB:Show() end
12611271
if panel.selfCB then panel.selfCB:Show() end
1272+
if panel.alwaysCastCB then panel.alwaysCastCB:Show() end
12621273
if panel.macroOptionsLabel then panel.macroOptionsLabel:Show() end
12631274
if panel.stopSpellTargetCB then panel.stopSpellTargetCB:Show() end
12641275
if panel.targetOnCastCB then panel.targetOnCastCB:Show() end
@@ -1275,14 +1286,14 @@ function CC:ShowEditBindingPanel(spellData, existingBinding, existingIndex)
12751286
end
12761287

12771288
-- Reset priority slider position for spells (shifted down for the
1278-
-- Target on cast row added to Macro Options)
1289+
-- Target on cast + Always Cast rows added to the advanced section)
12791290
if panel.priorityLabel then
12801291
panel.priorityLabel:ClearAllPoints()
1281-
panel.priorityLabel:SetPoint("TOPLEFT", panel.advancedContent, "TOPLEFT", 0, -182)
1292+
panel.priorityLabel:SetPoint("TOPLEFT", panel.advancedContent, "TOPLEFT", 0, -204)
12821293
end
12831294
if panel.prioritySlider then
12841295
panel.prioritySlider:ClearAllPoints()
1285-
panel.prioritySlider:SetPoint("TOPLEFT", panel.advancedContent, "TOPLEFT", 68, -179)
1296+
panel.prioritySlider:SetPoint("TOPLEFT", panel.advancedContent, "TOPLEFT", 68, -201)
12861297
end
12871298

12881299
-- Auto-expand if binding has advanced options
@@ -1360,7 +1371,7 @@ function CC:ShowEditBindingPanel(spellData, existingBinding, existingIndex)
13601371

13611372
-- Adjust panel height based on macro/item vs spell, and Advanced expanded state
13621373
local SPELL_COLLAPSED_HEIGHT = 502
1363-
local SPELL_EXPANDED_HEIGHT = 685
1374+
local SPELL_EXPANDED_HEIGHT = 707
13641375
local MACRO_COLLAPSED_HEIGHT = 475 -- With Global Keybind section above Active
13651376
local MACRO_EXPANDED_HEIGHT = 540 -- With Advanced expanded (just priority slider)
13661377
local SPECIAL_COLLAPSED_HEIGHT = 450 -- Target/menu: target type + combat, no Advanced section
@@ -1391,6 +1402,7 @@ function CC:ShowEditBindingPanel(spellData, existingBinding, existingIndex)
13911402
panel.mouseoverCB:SetChecked(fallback.mouseover == true)
13921403
panel.targetFallbackCB:SetChecked(fallback.target == true)
13931404
panel.selfCB:SetChecked(fallback.selfCast == true)
1405+
panel.alwaysCastCB:SetChecked(fallback.alwaysCast == true)
13941406
panel.stopSpellTargetCB:SetChecked(fallback.stopSpellTarget == true)
13951407
-- Show the effective state (global default unless this binding overrides it).
13961408
-- Clicking writes an explicit override; leaving it untouched keeps inheriting.

0 commit comments

Comments
 (0)