mirror of
https://github.com/brues-code/SuperCleveRoidMacros.git
synced 2026-09-16 03:38:00 +00:00
Compare commits
38
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7bbb8df010 | ||
|
|
96dbe9aca8 | ||
|
|
1de455f96e | ||
|
|
d93d97a469 | ||
|
|
2a562e0550 | ||
|
|
daaab2fd11 | ||
|
|
43f1ba378a | ||
|
|
828acc0c4b | ||
|
|
f296425c94 | ||
|
|
4dd5326172 | ||
|
|
c5ddc6c313 | ||
|
|
9f8a5553bc | ||
|
|
00caff0c9b | ||
|
|
080c1243b6 | ||
|
|
6f78be8394 | ||
|
|
10ad9eb980 | ||
|
|
0ef4fe1818 | ||
|
|
269c6ba67e | ||
|
|
c7905c2a70 | ||
|
|
77c0dba2c4 | ||
|
|
61bab6c734 | ||
|
|
daa7d6f2bf | ||
|
|
6d73c6b006 | ||
|
|
d5baecaf41 | ||
|
|
5c31232b52 | ||
|
|
d5cae2136c | ||
|
|
004eebc9f7 | ||
|
|
63b484b1ea | ||
|
|
cf47d8caba | ||
|
|
71f8f74237 | ||
|
|
1a98a05b7e | ||
|
|
64e3f93042 | ||
|
|
0c86ed67e9 | ||
|
|
60c1b7f235 | ||
|
|
8c10353567 | ||
|
|
fcd206756d | ||
|
|
8d86389a03 | ||
|
|
8c85ce572a |
@@ -18,6 +18,6 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Package and release to GitHub
|
- name: Package and release to GitHub
|
||||||
uses: BigWigsMods/packager@v2
|
uses: brues-code/packager@vCAPI
|
||||||
env:
|
env:
|
||||||
GITHUB_OAUTH: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_OAUTH: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"runtime": {
|
||||||
|
"version": "Lua 5.1"
|
||||||
|
},
|
||||||
|
"diagnostics": {
|
||||||
|
"disable": ["deprecated"],
|
||||||
|
"globals": [
|
||||||
|
"this",
|
||||||
|
"event",
|
||||||
|
"arg",
|
||||||
|
"arg1",
|
||||||
|
"arg2",
|
||||||
|
"arg3",
|
||||||
|
"arg4",
|
||||||
|
"arg5",
|
||||||
|
"arg6",
|
||||||
|
"arg7",
|
||||||
|
"arg8",
|
||||||
|
"arg9"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
+21
-9
@@ -3,9 +3,11 @@
|
|||||||
|
|
||||||
ClassicAPI is a client mod (sibling to Nampower/SuperWoW) that backports the
|
ClassicAPI is a client mod (sibling to Nampower/SuperWoW) that backports the
|
||||||
modern C_* API into the 1.12.1 Lua environment. It is a HARD REQUIREMENT of
|
modern C_* API into the 1.12.1 Lua environment. It is a HARD REQUIREMENT of
|
||||||
this addon, so the wrappers below call the API directly — no fallbacks. The
|
this addon (ClassicAPI v1.12.1+, which added the positional
|
||||||
load-time requirement check (Core.lua) uses IsAvailable() to warn when the
|
C_UnitAuras.UnitAura), so the wrappers below call the API directly — no
|
||||||
DLL is missing; users who don't want ClassicAPI should run the upstream addon.
|
fallbacks. The load-time requirement check (Core.lua) uses IsAvailable() to
|
||||||
|
warn when the DLL is missing and HasMinimumVersion() when it's too old; users
|
||||||
|
who don't want ClassicAPI should run the upstream addon.
|
||||||
|
|
||||||
Detection: the global CLASSIC_API_VERSION is defined once the client has
|
Detection: the global CLASSIC_API_VERSION is defined once the client has
|
||||||
booted, encoded as X*10000 + Y*100 + Z for a vX.Y.Z tag (untagged dev builds
|
booted, encoded as X*10000 + Y*100 + Z for a vX.Y.Z tag (untagged dev builds
|
||||||
@@ -53,15 +55,16 @@ end
|
|||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
-- Scan one aura range of `unit` (filter = "HELPFUL" or "HARMFUL") for an aura
|
-- Scan one aura range of `unit` (filter = "HELPFUL" or "HARMFUL") for an aura
|
||||||
-- matching the dispel type. The filtered index self-terminates at the end of the
|
-- matching the dispel type. Uses the positional C_UnitAuras.UnitAura (added in
|
||||||
-- range (nil); 48 is a backstop over vanilla's 32 helpful / 16 harmful slots.
|
-- ClassicAPI v1.12.1, this addon's minimum) -- no table allocated per slot, with
|
||||||
|
-- dispelName as the 4th return. The filtered index self-terminates at the end of
|
||||||
|
-- the range (nil name); 48 is a backstop over vanilla's 32 helpful / 16 harmful slots.
|
||||||
local function scanDispel(unit, filter, dispelType, wantAny)
|
local function scanDispel(unit, filter, dispelType, wantAny)
|
||||||
local i = 1
|
local i = 1
|
||||||
while i <= 48 do
|
while i <= 48 do
|
||||||
local data = C_UnitAuras.GetAuraDataByIndex(unit, i, filter)
|
local name, _, _, dispelName = C_UnitAuras.UnitAura(unit, i, filter)
|
||||||
if not data then return false end
|
if not name then return false end
|
||||||
local dn = data.dispelName
|
if dispelName and dispelName ~= "" and (wantAny or dispelName == dispelType) then
|
||||||
if dn and dn ~= "" and (wantAny or dn == dispelType) then
|
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
i = i + 1
|
i = i + 1
|
||||||
@@ -283,6 +286,15 @@ function API.GetSpellBonusHealing()
|
|||||||
return GetSpellBonusHealing()
|
return GetSpellBonusHealing()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Spell haste for `unit` as a percentage: 0 unhasted, positive when casts are
|
||||||
|
-- sped up, negative while slowed (Curse of Tongues). 0 for a unit that doesn't
|
||||||
|
-- resolve. Vanilla has no haste API at all -- ClassicAPI derives this from the
|
||||||
|
-- UNIT_MOD_CAST_SPEED descriptor field the server folds into the cast time, so
|
||||||
|
-- it's exact and needs no nampower GetUnitField dependency.
|
||||||
|
function API.UnitSpellHaste(unit)
|
||||||
|
return UnitSpellHaste(unit)
|
||||||
|
end
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Unit Health
|
-- Unit Health
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|||||||
+25
-372
@@ -22,19 +22,6 @@ function Extension.DLOG(msg)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Extension.FocusNameHook()
|
|
||||||
local hook = Extension.internal.memberHooks[CleveRoids]["GetFocusName"]
|
|
||||||
local target = hook.original()
|
|
||||||
|
|
||||||
if pfUI and pfUI.uf and pfUI.uf.focus and pfUI.uf.focus.unitname then
|
|
||||||
target = pfUI.uf.focus.unitname
|
|
||||||
end
|
|
||||||
|
|
||||||
--Extension.DLOG(target)
|
|
||||||
|
|
||||||
return target
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Check if pfUI's macrotweak module is loaded
|
-- Check if pfUI's macrotweak module is loaded
|
||||||
function Extension.IsPfUIMacrotweakLoaded()
|
function Extension.IsPfUIMacrotweakLoaded()
|
||||||
if not pfUI then return false end
|
if not pfUI then return false end
|
||||||
@@ -92,334 +79,6 @@ function Extension.HandleSendChatMessageHook()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Helper function to check for Carnage duration override
|
|
||||||
-- Returns override duration and timeleft if found, nil otherwise
|
|
||||||
local function GetCarnageOverride(effect)
|
|
||||||
if not effect or not CleveRoids.carnageDurationOverrides then
|
|
||||||
return nil, nil
|
|
||||||
end
|
|
||||||
|
|
||||||
for spellID, override in pairs(CleveRoids.carnageDurationOverrides) do
|
|
||||||
local spellName = C_Spell.GetSpellName(spellID)
|
|
||||||
if spellName then
|
|
||||||
local baseName = CleveRoids.StripRank(spellName)
|
|
||||||
if baseName == effect and override.timestamp and (GetTime() - override.timestamp) < 5 then
|
|
||||||
local timeleft = override.duration - (GetTime() - override.timestamp)
|
|
||||||
if timeleft < 0 then timeleft = 0 end
|
|
||||||
return override.duration, timeleft
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return nil, nil
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Hook pfUI's libdebuff to use our combo-aware durations
|
|
||||||
-- NOTE: pfUI 7.6+ (GetUnitField edition) handles combo durations and Carnage internally.
|
|
||||||
-- We only inject when there's a mismatch between pfUI's data and ours.
|
|
||||||
function Extension.HookPfUILibdebuff()
|
|
||||||
if not pfUI or not pfUI.api or not pfUI.api.libdebuff then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
local pflib = pfUI.api.libdebuff
|
|
||||||
|
|
||||||
-- Check if pfUI 7.6+ with GetUnitField-based libdebuff is active
|
|
||||||
-- If so, pfUI handles combo durations and Carnage internally - we only override on mismatch
|
|
||||||
local hasPfUI76 = CleveRoids.hasPfUI76
|
|
||||||
|
|
||||||
-- pfUI 7.6+ handles all durations internally - no hooks needed
|
|
||||||
if hasPfUI76 then
|
|
||||||
Extension.DLOG("Skipped all libdebuff hooks (pfUI 7.6+ handles internally)")
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Hook GetDuration if it exists
|
|
||||||
-- pfUI's GetDuration signature: function(effect, rank) where effect is spell NAME
|
|
||||||
if pflib.GetDuration and not Extension.pfLibDebuffHooked then
|
|
||||||
local originalGetDuration = pflib.GetDuration
|
|
||||||
|
|
||||||
pflib.GetDuration = function(self, effect, rank)
|
|
||||||
local pfuiDuration = originalGetDuration(self, effect, rank)
|
|
||||||
|
|
||||||
-- Check for Carnage duration overrides (only if pfUI doesn't have it)
|
|
||||||
local carnageDuration = GetCarnageOverride(effect)
|
|
||||||
if carnageDuration then
|
|
||||||
-- Only override if pfUI's duration is significantly different (>1s difference)
|
|
||||||
if not pfuiDuration or math.abs(carnageDuration - pfuiDuration) > 1 then
|
|
||||||
if Extension.Debug then
|
|
||||||
DEFAULT_CHAT_FRAME:AddMessage(
|
|
||||||
string.format("|cff00aaff[pfUI Duration Override]|r %s: Carnage %.1fs (pfUI: %.1fs)",
|
|
||||||
effect, carnageDuration, pfuiDuration or 0)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
return carnageDuration
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Check name-based tracking for fresh combo casts (only if pfUI returned 0 or nil)
|
|
||||||
if (not pfuiDuration or pfuiDuration == 0) and CleveRoids.ComboPointTracking and CleveRoids.ComboPointTracking[effect] then
|
|
||||||
local tracking = CleveRoids.ComboPointTracking[effect]
|
|
||||||
if tracking.duration and tracking.confirmed and (GetTime() - tracking.cast_time) < 0.5 then
|
|
||||||
if Extension.Debug then
|
|
||||||
DEFAULT_CHAT_FRAME:AddMessage(
|
|
||||||
string.format("|cff00aaff[pfUI Duration Override]|r %s: Combo tracking %.1fs (pfUI: %.1fs)",
|
|
||||||
effect, tracking.duration, pfuiDuration or 0)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
return tracking.duration
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return pfuiDuration
|
|
||||||
end
|
|
||||||
|
|
||||||
Extension.pfLibDebuffHooked = true
|
|
||||||
Extension.DLOG("Hooked pfUI.api.libdebuff.GetDuration (mismatch-only mode)")
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Hook AddEffect if it exists (pre-7.6 only - 7.6+ returns early above)
|
|
||||||
if pflib.AddEffect and not Extension.pfLibAddEffectHooked then
|
|
||||||
local originalAddEffect = pflib.AddEffect
|
|
||||||
|
|
||||||
pflib.AddEffect = function(self, unit, unitlevel, effect, duration, caster)
|
|
||||||
-- RANK CHECKING: Preserve higher rank's remaining time if lower rank was cast
|
|
||||||
-- NOTE: 'unit' is a unit NAME (e.g., "Expert Training Dummy"), not a unit ID
|
|
||||||
-- Defensive: verify libdebuff is a table, not a function
|
|
||||||
if caster == "player" and type(CleveRoids.libdebuff) == "table" and duration and duration > 0 then
|
|
||||||
-- Try to find the GUID for this unit name
|
|
||||||
local unitGUID = nil
|
|
||||||
|
|
||||||
-- Check if this is the current target
|
|
||||||
if UnitName("target") == unit then
|
|
||||||
unitGUID = CleveRoids.GetGUID("target")
|
|
||||||
end
|
|
||||||
|
|
||||||
-- If we couldn't match to current target, check guidToName mapping
|
|
||||||
if not unitGUID and CleveRoids.libdebuff.guidToName then
|
|
||||||
for guid, name in pairs(CleveRoids.libdebuff.guidToName) do
|
|
||||||
if name == unit then
|
|
||||||
unitGUID = guid
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Check if a higher rank of this spell is already active
|
|
||||||
if unitGUID and CleveRoids.libdebuff.objects and CleveRoids.libdebuff.objects[unitGUID] then
|
|
||||||
-- Find all spell IDs that match this effect name
|
|
||||||
for spellID, rec in pairs(CleveRoids.libdebuff.objects[unitGUID]) do
|
|
||||||
if rec and rec.start and rec.duration then
|
|
||||||
-- Get spell name for this ID
|
|
||||||
local spellName = C_Spell.GetSpellName(spellID)
|
|
||||||
if spellName then
|
|
||||||
local baseName = CleveRoids.StripRank(spellName)
|
|
||||||
if baseName == effect then
|
|
||||||
-- Same spell - check if still active
|
|
||||||
local remaining = rec.duration + rec.start - GetTime()
|
|
||||||
if remaining > 0 then
|
|
||||||
-- If incoming duration > remaining time, we're trying to add more time
|
|
||||||
-- This means either a refresh or lower rank cast - preserve existing timer
|
|
||||||
if duration > remaining then
|
|
||||||
if Extension.Debug then
|
|
||||||
DEFAULT_CHAT_FRAME:AddMessage(
|
|
||||||
string.format("|cff00aaff[pfUI Rank Preserve]|r %s: Preserving timer (%.1fs remaining vs %.1fs incoming)",
|
|
||||||
effect, remaining, duration)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Preserve the existing timer
|
|
||||||
duration = remaining
|
|
||||||
end
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Check for Carnage duration overrides FIRST (highest priority)
|
|
||||||
local carnageDuration = GetCarnageOverride(effect)
|
|
||||||
if carnageDuration then
|
|
||||||
duration = duration or carnageDuration
|
|
||||||
caster = caster or "player" -- Ensure caster is set for UnitOwnDebuff filtering
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Check if this is a combo scaling spell by name
|
|
||||||
if not duration and CleveRoids.IsComboScalingSpell and CleveRoids.IsComboScalingSpell(effect) then
|
|
||||||
if CleveRoids.ComboPointTracking and CleveRoids.ComboPointTracking[effect] then
|
|
||||||
local tracking = CleveRoids.ComboPointTracking[effect]
|
|
||||||
if tracking.duration and tracking.confirmed and (GetTime() - tracking.cast_time) < 0.5 then
|
|
||||||
duration = tracking.duration
|
|
||||||
caster = caster or "player"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return originalAddEffect(self, unit, unitlevel, effect, duration, caster)
|
|
||||||
end
|
|
||||||
|
|
||||||
Extension.pfLibAddEffectHooked = true
|
|
||||||
Extension.DLOG("Hooked pfUI.api.libdebuff.AddEffect")
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Hook UnitDebuff to return Carnage override duration to display code
|
|
||||||
-- Only override when pfUI's duration differs significantly from ours
|
|
||||||
if pflib.UnitDebuff and not Extension.pfLibUnitDebuffHooked then
|
|
||||||
local originalUnitDebuff = pflib.UnitDebuff
|
|
||||||
|
|
||||||
pflib.UnitDebuff = function(self, unit, id)
|
|
||||||
local effect, rank, texture, stacks, dtype, duration, timeleft, caster = originalUnitDebuff(self, unit, id)
|
|
||||||
|
|
||||||
-- Only check Carnage override if pfUI returned data but duration might be wrong
|
|
||||||
if effect then
|
|
||||||
local carnageDuration, carnageTimeleft = GetCarnageOverride(effect)
|
|
||||||
if carnageDuration then
|
|
||||||
-- Only override if there's a significant difference (>1s)
|
|
||||||
if not duration or math.abs(carnageDuration - duration) > 1 then
|
|
||||||
if Extension.Debug then
|
|
||||||
DEFAULT_CHAT_FRAME:AddMessage(
|
|
||||||
string.format("|cff00aaff[pfUI UnitDebuff Override]|r %s: Carnage %.1fs/%.1fs (pfUI: %.1fs/%.1fs)",
|
|
||||||
effect, carnageDuration, carnageTimeleft, duration or 0, timeleft or 0)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
duration = carnageDuration
|
|
||||||
timeleft = carnageTimeleft
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return effect, rank, texture, stacks, dtype, duration, timeleft, caster
|
|
||||||
end
|
|
||||||
|
|
||||||
Extension.pfLibUnitDebuffHooked = true
|
|
||||||
Extension.DLOG("Hooked pfUI.api.libdebuff.UnitDebuff (mismatch-only mode)")
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Hook UnitOwnDebuff to return Carnage override duration when selfdebuff is enabled
|
|
||||||
-- Only override when pfUI's duration differs significantly from ours
|
|
||||||
if pflib.UnitOwnDebuff and not Extension.pfLibUnitOwnDebuffHooked then
|
|
||||||
local originalUnitOwnDebuff = pflib.UnitOwnDebuff
|
|
||||||
|
|
||||||
pflib.UnitOwnDebuff = function(self, unit, id)
|
|
||||||
local effect, rank, texture, stacks, dtype, duration, timeleft, caster = originalUnitOwnDebuff(self, unit, id)
|
|
||||||
|
|
||||||
if effect then
|
|
||||||
local carnageDuration, carnageTimeleft = GetCarnageOverride(effect)
|
|
||||||
if carnageDuration then
|
|
||||||
-- Only override if there's a significant difference (>1s)
|
|
||||||
if not duration or math.abs(carnageDuration - duration) > 1 then
|
|
||||||
if Extension.Debug then
|
|
||||||
DEFAULT_CHAT_FRAME:AddMessage(
|
|
||||||
string.format("|cff00aaff[pfUI UnitOwnDebuff Override]|r %s: Carnage %.1fs/%.1fs (pfUI: %.1fs/%.1fs)",
|
|
||||||
effect, carnageDuration, carnageTimeleft, duration or 0, timeleft or 0)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
duration = carnageDuration
|
|
||||||
timeleft = carnageTimeleft
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- If UnitOwnDebuff returned nil but we have a Carnage override, synthesize from UnitDebuff
|
|
||||||
-- This fallback is only needed for edge cases where pfUI doesn't track the debuff yet
|
|
||||||
elseif not effect and CleveRoids.carnageDurationOverrides then
|
|
||||||
-- Use pflib:UnitDebuff which includes our Carnage override hook
|
|
||||||
local baseEffect, baseRank, baseTex, baseStacks, baseDtype, baseDur, baseLeft, _ = pflib:UnitDebuff(unit, id)
|
|
||||||
if baseEffect then
|
|
||||||
local carnageDuration2, carnageTimeleft2 = GetCarnageOverride(baseEffect)
|
|
||||||
if carnageDuration2 then
|
|
||||||
return baseEffect, baseRank, baseTex, baseStacks, baseDtype, carnageDuration2, carnageTimeleft2, "player"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return effect, rank, texture, stacks, dtype, duration, timeleft, caster
|
|
||||||
end
|
|
||||||
|
|
||||||
Extension.pfLibUnitOwnDebuffHooked = true
|
|
||||||
Extension.DLOG("Hooked pfUI.api.libdebuff.UnitOwnDebuff (mismatch-only mode)")
|
|
||||||
end
|
|
||||||
|
|
||||||
return Extension.pfLibDebuffHooked or Extension.pfLibAddEffectHooked or Extension.pfLibUnitDebuffHooked or Extension.pfLibUnitOwnDebuffHooked
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Synchronize combo durations to pfUI's libdebuff objects
|
|
||||||
-- NOTE: pfUI 7.6+ handles combo durations internally - skip sync entirely
|
|
||||||
function Extension.SyncComboDurationToPfUI(guid, spellID, duration)
|
|
||||||
if not pfUI or not pfUI.api or not pfUI.api.libdebuff then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
-- pfUI 7.6+ handles all durations internally
|
|
||||||
if CleveRoids.hasPfUI76 then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Get unit name from GUID
|
|
||||||
local unitName = nil
|
|
||||||
local unitLevel = 0
|
|
||||||
|
|
||||||
-- Check if this is the current target
|
|
||||||
local targetGUID = CleveRoids.GetGUID("target")
|
|
||||||
if targetGUID == guid then
|
|
||||||
unitName = UnitName("target")
|
|
||||||
unitLevel = UnitLevel("target") or 0
|
|
||||||
end
|
|
||||||
|
|
||||||
-- If we couldn't find the unit, use GUID to name mapping from libdebuff
|
|
||||||
if not unitName and CleveRoids.libdebuff and CleveRoids.libdebuff.guidToName then
|
|
||||||
unitName = CleveRoids.libdebuff.guidToName[guid]
|
|
||||||
-- Default to level 0 if we don't have the unit targeted
|
|
||||||
unitLevel = 0
|
|
||||||
end
|
|
||||||
|
|
||||||
if not unitName then
|
|
||||||
if CleveRoids.debug then
|
|
||||||
DEFAULT_CHAT_FRAME:AddMessage("|cffff0000[pfUI Sync]|r Could not find unit name for GUID")
|
|
||||||
end
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Get spell name from spell ID
|
|
||||||
local spellName = C_Spell.GetSpellName(spellID)
|
|
||||||
if not spellName then
|
|
||||||
if CleveRoids.debug then
|
|
||||||
DEFAULT_CHAT_FRAME:AddMessage("|cffff0000[pfUI Sync]|r Could not find spell name for ID " .. spellID)
|
|
||||||
end
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Remove rank from spell name to match pfUI's format
|
|
||||||
local effectName = CleveRoids.StripRank(spellName)
|
|
||||||
|
|
||||||
-- Update pfUI's stored debuff duration
|
|
||||||
local pflib = pfUI.api.libdebuff
|
|
||||||
if pflib.objects and pflib.objects[unitName] then
|
|
||||||
-- Try both the specific level and level 0 (fallback)
|
|
||||||
for _, level in ipairs({unitLevel, 0}) do
|
|
||||||
if pflib.objects[unitName][level] and pflib.objects[unitName][level][effectName] then
|
|
||||||
local old_duration = pflib.objects[unitName][level][effectName].duration
|
|
||||||
pflib.objects[unitName][level][effectName].duration = duration
|
|
||||||
|
|
||||||
if CleveRoids.debug then
|
|
||||||
DEFAULT_CHAT_FRAME:AddMessage(
|
|
||||||
string.format("|cff00ffaa[pfUI Sync]|r Updated %s on %s (L%d): %ds -> %ds",
|
|
||||||
effectName, unitName, level, old_duration or 0, duration)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if CleveRoids.debug then
|
|
||||||
DEFAULT_CHAT_FRAME:AddMessage(
|
|
||||||
string.format("|cffaaaa00[pfUI Sync]|r Effect not found in pfUI storage: %s on %s",
|
|
||||||
effectName, unitName)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Register action event handler for pfUI button updates
|
-- Register action event handler for pfUI button updates
|
||||||
function Extension.RegisterPfUIActionEventHandler()
|
function Extension.RegisterPfUIActionEventHandler()
|
||||||
if not pfUI or Extension.actionHandlerRegistered then
|
if not pfUI or Extension.actionHandlerRegistered then
|
||||||
@@ -429,7 +88,17 @@ function Extension.RegisterPfUIActionEventHandler()
|
|||||||
-- Register a handler that will be called whenever CleveRoids updates macro states
|
-- Register a handler that will be called whenever CleveRoids updates macro states
|
||||||
if CleveRoids.RegisterActionEventHandler then
|
if CleveRoids.RegisterActionEventHandler then
|
||||||
Extension.DLOG("Registering pfUI action event handler")
|
Extension.DLOG("Registering pfUI action event handler")
|
||||||
CleveRoids.RegisterActionEventHandler(function(slot, event, ...)
|
-- Declared without `...` on purpose: in 1.12's Lua 5.0 a vararg function
|
||||||
|
-- allocates an `arg` table on every call, and this handler never read it.
|
||||||
|
-- That mattered because UpdateAllManagedCooldowns fans
|
||||||
|
-- ACTIONBAR_UPDATE_COOLDOWN out across every managed slot -- up to 120 calls
|
||||||
|
-- -- on each SPELL_UPDATE_COOLDOWN, which fires on every GCD and cooldown
|
||||||
|
-- tick. Those calls all allocated a table and then did nothing, because the
|
||||||
|
-- whole body only ever applied to ACTIONBAR_SLOT_CHANGED. Hence the early
|
||||||
|
-- return before any work.
|
||||||
|
CleveRoids.RegisterActionEventHandler(function(slot, event)
|
||||||
|
if event ~= "ACTIONBAR_SLOT_CHANGED" then return end
|
||||||
|
|
||||||
local button = pfUI.bars and pfUI.bars.buttons and pfUI.bars.buttons[slot]
|
local button = pfUI.bars and pfUI.bars.buttons and pfUI.bars.buttons[slot]
|
||||||
|
|
||||||
if Extension.Debug then
|
if Extension.Debug then
|
||||||
@@ -441,23 +110,21 @@ function Extension.RegisterPfUIActionEventHandler()
|
|||||||
))
|
))
|
||||||
end
|
end
|
||||||
|
|
||||||
-- For slot change events, do a full button update so the icon,
|
-- Full button update so the icon, cooldown and tooltip refresh.
|
||||||
-- cooldown, and tooltip refresh through CleveRoids' hooked
|
|
||||||
-- GetActionTexture / GetActionCooldown / GameTooltip:SetAction.
|
|
||||||
-- pfUI's ButtonMacroScan defers to us for managed macros (leaves
|
-- pfUI's ButtonMacroScan defers to us for managed macros (leaves
|
||||||
-- spellslot nil), so its ButtonFullUpdate routes through those hooks
|
-- spellslot nil), so ButtonFullUpdate reads the stock action-bar
|
||||||
-- and follows the active conditional — no manual cooldown override
|
-- functions -- which now resolve through the value we publish with
|
||||||
-- needed.
|
-- C_Macro.SetMacroDisplay, rather than the Lua overrides this addon
|
||||||
if event == "ACTIONBAR_SLOT_CHANGED" then
|
-- used to install.
|
||||||
-- Mark the slot for update in pfUI's cache (processed next OnUpdate)
|
|
||||||
if pfUI.bars and pfUI.bars.update then
|
|
||||||
pfUI.bars.update[slot] = true
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Also directly call ButtonFullUpdate if the button exists
|
-- Mark the slot for update in pfUI's cache (processed next OnUpdate)
|
||||||
if button and pfUI.bars.ButtonFullUpdate then
|
if pfUI.bars and pfUI.bars.update then
|
||||||
pfUI.bars.ButtonFullUpdate(button)
|
pfUI.bars.update[slot] = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Also directly call ButtonFullUpdate if the button exists
|
||||||
|
if button and pfUI.bars.ButtonFullUpdate then
|
||||||
|
pfUI.bars.ButtonFullUpdate(button)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@@ -474,9 +141,6 @@ function Extension.SetupCompatibility()
|
|||||||
if Extension.pfUILoaded then
|
if Extension.pfUILoaded then
|
||||||
Extension.DLOG("pfUI detected")
|
Extension.DLOG("pfUI detected")
|
||||||
|
|
||||||
-- Hook libdebuff for combo duration support
|
|
||||||
Extension.HookPfUILibdebuff()
|
|
||||||
|
|
||||||
-- Register action event handler for button updates
|
-- Register action event handler for button updates
|
||||||
Extension.RegisterPfUIActionEventHandler()
|
Extension.RegisterPfUIActionEventHandler()
|
||||||
|
|
||||||
@@ -631,16 +295,9 @@ function Extension.SetupPfUIEventHooks(lib)
|
|||||||
ev:UnregisterEvent("AURA_CAST_ON_OTHER")
|
ev:UnregisterEvent("AURA_CAST_ON_OTHER")
|
||||||
ev:UnregisterEvent("DEBUFF_ADDED_OTHER")
|
ev:UnregisterEvent("DEBUFF_ADDED_OTHER")
|
||||||
ev:UnregisterEvent("DEBUFF_REMOVED_OTHER")
|
ev:UnregisterEvent("DEBUFF_REMOVED_OTHER")
|
||||||
ev:UnregisterEvent("BUFF_ADDED_OTHER")
|
|
||||||
ev:UnregisterEvent("BUFF_REMOVED_SELF")
|
ev:UnregisterEvent("BUFF_REMOVED_SELF")
|
||||||
ev:UnregisterEvent("BUFF_REMOVED_OTHER")
|
ev:UnregisterEvent("BUFF_REMOVED_OTHER")
|
||||||
|
|
||||||
-- pfUI 7.6+ also handles cast tracking internally
|
|
||||||
if lib.hasPfUI76 then
|
|
||||||
ev:UnregisterEvent("SPELL_START_OTHER")
|
|
||||||
ev:UnregisterEvent("SPELL_FAILED_OTHER")
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Keep registered: SPELL_START_SELF (channel duration capture before early return),
|
-- Keep registered: SPELL_START_SELF (channel duration capture before early return),
|
||||||
-- UNIT_DIED (AllCasterAuraTracking + OverflowBuff cleanup), UNIT_CASTEVENT (SuperWoW),
|
-- UNIT_DIED (AllCasterAuraTracking + OverflowBuff cleanup), UNIT_CASTEVENT (SuperWoW),
|
||||||
-- PLAYER_TARGET_CHANGED, UNIT_AURA (SeedUnit)
|
-- PLAYER_TARGET_CHANGED, UNIT_AURA (SeedUnit)
|
||||||
@@ -700,7 +357,6 @@ end
|
|||||||
|
|
||||||
function Extension.OnLoad()
|
function Extension.OnLoad()
|
||||||
Extension.DLOG("Extension pfUI Loaded.")
|
Extension.DLOG("Extension pfUI Loaded.")
|
||||||
Extension.HookMethod(CleveRoids, "GetFocusName", "FocusNameHook", true)
|
|
||||||
|
|
||||||
-- Export extension for external access
|
-- Export extension for external access
|
||||||
CleveRoids.Compatibility_pfUI = Extension
|
CleveRoids.Compatibility_pfUI = Extension
|
||||||
@@ -743,7 +399,7 @@ end
|
|||||||
|
|
||||||
function Extension.OnPlayerLogin()
|
function Extension.OnPlayerLogin()
|
||||||
-- Ensure lib.objects is linked correctly (InitPfUIIntegration is idempotent).
|
-- Ensure lib.objects is linked correctly (InitPfUIIntegration is idempotent).
|
||||||
if pfUI and not CleveRoids.hasPfUI76 then
|
if pfUI then
|
||||||
local lib = CleveRoids.libdebuff
|
local lib = CleveRoids.libdebuff
|
||||||
if lib and lib.InitPfUIIntegration then
|
if lib and lib.InitPfUIIntegration then
|
||||||
lib:InitPfUIIntegration()
|
lib:InitPfUIIntegration()
|
||||||
@@ -775,9 +431,6 @@ function Extension.OnPlayerLogin()
|
|||||||
-- Print startup status only if pfUI global exists and compatibility was set up
|
-- Print startup status only if pfUI global exists and compatibility was set up
|
||||||
if Extension.pfUILoaded and pfUI then
|
if Extension.pfUILoaded and pfUI then
|
||||||
local statusMsg = "|cff00ff00[SCRM]|r pfUI compatibility loaded"
|
local statusMsg = "|cff00ff00[SCRM]|r pfUI compatibility loaded"
|
||||||
if CleveRoids.hasPfUI76 then
|
|
||||||
statusMsg = statusMsg .. " (7.6+ GUID cast tracking)"
|
|
||||||
end
|
|
||||||
-- statusMsg = statusMsg .. ". Use /pfuicd for debug."
|
-- statusMsg = statusMsg .. ". Use /pfuicd for debug."
|
||||||
DEFAULT_CHAT_FRAME:AddMessage(statusMsg)
|
DEFAULT_CHAT_FRAME:AddMessage(statusMsg)
|
||||||
if not Extension.actionHandlerRegistered then
|
if not Extension.actionHandlerRegistered then
|
||||||
|
|||||||
+217
-370
@@ -197,102 +197,6 @@ local function IsSharedDebuffByIdOrName(lib, spellID, debuffName)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
-- PERFORMANCE: Equipment cache for HasGearEquipped (avoids 19-slot scan per call)
|
|
||||||
-- Invalidated on UNIT_INVENTORY_CHANGED via CleveRoids.InvalidateEquipmentCache()
|
|
||||||
-- Enhanced with Nampower v2.18+ GetEquippedItems when available
|
|
||||||
local _equippedItemIDs = {} -- [slot] = itemID (number)
|
|
||||||
local _equippedItemNames = {} -- [slot] = itemName (lowercase string)
|
|
||||||
local _equipmentCacheValid = false
|
|
||||||
|
|
||||||
-- Track if we've warned about GetEquippedItems errors (warn once per session)
|
|
||||||
local _getEquippedItemsErrorWarned = false
|
|
||||||
|
|
||||||
local function BuildEquipmentCache()
|
|
||||||
if _equipmentCacheValid then return end
|
|
||||||
_equipmentCacheValid = true
|
|
||||||
|
|
||||||
-- Clear old data
|
|
||||||
for i = 1, 19 do
|
|
||||||
_equippedItemIDs[i] = nil
|
|
||||||
_equippedItemNames[i] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local string_find = string.find
|
|
||||||
local string_lower = string.lower
|
|
||||||
|
|
||||||
-- Try Nampower GetEquippedItems for faster enumeration
|
|
||||||
-- Requires v2.22+ because earlier versions (e.g., v2.19.1) have internal bug
|
|
||||||
local API = CleveRoids.NampowerAPI
|
|
||||||
local hasValidNampower = API and API.HasMinimumVersion and API.HasMinimumVersion(2, 22, 0)
|
|
||||||
|
|
||||||
if hasValidNampower and GetEquippedItems then
|
|
||||||
-- Use pcall to catch any internal Nampower errors and fall back gracefully
|
|
||||||
local success, result = pcall(GetEquippedItems, "player")
|
|
||||||
|
|
||||||
if not success then
|
|
||||||
-- Log the error once per session for debugging
|
|
||||||
if not _getEquippedItemsErrorWarned then
|
|
||||||
_getEquippedItemsErrorWarned = true
|
|
||||||
local errMsg = tostring(result)
|
|
||||||
if CleveRoids.Print then
|
|
||||||
CleveRoids.Print("|cffff6600Warning:|r GetEquippedItems failed: " .. errMsg)
|
|
||||||
CleveRoids.Print("Using fallback equipment detection. Consider updating Nampower.")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- Fall through to manual enumeration
|
|
||||||
elseif result and type(result) == "table" then
|
|
||||||
local usedNampower = false
|
|
||||||
for nampowerSlot, itemInfo in pairs(result) do
|
|
||||||
-- Nampower uses 0-indexed slots, WoW API uses 1-indexed
|
|
||||||
-- tonumber() handles both string and numeric keys from different Nampower versions
|
|
||||||
-- Skip non-numeric keys (metadata fields, etc.)
|
|
||||||
local slotNum = tonumber(nampowerSlot)
|
|
||||||
if slotNum and type(itemInfo) == "table" and itemInfo.itemId then
|
|
||||||
local slot = slotNum + 1
|
|
||||||
-- itemInfo must be a table to access .itemId (userdata from some Nampower versions is not indexable)
|
|
||||||
if slot >= 1 and slot <= 19 then
|
|
||||||
_equippedItemIDs[slot] = itemInfo.itemId
|
|
||||||
usedNampower = true
|
|
||||||
|
|
||||||
-- Get item name via Nampower API or GetItemInfo
|
|
||||||
local itemName = API and API.GetItemName and API.GetItemName(itemInfo.itemId)
|
|
||||||
if not itemName then
|
|
||||||
itemName = GetItemInfo(itemInfo.itemId)
|
|
||||||
end
|
|
||||||
if itemName then
|
|
||||||
_equippedItemNames[slot] = string_lower(itemName)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if usedNampower then
|
|
||||||
return -- Done with Nampower path
|
|
||||||
end
|
|
||||||
-- Fall through to manual enumeration if Nampower returned userdata items
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Fallback: manual slot enumeration via ClassicAPI (id + decorated name),
|
|
||||||
-- no link string built. C_Item.GetItemName carries random-suffix decoration
|
|
||||||
-- and falls back to the base name internally, so it replaces the old
|
|
||||||
-- bracket-name / GetItemInfo two-step in a single call.
|
|
||||||
for slot = 1, 19 do
|
|
||||||
local id = GetInventoryItemID("player", slot)
|
|
||||||
if id then
|
|
||||||
_equippedItemIDs[slot] = id
|
|
||||||
local name = C_Item.GetItemName({ equipmentSlotIndex = slot })
|
|
||||||
if name then
|
|
||||||
_equippedItemNames[slot] = string_lower(name)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Invalidate equipment cache (call on UNIT_INVENTORY_CHANGED)
|
|
||||||
function CleveRoids.InvalidateEquipmentCache()
|
|
||||||
_equipmentCacheValid = false
|
|
||||||
end
|
|
||||||
|
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
-- PERFORMANCE: Unified item location lookup using CleveRoids.Items cache
|
-- PERFORMANCE: Unified item location lookup using CleveRoids.Items cache
|
||||||
-- Returns: { type="inventory"|"bag", inventoryID=N } or { type="bag", bag=N, slot=N }
|
-- Returns: { type="inventory"|"bag", inventoryID=N } or { type="bag", bag=N, slot=N }
|
||||||
@@ -875,14 +779,6 @@ CleveRoids.DownrankBlocked = CleveRoids.DownrankBlocked or {}
|
|||||||
function CleveRoids.GetAuraTrackingData(targetGuid)
|
function CleveRoids.GetAuraTrackingData(targetGuid)
|
||||||
if not targetGuid then return nil, false end
|
if not targetGuid then return nil, false end
|
||||||
|
|
||||||
-- pfUI path: read directly from pfUI's table (has downrank protection built-in)
|
|
||||||
if CleveRoids.hasPfUI76 and pfUI and pfUI.libdebuff_all_auras then
|
|
||||||
local data = pfUI.libdebuff_all_auras[targetGuid]
|
|
||||||
if data then return data, true end
|
|
||||||
-- Fall through: our table may have test entries even when pfUI is active
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Standalone path (or pfUI had no data for this GUID)
|
|
||||||
local data = CleveRoids.AllCasterAuraTracking[targetGuid]
|
local data = CleveRoids.AllCasterAuraTracking[targetGuid]
|
||||||
if data then return data, false end
|
if data then return data, false end
|
||||||
return nil, false
|
return nil, false
|
||||||
@@ -950,74 +846,6 @@ function CleveRoids.GetAllCasterAuraTimeRemaining(targetGuid, spellId)
|
|||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Helper to find aura by name (or spell ID string) for a target
|
|
||||||
-- Returns player's entry for personal debuffs, any caster for shared auras.
|
|
||||||
-- Reads from pfUI.libdebuff_all_auras when pfUI 7.6+ is active.
|
|
||||||
function CleveRoids.FindAllCasterAuraByName(targetGuid, searchName)
|
|
||||||
if not targetGuid or not searchName then return nil, nil end
|
|
||||||
local targetData, isPfUI = CleveRoids.GetAuraTrackingData(targetGuid)
|
|
||||||
if not targetData then return nil, nil end
|
|
||||||
|
|
||||||
-- Resolve spell ID to name for direct lookup
|
|
||||||
local searchID = tonumber(searchName)
|
|
||||||
if searchID then
|
|
||||||
local resolvedName = C_Spell.GetSpellName(searchID)
|
|
||||||
if not resolvedName then return nil, nil end
|
|
||||||
searchName = resolvedName
|
|
||||||
end
|
|
||||||
|
|
||||||
local now = GetTime()
|
|
||||||
|
|
||||||
-- Try exact match first (O(1) hash lookup)
|
|
||||||
local casters = targetData[searchName]
|
|
||||||
|
|
||||||
-- Case-insensitive fallback
|
|
||||||
if not casters then
|
|
||||||
local searchLower = string.lower(searchName)
|
|
||||||
for spellName, c in pairs(targetData) do
|
|
||||||
local baseName = CleveRoids.StripRank(spellName)
|
|
||||||
if string.lower(baseName) == searchLower then
|
|
||||||
casters = c
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if not casters then return nil, nil end
|
|
||||||
|
|
||||||
local playerGuid = CleveRoids.GetGUID("player")
|
|
||||||
|
|
||||||
-- Always check player's own entry first
|
|
||||||
if playerGuid and casters[playerGuid] then
|
|
||||||
local auraData = casters[playerGuid]
|
|
||||||
local startTime = AuraStart(auraData, isPfUI)
|
|
||||||
if startTime and auraData.duration then
|
|
||||||
local remaining = auraData.duration + startTime - now
|
|
||||||
if remaining > 0 then return remaining, playerGuid end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Get a spellId from any entry to check personal vs shared
|
|
||||||
local anySpellId = nil
|
|
||||||
for _, aData in pairs(casters) do
|
|
||||||
anySpellId = aData.spellId
|
|
||||||
break
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Personal debuff and player has no active entry → don't use other players' data
|
|
||||||
if IsPersonalAura(anySpellId, searchName) then return nil, nil end
|
|
||||||
|
|
||||||
-- Shared aura: return any active caster's entry
|
|
||||||
for cGuid, auraData in pairs(casters) do
|
|
||||||
local startTime = AuraStart(auraData, isPfUI)
|
|
||||||
if startTime and auraData.duration then
|
|
||||||
local remaining = auraData.duration + startTime - now
|
|
||||||
if remaining > 0 then return remaining, cGuid end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return nil, nil
|
|
||||||
end
|
|
||||||
|
|
||||||
-- HitInfo bitfield values (from NampowerAPI.lua, duplicated for local access)
|
-- HitInfo bitfield values (from NampowerAPI.lua, duplicated for local access)
|
||||||
-- Converted to decimal for Lua 5.0 compatibility (no hex literals)
|
-- Converted to decimal for Lua 5.0 compatibility (no hex literals)
|
||||||
local HITINFO_MISS = 16 -- 0x10
|
local HITINFO_MISS = 16 -- 0x10
|
||||||
@@ -1091,7 +919,7 @@ local function OnAutoAttackOther(attackerGuid, targetGuid, totalDamage, hitInfo,
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Sync to pfUI if loaded (pre-7.6 only)
|
-- Sync to pfUI if loaded (pre-7.6 only)
|
||||||
if not CleveRoids.hasPfUI76 and pfUI and pfUI.api and pfUI.api.libdebuff then
|
if pfUI and pfUI.api and pfUI.api.libdebuff then
|
||||||
local spellName = C_Spell.GetSpellName(spellID) or nil
|
local spellName = C_Spell.GetSpellName(spellID) or nil
|
||||||
local baseName = CleveRoids.StripRank(spellName)
|
local baseName = CleveRoids.StripRank(spellName)
|
||||||
local targetName = (lib.guidToName and lib.guidToName[normalizedTarget]) or UnitName("target")
|
local targetName = (lib.guidToName and lib.guidToName[normalizedTarget]) or UnitName("target")
|
||||||
@@ -1233,31 +1061,11 @@ local function OnAuraCastSelf(spellId, casterGuid, targetGuid, effect, effectAur
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- NEW: Populate ownBuffCasts and allBuffAuras for all player buffs (not just overflow)
|
-- Removed: this populated lib.ownBuffCasts and lib.allBuffAuras for every player
|
||||||
-- Use the isBuffNotDebuff result determined above (avoids redundant slot scanning)
|
-- buff. Both tables were write-only -- populated here and on BUFF_ADDED_OTHER,
|
||||||
local lib = CleveRoids.libdebuff
|
-- swept periodically, cleared on removal and death, and never read for aura state.
|
||||||
if isBuffNotDebuff and spellId and durationMs and durationMs > 0 and lib and not lib.hasPfUIEnhanced then
|
-- Player buff timing now comes from C_UnitAuras, which reads expirationTime out of
|
||||||
local spellName = C_Spell.GetSpellName(spellId)
|
-- the engine's own player-buff table.
|
||||||
if spellName then
|
|
||||||
local playerGuid = CleveRoids.GetGUID("player")
|
|
||||||
if playerGuid then
|
|
||||||
lib.ownBuffCasts[playerGuid] = lib.ownBuffCasts[playerGuid] or {}
|
|
||||||
lib.ownBuffCasts[playerGuid][spellName] = {
|
|
||||||
startTime = now,
|
|
||||||
duration = durationMs / 1000,
|
|
||||||
spellId = spellId,
|
|
||||||
casterGuid = casterGuid,
|
|
||||||
}
|
|
||||||
lib.allBuffAuras[playerGuid] = lib.allBuffAuras[playerGuid] or {}
|
|
||||||
lib.allBuffAuras[playerGuid][spellName] = lib.allBuffAuras[playerGuid][spellName] or {}
|
|
||||||
lib.allBuffAuras[playerGuid][spellName][casterGuid or "unknown"] = {
|
|
||||||
startTime = now,
|
|
||||||
duration = durationMs / 1000,
|
|
||||||
rank = 0,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function OnAuraCastOther(spellId, casterGuid, targetGuid, effect, effectAuraName,
|
local function OnAuraCastOther(spellId, casterGuid, targetGuid, effect, effectAuraName,
|
||||||
@@ -1271,7 +1079,7 @@ local function OnAuraCastOther(spellId, casterGuid, targetGuid, effect, effectAu
|
|||||||
-- full downrank protection — we read from that table via GetAuraTrackingData().
|
-- full downrank protection — we read from that table via GetAuraTrackingData().
|
||||||
if spellId and durationMs and durationMs > 0 then
|
if spellId and durationMs and durationMs > 0 then
|
||||||
local spellName = C_Spell.GetSpellName(spellId)
|
local spellName = C_Spell.GetSpellName(spellId)
|
||||||
if spellName and not CleveRoids.hasPfUI76 then
|
if spellName then
|
||||||
CleveRoids._allCasterAuraDirty = true
|
CleveRoids._allCasterAuraDirty = true
|
||||||
if not CleveRoids.AllCasterAuraTracking[targetGuid] then
|
if not CleveRoids.AllCasterAuraTracking[targetGuid] then
|
||||||
CleveRoids.AllCasterAuraTracking[targetGuid] = {}
|
CleveRoids.AllCasterAuraTracking[targetGuid] = {}
|
||||||
@@ -1294,7 +1102,7 @@ local function OnAuraCastOther(spellId, casterGuid, targetGuid, effect, effectAu
|
|||||||
string.format("|cffff6600[AuraTrack]|r %s Rank %d blocked by Rank %d (%.1fs left) on %s",
|
string.format("|cffff6600[AuraTrack]|r %s Rank %d blocked by Rank %d (%.1fs left) on %s",
|
||||||
spellName, newRank, existingRank, timeleft,
|
spellName, newRank, existingRank, timeleft,
|
||||||
string.sub(tostring(targetGuid), 1, 16)))
|
string.sub(tostring(targetGuid), 1, 16)))
|
||||||
spellName = nil -- skip pendingBuffCasts below too
|
spellName = nil -- downranked: don't record this cast
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1324,24 +1132,6 @@ local function OnAuraCastOther(spellId, casterGuid, targetGuid, effect, effectAu
|
|||||||
string.sub(tostring(casterGuid), 1, 16), durationMs / 1000))
|
string.sub(tostring(casterGuid), 1, 16), durationMs / 1000))
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Store in pendingBuffCasts for BUFF_ADDED_OTHER to confirm as buff
|
|
||||||
-- (AURA_CAST_ON_OTHER fires for both buffs and debuffs; BUFF_ADDED_OTHER confirms buff)
|
|
||||||
local lib = CleveRoids.libdebuff
|
|
||||||
if lib and not lib.hasPfUIEnhanced then
|
|
||||||
local spellNameForPending = C_Spell.GetSpellName(spellId)
|
|
||||||
if spellNameForPending then
|
|
||||||
local normTargetGuid = CleveRoids.NormalizeGUID(targetGuid)
|
|
||||||
if normTargetGuid then
|
|
||||||
lib.pendingBuffCasts[normTargetGuid] = lib.pendingBuffCasts[normTargetGuid] or {}
|
|
||||||
lib.pendingBuffCasts[normTargetGuid][spellId] = {
|
|
||||||
casterGuid = CleveRoids.NormalizeGUID(casterGuid),
|
|
||||||
duration = durationMs / 1000,
|
|
||||||
spellName = spellNameForPending,
|
|
||||||
time = now,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Store cap status for this target GUID (if available)
|
-- Store cap status for this target GUID (if available)
|
||||||
@@ -1474,7 +1264,7 @@ autoAttackFrame:SetScript("OnEvent", function()
|
|||||||
if spellId and spellId > 0 and durationMs and durationMs > 0 then
|
if spellId and spellId > 0 and durationMs and durationMs > 0 then
|
||||||
local playerGUID = CleveRoids.GetGUID("player")
|
local playerGUID = CleveRoids.GetGUID("player")
|
||||||
local durSpellName = C_Spell.GetSpellName(spellId)
|
local durSpellName = C_Spell.GetSpellName(spellId)
|
||||||
if playerGUID and durSpellName and not CleveRoids.hasPfUI76 then
|
if playerGUID and durSpellName then
|
||||||
CleveRoids._allCasterAuraDirty = true
|
CleveRoids._allCasterAuraDirty = true
|
||||||
if not CleveRoids.AllCasterAuraTracking[playerGUID] then
|
if not CleveRoids.AllCasterAuraTracking[playerGUID] then
|
||||||
CleveRoids.AllCasterAuraTracking[playerGUID] = {}
|
CleveRoids.AllCasterAuraTracking[playerGUID] = {}
|
||||||
@@ -2060,26 +1850,12 @@ function CleveRoids.CancelAura(auraName)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- ClassicAPI's C_Item.IsEquippedItem walks the 19 equipment slots natively and
|
||||||
|
-- short-circuits on the first match, so it replaces the old Lua-side equipment
|
||||||
|
-- cache entirely -- no per-call scan, no invalidation. Accepts itemID, item
|
||||||
|
-- link, or (case-insensitive, decorated) name.
|
||||||
function CleveRoids.HasGearEquipped(gearId)
|
function CleveRoids.HasGearEquipped(gearId)
|
||||||
if not gearId then return false end
|
return (gearId and C_Item.IsEquippedItem(gearId)) or false
|
||||||
|
|
||||||
-- PERFORMANCE: Build/refresh equipment cache if needed
|
|
||||||
BuildEquipmentCache()
|
|
||||||
|
|
||||||
-- Handle both numeric IDs and string IDs like "5196"
|
|
||||||
local wantId = tonumber(gearId)
|
|
||||||
local wantName = (type(gearId) == "string" and not wantId) and string.lower(gearId) or nil
|
|
||||||
|
|
||||||
-- PERFORMANCE: Use cached data instead of scanning all slots
|
|
||||||
for slot = 1, 19 do
|
|
||||||
if wantId and _equippedItemIDs[slot] == wantId then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
if wantName and _equippedItemNames[slot] == wantName then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
@@ -3359,6 +3135,25 @@ function CleveRoids.ValidatePowerLost(unit, operator, amount)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Checks the given unit's spell haste percentage vs the given amount
|
||||||
|
-- unit: The unit we're checking
|
||||||
|
-- operator: valid comparitive operator symbol
|
||||||
|
-- amount: The required amount, in percent (0 = unhasted, negative = slowed)
|
||||||
|
-- returns: True or false
|
||||||
|
-- NOTE: the shared arg parser doesn't accept a sign on the amount, so a
|
||||||
|
-- specific negative threshold ([myspellhaste:<-10]) can't be written; use
|
||||||
|
-- [myspellhaste:<0] to test for being slowed at all.
|
||||||
|
function CleveRoids.ValidateSpellHaste(unit, operator, amount)
|
||||||
|
if not unit or not operator or not amount then return false end
|
||||||
|
local haste = CleveRoids.ClassicAPI.UnitSpellHaste(unit)
|
||||||
|
|
||||||
|
if CleveRoids.operators[operator] then
|
||||||
|
return CleveRoids.comparators[operator](haste, amount)
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
-- Checks whether or not the given unit has hp in percent vs the given amount
|
-- Checks whether or not the given unit has hp in percent vs the given amount
|
||||||
-- unit: The unit we're checking
|
-- unit: The unit we're checking
|
||||||
-- operator: valid comparitive operator symbol
|
-- operator: valid comparitive operator symbol
|
||||||
@@ -3569,6 +3364,51 @@ function CleveRoids.ClearSpellNameCaches()
|
|||||||
_baseNameCacheSize = 0
|
_baseNameCacheSize = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Resolve one aura on `unit` straight from ClassicAPI. This is the source of truth
|
||||||
|
-- for aura state: C_UnitAuras reads the unit's own descriptor, and its Aura::Source
|
||||||
|
-- cache reconstructs duration/expirationTime for ANY unit from the observed
|
||||||
|
-- SMSG_SPELL_GO -- caster-modified, so talent extensions and combo-point finisher
|
||||||
|
-- scaling are already applied, and Carnage's roll-gated Rip/Rake refresh is handled
|
||||||
|
-- in the DLL rather than mirrored here.
|
||||||
|
--
|
||||||
|
-- Matching stays ours: by spellID when the conditional gave a number, else by
|
||||||
|
-- lowercased name (C_UnitAuras' own by-name lookup is case-sensitive and exact,
|
||||||
|
-- which would miss [debuff:thunder_clap]).
|
||||||
|
--
|
||||||
|
-- Returns found, stacks, remaining, spellId. `remaining` is -1 for an aura with no
|
||||||
|
-- duration (permanent), nil when ClassicAPI has no timing for it -- an aura cast
|
||||||
|
-- before we logged in, or one refreshed at max stacks -- and seconds otherwise.
|
||||||
|
-- Presence and stacks are always reliable; only timing is best-effort.
|
||||||
|
local function ResolveUnitAuraViaClassicAPI(unit, searchID, searchName, isbuff)
|
||||||
|
local filter = isbuff and "HELPFUL" or "HARMFUL"
|
||||||
|
local i = 1
|
||||||
|
while i <= 48 do
|
||||||
|
local name, _, count, _, duration, expirationTime, _, _, _, spellId =
|
||||||
|
C_UnitAuras.UnitAura(unit, i, filter)
|
||||||
|
if not name then break end
|
||||||
|
|
||||||
|
local hit
|
||||||
|
if searchID then
|
||||||
|
hit = (spellId == searchID)
|
||||||
|
elseif searchName then
|
||||||
|
hit = (_string_lower(name) == searchName)
|
||||||
|
end
|
||||||
|
|
||||||
|
if hit then
|
||||||
|
local remaining
|
||||||
|
if expirationTime and expirationTime > 0 then
|
||||||
|
remaining = expirationTime - GetTime()
|
||||||
|
if remaining < 0 then remaining = 0 end
|
||||||
|
elseif duration == 0 then
|
||||||
|
remaining = -1 -- no duration: permanent aura
|
||||||
|
end
|
||||||
|
return true, count or 0, remaining, spellId
|
||||||
|
end
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
function CleveRoids.ValidateAura(unit, args, isbuff)
|
function CleveRoids.ValidateAura(unit, args, isbuff)
|
||||||
if not args or not UnitExists(unit) then return false end
|
if not args or not UnitExists(unit) then return false end
|
||||||
|
|
||||||
@@ -3827,148 +3667,56 @@ function CleveRoids.ValidateAura(unit, args, isbuff)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- allBuffAuras fallback for player buff timing: when slow path found the buff but
|
-- Player timing gap-fill, from ClassicAPI. Replaces the lib.allBuffAuras lookup
|
||||||
-- returned no remaining time, check lib.allBuffAuras for cached AURA_CAST timing.
|
-- that cached AURA_CAST start/duration for this: for the player, C_UnitAuras reads
|
||||||
if found and remaining == nil and isPlayer and isbuff and searchName then
|
-- expirationTime out of the engine's own player-buff table, so it is the more
|
||||||
local lib = type(CleveRoids.libdebuff) == "table" and CleveRoids.libdebuff or nil
|
-- authoritative source, not a fallback. Only runs when the scans above found the
|
||||||
if lib and lib.allBuffAuras then
|
-- aura but produced no time.
|
||||||
local playerGuid = CleveRoids.GetGUID("player")
|
if found and remaining == nil and isPlayer and (searchID or searchName) then
|
||||||
if playerGuid and lib.allBuffAuras[playerGuid] then
|
local _, _, capRemaining = ResolveUnitAuraViaClassicAPI(unit, searchID, searchName, isbuff)
|
||||||
-- Try exact name match first
|
if capRemaining ~= nil then
|
||||||
local casters = lib.allBuffAuras[playerGuid][args.name]
|
remaining = capRemaining
|
||||||
-- Try lowercase match if exact didn't work
|
|
||||||
if not casters then
|
|
||||||
for bName, c in pairs(lib.allBuffAuras[playerGuid]) do
|
|
||||||
if _string_lower(bName) == searchName then
|
|
||||||
casters = c
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if casters then
|
|
||||||
for _, cData in pairs(casters) do
|
|
||||||
local elapsed = GetTime() - (cData.startTime or 0)
|
|
||||||
remaining = cData.duration > 0 and (cData.duration - elapsed) or -1
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Non-player overflow fallback: buff exists in server slots 33-48 (no client slot)
|
-- Removed: the non-player overflow fallback. It existed because a buff can sit in
|
||||||
-- AllCasterAuraTracking already has data from AURA_CAST_ON_OTHER for all aura applications.
|
-- server slots 33-48 with no client slot, so it read presence/duration out of
|
||||||
-- If the normal scan didn't find the buff, check there for presence + duration.
|
-- AllCasterAuraTracking, guarded by a UnitDebuff slot scan to stop [buff:Name]
|
||||||
-- Guard: verify the spell isn't a visible debuff on the target (AllCasterAuraTracking
|
-- matching a debuff (that table stores both). ClassicAPI makes all of it moot: its
|
||||||
-- stores both buffs and debuffs, so without this check [buff:DebuffName] could false-positive).
|
-- HELPFUL/HARMFUL filters select on each aura's real polarity flag rather than
|
||||||
if not found and not isPlayer and isbuff and (searchID or searchName) then
|
-- which slot range it happens to occupy, so "a debuff parked in a buff slot still
|
||||||
local targetGuid = CleveRoids.GetGUID(unit)
|
-- reads harmful" (docs/API.md) and an overflowed buff still reads helpful. The
|
||||||
if targetGuid then
|
-- ClassicAPI resolution above therefore already covers the overflow case, and it
|
||||||
-- Check if the spell is in a visible debuff slot — if so, it's a debuff, not a buff
|
-- classifies more accurately than the slot-range guard did.
|
||||||
local isDebuff = false
|
|
||||||
local di = 1
|
|
||||||
while true do
|
|
||||||
local dtex, _, _, dspellId = UnitDebuff(unit, di)
|
|
||||||
if not dtex then break end
|
|
||||||
if dspellId then
|
|
||||||
if searchID then
|
|
||||||
if dspellId == searchID then
|
|
||||||
isDebuff = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
elseif searchName then
|
|
||||||
local lowerName = GetLowercaseSpellName(dspellId)
|
|
||||||
if lowerName and lowerName == searchName then
|
|
||||||
isDebuff = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
di = di + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
if not isDebuff then
|
|
||||||
local trackRemaining = CleveRoids.FindAllCasterAuraByName(targetGuid,
|
|
||||||
searchID and tostring(searchID) or args.name)
|
|
||||||
if trackRemaining then
|
|
||||||
found = true
|
|
||||||
stacks = 0
|
|
||||||
remaining = trackRemaining
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local ops = CleveRoids.operators
|
local ops = CleveRoids.operators
|
||||||
local cmp = CleveRoids.comparators
|
local cmp = CleveRoids.comparators
|
||||||
|
|
||||||
-- For non-player units with time comparisons, try to get time from tracking systems
|
-- Non-player aura timing, straight from ClassicAPI. This replaces the old
|
||||||
|
-- lib.allBuffAuras lookup and the AllCasterAuraTracking / FindAllCasterAuraByName
|
||||||
|
-- fallback beneath it: both existed only because vanilla cannot report a timer for
|
||||||
|
-- an aura on another unit, which the Aura::Source cache now does. It also drops
|
||||||
|
-- the libdebuff UnitBuff timeleft bug those comments worked around.
|
||||||
local nonPlayerAuraTimeRemaining = nil
|
local nonPlayerAuraTimeRemaining = nil
|
||||||
if not isPlayer and args.name then
|
if not isPlayer and (searchID or searchName) then
|
||||||
-- NOTE: libdebuff's UnitBuff has a bug where timeleft returns incorrect values
|
local capFound, capStacks, capRemaining =
|
||||||
-- (showing ~1000s instead of actual remaining time). Skip it for buff time checks
|
ResolveUnitAuraViaClassicAPI(unit, searchID, searchName, isbuff)
|
||||||
-- and rely on all-caster tracking from AURA_CAST events instead.
|
if capFound then
|
||||||
|
if not found then
|
||||||
-- Fast path: Check lib.allBuffAuras (spellName-indexed, O(1) lookup)
|
found = true
|
||||||
-- More efficient than FindAllCasterAuraByName which does ID→name translation
|
stacks = capStacks or 0
|
||||||
if nonPlayerAuraTimeRemaining == nil and isbuff then
|
|
||||||
local lib = type(CleveRoids.libdebuff) == "table" and CleveRoids.libdebuff or nil
|
|
||||||
if lib and lib.allBuffAuras then
|
|
||||||
local targetGuid = CleveRoids.GetGUID(unit)
|
|
||||||
if targetGuid then
|
|
||||||
local buffEntries = lib.allBuffAuras[targetGuid]
|
|
||||||
if buffEntries then
|
|
||||||
-- Try exact name match first
|
|
||||||
local casters = buffEntries[args.name]
|
|
||||||
-- Try lowercase match if exact didn't work
|
|
||||||
if not casters and searchName then
|
|
||||||
for bName, c in pairs(buffEntries) do
|
|
||||||
if _string_lower(bName) == searchName then
|
|
||||||
casters = c
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if casters then
|
|
||||||
for _, cData in pairs(casters) do
|
|
||||||
local elapsed = GetTime() - (cData.startTime or 0)
|
|
||||||
local rem = cData.duration > 0 and (cData.duration - elapsed) or -1
|
|
||||||
if rem == nil or rem > 0 or cData.duration <= 0 then
|
|
||||||
nonPlayerAuraTimeRemaining = rem
|
|
||||||
if not found then
|
|
||||||
found = true
|
|
||||||
stacks = 0
|
|
||||||
end
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
-- Left nil when ClassicAPI has no timing (aura predates login, or a
|
||||||
|
-- max-stack refresh); the caller then falls back to its found-and-0 default.
|
||||||
|
nonPlayerAuraTimeRemaining = capRemaining
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Second try: All-caster tracking from AURA_CAST events (works for any caster)
|
if CleveRoids.debug then
|
||||||
-- Only use if libdebuff didn't find it (libdebuff has more accurate timing for player casts)
|
DEFAULT_CHAT_FRAME:AddMessage(string.format(
|
||||||
if nonPlayerAuraTimeRemaining == nil then
|
"|cffff9900[AuraLookup]|r %s on %s: found=%s, stacks=%s, remaining=%s",
|
||||||
local targetGuid = CleveRoids.GetGUID(unit)
|
tostring(args.name), tostring(unit), tostring(capFound),
|
||||||
if targetGuid then
|
tostring(capStacks), tostring(capRemaining)
|
||||||
local remaining, casterGuid = CleveRoids.FindAllCasterAuraByName(targetGuid, args.name)
|
))
|
||||||
|
|
||||||
-- Debug output when enabled
|
|
||||||
if CleveRoids.debug then
|
|
||||||
local hasData = CleveRoids.AllCasterAuraTracking[targetGuid] ~= nil
|
|
||||||
DEFAULT_CHAT_FRAME:AddMessage(string.format(
|
|
||||||
"|cffff9900[AuraLookup]|r %s on GUID %s: hasData=%s, remaining=%s",
|
|
||||||
tostring(args.name), string.sub(tostring(targetGuid), 1, 16),
|
|
||||||
tostring(hasData), tostring(remaining)
|
|
||||||
))
|
|
||||||
end
|
|
||||||
|
|
||||||
if remaining then
|
|
||||||
nonPlayerAuraTimeRemaining = remaining
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -5005,7 +4753,7 @@ function CleveRoids.ValidatePlayerAuraCount(bigger, amount)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function CleveRoids.IsReactive(name)
|
function CleveRoids.IsReactive(name)
|
||||||
return CleveRoids.reactiveSpells[spellName] ~= nil
|
return CleveRoids.reactiveSpells[name] ~= nil
|
||||||
end
|
end
|
||||||
|
|
||||||
-- NOTE: CleveRoids.GetActionButtonInfo is defined in Extensions/Tooltip/Generic.lua
|
-- NOTE: CleveRoids.GetActionButtonInfo is defined in Extensions/Tooltip/Generic.lua
|
||||||
@@ -5513,8 +5261,49 @@ local function MakeTypedPowerKeyword(condKey, unitDefault, powerType)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local PET_HAPPINESS_STATES = {
|
||||||
|
unhappy = 1,
|
||||||
|
content = 2,
|
||||||
|
happy = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
local function ResolvePetHappinessState(value)
|
||||||
|
local state = tonumber(value)
|
||||||
|
if state then
|
||||||
|
return state
|
||||||
|
end
|
||||||
|
if type(value) ~= "string" then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return PET_HAPPINESS_STATES[GetLowercaseString(value)]
|
||||||
|
end
|
||||||
|
|
||||||
-- A list of Conditionals and their functions to validate them
|
-- A list of Conditionals and their functions to validate them
|
||||||
CleveRoids.Keywords = {
|
CleveRoids.Keywords = {
|
||||||
|
-- [button:N] — true while mouse button N is held (1=Left, 2=Right, 3=Middle,
|
||||||
|
-- 4/5=extra). Routed through Multi so OR/AND lists and repeated groups behave
|
||||||
|
-- like every other argument conditional ([button:1/2] = left or right).
|
||||||
|
-- [button] with no argument — true if any mapped mouse button is held.
|
||||||
|
button = function(conditionals)
|
||||||
|
if type(conditionals.button) ~= "table" then
|
||||||
|
return CleveRoids.AnyMouseButtonDown()
|
||||||
|
end
|
||||||
|
return Multi(conditionals.button, function(button)
|
||||||
|
local name = CleveRoids.buttons[button]
|
||||||
|
return name and IsMouseButtonDown(name) or false
|
||||||
|
end, conditionals, "button")
|
||||||
|
end,
|
||||||
|
|
||||||
|
nobutton = function(conditionals)
|
||||||
|
if type(conditionals.nobutton) ~= "table" then
|
||||||
|
return not CleveRoids.AnyMouseButtonDown()
|
||||||
|
end
|
||||||
|
return NegatedMulti(conditionals.nobutton, function(button)
|
||||||
|
local name = CleveRoids.buttons[button]
|
||||||
|
return not (name and IsMouseButtonDown(name))
|
||||||
|
end, conditionals, "nobutton")
|
||||||
|
end,
|
||||||
|
|
||||||
exists = function(conditionals)
|
exists = function(conditionals)
|
||||||
return UnitExists(conditionals.target)
|
return UnitExists(conditionals.target)
|
||||||
end,
|
end,
|
||||||
@@ -6551,6 +6340,30 @@ CleveRoids.Keywords = {
|
|||||||
end, conditionals, "mylevel")
|
end, conditionals, "mylevel")
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
myspellhaste = function(conditionals)
|
||||||
|
return Multi(conditionals.myspellhaste, function(args)
|
||||||
|
if type(args) ~= "table" then return false end
|
||||||
|
|
||||||
|
-- Handle multi-comparison (e.g., >50&<80)
|
||||||
|
if args.comparisons and type(args.comparisons) == "table" then
|
||||||
|
local haste = CleveRoids.ClassicAPI.UnitSpellHaste("player")
|
||||||
|
|
||||||
|
-- ALL comparisons must pass (AND logic)
|
||||||
|
for _, comp in ipairs(args.comparisons) do
|
||||||
|
if not CleveRoids.operators[comp.operator] then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
if not CleveRoids.comparators[comp.operator](haste, comp.amount) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
return CleveRoids.ValidateSpellHaste("player", args.operator, args.amount)
|
||||||
|
end, conditionals, "myspellhaste")
|
||||||
|
end,
|
||||||
|
|
||||||
myhp = function(conditionals)
|
myhp = function(conditionals)
|
||||||
return Multi(conditionals.myhp, function(args)
|
return Multi(conditionals.myhp, function(args)
|
||||||
if type(args) ~= "table" then return false end
|
if type(args) ~= "table" then return false end
|
||||||
@@ -7332,6 +7145,39 @@ CleveRoids.Keywords = {
|
|||||||
end, conditionals, "nopet")
|
end, conditionals, "nopet")
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
-- Hunter pet happiness: 1=unhappy, 2=content, 3=happy.
|
||||||
|
pethappiness = function(conditionals)
|
||||||
|
local happiness = GetPetHappiness()
|
||||||
|
local _, isHunterPet = HasPetUI()
|
||||||
|
if not isHunterPet or not happiness then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if conditionals.pethappiness == true then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
return Multi(conditionals.pethappiness, function(requiredState)
|
||||||
|
return happiness == ResolvePetHappinessState(requiredState)
|
||||||
|
end, conditionals, "pethappiness")
|
||||||
|
end,
|
||||||
|
|
||||||
|
nopethappiness = function(conditionals)
|
||||||
|
local happiness = GetPetHappiness()
|
||||||
|
local _, isHunterPet = HasPetUI()
|
||||||
|
if not isHunterPet or not happiness then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
if conditionals.nopethappiness == true then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
return NegatedMulti(conditionals.nopethappiness, function(forbiddenState)
|
||||||
|
return happiness ~= ResolvePetHappinessState(forbiddenState)
|
||||||
|
end, conditionals, "nopethappiness")
|
||||||
|
end,
|
||||||
|
|
||||||
-- [focus] / [nofocus] - whether a focus is set. Covers pfUI's emulated focus
|
-- [focus] / [nofocus] - whether a focus is set. Covers pfUI's emulated focus
|
||||||
-- and ClassicAPI's native focus token (set via /focus or the FOCUSTARGET keybind).
|
-- and ClassicAPI's native focus token (set via /focus or the FOCUSTARGET keybind).
|
||||||
focus = function(conditionals)
|
focus = function(conditionals)
|
||||||
@@ -9390,6 +9236,7 @@ CleveRoids.STATIC_CONDITIONALS = {
|
|||||||
inbag = true, noinbag = true,
|
inbag = true, noinbag = true,
|
||||||
mod = true, nomod = true,
|
mod = true, nomod = true,
|
||||||
keydown = true, nokeydown = true,
|
keydown = true, nokeydown = true,
|
||||||
|
button = true, nobutton = true,
|
||||||
swimming = true, noswimming = true, swim = true, noswim = true,
|
swimming = true, noswimming = true, swim = true, noswim = true,
|
||||||
indoors = true, noindoors = true, outdoors = true, nooutdoors = true,
|
indoors = true, noindoors = true, outdoors = true, nooutdoors = true,
|
||||||
rooted = true, norooted = true,
|
rooted = true, norooted = true,
|
||||||
|
|||||||
+33
-8
@@ -130,6 +130,12 @@ local StopAttack = function(msg)
|
|||||||
CleveRoids.DeferStopAttack()
|
CleveRoids.DeferStopAttack()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local StopChanneling = function(msg)
|
||||||
|
-- No Blizzard equivalent: 1.12 has SpellStopCasting (immediate) and nothing
|
||||||
|
-- that waits for the next channel tick, so this is nampower-only.
|
||||||
|
CleveRoids.StopChanneling()
|
||||||
|
end
|
||||||
|
|
||||||
-- Register slash commands and assign original handlers.
|
-- Register slash commands and assign original handlers.
|
||||||
-- These will be hooked immediately after.
|
-- These will be hooked immediately after.
|
||||||
SLASH_STARTATTACK1 = "/startattack"
|
SLASH_STARTATTACK1 = "/startattack"
|
||||||
@@ -141,6 +147,9 @@ SlashCmdList.STOPATTACK = StopAttack
|
|||||||
SLASH_STOPCASTING1 = "/stopcasting"
|
SLASH_STOPCASTING1 = "/stopcasting"
|
||||||
SlashCmdList.STOPCASTING = SpellStopCasting
|
SlashCmdList.STOPCASTING = SpellStopCasting
|
||||||
|
|
||||||
|
SLASH_STOPCHANNELING1 = "/stopchanneling"
|
||||||
|
SlashCmdList.STOPCHANNELING = StopChanneling
|
||||||
|
|
||||||
SLASH_CLEARTARGET1 = "/cleartarget"
|
SLASH_CLEARTARGET1 = "/cleartarget"
|
||||||
SlashCmdList.CLEARTARGET = ClearTarget
|
SlashCmdList.CLEARTARGET = ClearTarget
|
||||||
|
|
||||||
@@ -202,6 +211,21 @@ SlashCmdList.STOPCASTING = function(msg)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- /stopchanneling hook
|
||||||
|
CleveRoids.Hooks.STOPCHANNELING_SlashCmd = SlashCmdList.STOPCHANNELING
|
||||||
|
SlashCmdList.STOPCHANNELING = function(msg)
|
||||||
|
if CleveRoids.stopMacroFlag then return end
|
||||||
|
msg = msg or ""
|
||||||
|
if string.find(msg, "%[") then
|
||||||
|
-- If conditionals are present, let the function handle it.
|
||||||
|
-- It will only stop the channel if the conditions are met.
|
||||||
|
CleveRoids.DoConditionalStopChanneling(msg)
|
||||||
|
else
|
||||||
|
-- If no conditionals, run the original command.
|
||||||
|
CleveRoids.Hooks.STOPCHANNELING_SlashCmd(msg)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- /unqueue hook
|
-- /unqueue hook
|
||||||
CleveRoids.Hooks.UNQUEUE_SlashCmd = SlashCmdList.UNQUEUE
|
CleveRoids.Hooks.UNQUEUE_SlashCmd = SlashCmdList.UNQUEUE
|
||||||
SlashCmdList.UNQUEUE = function(msg)
|
SlashCmdList.UNQUEUE = function(msg)
|
||||||
@@ -334,14 +358,15 @@ SlashCmdList.RUNMACRO = function(msg)
|
|||||||
return CleveRoids.ExecuteMacroByName(CleveRoids.Trim(msg))
|
return CleveRoids.ExecuteMacroByName(CleveRoids.Trim(msg))
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Global RunMacro wrapper for user convenience (delegates to namespaced internal function)
|
-- The global RunMacro is installed by Core.lua, which loads before this file. Do not
|
||||||
-- This pattern ensures internal logic uses CleveRoids.ExecuteMacroByName and won't break
|
-- redefine it here: Core's hook accepts a macro index as well as a name (Blizzard's
|
||||||
-- if another addon overwrites the global RunMacro
|
-- RunMacro takes either), delegates to SuperMacro when that is driving execution,
|
||||||
-- NOTE: When SuperMacro is also loaded, Compatibility/SuperMacro.lua redirects this to
|
-- falls back to the saved Blizzard original when a macro will not resolve, and clears
|
||||||
-- SuperMacro_RunMacro so macros go through RunLine (where CRM commands are intercepted)
|
-- the stop/skip flags at the start of a top-level run, which is what lets /stopmacro,
|
||||||
function RunMacro(name)
|
-- /skipmacro and /firstaction work across parent/child macro boundaries. A plain
|
||||||
return CleveRoids.ExecuteMacroByName(name)
|
-- name-only wrapper here silently replaced all of that.
|
||||||
end
|
-- When SuperMacro is loaded, Compatibility/SuperMacro.lua redirects the global to
|
||||||
|
-- SuperMacro_RunMacro so macros go through RunLine (where CRM commands are intercepted).
|
||||||
|
|
||||||
SLASH_RETARGET1 = "/retarget"
|
SLASH_RETARGET1 = "/retarget"
|
||||||
SlashCmdList.RETARGET = function(msg)
|
SlashCmdList.RETARGET = function(msg)
|
||||||
|
|||||||
+40
-24
@@ -697,30 +697,9 @@ local function EnsureCondHighlightPool()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Test whether conditionals pass for a given command + alternative text.
|
-- Evaluate one single-group clause of a non-dynamic command against Keywords.
|
||||||
-- Returns: true (passes), false (fails), nil (unconditional / no conditionals)
|
-- Returns: true (passes), false (fails), nil (could not parse)
|
||||||
local function TestConditionalPasses(cmd, alternative)
|
local function EvaluateNonDynamic(alternative)
|
||||||
-- Strip ? tooltip hints (irrelevant for conditional evaluation)
|
|
||||||
if string.find(alternative, "?", 1, true) then
|
|
||||||
alternative = string.gsub(alternative, "%?", "")
|
|
||||||
end
|
|
||||||
|
|
||||||
local hasConditional = string.find(alternative, "%[") ~= nil
|
|
||||||
|
|
||||||
-- Dynamic commands: delegate to TestAction
|
|
||||||
if CleveRoids.dynamicCmds[cmd] then
|
|
||||||
local result = CleveRoids.TestAction(cmd, alternative)
|
|
||||||
if not hasConditional then
|
|
||||||
return nil -- unconditional
|
|
||||||
end
|
|
||||||
return result ~= nil and result ~= false
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Non-dynamic commands: parse and evaluate Keywords manually
|
|
||||||
if not hasConditional then
|
|
||||||
return nil -- unconditional
|
|
||||||
end
|
|
||||||
|
|
||||||
local ok, action, conditionals = pcall(CleveRoids.GetParsedMsg, alternative)
|
local ok, action, conditionals = pcall(CleveRoids.GetParsedMsg, alternative)
|
||||||
if not ok or not conditionals then
|
if not ok or not conditionals then
|
||||||
return nil
|
return nil
|
||||||
@@ -745,6 +724,43 @@ local function TestConditionalPasses(cmd, alternative)
|
|||||||
return passes
|
return passes
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Test whether conditionals pass for a given command + alternative text.
|
||||||
|
-- Returns: true (passes), false (fails), nil (unconditional / no conditionals)
|
||||||
|
local function TestConditionalPasses(cmd, alternative)
|
||||||
|
-- Strip ? tooltip hints (irrelevant for conditional evaluation)
|
||||||
|
if string.find(alternative, "?", 1, true) then
|
||||||
|
alternative = string.gsub(alternative, "%?", "")
|
||||||
|
end
|
||||||
|
|
||||||
|
local hasConditional = string.find(alternative, "%[") ~= nil
|
||||||
|
|
||||||
|
-- Dynamic commands: delegate to TestAction
|
||||||
|
if CleveRoids.dynamicCmds[cmd] then
|
||||||
|
local result = CleveRoids.TestAction(cmd, alternative)
|
||||||
|
if not hasConditional then
|
||||||
|
return nil -- unconditional
|
||||||
|
end
|
||||||
|
return result ~= nil and result ~= false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Non-dynamic commands: evaluate Keywords manually. `[a][b] X` passes when
|
||||||
|
-- any of its groups does.
|
||||||
|
if not hasConditional then
|
||||||
|
return nil -- unconditional
|
||||||
|
end
|
||||||
|
|
||||||
|
local variants = CleveRoids.ExpandBracketGroups(alternative)
|
||||||
|
if not variants then
|
||||||
|
return EvaluateNonDynamic(alternative)
|
||||||
|
end
|
||||||
|
for i = 1, variants.n do
|
||||||
|
if EvaluateNonDynamic(variants[i]) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
-- Find character offset ranges for each semicolon-separated alternative.
|
-- Find character offset ranges for each semicolon-separated alternative.
|
||||||
-- Returns array of { start, finish } pairs (1-based indices within argsText).
|
-- Returns array of { start, finish } pairs (1-based indices within argsText).
|
||||||
local function FindAlternativeOffsets(argsText)
|
local function FindAlternativeOffsets(argsText)
|
||||||
|
|||||||
+249
-16
@@ -2,18 +2,15 @@
|
|||||||
Author: Dennis Werner Garske (DWG) / brian / Mewtiny
|
Author: Dennis Werner Garske (DWG) / brian / Mewtiny
|
||||||
License: MIT License
|
License: MIT License
|
||||||
|
|
||||||
pfUI integration. pfUI's own unitframes now set the native mouseover unit
|
Fixes pfUI mouseover issues by:
|
||||||
(Nampower SetMouseoverUnit, via pfUI.uf.OnEnter bound in pfUI.uf:EnableScripts
|
- Using a unique source key per pfUI frame (e.g., "pfui:party3", "pfui:raid7")
|
||||||
on every unitframe), so [@mouseover]/[mouseover] resolve against pfUI frames
|
- Pairing Set/Clear with the same per-frame key
|
||||||
through the native "mouseover" token -- every consumer checks UnitExists(
|
- Resolving a real UnitID when .unit isn't set
|
||||||
"mouseover") before the CleveRoids.mouseoverUnit fallback, so no per-frame
|
- Properly hooking party group[0] (your own party slot) with a safe closure and defaulting to "player"
|
||||||
hooking is needed here anymore. What remains is the two things pfUI doesn't
|
|
||||||
cover:
|
|
||||||
- Raid-marker rows: NOT unitframes (never go through EnableScripts), so pfUI
|
|
||||||
sets no mouseover for them. Hooked so hovering registers "mark1".."mark8"
|
|
||||||
via CleveRoids.mouseoverUnit.
|
|
||||||
- /pfcast: wrapped so its argument runs through CleveRoids conditionals.
|
|
||||||
]]
|
]]
|
||||||
|
|
||||||
|
if pfPlayer and pfPlayer.GetAttribute and pfPlayer:GetAttribute('unit') == 'player' then return end
|
||||||
|
|
||||||
local _G = _G or getfenv(0)
|
local _G = _G or getfenv(0)
|
||||||
local CleveRoids = _G.CleveRoids or {}
|
local CleveRoids = _G.CleveRoids or {}
|
||||||
|
|
||||||
@@ -37,7 +34,6 @@ local function ResolvePfUnit(frame, fallbackName)
|
|||||||
name = strlower(name)
|
name = strlower(name)
|
||||||
|
|
||||||
local candidates = { "target", "targettarget", "player", "pet" }
|
local candidates = { "target", "targettarget", "player", "pet" }
|
||||||
local i
|
|
||||||
for i = 1, 4 do
|
for i = 1, 4 do
|
||||||
table.insert(candidates, "party"..i)
|
table.insert(candidates, "party"..i)
|
||||||
table.insert(candidates, "partypet"..i)
|
table.insert(candidates, "partypet"..i)
|
||||||
@@ -101,15 +97,242 @@ local function PfClear(frame)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- PLAYER
|
||||||
|
function Extension.RegisterPlayerScripts()
|
||||||
|
if not pfUI or not pfUI.uf or not pfUI.uf.player then return end
|
||||||
|
local frame = pfUI.uf.player
|
||||||
|
local onEnterFunc = frame:GetScript("OnEnter")
|
||||||
|
local onLeaveFunc = frame:GetScript("OnLeave")
|
||||||
|
|
||||||
|
frame:SetScript("OnEnter", function()
|
||||||
|
PfSet(this, "player")
|
||||||
|
if onEnterFunc then onEnterFunc(this) end
|
||||||
|
end)
|
||||||
|
|
||||||
|
frame:SetScript("OnLeave", function()
|
||||||
|
PfClear(this)
|
||||||
|
if onLeaveFunc then onLeaveFunc(this) end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- TARGET
|
||||||
|
function Extension.RegisterTargetScripts()
|
||||||
|
if not pfUI or not pfUI.uf or not pfUI.uf.target then return end
|
||||||
|
local frame = pfUI.uf.target
|
||||||
|
local onEnterFunc = frame:GetScript("OnEnter")
|
||||||
|
local onLeaveFunc = frame:GetScript("OnLeave")
|
||||||
|
|
||||||
|
frame:SetScript("OnEnter", function()
|
||||||
|
PfSet(this, "target")
|
||||||
|
if onEnterFunc then onEnterFunc(this) end
|
||||||
|
end)
|
||||||
|
|
||||||
|
frame:SetScript("OnLeave", function()
|
||||||
|
PfClear(this)
|
||||||
|
if onLeaveFunc then onLeaveFunc(this) end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- TARGETTARGET
|
||||||
|
function Extension.RegisterTargetTargetScripts()
|
||||||
|
if not pfUI or not pfUI.uf or not pfUI.uf.targettarget then return end
|
||||||
|
local frame = pfUI.uf.targettarget
|
||||||
|
local onEnterFunc = frame:GetScript("OnEnter")
|
||||||
|
local onLeaveFunc = frame:GetScript("OnLeave")
|
||||||
|
|
||||||
|
frame:SetScript("OnEnter", function()
|
||||||
|
PfSet(this, "targettarget")
|
||||||
|
if onEnterFunc then onEnterFunc(this) end
|
||||||
|
end)
|
||||||
|
|
||||||
|
frame:SetScript("OnLeave", function()
|
||||||
|
PfClear(this)
|
||||||
|
if onLeaveFunc then onLeaveFunc(this) end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- PARTY (pfUI.uf.group[0..4]) -- include 0 to cover your own party slot
|
||||||
|
function Extension.RegisterPartyScripts()
|
||||||
|
if not pfUI or not pfUI.uf or not pfUI.uf.group then return end
|
||||||
|
|
||||||
|
for i = 0, 4 do
|
||||||
|
local frame = pfUI.uf.group[i]
|
||||||
|
if frame then
|
||||||
|
-- bind loop index for closures (Vanilla-safe)
|
||||||
|
local idx = i
|
||||||
|
local onEnterFunc = frame:GetScript("OnEnter")
|
||||||
|
local onLeaveFunc = frame:GetScript("OnLeave")
|
||||||
|
|
||||||
|
frame:SetScript("OnEnter", function()
|
||||||
|
-- For group[0] (your own party frame), default to "player"
|
||||||
|
local defaultUnit = (idx == 0) and "player" or nil
|
||||||
|
PfSet(this, defaultUnit)
|
||||||
|
if onEnterFunc then onEnterFunc(this) end
|
||||||
|
end)
|
||||||
|
|
||||||
|
frame:SetScript("OnLeave", function()
|
||||||
|
PfClear(this)
|
||||||
|
if onLeaveFunc then onLeaveFunc(this) end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- RAID (pfUI.uf.raid[1..40])
|
||||||
|
function Extension.RegisterRaidScripts()
|
||||||
|
if not pfUI or not pfUI.uf or not pfUI.uf.raid then return end
|
||||||
|
|
||||||
|
for i = 1, 40 do
|
||||||
|
local frame = pfUI.uf.raid[i]
|
||||||
|
if frame then
|
||||||
|
local onEnterFunc = frame:GetScript("OnEnter")
|
||||||
|
local onLeaveFunc = frame:GetScript("OnLeave")
|
||||||
|
|
||||||
|
frame:SetScript("OnEnter", function()
|
||||||
|
PfSet(this)
|
||||||
|
if onEnterFunc then onEnterFunc(this) end
|
||||||
|
end)
|
||||||
|
|
||||||
|
frame:SetScript("OnLeave", function()
|
||||||
|
PfClear(this)
|
||||||
|
if onLeaveFunc then onLeaveFunc(this) end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- FOCUS
|
||||||
|
function Extension.RegisterFocusScripts()
|
||||||
|
if not pfUI or not pfUI.uf or not pfUI.uf.focus then return end
|
||||||
|
local frame = pfUI.uf.focus
|
||||||
|
local onEnterFunc = frame:GetScript("OnEnter")
|
||||||
|
local onLeaveFunc = frame:GetScript("OnLeave")
|
||||||
|
|
||||||
|
frame:SetScript("OnEnter", function()
|
||||||
|
PfSet(this) -- ResolvePfUnit handles focus emulation
|
||||||
|
if onEnterFunc then onEnterFunc(this) end
|
||||||
|
end)
|
||||||
|
|
||||||
|
frame:SetScript("OnLeave", function()
|
||||||
|
PfClear(this)
|
||||||
|
if onLeaveFunc then onLeaveFunc(this) end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- FOCUSTARGET (if your pfUI build provides it)
|
||||||
|
function Extension.RegisterFocusTargetScripts()
|
||||||
|
if not pfUI or not pfUI.uf or not pfUI.uf.focustarget then return end
|
||||||
|
local frame = pfUI.uf.focustarget
|
||||||
|
local onEnterFunc = frame:GetScript("OnEnter")
|
||||||
|
local onLeaveFunc = frame:GetScript("OnLeave")
|
||||||
|
|
||||||
|
frame:SetScript("OnEnter", function()
|
||||||
|
PfSet(this)
|
||||||
|
if onEnterFunc then onEnterFunc(this) end
|
||||||
|
end)
|
||||||
|
|
||||||
|
frame:SetScript("OnLeave", function()
|
||||||
|
PfClear(this)
|
||||||
|
if onLeaveFunc then onLeaveFunc(this) end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- PETTARGET (if your pfUI build provides it)
|
||||||
|
function Extension.RegisterPetTargetScripts()
|
||||||
|
if not pfUI or not pfUI.uf or not pfUI.uf.pettarget then return end
|
||||||
|
local frame = pfUI.uf.pettarget
|
||||||
|
local onEnterFunc = frame:GetScript("OnEnter")
|
||||||
|
local onLeaveFunc = frame:GetScript("OnLeave")
|
||||||
|
|
||||||
|
frame:SetScript("OnEnter", function()
|
||||||
|
PfSet(this)
|
||||||
|
if onEnterFunc then onEnterFunc(this) end
|
||||||
|
end)
|
||||||
|
|
||||||
|
frame:SetScript("OnLeave", function()
|
||||||
|
PfClear(this)
|
||||||
|
if onLeaveFunc then onLeaveFunc(this) end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- TARGETTARGETTARGET (if your pfUI build provides it)
|
||||||
|
function Extension.RegisterTargetTargetTargetScripts()
|
||||||
|
if not pfUI or not pfUI.uf or not pfUI.uf.targettargettarget then return end
|
||||||
|
local frame = pfUI.uf.targettargettarget
|
||||||
|
local onEnterFunc = frame:GetScript("OnEnter")
|
||||||
|
local onLeaveFunc = frame:GetScript("OnLeave")
|
||||||
|
|
||||||
|
frame:SetScript("OnEnter", function()
|
||||||
|
PfSet(this)
|
||||||
|
if onEnterFunc then onEnterFunc(this) end
|
||||||
|
end)
|
||||||
|
|
||||||
|
frame:SetScript("OnLeave", function()
|
||||||
|
PfClear(this)
|
||||||
|
if onLeaveFunc then onLeaveFunc(this) end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- PARTYTARGET (party1target..party4target, plus player's target)
|
||||||
|
function Extension.RegisterPartyTargetScripts()
|
||||||
|
if not pfUI or not pfUI.uf then return end
|
||||||
|
|
||||||
|
-- This helper function is used to hook any given frame.
|
||||||
|
local function hookFrame(frame, defaultUnit)
|
||||||
|
if not frame then return end
|
||||||
|
local onEnterFunc = frame:GetScript("OnEnter")
|
||||||
|
local onLeaveFunc = frame:GetScript("OnLeave")
|
||||||
|
|
||||||
|
frame:SetScript("OnEnter", function()
|
||||||
|
PfSet(this, defaultUnit)
|
||||||
|
-- We remove the call to the original onEnterFunc to prevent overwritten tooltips.
|
||||||
|
end)
|
||||||
|
|
||||||
|
frame:SetScript("OnLeave", function()
|
||||||
|
PfClear(this)
|
||||||
|
if onLeaveFunc then onLeaveFunc(this) end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- This helper function is specifically for party member targets (1-4)
|
||||||
|
local function hookPartyMemberTarget(i, frame)
|
||||||
|
if not frame then return end
|
||||||
|
local defaultUnit = "party" .. i .. "target"
|
||||||
|
hookFrame(frame, defaultUnit)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Case A: Hook dedicated arrays for party members 1-4
|
||||||
|
if pfUI.uf.grouptarget then
|
||||||
|
for i = 1, 4 do hookPartyMemberTarget(i, pfUI.uf.grouptarget[i]) end
|
||||||
|
end
|
||||||
|
if pfUI.uf.partytarget then
|
||||||
|
for i = 1, 4 do hookPartyMemberTarget(i, pfUI.uf.partytarget[i]) end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Case B: Hook child target frames for party members 1-4
|
||||||
|
if pfUI.uf.group then
|
||||||
|
for i = 1, 4 do
|
||||||
|
local g = pfUI.uf.group[i]
|
||||||
|
if g and g.target then hookPartyMemberTarget(i, g.target) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- START OF FIX to include party0target ---
|
||||||
|
-- Case C: Specifically find and hook the player's own target frame (group[0].target)
|
||||||
|
if pfUI.uf.group and pfUI.uf.group[0] and pfUI.uf.group[0].target then
|
||||||
|
-- The player's target UnitID is always "target", not "party0target".
|
||||||
|
hookFrame(pfUI.uf.group[0].target, "target")
|
||||||
|
end
|
||||||
|
--- END OF FIX ---
|
||||||
|
end
|
||||||
|
|
||||||
-- RAID MARKERS (pfUI raidmarkers module)
|
-- RAID MARKERS (pfUI raidmarkers module)
|
||||||
-- Rows are plain Buttons with label="mark" and id=1-8. They have no OnEnter/OnLeave
|
-- Rows are plain Buttons with label="mark" and id=1-8. They have no OnEnter/OnLeave
|
||||||
-- by default, so [mouseover] macros are blind to them. We hook each row so hovering
|
-- by default, so [mouseover] macros are blind to them. We hook each row so hovering
|
||||||
-- registers "mark1".."mark8" through the normal priority system. pfUI's own
|
-- registers "mark1".."mark8" through the normal priority system.
|
||||||
-- SetMouseoverUnit path only covers unitframes, so this stays.
|
|
||||||
function Extension.RegisterRaidMarkScripts()
|
function Extension.RegisterRaidMarkScripts()
|
||||||
if not pfUI or not pfUI.raidmarkers or not pfUI.raidmarkers.rows then return end
|
if not pfUI or not pfUI.raidmarkers or not pfUI.raidmarkers.rows then return end
|
||||||
|
|
||||||
local i
|
|
||||||
for i = 1, 8 do
|
for i = 1, 8 do
|
||||||
local row = pfUI.raidmarkers.rows[i]
|
local row = pfUI.raidmarkers.rows[i]
|
||||||
if row then
|
if row then
|
||||||
@@ -147,7 +370,17 @@ function Extension.HookPfCast()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Extension.PLAYER_ENTERING_WORLD()
|
function Extension.PLAYER_ENTERING_WORLD()
|
||||||
if not pfUI then return end
|
if not pfUI or not pfUI.uf then return end
|
||||||
|
Extension.RegisterPlayerScripts()
|
||||||
|
Extension.RegisterTargetScripts()
|
||||||
|
Extension.RegisterTargetTargetScripts()
|
||||||
|
Extension.RegisterPartyScripts()
|
||||||
|
Extension.RegisterPartyTargetScripts()
|
||||||
|
Extension.RegisterRaidScripts()
|
||||||
|
Extension.RegisterFocusScripts()
|
||||||
|
Extension.RegisterFocusTargetScripts()
|
||||||
|
Extension.RegisterPetTargetScripts()
|
||||||
|
Extension.RegisterTargetTargetTargetScripts()
|
||||||
Extension.RegisterRaidMarkScripts()
|
Extension.RegisterRaidMarkScripts()
|
||||||
Extension.HookPfCast()
|
Extension.HookPfCast()
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -156,53 +156,27 @@ end
|
|||||||
|
|
||||||
-- Lightweight equipment-only indexing for combat situations
|
-- Lightweight equipment-only indexing for combat situations
|
||||||
-- Updates existing cache rather than rebuilding it
|
-- Updates existing cache rather than rebuilding it
|
||||||
function CleveRoids.IndexEquippedItems()
|
|
||||||
local items = CleveRoids.Items or {}
|
|
||||||
|
|
||||||
for inventoryID = 1, 19 do
|
|
||||||
local itemID = GetInventoryItemID("player", inventoryID)
|
|
||||||
if itemID then
|
|
||||||
local name, link, _, _, itemType, itemSubType, _, _, texture = GetItemInfo(itemID)
|
|
||||||
if name then
|
|
||||||
local count = GetInventoryItemCount("player", inventoryID)
|
|
||||||
if not items[name] then
|
|
||||||
items[name] = {
|
|
||||||
inventoryID = inventoryID,
|
|
||||||
id = itemID,
|
|
||||||
name = name,
|
|
||||||
count = count,
|
|
||||||
texture = texture,
|
|
||||||
link = link,
|
|
||||||
}
|
|
||||||
items[itemID] = name
|
|
||||||
local lowerName = string.lower(name)
|
|
||||||
if lowerName ~= name then
|
|
||||||
items[lowerName] = name
|
|
||||||
end
|
|
||||||
else
|
|
||||||
-- Update existing entry with current equipment state
|
|
||||||
items[name].inventoryID = inventoryID
|
|
||||||
items[name].count = count
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
-- Slot is now empty - clear inventoryID from any item that was there
|
|
||||||
-- This is handled lazily by GetItem() fallback, so we skip expensive iteration
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
CleveRoids.lastGetItem = nil
|
|
||||||
CleveRoids.Items = items
|
|
||||||
end
|
|
||||||
|
|
||||||
-- PERFORMANCE: Index a single equipment slot instead of all 20
|
-- PERFORMANCE: Index a single equipment slot instead of all 20
|
||||||
-- Use when we know exactly which slot changed (e.g., from EquipBagItem)
|
-- Use when we know exactly which slot changed (e.g., from EquipBagItem or
|
||||||
function CleveRoids.IndexEquipSlot(inventoryID)
|
-- PLAYER_EQUIPMENT_CHANGED). hasCurrent is the event's arg2 (does the slot now
|
||||||
|
-- hold an item); pass false to skip the API probe on a slot we know is empty.
|
||||||
|
function CleveRoids.IndexEquipSlot(inventoryID, hasCurrent)
|
||||||
if not inventoryID then return end
|
if not inventoryID then return end
|
||||||
|
|
||||||
local items = CleveRoids.Items or {}
|
local items = CleveRoids.Items or {}
|
||||||
local itemID = GetInventoryItemID("player", inventoryID)
|
|
||||||
|
|
||||||
|
-- Clear any stale inventoryID still pointing at this slot: the item that was
|
||||||
|
-- here is now unequipped or swapped out (its real location comes from the
|
||||||
|
-- paired BAG_UPDATE rebuild). Equip changes are user-paced, so this table
|
||||||
|
-- scan is off the hot path.
|
||||||
|
for _, entry in pairs(items) do
|
||||||
|
if type(entry) == "table" and entry.inventoryID == inventoryID then
|
||||||
|
entry.inventoryID = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- hasCurrent == false (arg2) => slot is now empty, nothing to add.
|
||||||
|
local itemID = hasCurrent ~= false and GetInventoryItemID("player", inventoryID)
|
||||||
if itemID then
|
if itemID then
|
||||||
local name, itemLink, _, _, itemType, itemSubType, _, _, texture = GetItemInfo(itemID)
|
local name, itemLink, _, _, itemType, itemSubType, _, _, texture = GetItemInfo(itemID)
|
||||||
if name then
|
if name then
|
||||||
@@ -264,6 +238,14 @@ function CleveRoids.IndexItems()
|
|||||||
local items = {}
|
local items = {}
|
||||||
local NUM_BAG_SLOTS = NUM_BAG_SLOTS -- Upvalue for bag constant
|
local NUM_BAG_SLOTS = NUM_BAG_SLOTS -- Upvalue for bag constant
|
||||||
|
|
||||||
|
-- Rebuilt each pass: itemIDs the player owns that GetItemInfo couldn't
|
||||||
|
-- resolve yet (cold cache under ClassicAPI's async warmup). The
|
||||||
|
-- GET_ITEM_INFO_RECEIVED handler consults this so it only re-indexes for
|
||||||
|
-- our own uncached items and ignores the flood of unrelated fills (quest
|
||||||
|
-- DB scans, AH, chat-link hovers, inspects) in O(1).
|
||||||
|
local pendingItemInfo = {}
|
||||||
|
CleveRoids.pendingItemInfo = pendingItemInfo
|
||||||
|
|
||||||
-- PERFORMANCE: Local function references
|
-- PERFORMANCE: Local function references
|
||||||
local GetContainerNumSlots = GetContainerNumSlots
|
local GetContainerNumSlots = GetContainerNumSlots
|
||||||
local GetContainerItemInfo = GetContainerItemInfo
|
local GetContainerItemInfo = GetContainerItemInfo
|
||||||
@@ -307,6 +289,9 @@ function CleveRoids.IndexItems()
|
|||||||
if lowerName ~= name then
|
if lowerName ~= name then
|
||||||
items[lowerName] = name
|
items[lowerName] = name
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
-- Owned but not cached yet; wait for GET_ITEM_INFO_RECEIVED.
|
||||||
|
pendingItemInfo[itemID] = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -344,6 +329,9 @@ function CleveRoids.IndexItems()
|
|||||||
if lowerName ~= name then
|
if lowerName ~= name then
|
||||||
items[lowerName] = name
|
items[lowerName] = name
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
-- Owned but not cached yet; wait for GET_ITEM_INFO_RECEIVED.
|
||||||
|
pendingItemInfo[itemID] = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -435,7 +423,9 @@ function CleveRoids.IndexActionSlot(slot)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
CleveRoids.TestForActiveAction(CleveRoids.GetAction(slot))
|
local actions = CleveRoids.GetAction(slot)
|
||||||
|
CleveRoids.TestForActiveAction(actions)
|
||||||
|
CleveRoids.PublishDisplay(actions)
|
||||||
CleveRoids.SendEventForAction(slot, "ACTIONBAR_SLOT_CHANGED", slot)
|
CleveRoids.SendEventForAction(slot, "ACTIONBAR_SLOT_CHANGED", slot)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -447,6 +437,27 @@ end
|
|||||||
|
|
||||||
function CleveRoids.GetSpell(text)
|
function CleveRoids.GetSpell(text)
|
||||||
text = CleveRoids.Trim(text)
|
text = CleveRoids.Trim(text)
|
||||||
|
|
||||||
|
-- Explicit "spell:<id>" form. If the player knows the spell, reuse its cached
|
||||||
|
-- spellbook entry (full cost/cooldown/usability). FindSpellBookSlotByID
|
||||||
|
-- (ClassicAPI) resolves per-rank IDs and pet spells natively and returns the
|
||||||
|
-- same bookType string CleveRoids.Spells is keyed by. If the spell is not in
|
||||||
|
-- either book, fall back to an id-only entry that SetAction renders via
|
||||||
|
-- SetSpellByID; cost=0 keeps the downstream active-action checks nil-safe.
|
||||||
|
local _, _, spellId = string.find(text, "^spell:(%d+)$")
|
||||||
|
if spellId then
|
||||||
|
spellId = tonumber(spellId)
|
||||||
|
local slot, book = FindSpellBookSlotByID(spellId)
|
||||||
|
if slot then
|
||||||
|
local name, rank = GetSpellInfo(slot, book)
|
||||||
|
local byName = name and CleveRoids.Spells[book] and CleveRoids.Spells[book][name]
|
||||||
|
if byName then
|
||||||
|
return (rank and rank ~= "" and byName[rank]) or byName.highest or byName
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return { id = spellId, texture = C_Spell.GetSpellTexture(spellId) or CleveRoids.unknownTexture, cost = 0 }
|
||||||
|
end
|
||||||
|
|
||||||
local rs, _, rank = string.find(text, "[^%s]%((Rank %d+)%)$")
|
local rs, _, rank = string.find(text, "[^%s]%((Rank %d+)%)$")
|
||||||
local name = rank and string.sub(text, 1, rs) or text
|
local name = rank and string.sub(text, 1, rs) or text
|
||||||
|
|
||||||
@@ -534,6 +545,12 @@ end
|
|||||||
function CleveRoids.GetItem(text)
|
function CleveRoids.GetItem(text)
|
||||||
if not text or text == "" then return end
|
if not text or text == "" then return end
|
||||||
|
|
||||||
|
-- Explicit "item:<id>" form: force resolution by item ID. Reuses the numeric
|
||||||
|
-- lookup below (equipped -> bags -> GetItemInfo), so location-aware tooltip
|
||||||
|
-- rendering (SetInventoryItem/SetBagItem) still applies when the player has it.
|
||||||
|
local _, _, prefixedId = string.find(text, "^item:(%d+)$")
|
||||||
|
if prefixedId then text = prefixedId end
|
||||||
|
|
||||||
local Items = CleveRoids.Items
|
local Items = CleveRoids.Items
|
||||||
local item = Items[text] or Items[tostring(text)]
|
local item = Items[text] or Items[tostring(text)]
|
||||||
if not item then
|
if not item then
|
||||||
|
|||||||
@@ -24,7 +24,22 @@ CleveRoids.hasSuperwow = SetAutoloot and true or false
|
|||||||
CleveRoids.hasTurtle = (type(_G.TURTLE_WOW_VERSION) ~= "nil")
|
CleveRoids.hasTurtle = (type(_G.TURTLE_WOW_VERSION) ~= "nil")
|
||||||
CleveRoids.supported = CleveRoids.hasTurtle
|
CleveRoids.supported = CleveRoids.hasTurtle
|
||||||
|
|
||||||
|
-- ClassicAPI macro display (C_Macro.SetMacroDisplay): hand ClassicAPI the resolved
|
||||||
|
-- action per macro instead of replacing the action-bar globals in Lua, so the icon,
|
||||||
|
-- tooltip, cooldown sweep, range and usable state all come from the client -- including
|
||||||
|
-- the drag cursor and the macro window grid, which Lua cannot reach.
|
||||||
|
--
|
||||||
|
-- Feature-detect rather than version-check: the API is unreleased, so
|
||||||
|
-- CLASSIC_API_VERSION reports the dev sentinel. ClassicAPI stands down from macro
|
||||||
|
-- display entirely when it sees this addon loaded; ClassicAPIMacroDisplay is what
|
||||||
|
-- tells it we drive it instead. A fork that leaves the flag unset keeps the old
|
||||||
|
-- behavior -- both must never drive the same buttons.
|
||||||
|
CleveRoids.useClassicAPIDisplay =
|
||||||
|
(type(C_Macro) == "table" and C_Macro.SetMacroDisplay ~= nil) and true or false
|
||||||
|
CleveRoids.ClassicAPIMacroDisplay = CleveRoids.useClassicAPIDisplay
|
||||||
|
|
||||||
CleveRoids.ParsedMsg = {}
|
CleveRoids.ParsedMsg = {}
|
||||||
|
CleveRoids.ExpandedGroups = {}
|
||||||
CleveRoids.Items = {}
|
CleveRoids.Items = {}
|
||||||
CleveRoids.Spells = {}
|
CleveRoids.Spells = {}
|
||||||
CleveRoids.PetSpells = {}
|
CleveRoids.PetSpells = {}
|
||||||
@@ -43,13 +58,11 @@ CleveRoids.unknownTexture = "Interface\\Icons\\INV_Misc_QuestionMark"
|
|||||||
|
|
||||||
CleveRoids.spell_tracking = {}
|
CleveRoids.spell_tracking = {}
|
||||||
|
|
||||||
-- GUID-based cast tracking (populated by pfUI 7.6 or standalone SPELL_START events)
|
-- GUID-based cast tracking, populated from our own SPELL_START handlers and pruned
|
||||||
-- Format: [casterGuid] = {spellID, spellName, icon, startTime, duration, endTime}
|
-- in OnUpdate. Format: [casterGuid] = {spellID, spellName, icon, startTime, duration,
|
||||||
|
-- endTime}
|
||||||
CleveRoids.castTracking = {}
|
CleveRoids.castTracking = {}
|
||||||
|
|
||||||
-- pfUI 7.6+ with Nampower 2.31.0+ detected (GUID-based cast tracking available)
|
|
||||||
CleveRoids.hasPfUI76 = false
|
|
||||||
|
|
||||||
-- Combo point tracking (initialized early for /cast hook)
|
-- Combo point tracking (initialized early for /cast hook)
|
||||||
CleveRoids.lastComboPoints = 0
|
CleveRoids.lastComboPoints = 0
|
||||||
CleveRoids.lastComboPointsTime = 0
|
CleveRoids.lastComboPointsTime = 0
|
||||||
@@ -124,6 +137,7 @@ CleveRoids.dynamicCmds = {
|
|||||||
["/castpet"] = true,
|
["/castpet"] = true,
|
||||||
["/castsequence"] = true,
|
["/castsequence"] = true,
|
||||||
["/use"] = true,
|
["/use"] = true,
|
||||||
|
["/feedpet"] = true,
|
||||||
["/equip"] = true,
|
["/equip"] = true,
|
||||||
["/equipmh"] = true,
|
["/equipmh"] = true,
|
||||||
["/equipoh"] = true,
|
["/equipoh"] = true,
|
||||||
@@ -186,11 +200,19 @@ CleveRoids.ignoreKeywords = {
|
|||||||
_operators = true, -- Metadata for AND/OR operator tracking
|
_operators = true, -- Metadata for AND/OR operator tracking
|
||||||
_groups = true, -- Grouped conditional values for AND/OR evaluation
|
_groups = true, -- Grouped conditional values for AND/OR evaluation
|
||||||
multiscan = true, -- Processed before Keywords loop (target resolution)
|
multiscan = true, -- Processed before Keywords loop (target resolution)
|
||||||
mouseuse = true, -- Post-cast modifier: auto-click AOE targeting circle at cursor
|
|
||||||
cursor = true, -- Modifier: place ground-target spell/item at cursor (CastAtCursor)
|
cursor = true, -- Modifier: place ground-target spell/item at cursor (CastAtCursor)
|
||||||
stopattack = true, -- Post-cast modifier: stop autoattack after cast (CheapShot pattern)
|
stopattack = true, -- Post-cast modifier: stop autoattack after cast (CheapShot pattern)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- Deprecated conditional names, rewritten to their current keyword in ParseMsg
|
||||||
|
-- before anything downstream (evaluation, _groups) sees them. This is the path
|
||||||
|
-- for renamed *modifiers* -- ignoreKeywords entries, which have no Keywords
|
||||||
|
-- predicate that an alias could just point at the way [stl] does for [stealth].
|
||||||
|
-- MacroErrorChecker also reads this table, so the old names stay valid syntax.
|
||||||
|
CleveRoids.conditionalAliases = {
|
||||||
|
mouseuse = "cursor", -- pre-ClassicAPI name, back when it clicked the AoE reticle
|
||||||
|
}
|
||||||
|
|
||||||
-- TODO: Localize?
|
-- TODO: Localize?
|
||||||
CleveRoids.countedItemTypes = {
|
CleveRoids.countedItemTypes = {
|
||||||
["Consumable"] = true,
|
["Consumable"] = true,
|
||||||
|
|||||||
+38
-17
@@ -35,6 +35,14 @@ if CleveRoids.ignoreKeywords then
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Deprecated names still valid in macros; ParseMsg rewrites them to the current
|
||||||
|
-- keyword, so they never reach Keywords/ignoreKeywords under their old name.
|
||||||
|
if CleveRoids.conditionalAliases then
|
||||||
|
for alias, _ in pairs(CleveRoids.conditionalAliases) do
|
||||||
|
VALID_CONDITIONALS[alias] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- Known valid commands
|
-- Known valid commands
|
||||||
local VALID_COMMANDS = {
|
local VALID_COMMANDS = {
|
||||||
-- Core commands NOT registered via SlashCmdList (so not auto-discoverable):
|
-- Core commands NOT registered via SlashCmdList (so not auto-discoverable):
|
||||||
@@ -498,6 +506,7 @@ local COMMANDS_NO_ACTION_NEEDED = {
|
|||||||
["/startattack"] = true,
|
["/startattack"] = true,
|
||||||
["/stopattack"] = true,
|
["/stopattack"] = true,
|
||||||
["/stopcasting"] = true,
|
["/stopcasting"] = true,
|
||||||
|
["/stopchanneling"] = true,
|
||||||
["/unqueue"] = true,
|
["/unqueue"] = true,
|
||||||
["/retarget"] = true,
|
["/retarget"] = true,
|
||||||
["/stopmacro"] = true,
|
["/stopmacro"] = true,
|
||||||
@@ -552,7 +561,6 @@ end
|
|||||||
local ERROR_TYPES = {
|
local ERROR_TYPES = {
|
||||||
INVALID_CONDITIONAL = "Invalid conditional",
|
INVALID_CONDITIONAL = "Invalid conditional",
|
||||||
MISMATCHED_BRACKETS = "Mismatched brackets",
|
MISMATCHED_BRACKETS = "Mismatched brackets",
|
||||||
EMPTY_CONDITIONAL = "Empty conditional block",
|
|
||||||
INVALID_OPERATOR = "Invalid operator",
|
INVALID_OPERATOR = "Invalid operator",
|
||||||
MISSING_ARGUMENT = "Missing argument",
|
MISSING_ARGUMENT = "Missing argument",
|
||||||
INVALID_COMMAND = "Unknown command",
|
INVALID_COMMAND = "Unknown command",
|
||||||
@@ -661,6 +669,7 @@ local function validateConditional(conditional, args, action)
|
|||||||
button = true,
|
button = true,
|
||||||
form = true, stance = true,
|
form = true, stance = true,
|
||||||
level = true, mylevel = true,
|
level = true, mylevel = true,
|
||||||
|
myspellhaste = true,
|
||||||
distance = true, nodistance = true,
|
distance = true, nodistance = true,
|
||||||
swingtimer = true, stimer = true,
|
swingtimer = true, stimer = true,
|
||||||
rangedtimer = true, rtimer = true,
|
rangedtimer = true, rtimer = true,
|
||||||
@@ -681,7 +690,7 @@ local function validateConditional(conditional, args, action)
|
|||||||
if args and type(args) == "string" then
|
if args and type(args) == "string" then
|
||||||
local hasHpOrPower = safeStringFind(baseCond, "hp") or safeStringFind(baseCond, "power") or
|
local hasHpOrPower = safeStringFind(baseCond, "hp") or safeStringFind(baseCond, "power") or
|
||||||
safeStringFind(baseCond, "mana") or safeStringFind(baseCond, "energy") or
|
safeStringFind(baseCond, "mana") or safeStringFind(baseCond, "energy") or
|
||||||
safeStringFind(baseCond, "rage") or
|
safeStringFind(baseCond, "rage") or safeStringFind(baseCond, "haste") or
|
||||||
safeStringFind(baseCond, "combo") or baseCond == "stat"
|
safeStringFind(baseCond, "combo") or baseCond == "stat"
|
||||||
if hasHpOrPower then
|
if hasHpOrPower then
|
||||||
local hasOperator = safeStringFind(args, "[<>=~]+")
|
local hasOperator = safeStringFind(args, "[<>=~]+")
|
||||||
@@ -839,16 +848,19 @@ local function validateLine(line, lineNum)
|
|||||||
actionPart = "/" .. actionPart -- Add leading slash if missing after split
|
actionPart = "/" .. actionPart -- Add leading slash if missing after split
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Parse conditionals if present - use non-greedy match
|
-- Parse the leading run of [conditional] blocks, if any. `[a][b] X` is
|
||||||
local condStart = safeStringFind(actionPart, "%[")
|
-- Blizzard-style OR chaining: every block is validated, and the action
|
||||||
|
-- is what follows the last one.
|
||||||
|
local blocks = {}
|
||||||
local condEnd = nil
|
local condEnd = nil
|
||||||
local conditionBlock = nil
|
local condStart = safeStringFind(actionPart, "%[")
|
||||||
|
local len = safeStringLen(actionPart)
|
||||||
|
|
||||||
if condStart then
|
while condStart do
|
||||||
-- Find matching closing bracket
|
-- Find matching closing bracket
|
||||||
local depth = 0
|
local depth = 0
|
||||||
local inQuotes = false
|
local inQuotes = false
|
||||||
local len = safeStringLen(actionPart)
|
local closePos = nil
|
||||||
|
|
||||||
for i = condStart, len do
|
for i = condStart, len do
|
||||||
local char = safeStringSub(actionPart, i, i)
|
local char = safeStringSub(actionPart, i, i)
|
||||||
@@ -862,23 +874,30 @@ local function validateLine(line, lineNum)
|
|||||||
elseif char == "]" then
|
elseif char == "]" then
|
||||||
depth = depth - 1
|
depth = depth - 1
|
||||||
if depth == 0 then
|
if depth == 0 then
|
||||||
condEnd = i
|
closePos = i
|
||||||
conditionBlock = safeStringSub(actionPart, condStart + 1, i - 1)
|
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
if not closePos then break end
|
||||||
|
|
||||||
|
table.insert(blocks, safeStringSub(actionPart, condStart + 1, closePos - 1))
|
||||||
|
condEnd = closePos
|
||||||
|
|
||||||
|
-- Another block directly after this one (whitespace allowed)?
|
||||||
|
local _, wsEnd = safeStringFind(actionPart, "^%s*", closePos + 1)
|
||||||
|
local nextPos = (wsEnd or closePos) + 1
|
||||||
|
if safeStringSub(actionPart, nextPos, nextPos) == "[" then
|
||||||
|
condStart = nextPos
|
||||||
|
else
|
||||||
|
condStart = nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if conditionBlock then
|
for _, conditionBlock in ipairs(blocks) do
|
||||||
if safeTrim(conditionBlock) == "" then
|
-- `[]` is the always-true group; nothing to validate
|
||||||
table.insert(localErrors, {
|
if safeTrim(conditionBlock) ~= "" then
|
||||||
type = ERROR_TYPES.EMPTY_CONDITIONAL,
|
|
||||||
line = lineNum,
|
|
||||||
message = "Empty conditional block []"
|
|
||||||
})
|
|
||||||
else
|
|
||||||
-- Check for invalid @ target syntax
|
-- Check for invalid @ target syntax
|
||||||
local _, _, target = safeStringFind(conditionBlock, "(@[^%s,]+)")
|
local _, _, target = safeStringFind(conditionBlock, "(@[^%s,]+)")
|
||||||
if target and not safeStringFind(target, "^@[a-z]+%d*") then
|
if target and not safeStringFind(target, "^@[a-z]+%d*") then
|
||||||
@@ -926,7 +945,9 @@ local function validateLine(line, lineNum)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if condEnd then
|
||||||
-- Check for action after conditionals
|
-- Check for action after conditionals
|
||||||
-- Extract the command from this action part
|
-- Extract the command from this action part
|
||||||
local _, _, cmdFromAction = safeStringFind(actionPart, "^(/[a-z]+%d*)")
|
local _, _, cmdFromAction = safeStringFind(actionPart, "^(/[a-z]+%d*)")
|
||||||
|
|||||||
+5
-5
@@ -1474,7 +1474,7 @@ end
|
|||||||
|
|
||||||
-- Get all equipped items for a unit
|
-- Get all equipped items for a unit
|
||||||
-- Requires v2.18+ for native GetEquippedItems (with Lua fallback for player only)
|
-- Requires v2.18+ for native GetEquippedItems (with Lua fallback for player only)
|
||||||
-- Returns table with slot indices (0-18) as keys, item info tables as values
|
-- Returns table with slot indices (1-19) as keys, item info tables as values
|
||||||
function API.GetEquippedItems(unitToken)
|
function API.GetEquippedItems(unitToken)
|
||||||
unitToken = unitToken or "player"
|
unitToken = unitToken or "player"
|
||||||
|
|
||||||
@@ -1489,8 +1489,8 @@ function API.GetEquippedItems(unitToken)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local items = {}
|
local items = {}
|
||||||
for slot = 0, 18 do
|
for slot = 1, 19 do
|
||||||
local itemId = CleveRoids.ClassicAPI.GetInventoryItemID("player", slot + 1)
|
local itemId = CleveRoids.ClassicAPI.GetInventoryItemID("player", slot)
|
||||||
if itemId then
|
if itemId then
|
||||||
items[slot] = {
|
items[slot] = {
|
||||||
itemId = itemId,
|
itemId = itemId,
|
||||||
@@ -1518,7 +1518,7 @@ function API.GetEquippedItem(unitToken, slot)
|
|||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local itemId = CleveRoids.ClassicAPI.GetInventoryItemID("player", slot + 1) -- 1-indexed
|
local itemId = CleveRoids.ClassicAPI.GetInventoryItemID("player", slot) -- both are 1-indexed
|
||||||
if itemId then
|
if itemId then
|
||||||
return {
|
return {
|
||||||
itemId = itemId,
|
itemId = itemId,
|
||||||
@@ -1672,7 +1672,7 @@ function API.IsItemInSlot(itemIdOrName, inventorySlot)
|
|||||||
|
|
||||||
-- Use native v2.18 GetEquippedItem if available
|
-- Use native v2.18 GetEquippedItem if available
|
||||||
if GetEquippedItem then
|
if GetEquippedItem then
|
||||||
local slotInfo = GetEquippedItem("player", inventorySlot - 1) -- 0-indexed
|
local slotInfo = GetEquippedItem("player", inventorySlot) -- 1-indexed (1-19)
|
||||||
if slotInfo and slotInfo.itemId then
|
if slotInfo and slotInfo.itemId then
|
||||||
local checkId = tonumber(itemIdOrName)
|
local checkId = tonumber(itemIdOrName)
|
||||||
if checkId then
|
if checkId then
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ Enhanced macro addon for World of Warcraft 1.12.1 (Vanilla/Turtle WoW) with dyna
|
|||||||
- **Arguments** use colon: `[mod:alt]`, `[hp:>50]`
|
- **Arguments** use colon: `[mod:alt]`, `[hp:>50]`
|
||||||
- **Negation** with `no` prefix: `[nobuff]`, `[nomod:alt]`
|
- **Negation** with `no` prefix: `[nobuff]`, `[nomod:alt]`
|
||||||
- **Target** with `@`: `[@mouseover,help]`, `[@party1,hp:<50]`
|
- **Target** with `@`: `[@mouseover,help]`, `[@party1,hp:<50]`
|
||||||
|
- **Fallback groups**, Blizzard-style: `[@mouseover,help][@focus,help][] Rejuvenation` tries each `[...]` in order and the first that passes casts the shared spell; `[]` always passes. Mixes freely with `;`
|
||||||
- **Spell names** with spaces: `"Mark of the Wild"` or `Mark_of_the_Wild`
|
- **Spell names** with spaces: `"Mark of the Wild"` or `Mark_of_the_Wild`
|
||||||
|
|
||||||
**Multi-value logic:**
|
**Multi-value logic:**
|
||||||
@@ -46,7 +47,7 @@ See the **[Wiki](https://github.com/brues-code/SuperCleveRoidMacros/wiki)** for
|
|||||||
|
|
||||||
- **[Quick Start](https://github.com/brues-code/SuperCleveRoidMacros/wiki/Quick-Start)** — Syntax, multi-value logic, comparisons, special prefixes
|
- **[Quick Start](https://github.com/brues-code/SuperCleveRoidMacros/wiki/Quick-Start)** — Syntax, multi-value logic, comparisons, special prefixes
|
||||||
- **[Slash Commands](https://github.com/brues-code/SuperCleveRoidMacros/wiki/Slash-Commands)** — All 35+ commands, priority macros, UnitXP scanning
|
- **[Slash Commands](https://github.com/brues-code/SuperCleveRoidMacros/wiki/Slash-Commands)** — All 35+ commands, priority macros, UnitXP scanning
|
||||||
- **[Conditionals](https://github.com/brues-code/SuperCleveRoidMacros/wiki/Conditionals)** — 70+ conditionals (player & target), extended unit tokens, multiscan
|
- **[Conditionals](https://github.com/brues-code/SuperCleveRoidMacros/wiki/Conditionals)** — 150+ conditionals (player & target), short aliases, extended unit tokens, multiscan
|
||||||
- **[Reference Tables](https://github.com/brues-code/SuperCleveRoidMacros/wiki/Reference-Tables)** — CC types, damage schools, stat types, swing types
|
- **[Reference Tables](https://github.com/brues-code/SuperCleveRoidMacros/wiki/Reference-Tables)** — CC types, damage schools, stat types, swing types
|
||||||
- **[Features](https://github.com/brues-code/SuperCleveRoidMacros/wiki/Features)** — Debuff timers, combo tracking, talent modifiers, TWoW mechanics
|
- **[Features](https://github.com/brues-code/SuperCleveRoidMacros/wiki/Features)** — Debuff timers, combo tracking, talent modifiers, TWoW mechanics
|
||||||
- **[Overflow Buff Frame](https://github.com/brues-code/SuperCleveRoidMacros/wiki/Overflow-Buff-Frame)** — Hidden buff display for 32+ buffs
|
- **[Overflow Buff Frame](https://github.com/brues-code/SuperCleveRoidMacros/wiki/Overflow-Buff-Frame)** — Hidden buff display for 32+ buffs
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
## Version: @project-version@
|
## Version: @project-version@
|
||||||
## OptionalDeps: ClassicFocus, FocusFrame, pfUI, SuperMacro, Bongos_ActionBar, Cursive, XPerl, LunaUnitFrames, DragonflightReloaded, -Dragonflight3
|
## OptionalDeps: ClassicFocus, FocusFrame, pfUI, SuperMacro, Bongos_ActionBar, Cursive, XPerl, LunaUnitFrames, DragonflightReloaded, -Dragonflight3
|
||||||
## SavedVariables: CleveRoidMacros, CleveRoids_LearnedDurations, CleveRoids_AuraTextures, CleveRoids_ImmunityData, CleveRoids_ComboDurations, CleveRoids_SpellSchools
|
## SavedVariables: CleveRoidMacros, CleveRoids_LearnedDurations, CleveRoids_AuraTextures, CleveRoids_ImmunityData, CleveRoids_ComboDurations, CleveRoids_SpellSchools
|
||||||
|
## LoadSavedVariablesFirst: 1
|
||||||
Localization.lua
|
Localization.lua
|
||||||
Init.lua
|
Init.lua
|
||||||
NampowerAPI.lua
|
NampowerAPI.lua
|
||||||
|
|||||||
+126
-353
@@ -117,18 +117,6 @@ local cachedSpellDurations = {}
|
|||||||
local spellDurationCacheTime = {}
|
local spellDurationCacheTime = {}
|
||||||
local SPELL_CACHE_DURATION = 0.5 -- Re-scan every 0.5 seconds (haste can change mid-fight)
|
local SPELL_CACHE_DURATION = 0.5 -- Re-scan every 0.5 seconds (haste can change mid-fight)
|
||||||
|
|
||||||
-- Get a spell's slot in the spellbook by spell ID
|
|
||||||
local function GetSpellSlotByID(targetSpellID)
|
|
||||||
local i = 1
|
|
||||||
while true do
|
|
||||||
local spellName = GetSpellName(i, BOOKTYPE_SPELL)
|
|
||||||
if not spellName then break end
|
|
||||||
-- Note: spell ID matching done via GetSpellTexture comparison if needed
|
|
||||||
i = i + 1
|
|
||||||
end
|
|
||||||
return nil, nil
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Get a spell's slot in the spellbook by name (finds highest rank by default)
|
-- Get a spell's slot in the spellbook by name (finds highest rank by default)
|
||||||
-- If targetRank is specified (e.g., "Rank 5"), finds that specific rank
|
-- If targetRank is specified (e.g., "Rank 5"), finds that specific rank
|
||||||
local function GetSpellSlotByName(targetSpellName, targetRank)
|
local function GetSpellSlotByName(targetSpellName, targetRank)
|
||||||
@@ -247,7 +235,6 @@ function CleveRoids.GetChannelDurationFromTooltipByID(spellID)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Fallback: resolve name and scan tooltip
|
-- Fallback: resolve name and scan tooltip
|
||||||
if not GetSpellRecField then return nil end
|
|
||||||
local spellName = C_Spell.GetSpellName(spellID)
|
local spellName = C_Spell.GetSpellName(spellID)
|
||||||
if not spellName then return nil end
|
if not spellName then return nil end
|
||||||
return CleveRoids.GetSpellDurationFromTooltip(spellName)
|
return CleveRoids.GetSpellDurationFromTooltip(spellName)
|
||||||
@@ -627,6 +614,76 @@ function CleveRoids.splitStringIgnoringQuotes(str, separator)
|
|||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Splits a macro clause into its leading run of [group] blocks and the action
|
||||||
|
-- that follows, Blizzard-style: `[a][b] Spell` is two groups sharing one action.
|
||||||
|
-- Returns prefix (the leading whitespace and ? ! ~ flag characters, kept so a
|
||||||
|
-- variant re-parses exactly like the original), groups (array with .n, each
|
||||||
|
-- entry including its brackets) and restStart (index of the action text; 1 when
|
||||||
|
-- there are no groups). Quote-aware for " so a ] inside a quoted argument does
|
||||||
|
-- not close a group; not nesting-aware. An unclosed [ ends the scan at that [,
|
||||||
|
-- which is what the old greedy "%[(.+)%]" produced for such input.
|
||||||
|
function CleveRoids.ScanBracketGroups(msg)
|
||||||
|
local groups = { n = 0 }
|
||||||
|
if not msg then return "", groups, 1 end
|
||||||
|
|
||||||
|
local len = string.len(msg)
|
||||||
|
local _, prefixEnd = string.find(msg, "^[%s%?!~]*")
|
||||||
|
local prefix = string.sub(msg, 1, prefixEnd)
|
||||||
|
local i = prefixEnd + 1
|
||||||
|
|
||||||
|
while i <= len and string.sub(msg, i, i) == "[" do
|
||||||
|
local close = nil
|
||||||
|
local inQuotes = false
|
||||||
|
for j = i + 1, len do
|
||||||
|
local c = string.sub(msg, j, j)
|
||||||
|
if c == "\"" then
|
||||||
|
inQuotes = not inQuotes
|
||||||
|
elseif c == "]" and not inQuotes then
|
||||||
|
close = j
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not close then break end
|
||||||
|
|
||||||
|
groups.n = groups.n + 1
|
||||||
|
groups[groups.n] = string.sub(msg, i, close)
|
||||||
|
local _, wsEnd = string.find(msg, "^%s*", close + 1)
|
||||||
|
i = wsEnd + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if groups.n == 0 then
|
||||||
|
return prefix, groups, 1
|
||||||
|
end
|
||||||
|
return prefix, groups, i
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Single-group variants of a multi-group clause: `[a][b] Spell` becomes
|
||||||
|
-- { "[a] Spell", "[b] Spell", n = 2 }. nil for anything with fewer than two
|
||||||
|
-- groups so callers take their normal path without allocating. Memoised per
|
||||||
|
-- clause string in CleveRoids.ExpandedGroups (false marks "nothing to expand").
|
||||||
|
function CleveRoids.ExpandBracketGroups(msg)
|
||||||
|
if not msg or not string.find(msg, "%[") then return nil end
|
||||||
|
|
||||||
|
local cached = CleveRoids.ExpandedGroups[msg]
|
||||||
|
if cached ~= nil then
|
||||||
|
return cached or nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local variants = nil
|
||||||
|
local prefix, groups, restStart = CleveRoids.ScanBracketGroups(msg)
|
||||||
|
if groups.n > 1 then
|
||||||
|
local rest = string.sub(msg, restStart)
|
||||||
|
if rest ~= "" then rest = " " .. rest end
|
||||||
|
variants = { n = groups.n }
|
||||||
|
for i = 1, groups.n do
|
||||||
|
variants[i] = prefix .. groups[i] .. rest
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
CleveRoids.ExpandedGroups[msg] = variants or false
|
||||||
|
return variants
|
||||||
|
end
|
||||||
|
|
||||||
function CleveRoids.Print(...)
|
function CleveRoids.Print(...)
|
||||||
local c = "|cFF4477FFCleveR|r|cFFFFFFFFoid :: |r"
|
local c = "|cFF4477FFCleveR|r|cFFFFFFFFoid :: |r"
|
||||||
local out = ""
|
local out = ""
|
||||||
@@ -669,6 +726,24 @@ function CleveRoids.PrintT(t, depth)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
CleveRoids.buttons = {
|
||||||
|
['1'] = 'LeftButton',
|
||||||
|
['2'] = 'RightButton',
|
||||||
|
['3'] = 'MiddleButton',
|
||||||
|
['4'] = 'Button4',
|
||||||
|
['5'] = 'Button5',
|
||||||
|
}
|
||||||
|
|
||||||
|
-- True while any mapped mouse button is held. Backs the argument-less [button] /
|
||||||
|
-- [nobutton], mirroring how a bare [mod] means "any modifier". Bare [nobutton] is
|
||||||
|
-- the practical "activated by a keybind, not a click" test.
|
||||||
|
function CleveRoids.AnyMouseButtonDown()
|
||||||
|
for _, name in pairs(CleveRoids.buttons) do
|
||||||
|
if IsMouseButtonDown(name) then return true end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
CleveRoids.kmods = {
|
CleveRoids.kmods = {
|
||||||
ctrl = IsControlKeyDown,
|
ctrl = IsControlKeyDown,
|
||||||
lctrl = IsLeftControlKeyDown,
|
lctrl = IsLeftControlKeyDown,
|
||||||
@@ -768,17 +843,10 @@ lib.iconCache = lib.iconCache or {} -- [spellId] = texture (shared with
|
|||||||
-- Buff tracking tables (parallel to debuff tables, standalone Nampower mode only)
|
-- Buff tracking tables (parallel to debuff tables, standalone Nampower mode only)
|
||||||
-- Buff tables are NOT linked to pfUI — pfUI has no public buff tracking tables.
|
-- Buff tables are NOT linked to pfUI — pfUI has no public buff tracking tables.
|
||||||
-- They remain empty when hasPfUIEnhanced=true since all buff event handlers are gated by it.
|
-- They remain empty when hasPfUIEnhanced=true since all buff event handlers are gated by it.
|
||||||
lib.ownBuffCasts = lib.ownBuffCasts or {} -- [targetGUID][buffName] = {startTime, duration, spellId, casterGuid}
|
|
||||||
-- Player's own buff casts on self or others (AURA_CAST events)
|
|
||||||
lib.allBuffAuras = lib.allBuffAuras or {} -- [targetGUID][buffName][casterGuid] = {startTime, duration, rank}
|
|
||||||
-- All buffs from all casters on any unit (confirmed by BUFF_ADDED events)
|
|
||||||
lib.pendingBuffCasts = lib.pendingBuffCasts or {} -- [targetGUID][spellId] = {casterGuid, duration, spellName, time}
|
|
||||||
-- Temp storage from AURA_CAST_ON_OTHER, consumed by BUFF_ADDED_OTHER
|
|
||||||
|
|
||||||
-- Flag indicating whether enhanced pfUI tracking is available
|
-- Flag indicating whether enhanced pfUI tracking is available
|
||||||
lib.hasPfUIEnhanced = false
|
lib.hasPfUIEnhanced = false
|
||||||
lib.hasStandaloneNampower = false
|
lib.hasStandaloneNampower = false
|
||||||
lib.hasPfUI76 = false
|
|
||||||
|
|
||||||
-- Check if pfUI v7.4.3+ with enhanced libdebuff is available
|
-- Check if pfUI v7.4.3+ with enhanced libdebuff is available
|
||||||
function lib:HasEnhancedPfUILibdebuff()
|
function lib:HasEnhancedPfUILibdebuff()
|
||||||
@@ -839,33 +907,6 @@ function lib:HasEnhancedPfUILibdebuff()
|
|||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Check if pfUI v7.6+ with enhanced cast tracking is available
|
|
||||||
-- pfUI 7.6+ requires Nampower v2.37.0+ and exposes additional tables
|
|
||||||
function lib:HasPfUI76()
|
|
||||||
if not pfUI then return false end
|
|
||||||
|
|
||||||
local v = pfUI.version
|
|
||||||
if not v or not v.major then return false end
|
|
||||||
|
|
||||||
-- Version comparison: 7.6+
|
|
||||||
if v.major < 7 then return false end
|
|
||||||
if v.major == 7 and (v.minor or 0) < 6 then return false end
|
|
||||||
|
|
||||||
-- Verify Nampower v2.40.0+ (pfUI 7.6+ hard requirement, bumped from 2.38 on 2026-02-21;
|
|
||||||
-- v2.40.0 fixes packed GUID parsing that caused target GUIDs to appear as 0x000000000
|
|
||||||
-- for some players, which directly affects cast tracking reliability)
|
|
||||||
if not GetNampowerVersion then return false end
|
|
||||||
local npMajor, npMinor, npPatch = GetNampowerVersion()
|
|
||||||
npPatch = npPatch or 0
|
|
||||||
if npMajor < 2 then return false end
|
|
||||||
if npMajor == 2 and npMinor < 40 then return false end
|
|
||||||
|
|
||||||
-- Verify the new tables exist
|
|
||||||
if not pfUI.libdebuff_casts then return false end
|
|
||||||
if not pfUI.libdebuff_objects_guid then return false end
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Icon caching helper: DBC lookup via GetSpellRecField
|
-- Icon caching helper: DBC lookup via GetSpellRecField
|
||||||
function lib:GetCachedIcon(spellId)
|
function lib:GetCachedIcon(spellId)
|
||||||
@@ -899,19 +940,6 @@ function lib:InitPfUIIntegration()
|
|||||||
lib.hasPfUIEnhanced = true
|
lib.hasPfUIEnhanced = true
|
||||||
lib.hasStandaloneNampower = false
|
lib.hasStandaloneNampower = false
|
||||||
|
|
||||||
-- Check for pfUI 7.6+ additional tables (cast tracking, GUID objects, icon cache)
|
|
||||||
if lib:HasPfUI76() then
|
|
||||||
CleveRoids.hasPfUI76 = true
|
|
||||||
lib.hasPfUI76 = true
|
|
||||||
CleveRoids.castTracking = pfUI.libdebuff_casts
|
|
||||||
lib.iconCache = pfUI.libdebuff_icon_cache or lib.iconCache
|
|
||||||
-- lib.objects is already set by pfUI's CleveRoids.libdebuff = libdebuff override
|
|
||||||
-- but explicitly sync if pfUI.libdebuff_objects_guid is available
|
|
||||||
if pfUI.libdebuff_objects_guid then
|
|
||||||
lib.objects = pfUI.libdebuff_objects_guid
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Unregister chat log events since SPELL_GO provides miss detection
|
-- Unregister chat log events since SPELL_GO provides miss detection
|
||||||
if CleveRoidsLibDebuffLearnFrame then
|
if CleveRoidsLibDebuffLearnFrame then
|
||||||
CleveRoidsLibDebuffLearnFrame:UnregisterEvent("CHAT_MSG_SPELL_SELF_DAMAGE")
|
CleveRoidsLibDebuffLearnFrame:UnregisterEvent("CHAT_MSG_SPELL_SELF_DAMAGE")
|
||||||
@@ -928,10 +956,9 @@ function lib:InitPfUIIntegration()
|
|||||||
|
|
||||||
if CleveRoids.debug then
|
if CleveRoids.debug then
|
||||||
local v = pfUI.version
|
local v = pfUI.version
|
||||||
local tierMsg = lib.hasPfUI76 and " (7.6+ cast tracking)" or ""
|
|
||||||
DEFAULT_CHAT_FRAME:AddMessage(
|
DEFAULT_CHAT_FRAME:AddMessage(
|
||||||
string.format("|cff33ff99[libdebuff]|r pfUI v%d.%d.%d enhanced tracking enabled%s",
|
string.format("|cff33ff99[libdebuff]|r pfUI v%d.%d.%d enhanced tracking enabled",
|
||||||
v.major, v.minor, v.fix or 0, tierMsg)
|
v.major, v.minor, v.fix or 0)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1301,57 +1328,6 @@ function lib:CleanupStaleTrackingData()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Clean ownBuffCasts (player-cast buffs on targets)
|
|
||||||
if lib.ownBuffCasts then
|
|
||||||
for guid, buffs in pairs(lib.ownBuffCasts) do
|
|
||||||
for buffName, data in pairs(buffs) do
|
|
||||||
local elapsed = now - (data.startTime or 0)
|
|
||||||
local dur = data.duration or 0
|
|
||||||
-- Remove if expired (duration > 0 and past end time) or stale (no duration and old)
|
|
||||||
if (dur > 0 and elapsed > dur) or (dur <= 0 and elapsed > staleTime) then
|
|
||||||
buffs[buffName] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if not next(buffs) then
|
|
||||||
lib.ownBuffCasts[guid] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Clean allBuffAuras (all-caster buff tracking)
|
|
||||||
if lib.allBuffAuras then
|
|
||||||
for guid, buffs in pairs(lib.allBuffAuras) do
|
|
||||||
for buffName, casters in pairs(buffs) do
|
|
||||||
for cGuid, data in pairs(casters) do
|
|
||||||
local elapsed = now - (data.startTime or 0)
|
|
||||||
local dur = data.duration or 0
|
|
||||||
if (dur > 0 and elapsed > dur) or (dur <= 0 and elapsed > staleTime) then
|
|
||||||
casters[cGuid] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if not next(casters) then
|
|
||||||
buffs[buffName] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if not next(buffs) then
|
|
||||||
lib.allBuffAuras[guid] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Clean pendingBuffCasts (short-lived correlation data, 2 second TTL)
|
|
||||||
if lib.pendingBuffCasts then
|
|
||||||
for guid, spells in pairs(lib.pendingBuffCasts) do
|
|
||||||
for spellId, data in pairs(spells) do
|
|
||||||
if (now - (data.time or 0)) > 2 then
|
|
||||||
spells[spellId] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if not next(spells) then
|
|
||||||
lib.pendingBuffCasts[guid] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Get the caster GUID for a debuff on a target
|
-- Get the caster GUID for a debuff on a target
|
||||||
@@ -2136,7 +2112,7 @@ function lib:ShouldApplyDebuffRank(targetGUID, newSpellID)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Also clean up pfUI's tracking to prevent it from showing old ranks
|
-- Also clean up pfUI's tracking to prevent it from showing old ranks
|
||||||
if not CleveRoids.hasPfUI76 and pfUI and pfUI.api and pfUI.api.libdebuff and targetName then
|
if pfUI and pfUI.api and pfUI.api.libdebuff and targetName then
|
||||||
local pflib = pfUI.api.libdebuff
|
local pflib = pfUI.api.libdebuff
|
||||||
|
|
||||||
if pflib.objects and pflib.objects[targetName] then
|
if pflib.objects and pflib.objects[targetName] then
|
||||||
@@ -2183,7 +2159,7 @@ function lib:GetDuration(spellID, casterGUID, comboPoints)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Check caster-specific learned durations
|
-- Check caster-specific learned durations
|
||||||
if casterGUID and CleveRoids_LearnedDurations[spellID] then
|
if casterGUID and CleveRoids_LearnedDurations and CleveRoids_LearnedDurations[spellID] then
|
||||||
local learned = CleveRoids_LearnedDurations[spellID][casterGUID]
|
local learned = CleveRoids_LearnedDurations[spellID][casterGUID]
|
||||||
if learned and learned > 0 then
|
if learned and learned > 0 then
|
||||||
if CleveRoids.debug then
|
if CleveRoids.debug then
|
||||||
@@ -2286,38 +2262,6 @@ function lib:AddEffect(guid, unitName, spellID, duration, stacks, caster)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- PFUI INTEGRATION: Inject all tracked debuffs into pfUI's libdebuff (pre-7.6 only)
|
|
||||||
-- pfUI 7.6+ handles all duration tracking internally via GetUnitField
|
|
||||||
if pfUI and pfUI.api and pfUI.api.libdebuff and unitName and not CleveRoids.hasPfUI76 then
|
|
||||||
local pflib = pfUI.api.libdebuff
|
|
||||||
local spellName = C_Spell.GetSpellName(spellID)
|
|
||||||
|
|
||||||
if spellName and pflib.AddEffect then
|
|
||||||
-- Get target level for pfUI's tracking structure
|
|
||||||
local targetLevel = UnitLevel(guid) or UnitLevel("target") or 1
|
|
||||||
|
|
||||||
-- Strip rank from spell name for pfUI (it uses base names)
|
|
||||||
local baseName = CleveRoids.StripRank(spellName)
|
|
||||||
|
|
||||||
-- Also register the duration in pfUI's duration table
|
|
||||||
if pflib.debuffs then
|
|
||||||
pflib.debuffs[baseName] = duration
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Add the effect to pfUI's tracking
|
|
||||||
-- Use "player" as caster for pfUI compatibility (it expects this format)
|
|
||||||
pflib:AddEffect(unitName, targetLevel, baseName, duration, "player")
|
|
||||||
|
|
||||||
if CleveRoids.debug then
|
|
||||||
local casterStr = (caster == "player") and "player" or "other"
|
|
||||||
DEFAULT_CHAT_FRAME:AddMessage(
|
|
||||||
string.format("|cff00ff00[pfUI Inject]|r %s (%ds) on %s (level %d) [caster: %s]",
|
|
||||||
baseName, duration, unitName, targetLevel, casterStr)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if CleveRoids.debug then
|
if CleveRoids.debug then
|
||||||
local spellName = C_Spell.GetSpellName(spellID) or "Unknown"
|
local spellName = C_Spell.GetSpellName(spellID) or "Unknown"
|
||||||
CleveRoids.DebugChanged("addeffect_" .. spellID .. "_" .. tostring(guid),
|
CleveRoids.DebugChanged("addeffect_" .. spellID .. "_" .. tostring(guid),
|
||||||
@@ -2523,20 +2467,6 @@ local function SeedUnit(unit)
|
|||||||
existing.start = GetTime()
|
existing.start = GetTime()
|
||||||
existing.duration = duration
|
existing.duration = duration
|
||||||
|
|
||||||
-- PFUI INTEGRATION: Inject refreshed timer into pfUI (pre-7.6 only)
|
|
||||||
if pfUI and pfUI.api and pfUI.api.libdebuff and unitName and not CleveRoids.hasPfUI76 then
|
|
||||||
local pflib = pfUI.api.libdebuff
|
|
||||||
local spellName = C_Spell.GetSpellName(spellID)
|
|
||||||
if spellName and pflib.AddEffect then
|
|
||||||
local targetLevel = UnitLevel(unit) or 1
|
|
||||||
local baseName = CleveRoids.StripRank(spellName)
|
|
||||||
if pflib.debuffs then
|
|
||||||
pflib.debuffs[baseName] = duration
|
|
||||||
end
|
|
||||||
pflib:AddEffect(unitName, targetLevel, baseName, duration, "player")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if CleveRoids.debug then
|
if CleveRoids.debug then
|
||||||
local spellName = C_Spell.GetSpellName(spellID) or "Unknown"
|
local spellName = C_Spell.GetSpellName(spellID) or "Unknown"
|
||||||
DEFAULT_CHAT_FRAME:AddMessage(
|
DEFAULT_CHAT_FRAME:AddMessage(
|
||||||
@@ -2612,20 +2542,6 @@ local function SeedUnit(unit)
|
|||||||
existing.start = GetTime()
|
existing.start = GetTime()
|
||||||
existing.duration = duration
|
existing.duration = duration
|
||||||
|
|
||||||
-- PFUI INTEGRATION: Inject refreshed timer into pfUI (pre-7.6 only)
|
|
||||||
if pfUI and pfUI.api and pfUI.api.libdebuff and unitName and not CleveRoids.hasPfUI76 then
|
|
||||||
local pflib = pfUI.api.libdebuff
|
|
||||||
local spellName = C_Spell.GetSpellName(spellID)
|
|
||||||
if spellName and pflib.AddEffect then
|
|
||||||
local targetLevel = UnitLevel(unit) or 1
|
|
||||||
local baseName = CleveRoids.StripRank(spellName)
|
|
||||||
if pflib.debuffs then
|
|
||||||
pflib.debuffs[baseName] = duration
|
|
||||||
end
|
|
||||||
pflib:AddEffect(unitName, targetLevel, baseName, duration, "player")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if CleveRoids.debug then
|
if CleveRoids.debug then
|
||||||
local spellName = C_Spell.GetSpellName(spellID) or "Unknown"
|
local spellName = C_Spell.GetSpellName(spellID) or "Unknown"
|
||||||
DEFAULT_CHAT_FRAME:AddMessage(
|
DEFAULT_CHAT_FRAME:AddMessage(
|
||||||
@@ -2885,7 +2801,7 @@ function lib.ApplyCarnageRefresh(targetGUID, targetName, biteSpellID)
|
|||||||
|
|
||||||
-- DON'T call pfUI's AddEffect - just update the existing entry directly
|
-- DON'T call pfUI's AddEffect - just update the existing entry directly
|
||||||
-- pfUI will pick up the new duration through our GetDuration/UnitDebuff hooks
|
-- pfUI will pick up the new duration through our GetDuration/UnitDebuff hooks
|
||||||
if not CleveRoids.hasPfUI76 and pfUI and pfUI.api and pfUI.api.libdebuff then
|
if pfUI and pfUI.api and pfUI.api.libdebuff then
|
||||||
local pflib = pfUI.api.libdebuff
|
local pflib = pfUI.api.libdebuff
|
||||||
local ripSpellName = C_Spell.GetSpellName(ripSpellID)
|
local ripSpellName = C_Spell.GetSpellName(ripSpellID)
|
||||||
local baseName = CleveRoids.StripRank(ripSpellName) or "Rip"
|
local baseName = CleveRoids.StripRank(ripSpellName) or "Rip"
|
||||||
@@ -2998,7 +2914,7 @@ function lib.ApplyCarnageRefresh(targetGUID, targetName, biteSpellID)
|
|||||||
|
|
||||||
-- DON'T call pfUI's AddEffect - just update the existing entry directly
|
-- DON'T call pfUI's AddEffect - just update the existing entry directly
|
||||||
-- pfUI will pick up the new duration through our GetDuration/UnitDebuff hooks
|
-- pfUI will pick up the new duration through our GetDuration/UnitDebuff hooks
|
||||||
if not CleveRoids.hasPfUI76 and pfUI and pfUI.api and pfUI.api.libdebuff then
|
if pfUI and pfUI.api and pfUI.api.libdebuff then
|
||||||
local pflib = pfUI.api.libdebuff
|
local pflib = pfUI.api.libdebuff
|
||||||
local rakeSpellName = C_Spell.GetSpellName(rakeSpellID)
|
local rakeSpellName = C_Spell.GetSpellName(rakeSpellID)
|
||||||
local baseName = CleveRoids.StripRank(rakeSpellName) or "Rake"
|
local baseName = CleveRoids.StripRank(rakeSpellName) or "Rake"
|
||||||
@@ -4222,7 +4138,6 @@ if CleveRoids.hasNampower then
|
|||||||
|
|
||||||
-- BUFF_ADDED/REMOVED events require v2.30+ for auraSlot and state args
|
-- BUFF_ADDED/REMOVED events require v2.30+ for auraSlot and state args
|
||||||
if npMajor > 2 or (npMajor == 2 and npMinor >= 30) then
|
if npMajor > 2 or (npMajor == 2 and npMinor >= 30) then
|
||||||
ev:RegisterEvent("BUFF_ADDED_OTHER")
|
|
||||||
ev:RegisterEvent("BUFF_REMOVED_SELF")
|
ev:RegisterEvent("BUFF_REMOVED_SELF")
|
||||||
ev:RegisterEvent("BUFF_REMOVED_OTHER")
|
ev:RegisterEvent("BUFF_REMOVED_OTHER")
|
||||||
end
|
end
|
||||||
@@ -4581,7 +4496,7 @@ ev:SetScript("OnEvent", function()
|
|||||||
|
|
||||||
-- Update pfUI's duration database directly (pre-7.6 only)
|
-- Update pfUI's duration database directly (pre-7.6 only)
|
||||||
-- pfUI 7.6+ handles combo durations internally via GetStoredComboPoints()
|
-- pfUI 7.6+ handles combo durations internally via GetStoredComboPoints()
|
||||||
if not CleveRoids.hasPfUI76 and pfUI and pfUI.api and pfUI.api.libdebuff and pfUI.api.libdebuff.debuffs then
|
if pfUI and pfUI.api and pfUI.api.libdebuff and pfUI.api.libdebuff.debuffs then
|
||||||
pfUI.api.libdebuff.debuffs[baseName] = duration
|
pfUI.api.libdebuff.debuffs[baseName] = duration
|
||||||
if CleveRoids.debug then
|
if CleveRoids.debug then
|
||||||
DEFAULT_CHAT_FRAME:AddMessage(
|
DEFAULT_CHAT_FRAME:AddMessage(
|
||||||
@@ -4837,12 +4752,6 @@ ev:SetScript("OnEvent", function()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Sync combo duration to pfUI if it's loaded
|
|
||||||
if comboPoints and CleveRoids.Compatibility_pfUI and
|
|
||||||
CleveRoids.Compatibility_pfUI.SyncComboDurationToPfUI then
|
|
||||||
CleveRoids.Compatibility_pfUI.SyncComboDurationToPfUI(targetGUID, spellID, duration)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- ALWAYS set up learning for combo spells (even if we have calculated duration)
|
-- ALWAYS set up learning for combo spells (even if we have calculated duration)
|
||||||
if comboPoints then
|
if comboPoints then
|
||||||
lib.learnCastTimers[targetGUID] = lib.learnCastTimers[targetGUID] or {}
|
lib.learnCastTimers[targetGUID] = lib.learnCastTimers[targetGUID] or {}
|
||||||
@@ -5003,7 +4912,6 @@ ev:SetScript("OnEvent", function()
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- pfUI 7.6 manages castTracking via its own SPELL_START handler
|
-- pfUI 7.6 manages castTracking via its own SPELL_START handler
|
||||||
if lib.hasPfUI76 then return end
|
|
||||||
|
|
||||||
local spellId = arg2
|
local spellId = arg2
|
||||||
local casterGuid = arg3
|
local casterGuid = arg3
|
||||||
@@ -5050,7 +4958,6 @@ ev:SetScript("OnEvent", function()
|
|||||||
|
|
||||||
elseif event == "SPELL_FAILED_OTHER" then
|
elseif event == "SPELL_FAILED_OTHER" then
|
||||||
-- pfUI 7.6 manages castTracking cleanup itself
|
-- pfUI 7.6 manages castTracking cleanup itself
|
||||||
if lib.hasPfUI76 then return end
|
|
||||||
|
|
||||||
local casterGuid = arg1
|
local casterGuid = arg1
|
||||||
if casterGuid and CleveRoids.castTracking[casterGuid] then
|
if casterGuid and CleveRoids.castTracking[casterGuid] then
|
||||||
@@ -5069,18 +4976,23 @@ ev:SetScript("OnEvent", function()
|
|||||||
-- When pfUI is available, we use its tables directly instead.
|
-- When pfUI is available, we use its tables directly instead.
|
||||||
|
|
||||||
elseif event == "SPELL_GO_SELF" or event == "SPELL_GO_OTHER" then
|
elseif event == "SPELL_GO_SELF" or event == "SPELL_GO_OTHER" then
|
||||||
-- Skip if pfUI enhanced tracking is active (it handles this)
|
-- Clear the cast tracking entry FIRST, before the hasPfUIEnhanced bail below.
|
||||||
if lib.hasPfUIEnhanced then return end
|
-- We always own castTracking (SPELL_START populates it unconditionally), whereas
|
||||||
|
-- hasPfUIEnhanced only gates the miss/debuff work further down. Clearing used to
|
||||||
-- Clear cast tracking entry - cast completed/fired (standalone mode only).
|
-- sit after that bail, so with pfUI's enhanced libdebuff tables present
|
||||||
|
-- SPELL_START kept adding entries while this cleanup never ran and castTracking
|
||||||
|
-- grew without bound.
|
||||||
-- v2.40+: Save the SPELL_START targetGuid before clearing so we can fall back
|
-- v2.40+: Save the SPELL_START targetGuid before clearing so we can fall back
|
||||||
-- to it below when SPELL_GO arg4 is empty (e.g. AoE spells with no single target).
|
-- to it below when SPELL_GO arg4 is empty (e.g. AoE spells with no single target).
|
||||||
local startTargetGuid
|
local startTargetGuid
|
||||||
if not lib.hasPfUI76 and arg3 and CleveRoids.castTracking[arg3] then
|
if arg3 and CleveRoids.castTracking[arg3] then
|
||||||
startTargetGuid = CleveRoids.castTracking[arg3].targetGuid
|
startTargetGuid = CleveRoids.castTracking[arg3].targetGuid
|
||||||
CleveRoids.castTracking[arg3] = nil
|
CleveRoids.castTracking[arg3] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Skip the remaining miss/debuff work if pfUI enhanced tracking is active
|
||||||
|
if lib.hasPfUIEnhanced then return end
|
||||||
|
|
||||||
local spellId = arg2
|
local spellId = arg2
|
||||||
local casterGuid = arg3
|
local casterGuid = arg3
|
||||||
-- v2.40+: SPELL_GO targetGuid is now correct for friendly player GUIDs.
|
-- v2.40+: SPELL_GO targetGuid is now correct for friendly player GUIDs.
|
||||||
@@ -5519,17 +5431,11 @@ ev:SetScript("OnEvent", function()
|
|||||||
confirmed = true
|
confirmed = true
|
||||||
}
|
}
|
||||||
-- Update pfUI's duration database directly (pre-7.6 only)
|
-- Update pfUI's duration database directly (pre-7.6 only)
|
||||||
if not CleveRoids.hasPfUI76 and pfUI and pfUI.api and pfUI.api.libdebuff and pfUI.api.libdebuff.debuffs then
|
if pfUI and pfUI.api and pfUI.api.libdebuff and pfUI.api.libdebuff.debuffs then
|
||||||
pfUI.api.libdebuff.debuffs[baseName] = debuffDuration
|
pfUI.api.libdebuff.debuffs[baseName] = debuffDuration
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Sync combo duration to pfUI
|
|
||||||
if debuffComboPoints and CleveRoids.Compatibility_pfUI and
|
|
||||||
CleveRoids.Compatibility_pfUI.SyncComboDurationToPfUI then
|
|
||||||
CleveRoids.Compatibility_pfUI.SyncComboDurationToPfUI(targetGuid, spellId, debuffDuration)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Set up learning for combo spells
|
-- Set up learning for combo spells
|
||||||
if debuffComboPoints then
|
if debuffComboPoints then
|
||||||
lib.learnCastTimers[targetGuid] = lib.learnCastTimers[targetGuid] or {}
|
lib.learnCastTimers[targetGuid] = lib.learnCastTimers[targetGuid] or {}
|
||||||
@@ -5811,49 +5717,6 @@ ev:SetScript("OnEvent", function()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- NAMPOWER v2.30+ BUFF_ADDED_OTHER - Confirm pending AURA_CAST_ON_OTHER data as buff
|
|
||||||
elseif event == "BUFF_ADDED_OTHER" then
|
|
||||||
if lib.hasPfUIEnhanced then return end
|
|
||||||
|
|
||||||
local guid = CleveRoids.NormalizeGUID(arg1)
|
|
||||||
local spellId = arg3
|
|
||||||
local state = arg7 -- 0=added, 1=removed, 2=modified
|
|
||||||
|
|
||||||
if not guid or not spellId then return end
|
|
||||||
|
|
||||||
-- Consume pending AURA_CAST data for this buff
|
|
||||||
local pending = lib.pendingBuffCasts[guid] and lib.pendingBuffCasts[guid][spellId]
|
|
||||||
if pending then
|
|
||||||
local spellName = pending.spellName or (C_Spell.GetSpellName(spellId))
|
|
||||||
if spellName then
|
|
||||||
local casterGuid = pending.casterGuid
|
|
||||||
local duration = pending.duration
|
|
||||||
local now = GetTime()
|
|
||||||
|
|
||||||
-- allBuffAuras: confirmed buff from any caster
|
|
||||||
lib.allBuffAuras[guid] = lib.allBuffAuras[guid] or {}
|
|
||||||
lib.allBuffAuras[guid][spellName] = lib.allBuffAuras[guid][spellName] or {}
|
|
||||||
lib.allBuffAuras[guid][spellName][casterGuid or "unknown"] = {
|
|
||||||
startTime = now,
|
|
||||||
duration = duration or 0,
|
|
||||||
rank = 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
-- ownBuffCasts: only if player is the caster
|
|
||||||
local playerGuid = CleveRoids.GetGUID("player")
|
|
||||||
if playerGuid and casterGuid == playerGuid then
|
|
||||||
lib.ownBuffCasts[guid] = lib.ownBuffCasts[guid] or {}
|
|
||||||
lib.ownBuffCasts[guid][spellName] = {
|
|
||||||
startTime = now,
|
|
||||||
duration = duration or 0,
|
|
||||||
spellId = spellId,
|
|
||||||
casterGuid = casterGuid,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
lib.pendingBuffCasts[guid][spellId] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
-- NAMPOWER v2.30+ BUFF_REMOVED_SELF - Player's own buffs removed
|
-- NAMPOWER v2.30+ BUFF_REMOVED_SELF - Player's own buffs removed
|
||||||
elseif event == "BUFF_REMOVED_SELF" then
|
elseif event == "BUFF_REMOVED_SELF" then
|
||||||
if lib.hasPfUIEnhanced then return end
|
if lib.hasPfUIEnhanced then return end
|
||||||
@@ -5864,22 +5727,6 @@ ev:SetScript("OnEvent", function()
|
|||||||
if state == 2 then return end -- Stack decrease only, not full removal
|
if state == 2 then return end -- Stack decrease only, not full removal
|
||||||
|
|
||||||
local spellName = C_Spell.GetSpellName(spellId)
|
local spellName = C_Spell.GetSpellName(spellId)
|
||||||
local playerGuid = CleveRoids.GetGUID("player")
|
|
||||||
|
|
||||||
if spellName and playerGuid then
|
|
||||||
if lib.ownBuffCasts[playerGuid] then
|
|
||||||
lib.ownBuffCasts[playerGuid][spellName] = nil
|
|
||||||
if not next(lib.ownBuffCasts[playerGuid]) then
|
|
||||||
lib.ownBuffCasts[playerGuid] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if lib.allBuffAuras[playerGuid] then
|
|
||||||
lib.allBuffAuras[playerGuid][spellName] = nil
|
|
||||||
if not next(lib.allBuffAuras[playerGuid]) then
|
|
||||||
lib.allBuffAuras[playerGuid] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Also prune from OverflowBuffs (existing system)
|
-- Also prune from OverflowBuffs (existing system)
|
||||||
if spellId and CleveRoids.OverflowBuffs and CleveRoids.OverflowBuffs[spellId] then
|
if spellId and CleveRoids.OverflowBuffs and CleveRoids.OverflowBuffs[spellId] then
|
||||||
@@ -5905,28 +5752,6 @@ ev:SetScript("OnEvent", function()
|
|||||||
if state == 2 then return end -- Stack decrease only, not full removal
|
if state == 2 then return end -- Stack decrease only, not full removal
|
||||||
|
|
||||||
local spellName = C_Spell.GetSpellName(spellId)
|
local spellName = C_Spell.GetSpellName(spellId)
|
||||||
if spellName then
|
|
||||||
if lib.allBuffAuras[guid] then
|
|
||||||
lib.allBuffAuras[guid][spellName] = nil
|
|
||||||
if not next(lib.allBuffAuras[guid]) then
|
|
||||||
lib.allBuffAuras[guid] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if lib.ownBuffCasts[guid] then
|
|
||||||
lib.ownBuffCasts[guid][spellName] = nil
|
|
||||||
if not next(lib.ownBuffCasts[guid]) then
|
|
||||||
lib.ownBuffCasts[guid] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Clean pending correlation data
|
|
||||||
if lib.pendingBuffCasts[guid] then
|
|
||||||
lib.pendingBuffCasts[guid][spellId] = nil
|
|
||||||
if not next(lib.pendingBuffCasts[guid]) then
|
|
||||||
lib.pendingBuffCasts[guid] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Also clean AllCasterAuraTracking (keyed by spellName)
|
-- Also clean AllCasterAuraTracking (keyed by spellName)
|
||||||
if spellName and CleveRoids.AllCasterAuraTracking[guid] then
|
if spellName and CleveRoids.AllCasterAuraTracking[guid] then
|
||||||
@@ -5978,18 +5803,9 @@ ev:SetScript("OnEvent", function()
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Clean up buff tracking tables
|
-- Clean up buff tracking tables
|
||||||
if lib.ownBuffCasts[guid] then
|
|
||||||
lib.ownBuffCasts[guid] = nil
|
|
||||||
end
|
|
||||||
if lib.allBuffAuras[guid] then
|
|
||||||
lib.allBuffAuras[guid] = nil
|
|
||||||
end
|
|
||||||
if lib.pendingBuffCasts[guid] then
|
|
||||||
lib.pendingBuffCasts[guid] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Clean up cast tracking for this unit (they can't be casting if dead)
|
-- Clean up cast tracking for this unit (they can't be casting if dead)
|
||||||
if not lib.hasPfUI76 and CleveRoids.castTracking[guid] then
|
if CleveRoids.castTracking[guid] then
|
||||||
CleveRoids.castTracking[guid] = nil
|
CleveRoids.castTracking[guid] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -6354,7 +6170,7 @@ evLearn:SetScript("OnEvent", function()
|
|||||||
|
|
||||||
-- Sync refresh to pfUI
|
-- Sync refresh to pfUI
|
||||||
local targetName = lib.guidToName[targetGUID]
|
local targetName = lib.guidToName[targetGUID]
|
||||||
if not CleveRoids.hasPfUI76 and pfUI and pfUI.api and pfUI.api.libdebuff and targetName then
|
if pfUI and pfUI.api and pfUI.api.libdebuff and targetName then
|
||||||
local pflib = pfUI.api.libdebuff
|
local pflib = pfUI.api.libdebuff
|
||||||
local spellName = C_Spell.GetSpellName(flameShockID)
|
local spellName = C_Spell.GetSpellName(flameShockID)
|
||||||
if spellName and pflib.AddEffect then
|
if spellName and pflib.AddEffect then
|
||||||
@@ -6406,15 +6222,7 @@ evCleanup:SetScript("OnEvent", function()
|
|||||||
if event == "PLAYER_ENTERING_WORLD" or event == "PLAYER_DEAD" then
|
if event == "PLAYER_ENTERING_WORLD" or event == "PLAYER_DEAD" then
|
||||||
-- Keep only current target's data
|
-- Keep only current target's data
|
||||||
local currentGUID = CleveRoids.GetGUID("target")
|
local currentGUID = CleveRoids.GetGUID("target")
|
||||||
if lib.hasPfUI76 then
|
if currentGUID then
|
||||||
-- pfUI76: lib.objects is pfUI.libdebuff_objects_guid - never replace the reference!
|
|
||||||
-- pfUI handles its own cleanup; just clear SCRM-side entries
|
|
||||||
for guid in pairs(lib.objects) do
|
|
||||||
if guid ~= currentGUID then
|
|
||||||
lib.objects[guid] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elseif currentGUID then
|
|
||||||
local temp = lib.objects[currentGUID]
|
local temp = lib.objects[currentGUID]
|
||||||
lib.objects = {}
|
lib.objects = {}
|
||||||
if temp then
|
if temp then
|
||||||
@@ -6531,7 +6339,7 @@ evJudgement:SetScript("OnEvent", function()
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Also sync to pfUI if it's loaded (pre-7.6 only)
|
-- Also sync to pfUI if it's loaded (pre-7.6 only)
|
||||||
if not CleveRoids.hasPfUI76 and pfUI and pfUI.api and pfUI.api.libdebuff then
|
if pfUI and pfUI.api and pfUI.api.libdebuff then
|
||||||
local targetName = lib.guidToName[targetGUID] or UnitName("target")
|
local targetName = lib.guidToName[targetGUID] or UnitName("target")
|
||||||
local targetLevel = UnitLevel("target") or 0
|
local targetLevel = UnitLevel("target") or 0
|
||||||
local spellName = C_Spell.GetSpellName(spellID)
|
local spellName = C_Spell.GetSpellName(spellID)
|
||||||
@@ -7105,10 +6913,7 @@ function CleveRoids.ApplySetBonusModifier(spellID, baseDuration)
|
|||||||
local modifiedDuration = modifier.modifier(baseDuration)
|
local modifiedDuration = modifier.modifier(baseDuration)
|
||||||
|
|
||||||
if modifiedDuration ~= baseDuration and CleveRoids.debug then
|
if modifiedDuration ~= baseDuration and CleveRoids.debug then
|
||||||
local spellName = "Unknown"
|
local spellName = C_Spell.GetSpellName(spellID) or "Unknown"
|
||||||
if GetSpellRecField then
|
|
||||||
spellName = C_Spell.GetSpellName(spellID) or spellName
|
|
||||||
end
|
|
||||||
DEFAULT_CHAT_FRAME:AddMessage(
|
DEFAULT_CHAT_FRAME:AddMessage(
|
||||||
string.format("|cff00ffff[Set Bonus Modifier]|r %s (ID:%d): %.1fs -> %.1fs (%d/%d pieces)",
|
string.format("|cff00ffff[Set Bonus Modifier]|r %s (ID:%d): %.1fs -> %.1fs (%d/%d pieces)",
|
||||||
spellName, spellID, baseDuration, modifiedDuration,
|
spellName, spellID, baseDuration, modifiedDuration,
|
||||||
@@ -7585,7 +7390,7 @@ local function GetSpellSchool(spellName, spellID)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- If we only have spellID but no name, try to get name from GetSpellRecField
|
-- If we only have spellID but no name, try to get name from GetSpellRecField
|
||||||
if spellID and not spellName and GetSpellRecField then
|
if spellID and not spellName then
|
||||||
spellName = C_Spell.GetSpellName(spellID)
|
spellName = C_Spell.GetSpellName(spellID)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -9244,52 +9049,20 @@ function CleveRoids.ParseReactiveCombatLog(lowerMsg)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Clear reactive proc when spell is cast
|
-- Removed: ClearReactiveProcOnCast plus wrappers around Frame.UNIT_CASTEVENT and
|
||||||
function CleveRoids.ClearReactiveProcOnCast(spellName)
|
-- Frame.SPELL_START_SELF that tried to clear a reactive proc when its spell was cast.
|
||||||
if not spellName then return end
|
-- The feature never worked, for three independent reasons:
|
||||||
|
-- 1. Both wrappers guarded on `CleveRoids.Frame and CleveRoids.Frame.<handler>`,
|
||||||
-- Check if this is a reactive spell
|
-- but CleveRoids.Frame is created in Core.lua, which loads AFTER this file, so
|
||||||
if CleveRoids.reactiveSpells and CleveRoids.reactiveSpells[spellName] then
|
-- the guard was always false and neither wrapper was ever installed.
|
||||||
CleveRoids.ClearReactiveProc(spellName)
|
-- 2. The UNIT_CASTEVENT wrapper tested arg2 == "START", but that signature is
|
||||||
CleveRoids.QueueActionUpdate()
|
-- (caster, target, action, spell_id, cast_time) -- the action is arg3, so it was
|
||||||
end
|
-- comparing the target.
|
||||||
end
|
-- 3. Both passed a spell *ID* to a function that looks the name up in
|
||||||
|
-- CleveRoids.reactiveSpells, which is keyed by name, so nothing could match.
|
||||||
-- Hook UNIT_CASTEVENT to clear reactive procs
|
-- They also each allocated an `arg` table per call and unpack()'d it. Deleting is
|
||||||
local originalUnitCastEvent = CleveRoids.Frame and CleveRoids.Frame.UNIT_CASTEVENT
|
-- behaviour-preserving; if clearing a reactive proc on cast is still wanted it needs
|
||||||
if originalUnitCastEvent then
|
-- to be written fresh against the real handler signatures.
|
||||||
CleveRoids.Frame.UNIT_CASTEVENT = function(...)
|
|
||||||
-- Call original handler first
|
|
||||||
if type(originalUnitCastEvent) == "function" then
|
|
||||||
originalUnitCastEvent(unpack(arg))
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Clear reactive proc and resist state on spell cast start
|
|
||||||
if arg1 == "player" and arg2 == "START" and arg4 then
|
|
||||||
CleveRoids.ClearReactiveProcOnCast(arg4)
|
|
||||||
CleveRoids.ClearResistState()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Hook SPELL_START_SELF to clear reactive procs (Nampower fallback when SuperWoW not available)
|
|
||||||
local originalSpellStartSelf = CleveRoids.Frame and CleveRoids.Frame.SPELL_START_SELF
|
|
||||||
if originalSpellStartSelf and not CleveRoids.hasSuperwow then
|
|
||||||
CleveRoids.Frame.SPELL_START_SELF = function(...)
|
|
||||||
-- Call original handler first
|
|
||||||
if type(originalSpellStartSelf) == "function" then
|
|
||||||
originalSpellStartSelf(unpack(arg))
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Clear reactive proc and resist state on spell cast start
|
|
||||||
-- SPELL_START_SELF args: casterGuid, targetGuid, spellId, ...
|
|
||||||
local spellId = arg[3]
|
|
||||||
if spellId then
|
|
||||||
CleveRoids.ClearReactiveProcOnCast(spellId)
|
|
||||||
CleveRoids.ClearResistState()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- NAMPOWER v2.24+ AUTO_ATTACK EVENT HANDLER FOR REACTIVE ABILITIES
|
-- NAMPOWER v2.24+ AUTO_ATTACK EVENT HANDLER FOR REACTIVE ABILITIES
|
||||||
-- Uses native events for dodge/parry/block detection when available.
|
-- Uses native events for dodge/parry/block detection when available.
|
||||||
|
|||||||
+57
-9
@@ -25,10 +25,23 @@ API references are line numbers into `C:\Git\ClassicAPI\docs\API.md`.
|
|||||||
`applications` (stacks), `duration`.
|
`applications` (stacks), `duration`.
|
||||||
- **Unlocks:** `[dispellable]` / `[curse]` / `[magic]` target conditionals and
|
- **Unlocks:** `[dispellable]` / `[curse]` / `[magic]` target conditionals and
|
||||||
spellID-based (rank/locale-proof) aura matching.
|
spellID-based (rank/locale-proof) aura matching.
|
||||||
- **Caveat:** `expirationTime` is only populated for `unit=="player"`; it's `0`
|
- **~~Caveat: `expirationTime` is player-only~~ — NO LONGER TRUE.** This note said
|
||||||
for target/focus (vanilla server limitation). Target debuff *timers* still
|
target/focus `expirationTime` was always `0`, so debuff *timers* had to stay on
|
||||||
need the existing libdebuff tracking — only presence/stacks/school/spellId
|
libdebuff. ClassicAPI has since added the `Aura::Source` cache: for a non-player
|
||||||
are reliable cross-unit.
|
unit `expirationTime` is reconstructed from the observed `SMSG_SPELL_GO`, and
|
||||||
|
`duration` is the **caster-modified** value (talent extensions like Improved
|
||||||
|
Shadow Word: Pain included). It also handles combo-point finisher scaling
|
||||||
|
automatically (Rupture at 4 CP reads 14s, no registration) and ships Carnage's
|
||||||
|
roll-gated Rip/Rake refresh in the DLL (`src/turtle/Carnage.cpp`, exposed as
|
||||||
|
`RegisterAuraDurationModifierByTrigger`).
|
||||||
|
- **Real remaining caveats** (all best-effort, from `docs/API.md`):
|
||||||
|
- Only auras observed *after login* carry caster/timing; older ones report
|
||||||
|
`expirationTime` 0 and `sourceUnit` nil.
|
||||||
|
- Max-stack refresh is a blind spot: re-applying at max stacks (Shadow Weaving
|
||||||
|
5→5) emits no client-visible change, so the entry elapses and evicts.
|
||||||
|
- Out-of-range group members are spell-ID only, with `applications` always 1.
|
||||||
|
- **Consequence:** most of libdebuff is now redundant — see
|
||||||
|
"libdebuff retirement" below.
|
||||||
|
|
||||||
- **DONE (slice 1 — dispel-type conditionals):** added `ClassicAPI.lua`
|
- **DONE (slice 1 — dispel-type conditionals):** added `ClassicAPI.lua`
|
||||||
detection module (`CleveRoids.ClassicAPI`, mirrors NampowerAPI's
|
detection module (`CleveRoids.ClassicAPI`, mirrors NampowerAPI's
|
||||||
@@ -150,11 +163,13 @@ Findings from the full Conditionals.lua audit. These look like ClassicAPI
|
|||||||
candidates but are **worse** than the current implementation — recorded so we
|
candidates but are **worse** than the current implementation — recorded so we
|
||||||
don't re-investigate.
|
don't re-investigate.
|
||||||
|
|
||||||
- **Auras / `ValidateAura`** — `C_UnitAuras.expirationTime` is **player-only**
|
- ~~**Auras / `ValidateAura`** — `C_UnitAuras.expirationTime` is player-only~~ —
|
||||||
(`0` for every other unit). It cannot replace the nampower `GetUnitField`
|
**STALE, do not trust this entry.** It was written before the `Aura::Source`
|
||||||
batch read or the remote-duration tracking (libdebuff / overflow slots). The
|
cache landed. Non-player `expirationTime`/`duration` now work (caster-modified,
|
||||||
dispel-*type* path already uses `C_UnitAuras` (that's the one thing it's good
|
talent extensions included), so this is no longer a reason to keep libdebuff's
|
||||||
for); aura *timing/stacks* on non-player units must stay on nampower/libdebuff.
|
remote-duration tracking. See Tier 1 §1 and "libdebuff retirement" for the
|
||||||
|
current picture and the real caveats. The nampower `GetUnitField` batch read is
|
||||||
|
a separate question and has not been re-examined.
|
||||||
- **`GetCurrentShapeshiftIndex` form loop / `[stance]`·`[form]`** —
|
- **`GetCurrentShapeshiftIndex` form loop / `[stance]`·`[form]`** —
|
||||||
`GetShapeshiftFormID()` returns the **DBC form id** (Cat=1, Bear=5,
|
`GetShapeshiftFormID()` returns the **DBC form id** (Cat=1, Bear=5,
|
||||||
Shadowform=28…), NOT the 1-based **bar index** these conditionals compare
|
Shadowform=28…), NOT the 1-based **bar index** these conditionals compare
|
||||||
@@ -183,6 +198,39 @@ don't re-investigate.
|
|||||||
added `[focus]`/`[nofocus]`. `/focus` is provided by ClassicAPI's companion
|
added `[focus]`/`[nofocus]`. `/focus` is provided by ClassicAPI's companion
|
||||||
addon.
|
addon.
|
||||||
|
|
||||||
|
## libdebuff retirement
|
||||||
|
|
||||||
|
libdebuff exists because vanilla cannot report debuff durations on units other
|
||||||
|
than the player. ClassicAPI's `Aura::Source` cache now does exactly that, so most
|
||||||
|
of the library is redundant. This is a staged replacement, not a delete: measure
|
||||||
|
first, then remove per group.
|
||||||
|
|
||||||
|
Current footprint: 28 public `lib:` methods, ~726 internal references in
|
||||||
|
`Utility.lua`, consumers in 8 files (`Conditionals.lua` 45, `Compatibility/pfUI.lua`
|
||||||
|
39, `Core.lua` 9). Note pfUI did **not** delete its own libdebuff — v9.0.25 still
|
||||||
|
ships ~1695 lines of it, re-based on `C_UnitAuras.GetAuraDataByIndex` /
|
||||||
|
`GetAuraDataBySpellName`. "Re-base on C_UnitAuras", not "remove", is the precedent.
|
||||||
|
|
||||||
|
**Group A — replaceable by `C_UnitAuras` (do these first):**
|
||||||
|
`GetDuration`, `GetDebuffCaster`, `IsOurDebuff`, `UnitBuff`/`UnitDebuff`,
|
||||||
|
`FindPlayerDebuff`/`FindPlayerBuff`, `GetAllDebuffsOnTarget`, `GetCachedIcon`,
|
||||||
|
`ApplyCarnageRefresh`, and the Dark Harvest trio (`ApplyDarkHarvestStart`/`End`,
|
||||||
|
`GetDarkHarvestReduction`, `GetTimeRemainingWithDarkHarvest`) — ClassicAPI ships
|
||||||
|
Carnage refresh and Dark Harvest tick compression in the DLL.
|
||||||
|
|
||||||
|
**Group B — no `C_UnitAuras` equivalent, keep:**
|
||||||
|
`ShouldApplyDebuffRank`, `DidSpellFail`, `WasSpellReflected`, `DidTargetEvade`,
|
||||||
|
`ProcessMissReason`, `IsPersonalDebuff`, `GetSpellRank`/`GetSpellBaseName`,
|
||||||
|
`HasPendingCast`. These are miss/rank/learning logic, not aura state.
|
||||||
|
|
||||||
|
**Gate before removing Group A:** confirm parity in-game on one spell where
|
||||||
|
ClassicAPI does the hard part — Rip under Carnage. Compare
|
||||||
|
`CleveRoids.libdebuff:GetDuration(spellID)` against
|
||||||
|
`C_UnitAuras.GetUnitAuraBySpellID(unit, spellID).duration` and the derived
|
||||||
|
remaining (`expirationTime - GetTime()`), on a target you have debuffed. If those
|
||||||
|
agree across a Carnage proc, Group A can go. Watch the documented best-effort
|
||||||
|
gaps: an aura cast before you logged in, and refresh-at-max-stacks.
|
||||||
|
|
||||||
## Marginal / optional follow-ups
|
## Marginal / optional follow-ups
|
||||||
|
|
||||||
- **`[swimming]`** → `IsSwimming()` would drop the nampower-2.36 version gate +
|
- **`[swimming]`** → `IsSwimming()` would drop the nampower-2.36 version gate +
|
||||||
|
|||||||
Reference in New Issue
Block a user