diff --git a/Conditionals.lua b/Conditionals.lua index f053a89..dbab3c9 100644 --- a/Conditionals.lua +++ b/Conditionals.lua @@ -2664,25 +2664,18 @@ function CleveRoids.IsPlayerMoving() local headIdx = CleveRoids._posHistoryIndex or 0 if history and count >= 2 then - -- Iterate consecutive pairs in circular buffer order (oldest to newest) - -- Head is the most recent entry, walk backwards through the ring - local samplesAvail = count - for i = 1, samplesAvail - 1 do - -- prevIdx and currIdx walk from oldest to newest - local currOffset = samplesAvail - i -- e.g., 3,2,1,0 for count=4 - local prevOffset = samplesAvail - i + 1 -- e.g., 4,3,2,1 - local currIdx = math.mod((headIdx - currOffset - 1 + 4), 4) + 1 - local prevIdx = math.mod((headIdx - prevOffset - 1 + 4), 4) + 1 - local prev = history[prevIdx] - local curr = history[currIdx] + -- Compare the most recent pair: headIdx (newest) vs. the slot before it. + -- Use (headIdx - 2 + 4) mod 4 to safely wrap backwards in the 1-based ring. + -- This avoids Lua 5.0 math.mod returning negative values for negative inputs. + local prevIdx = math.mod((headIdx - 2 + 4), 4) + 1 + local curr = history[headIdx] + local prev = history[prevIdx] + if curr and prev then local dx = curr.x - prev.x local dy = curr.y - prev.y - local dist = math.sqrt(dx * dx + dy * dy) - if dist > 0.001 then - return true -- Any movement in recent history = moving - end + return math.sqrt(dx * dx + dy * dy) > 0.001 end - return false -- No movement in any recent sample = stopped + return false end -- Legacy fallback for code that might not have history yet diff --git a/Core.lua b/Core.lua index ba19a77..c303348 100644 --- a/Core.lua +++ b/Core.lua @@ -5208,6 +5208,18 @@ function CleveRoids.Frame:SPELLCAST_CHANNEL_START() end end + -- v2.38+: Override channelDuration with the accurate server-provided value stored by + -- SPELL_START_SELF (which fires before this event). This beats the tooltip scan or + -- UNIT_CASTEVENT cast_time fallback, because the server already applied all modifiers. + if CleveRoids._v238ChannelDuration then + if not spellId or not CleveRoids._v238ChannelSpellId + or CleveRoids._v238ChannelSpellId == spellId then + CleveRoids.channelDuration = CleveRoids._v238ChannelDuration + end + CleveRoids._v238ChannelDuration = nil + CleveRoids._v238ChannelSpellId = nil + end + -- Force immediate action update CleveRoids.TestForAllActiveActions() end diff --git a/NampowerAPI.lua b/NampowerAPI.lua index 9b178fc..6c2a7b1 100644 --- a/NampowerAPI.lua +++ b/NampowerAPI.lua @@ -109,7 +109,19 @@ - SetMouseoverUnit(unitToken) - Programmatically set the mouseover unit. Previously only available via SuperWoW; now also provided by Nampower. - Current version: v2.37.0 + Spell Duration & Enhanced SPELL_START (v2.38+): + - GetSpellDuration(spellId, [ignoreModifiers]) - Returns duration in ms. + For channeling spells: channel duration. For others: first aura effect duration. + Pass ignoreModifiers=1 to get base duration (ignores talents/buffs). + Returns 0 if spell has no duration, nil if spellId invalid. + - SPELL_START_SELF / SPELL_START_OTHER now include two new parameters: + arg7 = duration (ms) - channel duration for channeling spells, 0 otherwise + arg8 = spellType - 0=Normal, 1=Channeling, 2=Autorepeating + Use spellType to distinguish channeling spells (SPELL_START_OTHER carries both + cast-time and channeling spells since there is no separate SPELL_CHANNEL_START + packet for other players). + + Current version: v2.38.0 ]] local _G = _G or getfenv(0) @@ -283,6 +295,10 @@ API.VERSION_REQUIREMENTS = { -- v2.37+ - Unit token cast support and SetMouseoverUnit ["CastSpellByNameUnitToken"]= { 2, 37, 0 }, -- CastSpellByName accepts unit token strings as 2nd param ["SetMouseoverUnit"] = { 2, 37, 0, "SetMouseoverUnit" }, + + -- v2.38+ - GetSpellDuration and enhanced SPELL_START parameters + ["GetSpellDuration"] = { 2, 38, 0, "GetSpellDuration" }, + ["SpellStartSpellType"] = { 2, 38, 0 }, -- duration + spellType params added to SPELL_START events } -- Check if a specific feature is available @@ -440,6 +456,10 @@ local function InitializeFeatures() f.hasCastSpellByNameUnitToken = API.HasFeature("CastSpellByNameUnitToken") f.hasSetMouseoverUnit = API.HasFeature("SetMouseoverUnit") + -- v2.38+ GetSpellDuration and enhanced SPELL_START parameters + f.hasGetSpellDuration = API.HasFeature("GetSpellDuration") + f.hasSpellStartSpellType = API.HasFeature("SpellStartSpellType") + -- Runtime detection for enhanced spell functions (verify by testing) if f.hasEnhancedSpellFunctions and GetSpellTexture then local success, result = pcall(function() @@ -3040,6 +3060,19 @@ function API.GetSpellPower(mode) return _G.GetSpellPower(mode) end +-- Get duration of a spell in milliseconds (v2.38+) +-- For channeling spells: returns the channel duration. +-- For non-channeling spells: returns the first aura effect duration. +-- spellId: spell ID to look up +-- ignoreModifiers: pass 1 to ignore talent/buff modifiers (returns base duration) +-- Returns: duration in ms (0 if spell has no duration), or nil if spellId invalid +function API.GetSpellDuration(spellId, ignoreModifiers) + if not API.features.hasGetSpellDuration or not _G.GetSpellDuration then + return nil + end + return _G.GetSpellDuration(spellId, ignoreModifiers) +end + -------------------------------------------------------------------------------- -- AURA CANCEL FUNCTIONS (v2.34+) -------------------------------------------------------------------------------- diff --git a/Utility.lua b/Utility.lua index 0e7001c..37575c1 100644 --- a/Utility.lua +++ b/Utility.lua @@ -754,12 +754,12 @@ function lib:HasPfUI76() 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.37.0+ (pfUI 7.6+ hard requirement) + -- Verify Nampower v2.38.0+ (pfUI 7.6+ hard requirement, bumped from 2.37 in pfUI update 2026-02-18) 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 < 37 then return false end + if npMajor == 2 and npMinor < 38 then return false end -- Verify the new tables exist if not pfUI.libdebuff_casts then return false end @@ -4450,19 +4450,38 @@ ev:SetScript("OnEvent", function() -- and these handlers return early (pfUI handles it). elseif event == "SPELL_START_SELF" or event == "SPELL_START_OTHER" then + -- v2.38+: For player's own channeling spells, pre-store the accurate server channel duration + -- BEFORE the pfUI early-return so SPELLCAST_CHANNEL_START (which fires later) can use it. + -- arg8 == 1 means spellType=Channeling; arg7 is the channel duration in ms. + if event == "SPELL_START_SELF" and arg8 == 1 and arg7 and arg7 > 0 then + CleveRoids._v238ChannelDuration = arg7 / 1000 + CleveRoids._v238ChannelSpellId = arg2 + end -- pfUI 7.6 manages castTracking via its own SPELL_START handler if lib.hasPfUI76 then return end local spellId = arg2 local casterGuid = arg3 - local castTime = arg6 -- milliseconds + local castTime = arg6 -- cast time in milliseconds + -- v2.38+: arg7 = channel duration (ms, channeling spells only), arg8 = spellType (0=Normal,1=Channeling,2=Autorepeating) + local channelDuration = arg7 + local spellType = arg8 if not casterGuid or not spellId then return end local spellName = SpellInfo and SpellInfo(spellId) local icon = lib:GetCachedIcon(spellId) local now = GetTime() - local durationSec = castTime and (castTime / 1000) or 0 + + -- For channeling spells (v2.38+: spellType==1), use the channel duration. + -- For normal cast-time spells, use castTime. Fall back to castTime if params unavailable. + local durationMs + if spellType == 1 and channelDuration and channelDuration > 0 then + durationMs = channelDuration + else + durationMs = castTime or 0 + end + local durationSec = durationMs / 1000 CleveRoids.castTracking[casterGuid] = { spellID = spellId, @@ -4474,9 +4493,10 @@ ev:SetScript("OnEvent", function() } if CleveRoids.debug then + local typeStr = spellType == 1 and "channel" or (spellType == 2 and "auto" or "cast") DEFAULT_CHAT_FRAME:AddMessage( - string.format("|cff00ccff[SPELL_START]|r %s (ID:%d) cast by %s - %.1fs", - spellName or "Unknown", spellId, casterGuid, durationSec) + string.format("|cff00ccff[SPELL_START]|r %s (ID:%d) cast by %s - %.1fs [%s]", + spellName or "Unknown", spellId, casterGuid, durationSec, typeStr) ) end