mirror of
https://github.com/brues-code/pfUI.git
synced 2026-10-06 21:50:00 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
deeec89955 | ||
|
|
cca9771ef0 | ||
|
|
371872bb90 | ||
|
|
591045a606 | ||
|
|
f657a9e7e8 | ||
|
|
0cbd9ecf98 | ||
|
|
6ca482fcaa | ||
|
|
8902d43f40 | ||
|
|
8f006c0643 |
@@ -4,16 +4,18 @@ about: Report a bug or issue with pfUI
|
|||||||
title: "[Bug Description] "
|
title: "[Bug Description] "
|
||||||
labels: bug
|
labels: bug
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<!-- Please fill out ALL fields below. Issues without this info may be closed. -->
|
<!-- Please fill out ALL fields below. Issues without this info may be closed. -->
|
||||||
|
|
||||||
**pfUI Branch:**
|
**pfUI Version:**
|
||||||
- [ ] Master
|
|
||||||
- [ ] experiment
|
**Your ClassicAPI Version:**
|
||||||
|
|
||||||
|
**Your Nampower Version:**
|
||||||
|
|
||||||
|
|
||||||
**Your Nampower Version:** (e.g. latest, or specific commit/date)
|
|
||||||
<!-- Skip if using Compat branch -->
|
|
||||||
|
|
||||||
**Other Addons installed:**
|
**Other Addons installed:**
|
||||||
<!-- List any other addons you are using, or "none" -->
|
<!-- List any other addons you are using, or "none" -->
|
||||||
@@ -45,4 +47,3 @@ Paste error log here
|
|||||||
## Screenshot / Video
|
## Screenshot / Video
|
||||||
<!-- Attach a screenshot or video showing the issue. This is REQUIRED. -->
|
<!-- Attach a screenshot or video showing the issue. This is REQUIRED. -->
|
||||||
<!-- Drag & drop images here or paste a link -->
|
<!-- Drag & drop images here or paste a link -->
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ about: Suggest a new feature or improvement for pfUI
|
|||||||
title: "[Feature] "
|
title: "[Feature] "
|
||||||
labels: enhancement
|
labels: enhancement
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Feature Description
|
## Feature Description
|
||||||
@@ -20,4 +21,3 @@ assignees: ''
|
|||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
<!-- If possible, show examples from other addons or screenshots/mockups of how it could look -->
|
<!-- If possible, show examples from other addons or screenshots/mockups of how it could look -->
|
||||||
|
|
||||||
|
|||||||
+13
-1
@@ -1387,5 +1387,17 @@ function pfUI:MigrateConfig()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
pfUI_config.version = pfUI.version.string
|
-- Stamp the version we've migrated to. Dev / git-cloned builds carry
|
||||||
|
-- "@project-version@" in the toc, which pfUI.lua normalizes to "dev" —
|
||||||
|
-- writing that here would have checkversion() parse it back as 0/0/0 on
|
||||||
|
-- the next /reload and re-fire every migration block, clobbering user
|
||||||
|
-- toggles (notably the >3.6.1 buff migration that rewrites
|
||||||
|
-- buffs.{buffs,debuffs,weapons} from the deprecated global.hide{,w}buff
|
||||||
|
-- knobs). Stamp a far-future sentinel instead so checkversion() can't
|
||||||
|
-- flip true again on subsequent loads.
|
||||||
|
if pfUI.version.string == "dev" then
|
||||||
|
pfUI_config.version = "999.999.999"
|
||||||
|
else
|
||||||
|
pfUI_config.version = pfUI.version.string
|
||||||
|
end
|
||||||
end
|
end
|
||||||
@@ -2838,8 +2838,6 @@ function pfUI.uf:GetStatusValue(unit, pos)
|
|||||||
-- Use libhealth for mob health estimation (overrides Nampower/Standard)
|
-- Use libhealth for mob health estimation (overrides Nampower/Standard)
|
||||||
if pfUI.libhealth and pfUI.libhealth.enabled then
|
if pfUI.libhealth and pfUI.libhealth.enabled then
|
||||||
rhp, rhpmax = pfUI.libhealth:GetUnitHealth(unitstr)
|
rhp, rhpmax = pfUI.libhealth:GetUnitHealth(unitstr)
|
||||||
elseif unit.label == "target" and (MobHealth3 or MobHealthFrame) and MobHealth_GetTargetCurHP() then
|
|
||||||
rhp, rhpmax = MobHealth_GetTargetCurHP(), MobHealth_GetTargetMaxHP()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if config == "unit" then
|
if config == "unit" then
|
||||||
|
|||||||
+13
-13
@@ -427,7 +427,7 @@ local function GetDebuffSlotMap(guid)
|
|||||||
local spellId = auras[auraSlot]
|
local spellId = auras[auraSlot]
|
||||||
if spellId and spellId > 0 then
|
if spellId and spellId > 0 then
|
||||||
displaySlot = displaySlot + 1
|
displaySlot = displaySlot + 1
|
||||||
local spellName = GetSpellRecField and GetSpellRecField(spellId, "name")
|
local spellName = C_Spell.GetSpellName(spellId)
|
||||||
local texture = libdebuff:GetSpellIcon(spellId)
|
local texture = libdebuff:GetSpellIcon(spellId)
|
||||||
local stacks = (auraApps and auraApps[auraSlot] or 0) + 1
|
local stacks = (auraApps and auraApps[auraSlot] or 0) + 1
|
||||||
local dtype = nil
|
local dtype = nil
|
||||||
@@ -1024,7 +1024,7 @@ if hasNampower then
|
|||||||
local targetGuid = arg4
|
local targetGuid = arg4
|
||||||
local numHit = arg6 or 0
|
local numHit = arg6 or 0
|
||||||
local numMissed = arg7 or 0
|
local numMissed = arg7 or 0
|
||||||
|
|
||||||
-- Fire registered SPELL_GO_SELF hooks BEFORE miss guard
|
-- Fire registered SPELL_GO_SELF hooks BEFORE miss guard
|
||||||
-- (Swingtimer needs to see ALL casts, even misses, for swing reset)
|
-- (Swingtimer needs to see ALL casts, even misses, for swing reset)
|
||||||
if event == "SPELL_GO_SELF" and pfUI.libdebuff_spell_go_hooks then
|
if event == "SPELL_GO_SELF" and pfUI.libdebuff_spell_go_hooks then
|
||||||
@@ -1035,15 +1035,15 @@ if hasNampower then
|
|||||||
|
|
||||||
if numMissed > 0 or numHit == 0 then return end
|
if numMissed > 0 or numHit == 0 then return end
|
||||||
|
|
||||||
local spellName = GetSpellRecField and GetSpellRecField(spellId, "name")
|
local spellName = C_Spell.GetSpellName(spellId)
|
||||||
local spellRankString = GetSpellRecField and GetSpellRecField(spellId, "rank")
|
|
||||||
if not spellName then return end
|
if not spellName then return end
|
||||||
|
local spellRankString = C_Spell.GetSpellSubtext(spellId)
|
||||||
|
|
||||||
local castRank = 0
|
local castRank = 0
|
||||||
if spellRankString and spellRankString ~= "" then
|
if spellRankString and spellRankString ~= "" then
|
||||||
castRank = tonumber((string.gsub(spellRankString, "Rank ", ""))) or 0
|
castRank = tonumber((string.gsub(spellRankString, "Rank ", ""))) or 0
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Store in pendingCasts for DEBUFF_ADDED correlation.
|
-- Store in pendingCasts for DEBUFF_ADDED correlation.
|
||||||
-- If this cast is a downrank of an already active debuff, fire the downrank blocked hook
|
-- If this cast is a downrank of an already active debuff, fire the downrank blocked hook
|
||||||
-- so external addons (e.g. SuperCleveRoidMacros) don't need to re-implement this check.
|
-- so external addons (e.g. SuperCleveRoidMacros) don't need to re-implement this check.
|
||||||
@@ -1209,7 +1209,7 @@ if hasNampower then
|
|||||||
if not spellId then return end
|
if not spellId then return end
|
||||||
if not targetGuid or targetGuid == "" or targetGuid == "0x0000000000000000" then return end
|
if not targetGuid or targetGuid == "" or targetGuid == "0x0000000000000000" then return end
|
||||||
|
|
||||||
local spellName = GetSpellRecField and GetSpellRecField(spellId, "name")
|
local spellName = C_Spell.GetSpellName(spellId)
|
||||||
if not spellName then return end
|
if not spellName then return end
|
||||||
|
|
||||||
-- Deduplicate: Ignore if we processed this exact cast recently (within 100ms)
|
-- Deduplicate: Ignore if we processed this exact cast recently (within 100ms)
|
||||||
@@ -1469,7 +1469,7 @@ if hasNampower then
|
|||||||
|
|
||||||
-- Invalidate slot map cache for this GUID
|
-- Invalidate slot map cache for this GUID
|
||||||
|
|
||||||
local spellName = GetSpellRecField and GetSpellRecField(spellId, "name")
|
local spellName = C_Spell.GetSpellName(spellId)
|
||||||
if not spellName then return end
|
if not spellName then return end
|
||||||
|
|
||||||
if debugStats.enabled then
|
if debugStats.enabled then
|
||||||
@@ -1599,9 +1599,9 @@ if hasNampower then
|
|||||||
local auraSlot = auraSlot_0based and (auraSlot_0based + 1) or nil
|
local auraSlot = auraSlot_0based and (auraSlot_0based + 1) or nil
|
||||||
|
|
||||||
-- Invalidate slot map cache for this GUID
|
-- Invalidate slot map cache for this GUID
|
||||||
|
|
||||||
local spellName = (GetSpellRecField and GetSpellRecField(spellId, "name")) or "?"
|
local spellName = C_Spell.GetSpellName(spellId) or "?"
|
||||||
|
|
||||||
if debugStats.enabled then
|
if debugStats.enabled then
|
||||||
debugStats.debuff_removed = debugStats.debuff_removed + 1
|
debugStats.debuff_removed = debugStats.debuff_removed + 1
|
||||||
if IsCurrentTarget(guid) then
|
if IsCurrentTarget(guid) then
|
||||||
@@ -1609,13 +1609,13 @@ if hasNampower then
|
|||||||
GetDebugTimestamp(), displaySlot, auraSlot or -1, auraSlot_0based or -1, spellName))
|
GetDebugTimestamp(), displaySlot, auraSlot or -1, auraSlot_0based or -1, spellName))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- If unit is dead, cleanup all
|
-- If unit is dead, cleanup all
|
||||||
if UnitIsDead and UnitIsDead(guid) then
|
if UnitIsDead and UnitIsDead(guid) then
|
||||||
CleanupUnit(guid)
|
CleanupUnit(guid)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Get auraSlot from event parameter (Nampower 2.29+)
|
-- Get auraSlot from event parameter (Nampower 2.29+)
|
||||||
-- Fallback to displayToAura mapping if not available
|
-- Fallback to displayToAura mapping if not available
|
||||||
local foundAuraSlot = auraSlot
|
local foundAuraSlot = auraSlot
|
||||||
|
|||||||
+21
-81
@@ -44,78 +44,13 @@ local healGuidToName = {} -- [casterGuid] = casterName, for SPELL_FAILED_OTHER c
|
|||||||
local ress_timers = {} -- [target][sender] = expiry_timestamp (60s rez window)
|
local ress_timers = {} -- [target][sender] = expiry_timestamp (60s rez window)
|
||||||
local RESS_TIMEOUT = 60 -- Vanilla: rez offer expires after 60s
|
local RESS_TIMEOUT = 60 -- Vanilla: rez offer expires after 60s
|
||||||
|
|
||||||
local PRAYER_OF_HEALING
|
-- Localized spell names resolved once from canonical rank-1 spellIDs.
|
||||||
do -- Prayer of Healing
|
-- Every rank shares the same name, so per-rank comparisons elsewhere can
|
||||||
local locales = {
|
-- be done against these constants without per-locale or per-rank tables.
|
||||||
["deDE"] = "Gebet der Heilung",
|
local PRAYER_OF_HEALING = C_Spell.GetSpellName(596) -- Prayer of Healing (Rank 1)
|
||||||
["enUS"] = "Prayer of Healing",
|
local REJUVENATION = C_Spell.GetSpellName(774) -- Rejuvenation (Rank 1)
|
||||||
["esES"] = "Rezo de curación",
|
local RENEW = C_Spell.GetSpellName(139) -- Renew (Rank 1)
|
||||||
["frFR"] = "Prière de soins",
|
local REGROWTH = C_Spell.GetSpellName(8936) -- Regrowth (Rank 1)
|
||||||
["koKR"] = "치유의 기원",
|
|
||||||
["ruRU"] = "Молитва исцеления",
|
|
||||||
["zhCN"] = "治疗祷言",
|
|
||||||
}
|
|
||||||
|
|
||||||
PRAYER_OF_HEALING = locales[GetLocale()] or locales["enUS"]
|
|
||||||
end
|
|
||||||
|
|
||||||
local REJUVENATION
|
|
||||||
do -- Rejuvenation
|
|
||||||
local locales = {
|
|
||||||
["deDE"] = "Verjüngung",
|
|
||||||
["enUS"] = "Rejuvenation",
|
|
||||||
["esES"] = "Rejuvenecimiento",
|
|
||||||
["frFR"] = "Récupération",
|
|
||||||
["koKR"] = "회복",
|
|
||||||
["ruRU"] = "Омоложение",
|
|
||||||
["zhCN"] = "回春术",
|
|
||||||
}
|
|
||||||
|
|
||||||
REJUVENATION = locales[GetLocale()] or locales["enUS"]
|
|
||||||
end
|
|
||||||
|
|
||||||
local RENEW
|
|
||||||
do -- Renew
|
|
||||||
local locales = {
|
|
||||||
["deDE"] = "Erneuerung",
|
|
||||||
["enUS"] = "Renew",
|
|
||||||
["esES"] = "Renovar",
|
|
||||||
["frFR"] = "Rénovation",
|
|
||||||
["koKR"] = "소생",
|
|
||||||
["ruRU"] = "Обновление",
|
|
||||||
["zhCN"] = "恢复",
|
|
||||||
}
|
|
||||||
|
|
||||||
RENEW = locales[GetLocale()] or locales["enUS"]
|
|
||||||
end
|
|
||||||
|
|
||||||
local REGROWTH
|
|
||||||
do -- Regrowth
|
|
||||||
local locales = {
|
|
||||||
["deDE"] = "Nachwachsen",
|
|
||||||
["enUS"] = "Regrowth",
|
|
||||||
["esES"] = "Recrecimiento",
|
|
||||||
["frFR"] = "Rétablissement",
|
|
||||||
["koKR"] = "재생",
|
|
||||||
["ruRU"] = "Восстановление",
|
|
||||||
["zhCN"] = "愈合",
|
|
||||||
}
|
|
||||||
|
|
||||||
REGROWTH = locales[GetLocale()] or locales["enUS"]
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- Spell IDs for SPELL_GO_SELF callback (Nampower) - Instant HoT detection
|
|
||||||
local SPELL_IDS = {
|
|
||||||
-- Rejuvenation (all ranks)
|
|
||||||
[774] = "Reju", [1058] = "Reju", [1430] = "Reju", [2090] = "Reju", [2091] = "Reju",
|
|
||||||
[3627] = "Reju", [8910] = "Reju", [9839] = "Reju", [9840] = "Reju", [9841] = "Reju",
|
|
||||||
[25299] = "Reju", [26981] = "Reju", [26982] = "Reju",
|
|
||||||
-- Renew (all ranks)
|
|
||||||
[139] = "Renew", [6074] = "Renew", [6075] = "Renew", [6076] = "Renew", [6077] = "Renew",
|
|
||||||
[6078] = "Renew", [10927] = "Renew", [10928] = "Renew", [10929] = "Renew", [25315] = "Renew",
|
|
||||||
[25221] = "Renew", [25222] = "Renew",
|
|
||||||
}
|
|
||||||
|
|
||||||
local libpredict = CreateFrame("Frame")
|
local libpredict = CreateFrame("Frame")
|
||||||
libpredict:RegisterEvent("UNIT_HEALTH")
|
libpredict:RegisterEvent("UNIT_HEALTH")
|
||||||
@@ -173,7 +108,7 @@ end
|
|||||||
|
|
||||||
local function isRezSpell(spellId)
|
local function isRezSpell(spellId)
|
||||||
if not L["resurrections"] then return false end
|
if not L["resurrections"] then return false end
|
||||||
local spellName = GetSpellRecField and GetSpellRecField(spellId, "name")
|
local spellName = C_Spell.GetSpellName(spellId)
|
||||||
return spellName and L["resurrections"][spellName]
|
return spellName and L["resurrections"][spellName]
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -192,7 +127,7 @@ end)
|
|||||||
-- SPELL_START_SELF: own cast started (heals + rez)
|
-- SPELL_START_SELF: own cast started (heals + rez)
|
||||||
pfUI.libdebuff_spell_start_self_hooks = pfUI.libdebuff_spell_start_self_hooks or {}
|
pfUI.libdebuff_spell_start_self_hooks = pfUI.libdebuff_spell_start_self_hooks or {}
|
||||||
pfUI.libdebuff_spell_start_self_hooks["libpredict"] = function(spellId, casterGuid, targetGuid, castTime)
|
pfUI.libdebuff_spell_start_self_hooks["libpredict"] = function(spellId, casterGuid, targetGuid, castTime)
|
||||||
local spellName = GetSpellRecField and GetSpellRecField(spellId, "name")
|
local spellName = C_Spell.GetSpellName(spellId)
|
||||||
if not spellName then return end
|
if not spellName then return end
|
||||||
|
|
||||||
local pendingTarget = nil
|
local pendingTarget = nil
|
||||||
@@ -319,7 +254,7 @@ end
|
|||||||
-- SPELL_GO_SELF: own cast landed (HealStop + Regrowth timer)
|
-- SPELL_GO_SELF: own cast landed (HealStop + Regrowth timer)
|
||||||
pfUI.libdebuff_spell_go_hooks["libpredict_sender"] = function(spellId)
|
pfUI.libdebuff_spell_go_hooks["libpredict_sender"] = function(spellId)
|
||||||
libpredict:HealStop(player)
|
libpredict:HealStop(player)
|
||||||
local spellName = GetSpellRecField and GetSpellRecField(spellId, "name")
|
local spellName = C_Spell.GetSpellName(spellId)
|
||||||
if spellName == REGROWTH then
|
if spellName == REGROWTH then
|
||||||
local now = pfUI.uf.now or GetTime()
|
local now = pfUI.uf.now or GetTime()
|
||||||
if libpredict.sender.regrowth_timer then
|
if libpredict.sender.regrowth_timer then
|
||||||
@@ -337,7 +272,7 @@ end
|
|||||||
-- Signature: fn(spellId, casterGuid, targetGuid, castTime)
|
-- Signature: fn(spellId, casterGuid, targetGuid, castTime)
|
||||||
pfUI.libdebuff_spell_start_other_hooks = pfUI.libdebuff_spell_start_other_hooks or {}
|
pfUI.libdebuff_spell_start_other_hooks = pfUI.libdebuff_spell_start_other_hooks or {}
|
||||||
pfUI.libdebuff_spell_start_other_hooks["libpredict"] = function(spellId, casterGuid, targetGuid, castTime)
|
pfUI.libdebuff_spell_start_other_hooks["libpredict"] = function(spellId, casterGuid, targetGuid, castTime)
|
||||||
local spellName = GetSpellRecField and GetSpellRecField(spellId, "name")
|
local spellName = C_Spell.GetSpellName(spellId)
|
||||||
if not spellName then return end
|
if not spellName then return end
|
||||||
|
|
||||||
local casterName = resolveNameFromGuid(casterGuid)
|
local casterName = resolveNameFromGuid(casterGuid)
|
||||||
@@ -404,8 +339,13 @@ end
|
|||||||
-- Signature: fn(spellId, arg1, arg2, arg3, arg4, arg5, arg6, arg7)
|
-- Signature: fn(spellId, arg1, arg2, arg3, arg4, arg5, arg6, arg7)
|
||||||
pfUI.libdebuff_spell_go_hooks = pfUI.libdebuff_spell_go_hooks or {}
|
pfUI.libdebuff_spell_go_hooks = pfUI.libdebuff_spell_go_hooks or {}
|
||||||
pfUI.libdebuff_spell_go_hooks["libpredict"] = function(spellId, a1, a2, a3, a4, a5, a6, a7)
|
pfUI.libdebuff_spell_go_hooks["libpredict"] = function(spellId, a1, a2, a3, a4, a5, a6, a7)
|
||||||
-- Instant HoTs
|
-- Instant HoTs — classify by name (rank-independent) instead of a
|
||||||
local hotType = SPELL_IDS[spellId]
|
-- hardcoded per-rank ID table.
|
||||||
|
local spellName = C_Spell.GetSpellName(spellId)
|
||||||
|
local hotType
|
||||||
|
if spellName == REJUVENATION then hotType = "Reju"
|
||||||
|
elseif spellName == RENEW then hotType = "Renew"
|
||||||
|
end
|
||||||
if hotType then
|
if hotType then
|
||||||
local targetGuid = a4
|
local targetGuid = a4
|
||||||
local targetName = resolveNameFromGuid(targetGuid)
|
local targetName = resolveNameFromGuid(targetGuid)
|
||||||
@@ -1236,7 +1176,7 @@ libpredict.sender:SetScript("OnEvent", function()
|
|||||||
local amount = arg4
|
local amount = arg4
|
||||||
local isCrit = arg5 == 1
|
local isCrit = arg5 == 1
|
||||||
local isPeriodic = arg6 == 1
|
local isPeriodic = arg6 == 1
|
||||||
local spellName = GetSpellRecField and GetSpellRecField(spellId, "name")
|
local spellName = C_Spell.GetSpellName(spellId)
|
||||||
if spellName and spell_queue[1] == spellName then
|
if spellName and spell_queue[1] == spellName then
|
||||||
UpdateCache(spell_queue[2], amount, isCrit)
|
UpdateCache(spell_queue[2], amount, isCrit)
|
||||||
end
|
end
|
||||||
@@ -1256,9 +1196,9 @@ libpredict.sender:SetScript("OnEvent", function()
|
|||||||
local casterName = resolveNameFromGuid(casterGuid)
|
local casterName = resolveNameFromGuid(casterGuid)
|
||||||
if not casterName or casterName == player then return end -- own heals handled by SPELL_HEAL_BY_SELF
|
if not casterName or casterName == player then return end -- own heals handled by SPELL_HEAL_BY_SELF
|
||||||
|
|
||||||
local spellName = GetSpellRecField and GetSpellRecField(spellId, "name")
|
local spellName = C_Spell.GetSpellName(spellId)
|
||||||
if not spellName then return end
|
if not spellName then return end
|
||||||
local rankStr = GetSpellRecField and GetSpellRecField(spellId, "rank") or ""
|
local rankStr = C_Spell.GetSpellSubtext(spellId)
|
||||||
local spellKey = spellName .. (rankStr or "")
|
local spellKey = spellName .. (rankStr or "")
|
||||||
|
|
||||||
foreignCache[casterName] = foreignCache[casterName] or {}
|
foreignCache[casterName] = foreignCache[casterName] or {}
|
||||||
|
|||||||
@@ -937,10 +937,15 @@ end
|
|||||||
-- name — name alone misses pool reuse between same-named units (e.g. plate
|
-- name — name alone misses pool reuse between same-named units (e.g. plate
|
||||||
-- held a player "Ironforge Guard" and is now reassigned to the NPC by the
|
-- held a player "Ironforge Guard" and is now reassigned to the NPC by the
|
||||||
-- same name), which would leak a stale "PLAYER" hint into GetUnitInfo.
|
-- same name), which would leak a stale "PLAYER" hint into GetUnitInfo.
|
||||||
|
-- Wipe the whole cache table: the PERF gates below ("only update X when
|
||||||
|
-- X changed") would otherwise skip bar/color/text updates when the new
|
||||||
|
-- unit happens to share a cached value with the previous occupant
|
||||||
|
-- (e.g., both at 60% HP percentage on plate pool reuse → bar stays at
|
||||||
|
-- the old fill until the new mob actually changes HP).
|
||||||
if plate.cache.name ~= name or plate.cache.guid ~= plate.cachedGuid then
|
if plate.cache.name ~= name or plate.cache.guid ~= plate.cachedGuid then
|
||||||
|
table.wipe(plate.cache)
|
||||||
plate.cache.name = name
|
plate.cache.name = name
|
||||||
plate.cache.guid = plate.cachedGuid
|
plate.cache.guid = plate.cachedGuid
|
||||||
plate.cache.player = nil
|
|
||||||
plate.cdCache = nil -- new unit, reset spell-keyed timer cache
|
plate.cdCache = nil -- new unit, reset spell-keyed timer cache
|
||||||
plate.name:SetText(GetNameString(name))
|
plate.name:SetText(GetNameString(name))
|
||||||
end
|
end
|
||||||
@@ -987,12 +992,6 @@ end
|
|||||||
-- remove unitstr on unit name mismatch
|
-- remove unitstr on unit name mismatch
|
||||||
if unitstr and UnitName(unitstr) ~= name then unitstr = nil end
|
if unitstr and UnitName(unitstr) ~= name then unitstr = nil end
|
||||||
|
|
||||||
-- use mobhealth values if addon is running
|
|
||||||
if (MobHealth3 or MobHealthFrame) and target and name == UnitName('target') and MobHealth_GetTargetCurHP() then
|
|
||||||
hp = MobHealth_GetTargetCurHP() > 0 and MobHealth_GetTargetCurHP() or hp
|
|
||||||
hpmax = MobHealth_GetTargetMaxHP() > 0 and MobHealth_GetTargetMaxHP() or hpmax
|
|
||||||
end
|
|
||||||
|
|
||||||
-- always make sure to keep plate visible
|
-- always make sure to keep plate visible
|
||||||
plate:Show()
|
plate:Show()
|
||||||
|
|
||||||
|
|||||||
+101
-89
@@ -10,6 +10,11 @@ pfUI:RegisterModule("swingtimer", function ()
|
|||||||
local ON_SWING_QUEUED = 0
|
local ON_SWING_QUEUED = 0
|
||||||
local ON_SWING_QUEUE_POPPED = 1
|
local ON_SWING_QUEUE_POPPED = 1
|
||||||
|
|
||||||
|
-- Spell.dbc bits used to mirror server-side swing-reset rules.
|
||||||
|
local FLAG_AUTOATTACK = tonumber("0x08", 16) -- SPELL_INTERRUPT_FLAG_AUTOATTACK
|
||||||
|
local ATTR_KEEP_SWINGS = tonumber("0x20000", 16) -- SPELL_ATTR_EX2_NOT_RESET_AUTO_ACTIONS
|
||||||
|
local ATTR_ON_NEXT_SWING = tonumber("0x04", 16) -- SPELL_ATTR_ON_NEXT_SWING
|
||||||
|
|
||||||
-- Consolidate state into a table to avoid Lua 5.0 upvalue limit (32 max)
|
-- Consolidate state into a table to avoid Lua 5.0 upvalue limit (32 max)
|
||||||
local S = {
|
local S = {
|
||||||
mhTimer = 0, mhTimerMax = 1,
|
mhTimer = 0, mhTimerMax = 1,
|
||||||
@@ -32,51 +37,26 @@ pfUI:RegisterModule("swingtimer", function ()
|
|||||||
onSwingCache = {},
|
onSwingCache = {},
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Ranged spell IDs
|
-- Wand "Shoot" runs on the ranged bar but is INDEPENDENT of the melee
|
||||||
local RANGED_SPELLIDS = {
|
-- swing clock — casters melee-weave between mainhand swings and wand
|
||||||
[75] = true, -- Auto Shot (Hunter)
|
-- fires, both timers tick concurrently. Every other ranged-auto-attack
|
||||||
[2764] = true, -- Throw (Warrior/Rogue)
|
-- (Hunter Auto Shot, any future auto-repeat ranged spell) replaces MH.
|
||||||
}
|
local WAND_SHOOT_SPELLID = 5019
|
||||||
|
local THROW_SPELLID = 2764 -- one-shot ranged, not auto-repeat
|
||||||
|
|
||||||
-- Spells that DELAY the swing timer by their cast duration but do NOT reset it.
|
-- ATTR_ON_NEXT_SWING: spell replaces next auto-attack swing.
|
||||||
-- Slam: vanilla behavior on Turtle WoW - delays swing, does not reset.
|
|
||||||
-- Hammer of Wrath: Turtle WoW changed behavior - does not reset swing timer.
|
|
||||||
local swingDelaySpells = {
|
|
||||||
[1464] = true, [8820] = true, [11604] = true, [11605] = true, -- Slam R1-R4
|
|
||||||
[24275] = true, [24274] = true, [24239] = true, -- Hammer of Wrath R1-R3
|
|
||||||
}
|
|
||||||
|
|
||||||
-- SPELL_ATTR_ON_NEXT_SWING (bit 2, value 4): spell replaces next auto-attack swing.
|
|
||||||
-- Covers Raptor Strike, Maul, Mongoose Bite, Holy Strike, etc. automatically.
|
-- Covers Raptor Strike, Maul, Mongoose Bite, Holy Strike, etc. automatically.
|
||||||
local ATTR_ON_NEXT_SWING = 4
|
|
||||||
local function IsOnSwingSpell(spellId)
|
local function IsOnSwingSpell(spellId)
|
||||||
if S.onSwingCache[spellId] ~= nil then return S.onSwingCache[spellId] end
|
if S.onSwingCache[spellId] ~= nil then return S.onSwingCache[spellId] end
|
||||||
local rec = GetSpellRec(spellId)
|
local attr = GetSpellRecField(spellId, "attributes") or 0
|
||||||
local result = rec and bit.band(rec.attributes, ATTR_ON_NEXT_SWING) ~= 0 or false
|
local result = bit.band(attr, ATTR_ON_NEXT_SWING) ~= 0
|
||||||
S.onSwingCache[spellId] = result
|
S.onSwingCache[spellId] = result
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Heroic Strike spell IDs (all ranks)
|
-- HS / Cleave / Maul are detected dynamically via IsOnSwingSpell (the
|
||||||
local hsSpellIDs = {
|
-- SPELL_ATTR_ON_NEXT_SWING bit) + name comparison against the rank-1
|
||||||
[78] = true, [284] = true, [285] = true, [1608] = true,
|
-- canonical names cached below — no per-rank ID maintenance.
|
||||||
[11564] = true, [11565] = true, [11566] = true, [11567] = true,
|
|
||||||
}
|
|
||||||
|
|
||||||
-- Cleave spell IDs (all ranks)
|
|
||||||
local cleaveSpellIDs = {
|
|
||||||
[845] = true, [7369] = true, [11608] = true, [11609] = true,
|
|
||||||
[20569] = true,
|
|
||||||
}
|
|
||||||
|
|
||||||
-- Maul spell IDs (all ranks 1 to 7)
|
|
||||||
local maulSpellIDs = {
|
|
||||||
[6807] = true,
|
|
||||||
[6808] = true,
|
|
||||||
[6809] = true,
|
|
||||||
[8972] = true,
|
|
||||||
[9745] = true, [9880] = true, [9881] = true,
|
|
||||||
}
|
|
||||||
|
|
||||||
-- Read config
|
-- Read config
|
||||||
local sw_width = tonumber(C.unitframes.swingtimerwidth) or 200
|
local sw_width = tonumber(C.unitframes.swingtimerwidth) or 200
|
||||||
@@ -355,14 +335,17 @@ pfUI:RegisterModule("swingtimer", function ()
|
|||||||
pfUI.swingtimer:Show()
|
pfUI.swingtimer:Show()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Reset ranged countdown
|
-- Reset ranged countdown. replaceMH=true (Hunter Auto Shot, Throw) stops
|
||||||
local function ResetRanged()
|
-- the melee swing clock while ranged ticks; replaceMH=false (wand Shoot)
|
||||||
|
-- leaves it running for melee weaving.
|
||||||
|
local function ResetRanged(replaceMH)
|
||||||
if not sw_showranged then return end
|
if not sw_showranged then return end
|
||||||
UpdateWeaponSpeeds()
|
UpdateWeaponSpeeds()
|
||||||
if S.raSpeed <= 0 then return end
|
if S.raSpeed <= 0 then return end
|
||||||
-- Ranged replaces MH bar
|
if replaceMH then
|
||||||
S.mhActive = false
|
S.mhActive = false
|
||||||
pfUI.swingtimer.mainhand:Hide()
|
pfUI.swingtimer.mainhand:Hide()
|
||||||
|
end
|
||||||
S.raTimerMax = S.raSpeed
|
S.raTimerMax = S.raSpeed
|
||||||
S.raTimer = S.raSpeed
|
S.raTimer = S.raSpeed
|
||||||
S.raActive = true
|
S.raActive = true
|
||||||
@@ -406,11 +389,35 @@ pfUI:RegisterModule("swingtimer", function ()
|
|||||||
pfUI.swingtimer:Hide()
|
pfUI.swingtimer:Hide()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- HS/Cleave helpers. Canonical rank-1 spellIDs resolve to the localized
|
-- HS/Cleave/Maul helpers. Canonical rank-1 spellIDs resolve to the
|
||||||
-- spell name once, so the per-slot comparison is locale-independent without
|
-- localized spell name once, so per-spell classification is rank- and
|
||||||
-- per-rank hardcoding (every rank of Heroic Strike returns the same name).
|
-- locale-independent without per-rank ID hardcoding (every rank of
|
||||||
local HS_NAME = C_Spell.GetSpellName(78) -- Heroic Strike (Rank 1)
|
-- Heroic Strike etc. returns the same name).
|
||||||
local CLEAVE_NAME = C_Spell.GetSpellName(845) -- Cleave (Rank 1)
|
local HS_NAME = C_Spell.GetSpellName(78) -- Heroic Strike (Rank 1)
|
||||||
|
local CLEAVE_NAME = C_Spell.GetSpellName(845) -- Cleave (Rank 1)
|
||||||
|
local MAUL_NAME = C_Spell.GetSpellName(6807) -- Maul (Rank 1)
|
||||||
|
|
||||||
|
-- Classify an on-next-swing spell as "hs" / "cleave" / "maul" / nil.
|
||||||
|
-- IsOnSwingSpell gates the family (ATTR_ON_NEXT_SWING bit 0x04) so
|
||||||
|
-- unrelated spells return nil cheaply.
|
||||||
|
local function ClassifyOnSwingSpell(spellId)
|
||||||
|
if not IsOnSwingSpell(spellId) then return nil end
|
||||||
|
local name = C_Spell.GetSpellName(spellId)
|
||||||
|
if name == HS_NAME then return "hs"
|
||||||
|
elseif name == CLEAVE_NAME then return "cleave"
|
||||||
|
elseif name == MAUL_NAME then return "maul"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Set the queue flags from a ClassifyOnSwingSpell result. nil = no-op
|
||||||
|
-- (preserves prior flag state when the queued spell isn't one we
|
||||||
|
-- color-code, matching the legacy table-lookup behavior).
|
||||||
|
local function SetQueuedKind(kind)
|
||||||
|
if not kind then return end
|
||||||
|
S.hsQueued = (kind == "hs")
|
||||||
|
S.cleaveQueued = (kind == "cleave")
|
||||||
|
S.maulQueued = (kind == "maul")
|
||||||
|
end
|
||||||
|
|
||||||
local function RebuildQueueSlotCache()
|
local function RebuildQueueSlotCache()
|
||||||
if not S.isWarrior or not sw_hsqueue or S.useSpellQueueEvent then return end
|
if not S.isWarrior or not sw_hsqueue or S.useSpellQueueEvent then return end
|
||||||
@@ -694,11 +701,17 @@ pfUI:RegisterModule("swingtimer", function ()
|
|||||||
local spellStartFrame = CreateFrame("Frame")
|
local spellStartFrame = CreateFrame("Frame")
|
||||||
spellStartFrame:RegisterEvent("SPELL_START_SELF")
|
spellStartFrame:RegisterEvent("SPELL_START_SELF")
|
||||||
spellStartFrame:SetScript("OnEvent", function()
|
spellStartFrame:SetScript("OnEvent", function()
|
||||||
if arg1 and arg1 > 0 then
|
if not (arg1 and arg1 > 0) then return end
|
||||||
S.pendingCastSpellId = arg1
|
S.pendingCastSpellId = arg1
|
||||||
-- Slam (and other delay-only spells): freeze the swing timer during cast
|
-- Freeze the swing timer for cast-time spells that DON'T reset auto-
|
||||||
-- so it pauses instead of ticking down and expiring mid-cast
|
-- attack on completion (Slam, Hammer of Wrath on Turtle, etc.) — those
|
||||||
if swingDelaySpells[arg1] and S.mhActive then
|
-- let the swing resume from where it paused. Detect dynamically via the
|
||||||
|
-- absent AUTOATTACK interrupt flag (8); spells with that bit reset on
|
||||||
|
-- SPELL_GO_SELF so freezing isn't necessary. Subsumes the old hardcoded
|
||||||
|
-- swingDelaySpells list (no list maintenance for new Slam-style spells).
|
||||||
|
if S.mhActive then
|
||||||
|
local iflags = GetSpellRecField(arg1, "interruptFlags") or 0
|
||||||
|
if bit.band(iflags, FLAG_AUTOATTACK) == 0 then
|
||||||
S.mhFrozenAt = GetTime()
|
S.mhFrozenAt = GetTime()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -718,52 +731,57 @@ pfUI:RegisterModule("swingtimer", function ()
|
|||||||
-- SPELL_GO hook via libdebuff
|
-- SPELL_GO hook via libdebuff
|
||||||
pfUI.libdebuff_spell_go_hooks = pfUI.libdebuff_spell_go_hooks or {}
|
pfUI.libdebuff_spell_go_hooks = pfUI.libdebuff_spell_go_hooks or {}
|
||||||
pfUI.libdebuff_spell_go_hooks["swingtimer"] = function(spellId)
|
pfUI.libdebuff_spell_go_hooks["swingtimer"] = function(spellId)
|
||||||
if RANGED_SPELLIDS[spellId] then
|
-- C_Spell.IsRangedAutoAttackSpell catches both Auto Shot (75) and
|
||||||
ResetRanged()
|
-- wand Shoot (5019) via Spell.dbc's AUTO_REPEAT attribute (covers
|
||||||
|
-- any future auto-repeat ranged spell automatically). Wand is the
|
||||||
|
-- one independent of the MH swing — everything else replaces it.
|
||||||
|
-- Throw isn't auto-repeat (single-shot) so it's handled explicitly.
|
||||||
|
if C_Spell.IsRangedAutoAttackSpell(spellId) then
|
||||||
|
ResetRanged(spellId ~= WAND_SHOOT_SPELLID)
|
||||||
return
|
return
|
||||||
elseif swingDelaySpells[spellId] then
|
elseif spellId == THROW_SPELLID then
|
||||||
-- Swing-delay spells (Slam, Hammer of Wrath on Turtle WoW):
|
ResetRanged(true)
|
||||||
-- Delay the swing timer by cast duration, do NOT reset it.
|
|
||||||
if S.mhFrozenAt then
|
|
||||||
local castDuration = GetTime() - S.mhFrozenAt
|
|
||||||
S.mhTimer = S.mhTimer + castDuration
|
|
||||||
S.mhTimerMax = S.mhTimerMax + castDuration
|
|
||||||
S.mhFrozenAt = nil
|
|
||||||
end
|
|
||||||
S.pendingCastSpellId = nil
|
|
||||||
return
|
return
|
||||||
elseif hsSpellIDs[spellId] or cleaveSpellIDs[spellId] or maulSpellIDs[spellId] or IsOnSwingSpell(spellId) then
|
elseif IsOnSwingSpell(spellId) then
|
||||||
S.hsQueued = false; S.cleaveQueued = false
|
-- On-next-swing ability (HS / Cleave / Maul / Raptor Strike / etc.)
|
||||||
S.maulQueued = false
|
-- — the swing fires as the spell consumes it. Drop the queued color.
|
||||||
ResetMH()
|
S.hsQueued = false; S.cleaveQueued = false; S.maulQueued = false
|
||||||
elseif cleaveSpellIDs[spellId] then
|
|
||||||
S.hsQueued = false; S.cleaveQueued = false
|
|
||||||
ResetMH()
|
ResetMH()
|
||||||
else
|
else
|
||||||
-- Any spell with interruptFlags > 0 resets the swing timer
|
-- Mirror the server rule for "does this spell reset the auto-attack
|
||||||
-- (Moonfire, Faerie Fire, Wrath, Starfire etc. - NOT Insect Swarm which has flags=0)
|
-- swing" (Spell::IsMeleeAttackResetSpell in Turtle's core):
|
||||||
local _rec = GetSpellRec(spellId)
|
-- InterruptFlags has SPELL_INTERRUPT_FLAG_AUTOATTACK (0x08)
|
||||||
if _rec and _rec.interruptFlags and _rec.interruptFlags > 0 then
|
-- AND AttributesEx2 lacks NOT_RESET_AUTO_ACTIONS (0x20000).
|
||||||
|
-- If neither path resets and we're holding a frozen-swing-during-cast
|
||||||
|
-- (mhFrozenAt set by SPELL_START_SELF for non-AUTOATTACK spells), this
|
||||||
|
-- is a Slam-style cast — push the timer forward by the cast duration
|
||||||
|
-- so the bar resumes from where it paused.
|
||||||
|
local iflags = GetSpellRecField(spellId, "interruptFlags") or 0
|
||||||
|
if bit.band(iflags, FLAG_AUTOATTACK) ~= 0
|
||||||
|
and bit.band(GetSpellRecField(spellId, "attributesEx2") or 0, ATTR_KEEP_SWINGS) == 0 then
|
||||||
if S.mhActive and S.mhSpeed > 0 then
|
if S.mhActive and S.mhSpeed > 0 then
|
||||||
UpdateWeaponSpeeds()
|
UpdateWeaponSpeeds()
|
||||||
S.mhTimerMax = S.mhSpeed
|
S.mhTimerMax = S.mhSpeed
|
||||||
S.mhTimer = S.mhSpeed
|
S.mhTimer = S.mhSpeed
|
||||||
end
|
end
|
||||||
|
if S.ohActive and S.ohSpeed > 0 then
|
||||||
|
S.ohTimerMax = S.ohSpeed
|
||||||
|
S.ohTimer = S.ohSpeed
|
||||||
|
end
|
||||||
|
elseif S.mhFrozenAt then
|
||||||
|
local castDuration = GetTime() - S.mhFrozenAt
|
||||||
|
S.mhTimer = S.mhTimer + castDuration
|
||||||
|
S.mhTimerMax = S.mhTimerMax + castDuration
|
||||||
|
S.mhFrozenAt = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
S.pendingCastSpellId = nil
|
S.pendingCastSpellId = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
-- SPELL_CAST_EVENT hook: HS/Cleave queue tracking
|
-- SPELL_CAST_EVENT hook: HS/Cleave/Maul queue tracking
|
||||||
pfUI.libdebuff_spell_cast_hooks = pfUI.libdebuff_spell_cast_hooks or {}
|
pfUI.libdebuff_spell_cast_hooks = pfUI.libdebuff_spell_cast_hooks or {}
|
||||||
pfUI.libdebuff_spell_cast_hooks["swingtimer"] = function(success, spellId)
|
pfUI.libdebuff_spell_cast_hooks["swingtimer"] = function(success, spellId)
|
||||||
if hsSpellIDs[spellId] then
|
SetQueuedKind(ClassifyOnSwingSpell(spellId))
|
||||||
S.hsQueued = true; S.cleaveQueued = false; S.maulQueued = false
|
|
||||||
elseif cleaveSpellIDs[spellId] then
|
|
||||||
S.cleaveQueued = true; S.hsQueued = false; S.maulQueued = false
|
|
||||||
elseif maulSpellIDs[spellId] then
|
|
||||||
S.maulQueued = true; S.hsQueued = false; S.cleaveQueued = false
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
@@ -847,13 +865,7 @@ pfUI:RegisterModule("swingtimer", function ()
|
|||||||
local spellId = arg2 or 0
|
local spellId = arg2 or 0
|
||||||
if eventCode == ON_SWING_QUEUED then
|
if eventCode == ON_SWING_QUEUED then
|
||||||
S.useSpellQueueEvent = true
|
S.useSpellQueueEvent = true
|
||||||
if hsSpellIDs[spellId] then
|
SetQueuedKind(ClassifyOnSwingSpell(spellId))
|
||||||
S.hsQueued = true; S.cleaveQueued = false; S.maulQueued = false
|
|
||||||
elseif cleaveSpellIDs[spellId] then
|
|
||||||
S.cleaveQueued = true; S.hsQueued = false; S.maulQueued = false
|
|
||||||
elseif maulSpellIDs[spellId] then
|
|
||||||
S.maulQueued = true; S.hsQueued = false; S.cleaveQueued = false
|
|
||||||
end
|
|
||||||
elseif eventCode == ON_SWING_QUEUE_POPPED then
|
elseif eventCode == ON_SWING_QUEUE_POPPED then
|
||||||
S.hsQueued = false; S.cleaveQueued = false; S.maulQueued = false
|
S.hsQueued = false; S.cleaveQueued = false; S.maulQueued = false
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -146,14 +146,6 @@ pfUI:RegisterModule("tooltip", function ()
|
|||||||
rhp, rhpmax = hp, hpmax
|
rhp, rhpmax = hp, hpmax
|
||||||
elseif pfUI.libhealth and pfUI.libhealth.enabled then
|
elseif pfUI.libhealth and pfUI.libhealth.enabled then
|
||||||
rhp, rhpmax, estimated = pfUI.libhealth:GetUnitHealthByName(this.name, this.level, tonumber(hp), tonumber(hpmax))
|
rhp, rhpmax, estimated = pfUI.libhealth:GetUnitHealthByName(this.name, this.level, tonumber(hp), tonumber(hpmax))
|
||||||
elseif MobHealthFrame then
|
|
||||||
local index = (this.name or "") .. ":" .. (this.level or "")
|
|
||||||
local ppp = MobHealth_PPP(index)
|
|
||||||
if perc and ppp and ppp > 0 and not UnitIsUnit("mouseover", "pet") then
|
|
||||||
rhp = round(hp * ppp)
|
|
||||||
rhpmax = round(100 * ppp)
|
|
||||||
estimated = true
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if C.tooltip.alwaysperc == "0" and ( estimated or hpmax > 100 or round(hpmax/100*hp) ~= hp ) then
|
if C.tooltip.alwaysperc == "0" and ( estimated or hpmax > 100 or round(hpmax/100*hp) ~= hp ) then
|
||||||
|
|||||||
Reference in New Issue
Block a user