mirror of
https://github.com/brues-code/pfUI.git
synced 2026-09-26 17:36:04 +00:00
Refactor nameplate cast tracking to be fully GUID-based
Problem:
Since a recent Turtle-WoW update, nameplates would show the same castbar on all mobs sharing a name (e.g. multiple "Defias Rogue Wizard"). Additionally, a targeted mob that was not casting would incorrectly display the castbar of a nearby mob with the same name.
Changes:
**`modules/nameplates.lua`**
- Removed `hasNampower` version-sniffing entirely. Nampower is a hard requirement for pfUI; all related guards have been removed and the code runs unconditionally.
- Castbar lookup now always uses `nameplate.cachedGuid` directly — never `GetUnitGUID("target")` or a unit name string.
- Removed dead fallback block using `UnitCastingInfo("target")`.
- Restored fallback via `pfGetCastInfo(cachedGuid)` / `pfGetChannelInfo(cachedGuid)` (see libcast changes below) for casts not tracked by libdebuff (e.g. already-in-progress casts on login).
**`libs/libcast.lua`**
- Renamed `UnitCastingInfo` → `pfGetCastInfo` and `UnitChannelInfo` → `pfGetChannelInfo` to avoid confusion with the Blizzard API of the same name, which does not exist in Vanilla.
- Name-based fallback lookup (`libcast.db[unitName]`) is now skipped when a GUID is available. This prevents cast bleed between mobs that share a name.
**`libs/libpredict.lua`, `modules/castbar.lua`, `modules/afkcam.lua`**
- Updated all call sites to use the renamed `pfGetCastInfo` / `pfGetChannelInfo`.
This commit is contained in:
+10
-8
@@ -4,10 +4,10 @@ setfenv(1, pfUI:GetEnvironment())
|
||||
--[[ libcast ]]--
|
||||
-- A pfUI library that detects and saves all ongoing castbars of players, NPCs and enemies.
|
||||
-- The library also includes spells that usually don't have a castbar like Multi-Shot and Aimed Shot.
|
||||
-- This is exclusivly used for vanilla in order to provide UnitChannelInfo and UnitCastingInfo functions.
|
||||
-- This is exclusivly used for vanilla in order to provide pfGetChannelInfo and pfGetCastInfo functions.
|
||||
--
|
||||
-- External functions:
|
||||
-- UnitChannelInfo(unit)
|
||||
-- pfGetChannelInfo(unit)
|
||||
-- Returns information on the spell currently cast by the specified unit.
|
||||
-- Returns nil if no spell is being cast.
|
||||
--
|
||||
@@ -19,7 +19,7 @@ setfenv(1, pfUI:GetEnvironment())
|
||||
-- endTime[Number] - Specifies when casting will end, in milliseconds.
|
||||
-- isTradeSkill[Boolean] - (DUMMY) Specifies if the cast is a tradeskill
|
||||
--
|
||||
-- UnitCastingInfo(unit)
|
||||
-- pfGetCastInfo(unit)
|
||||
-- Returns information on the spell currently channeled by the specified unit.
|
||||
-- Returns nil if no spell is being channeled.
|
||||
--
|
||||
@@ -53,7 +53,7 @@ local scanner = libtipscan:GetScanner("libcast")
|
||||
local libcast = CreateFrame("Frame", "pfEnemyCast")
|
||||
local player = UnitName("player")
|
||||
|
||||
UnitChannelInfo = function(unit)
|
||||
pfGetChannelInfo = function(unit)
|
||||
-- convert to name if unitstring was given
|
||||
local unitName = pfValidUnits[unit] and UnitName(unit) or unit
|
||||
|
||||
@@ -117,7 +117,8 @@ UnitChannelInfo = function(unit)
|
||||
end
|
||||
|
||||
-- Fallback to name-based lookup (CHAT_MSG castbars)
|
||||
if not db and libcast.db[unitName] then
|
||||
-- Skip when GUID is available to avoid same-name mob bleed
|
||||
if not db and not guid and libcast.db[unitName] then
|
||||
db = libcast.db[unitName]
|
||||
end
|
||||
|
||||
@@ -145,7 +146,7 @@ UnitChannelInfo = function(unit)
|
||||
return cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill
|
||||
end
|
||||
|
||||
UnitCastingInfo = function(unit)
|
||||
pfGetCastInfo = function(unit)
|
||||
-- convert to name if unitstring was given
|
||||
local unitName = pfValidUnits[unit] and UnitName(unit) or unit
|
||||
|
||||
@@ -209,7 +210,8 @@ UnitCastingInfo = function(unit)
|
||||
end
|
||||
|
||||
-- Fallback to name-based lookup (CHAT_MSG castbars)
|
||||
if not db and libcast.db[unitName] then
|
||||
-- Skip when GUID is available to avoid same-name mob bleed
|
||||
if not db and not guid and libcast.db[unitName] then
|
||||
db = libcast.db[unitName]
|
||||
end
|
||||
|
||||
@@ -533,7 +535,7 @@ local function CastCustom(id, bookType, rawSpellName, rank, texture, castingTime
|
||||
local func = libcast.customcast[strlower(rawSpellName)]
|
||||
if not func then return end
|
||||
|
||||
if GetSpellCooldown(id, bookType) == 0 or UnitCastingInfo(player) then return end -- detect casting
|
||||
if GetSpellCooldown(id, bookType) == 0 or pfGetCastInfo(player) then return end -- detect casting
|
||||
|
||||
func(true)
|
||||
end
|
||||
|
||||
+5
-5
@@ -446,7 +446,7 @@ function libpredict:ParseComm(sender, msg)
|
||||
rank = tonumber(rankStr)
|
||||
end
|
||||
end
|
||||
elseif select and UnitCastingInfo then
|
||||
elseif select and pfGetCastInfo then
|
||||
-- latest healcomm
|
||||
msgtype = tonumber(string.sub(msg, 1, 3))
|
||||
if not msgtype then return end
|
||||
@@ -456,8 +456,8 @@ function libpredict:ParseComm(sender, msg)
|
||||
heal = tonumber(string.sub(msg, 4, 8))
|
||||
target = string.sub(msg,9, -1)
|
||||
|
||||
local starttime = select(5, UnitCastingInfo(sender))
|
||||
local endtime = select(6, UnitCastingInfo(sender))
|
||||
local starttime = select(5, pfGetCastInfo(sender))
|
||||
local endtime = select(6, pfGetCastInfo(sender))
|
||||
if not starttime or not endtime then return end
|
||||
time = endtime - starttime
|
||||
elseif msgtype == 1 then
|
||||
@@ -466,8 +466,8 @@ function libpredict:ParseComm(sender, msg)
|
||||
msgtype = "Heal"
|
||||
heal = tonumber(string.sub(msg,4, 8))
|
||||
target = {strsplit(":", string.sub(msg,9, -1))}
|
||||
local starttime = select(5, UnitCastingInfo(sender))
|
||||
local endtime = select(6, UnitCastingInfo(sender))
|
||||
local starttime = select(5, pfGetCastInfo(sender))
|
||||
local endtime = select(6, pfGetCastInfo(sender))
|
||||
if not starttime or not endtime then return end
|
||||
time = endtime - starttime
|
||||
end
|
||||
|
||||
+2
-2
@@ -170,8 +170,8 @@ pfUI:RegisterModule("afkcam", "vanilla:tbc", function ()
|
||||
if ( this.tick or 0) > GetTime() then return else this.tick = GetTime() + 1 end
|
||||
|
||||
local name = UnitName("player")
|
||||
local cast = UnitCastingInfo(name)
|
||||
if not cast then cast = UnitChannelInfo(name) end
|
||||
local cast = pfGetCastInfo(name)
|
||||
if not cast then cast = pfGetChannelInfo(name) end
|
||||
if not this.delay then this.delay = 0 end
|
||||
|
||||
if cast then
|
||||
|
||||
+7
-7
@@ -139,13 +139,13 @@ pfUI:RegisterModule("castbar", "vanilla", function ()
|
||||
query = UnitName("player")
|
||||
end
|
||||
|
||||
-- Fallback: UnitCastingInfo only when no focusGuid (Nampower not available for this unit)
|
||||
if not cast and not castBlocked and not focusGuid and UnitCastingInfo then
|
||||
cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill = UnitCastingInfo(query)
|
||||
-- Fallback: pfGetCastInfo only when no focusGuid (Nampower not available for this unit)
|
||||
if not cast and not castBlocked and not focusGuid and pfGetCastInfo then
|
||||
cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill = pfGetCastInfo(query)
|
||||
end
|
||||
|
||||
if not cast and not castBlocked and not focusGuid and UnitChannelInfo then
|
||||
channel, nameSubtext, text, texture, startTime, endTime, isTradeSkill = UnitChannelInfo(this.unitstr or this.unitname)
|
||||
if not cast and not castBlocked and not focusGuid and pfGetChannelInfo then
|
||||
channel, nameSubtext, text, texture, startTime, endTime, isTradeSkill = pfGetChannelInfo(this.unitstr or this.unitname)
|
||||
cast = channel
|
||||
end
|
||||
|
||||
@@ -254,12 +254,12 @@ pfUI:RegisterModule("castbar", "vanilla", function ()
|
||||
playerarg = pfUI.client <= 11200 or arg1 == "player" and true or nil
|
||||
|
||||
if event == CASTBAR_EVENT_CAST_DELAY and playerarg then
|
||||
local isCast, nameSubtext, text, texture, startTime, endTime, isTradeSkill = UnitCastingInfo(this.unitstr or this.unitname)
|
||||
local isCast, nameSubtext, text, texture, startTime, endTime, isTradeSkill = pfGetCastInfo(this.unitstr or this.unitname)
|
||||
if not isCast then return end
|
||||
if not this.endTime then return end
|
||||
this.delay = this.delay + (endTime - this.endTime) / 1000
|
||||
elseif event == CASTBAR_EVENT_CHANNEL_DELAY and playerarg then
|
||||
local isChannel, _, _, _, startTime, endTime = UnitChannelInfo(this.unitstr or this.unitname)
|
||||
local isChannel, _, _, _, startTime, endTime = pfGetChannelInfo(this.unitstr or this.unitname)
|
||||
if not isChannel then return end
|
||||
this.delay = ( this.delay or 0 ) + this.bar:GetValue() - (endTime/1000 - GetTime())
|
||||
elseif playerarg then
|
||||
|
||||
+60
-113
@@ -2,17 +2,10 @@ pfUI:RegisterModule("nameplates", "vanilla", function ()
|
||||
-- disable original castbars
|
||||
pcall(SetCVar, "ShowVKeyCastbar", 0)
|
||||
|
||||
-- Check for Nampower support (preferred)
|
||||
local hasNampower = false
|
||||
if GetNampowerVersion then
|
||||
local major, minor, patch = GetNampowerVersion()
|
||||
patch = patch or 0
|
||||
if major > 2 or (major == 2 and minor > 27) or (major == 2 and minor == 27 and patch >= 2) then
|
||||
hasNampower = true
|
||||
end
|
||||
end
|
||||
|
||||
-- Local function references for performance
|
||||
local pfGetCastInfo = pfGetCastInfo -- provided by libcast for vanilla
|
||||
local pfGetChannelInfo = pfGetChannelInfo -- provided by libcast for vanilla
|
||||
local GetTime = GetTime
|
||||
local UnitExists = UnitExists
|
||||
local UnitName = UnitName
|
||||
@@ -23,8 +16,6 @@ pfUI:RegisterModule("nameplates", "vanilla", function ()
|
||||
local UnitAffectingCombat = UnitAffectingCombat
|
||||
local UnitIsUnit = UnitIsUnit
|
||||
local UnitCanAssist = UnitCanAssist
|
||||
local UnitCastingInfo = UnitCastingInfo
|
||||
local UnitChannelInfo = UnitChannelInfo
|
||||
local UnitHealth = UnitHealth
|
||||
local UnitHealthMax = UnitHealthMax
|
||||
local UnitMana = UnitMana
|
||||
@@ -503,6 +494,7 @@ nameplates:RegisterEvent("ZONE_CHANGED_NEW_AREA")
|
||||
_, PlayerGUID = UnitExists("player")
|
||||
CacheConfig()
|
||||
this:SetGameVariables()
|
||||
|
||||
end
|
||||
|
||||
-- Handle friendly zone nameplate disable feature
|
||||
@@ -945,8 +937,8 @@ nameplates:RegisterEvent("ZONE_CHANGED_NEW_AREA")
|
||||
local unittype = GetUnitType(red, green, blue) or "ENEMY_NPC"
|
||||
local font_size = C.nameplates.use_unitfonts == "1" and C.global.font_unit_size or C.global.font_size
|
||||
|
||||
-- use superwow unit guid as unitstr if possible
|
||||
if hasNampower and not unitstr then
|
||||
-- use unit guid as unitstr if possible
|
||||
if not unitstr then
|
||||
unitstr = plate.parent:GetName(1)
|
||||
end
|
||||
|
||||
@@ -997,7 +989,7 @@ nameplates:RegisterEvent("ZONE_CHANGED_NEW_AREA")
|
||||
end
|
||||
|
||||
-- target indicator
|
||||
if hasNampower and cfg.outcombatstate then
|
||||
if cfg.outcombatstate then
|
||||
local guid = plate.parent:GetName(1) or ""
|
||||
|
||||
-- determine color based on combat state
|
||||
@@ -1087,7 +1079,7 @@ nameplates:RegisterEvent("ZONE_CHANGED_NEW_AREA")
|
||||
local rhp, rhpmax, estimated
|
||||
|
||||
-- Try Nampower first for real HP values via GUID
|
||||
local guid = hasNampower and plate.parent:GetName(1) or nil
|
||||
local guid = plate.parent:GetName(1)
|
||||
if guid and GetUnitField then
|
||||
local npHp = GetUnitField(guid, "health")
|
||||
local npMaxHp = GetUnitField(guid, "maxHealth")
|
||||
@@ -1137,11 +1129,11 @@ nameplates:RegisterEvent("ZONE_CHANGED_NEW_AREA")
|
||||
r, g, b, a = RAID_CLASS_COLORS[class].r, RAID_CLASS_COLORS[class].g, RAID_CLASS_COLORS[class].b, 1
|
||||
end
|
||||
|
||||
if hasNampower and unitstr and UnitIsTapped(unitstr) and not UnitIsTappedByPlayer(unitstr) then
|
||||
if unitstr and UnitIsTapped(unitstr) and not UnitIsTappedByPlayer(unitstr) then
|
||||
r, g, b, a = .5, .5, .5, .8
|
||||
end
|
||||
|
||||
if hasNampower and cfg.barcombatstate then
|
||||
if cfg.barcombatstate then
|
||||
local guid = plate.parent:GetName(1) or ""
|
||||
local color = GetCombatStateColor(guid)
|
||||
|
||||
@@ -1248,12 +1240,10 @@ nameplates:RegisterEvent("ZONE_CHANGED_NEW_AREA")
|
||||
local nameplate = frame.nameplate
|
||||
|
||||
-- Register GUID when plate becomes visible
|
||||
if hasNampower then
|
||||
local guid = frame:GetName(1)
|
||||
if guid then
|
||||
nameplate.cachedGuid = guid
|
||||
guidRegistry[guid] = frame
|
||||
end
|
||||
local guid = frame:GetName(1)
|
||||
if guid then
|
||||
nameplate.cachedGuid = guid
|
||||
guidRegistry[guid] = frame
|
||||
end
|
||||
|
||||
nameplates:OnDataChanged(nameplate)
|
||||
@@ -1264,15 +1254,13 @@ nameplates:RegisterEvent("ZONE_CHANGED_NEW_AREA")
|
||||
local now = state and state.now or GetTime()
|
||||
|
||||
-- Update GUID registry (lightweight, needed for event routing)
|
||||
if hasNampower then
|
||||
local guid = frame:GetName(1)
|
||||
if guid and guid ~= nameplate.cachedGuid then
|
||||
if nameplate.cachedGuid and guidRegistry[nameplate.cachedGuid] == frame then
|
||||
guidRegistry[nameplate.cachedGuid] = nil
|
||||
end
|
||||
nameplate.cachedGuid = guid
|
||||
guidRegistry[guid] = frame
|
||||
local guid = frame:GetName(1)
|
||||
if guid and guid ~= nameplate.cachedGuid then
|
||||
if nameplate.cachedGuid and guidRegistry[nameplate.cachedGuid] == frame then
|
||||
guidRegistry[nameplate.cachedGuid] = nil
|
||||
end
|
||||
nameplate.cachedGuid = guid
|
||||
guidRegistry[guid] = frame
|
||||
end
|
||||
|
||||
-- PERF: Intelligent throttling based on target/castbar status and plate count
|
||||
@@ -1407,7 +1395,7 @@ nameplates:RegisterEvent("ZONE_CHANGED_NEW_AREA")
|
||||
-- trigger update when name color changed (includes combat state check)
|
||||
local r, g, b = original.name:GetTextColor()
|
||||
local inCombatWithPlayer = false
|
||||
if hasNampower and cfg.namefightcolor then
|
||||
if cfg.namefightcolor then
|
||||
local guid = nameplate.cachedGuid
|
||||
if guid then
|
||||
inCombatWithPlayer = UnitAffectingCombat(guid) and UnitAffectingCombat("player")
|
||||
@@ -1513,25 +1501,11 @@ nameplates:RegisterEvent("ZONE_CHANGED_NEW_AREA")
|
||||
-- Use multiple checks for target detection (target variable, istarget flag, or zoomed state)
|
||||
local isTargetPlate = target or nameplate.istarget or (nameplate.health and nameplate.health.zoomed)
|
||||
if cfg.showcastbar and ( not cfg.targetcastbar or isTargetPlate ) then
|
||||
local unitstr = nil
|
||||
local targetGUID = nil
|
||||
|
||||
-- Get GUID for CastEvents lookup - use cached GUID when available
|
||||
if isTargetPlate then
|
||||
targetGUID = state and state.targetGuid
|
||||
if not targetGUID then
|
||||
local guid = GetUnitGUID("target")
|
||||
targetGUID = guid
|
||||
end
|
||||
end
|
||||
|
||||
-- Use cached GUID for non-target plates
|
||||
if not isTargetPlate then
|
||||
unitstr = nameplate.cachedGuid
|
||||
end
|
||||
|
||||
-- Check event-based cast cache first (use GUID)
|
||||
local castInfo = GetCastInfo(targetGUID) or (unitstr and GetCastInfo(unitstr))
|
||||
-- Always use the plate's own cachedGuid for the cast lookup.
|
||||
-- Never fall back to GetUnitGUID("target") - that would show a cast from
|
||||
-- a different mob just because it shares a name with the targeted plate.
|
||||
local unitstr = nameplate.cachedGuid
|
||||
local castInfo = unitstr and GetCastInfo(unitstr)
|
||||
|
||||
if castInfo and castInfo.spellID then
|
||||
-- Check if cast is still valid
|
||||
@@ -1575,72 +1549,45 @@ nameplates:RegisterEvent("ZONE_CHANGED_NEW_AREA")
|
||||
nameplate.castbar:Show()
|
||||
end
|
||||
else
|
||||
-- Fallback to API calls only when no GUID available (Nampower not tracking this unit)
|
||||
local channel, cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill
|
||||
|
||||
if targetGUID then
|
||||
-- We have a GUID but Nampower has no cast info.
|
||||
-- Fall back to API for the target plate (channels may not be in cast cache)
|
||||
if isTargetPlate and UnitExists("target") then
|
||||
cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill = UnitCastingInfo("target")
|
||||
if not cast then
|
||||
channel, nameSubtext, text, texture, startTime, endTime, isTradeSkill = UnitChannelInfo("target")
|
||||
end
|
||||
-- libcast provides pfGetCastInfo/pfGetChannelInfo for vanilla.
|
||||
-- Pass cachedGuid directly so it's always mob-specific, never name-based.
|
||||
if unitstr then
|
||||
local cast, _, _, texture, startTime, endTime = pfGetCastInfo(unitstr)
|
||||
local channel
|
||||
if not cast then
|
||||
channel, _, _, texture, startTime, endTime = pfGetChannelInfo(unitstr)
|
||||
end
|
||||
if not cast and not channel then
|
||||
|
||||
if cast or channel then
|
||||
local effect = cast or channel
|
||||
local duration = endTime - startTime
|
||||
local max = duration / 1000
|
||||
local cur = GetTime() - startTime / 1000
|
||||
if channel then cur = max + startTime / 1000 - GetTime() end
|
||||
|
||||
nameplate.castbar:SetMinMaxValues(0, max)
|
||||
nameplate.castbar:SetValue(cur)
|
||||
local remaining = channel and cur or (max - cur)
|
||||
if C.unitframes.castbardecimals == "1" then
|
||||
nameplate.castbar.text:SetText(floor(remaining * 10) / 10)
|
||||
else
|
||||
nameplate.castbar.text:SetText(string.format("%.2f", remaining))
|
||||
end
|
||||
if C.nameplates.spellname == "1" then
|
||||
nameplate.castbar.spell:SetText(effect)
|
||||
else
|
||||
nameplate.castbar.spell:SetText("")
|
||||
end
|
||||
if texture then
|
||||
nameplate.castbar.icon.tex:SetTexture(texture)
|
||||
nameplate.castbar.icon.tex:SetTexCoord(.1, .9, .1, .9)
|
||||
end
|
||||
nameplate.castbar:Show()
|
||||
else
|
||||
nameplate.castbar:Hide()
|
||||
end
|
||||
elseif isTargetPlate and UnitExists("target") then
|
||||
cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill = UnitCastingInfo("target")
|
||||
if not cast then
|
||||
channel, nameSubtext, text, texture, startTime, endTime, isTradeSkill = UnitChannelInfo("target")
|
||||
end
|
||||
elseif unitstr then
|
||||
local guid = GetUnitGUID(unitstr)
|
||||
local q = guid or unitstr
|
||||
cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill = UnitCastingInfo(q)
|
||||
if not cast then
|
||||
channel, nameSubtext, text, texture, startTime, endTime, isTradeSkill = UnitChannelInfo(q)
|
||||
end
|
||||
elseif name then
|
||||
cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill = UnitCastingInfo(name)
|
||||
if not cast then
|
||||
channel, nameSubtext, text, texture, startTime, endTime, isTradeSkill = UnitChannelInfo(name)
|
||||
end
|
||||
end
|
||||
|
||||
if not cast and not channel then
|
||||
nameplate.castbar:Hide()
|
||||
else
|
||||
local effect = cast or channel
|
||||
local duration = endTime - startTime
|
||||
local max = duration / 1000
|
||||
local cur = GetTime() - startTime / 1000
|
||||
|
||||
if channel then cur = max + startTime/1000 - GetTime() end
|
||||
|
||||
nameplate.castbar:SetMinMaxValues(0, duration/1000)
|
||||
nameplate.castbar:SetValue(cur)
|
||||
local remaining = max - cur
|
||||
if channel then remaining = cur end
|
||||
if C.unitframes.castbardecimals == "1" then
|
||||
nameplate.castbar.text:SetText(floor(remaining * 10) / 10)
|
||||
else
|
||||
nameplate.castbar.text:SetText(string.format("%.2f", remaining))
|
||||
end
|
||||
|
||||
if C.nameplates.spellname == "1" then
|
||||
nameplate.castbar.spell:SetText(effect)
|
||||
else
|
||||
nameplate.castbar.spell:SetText("")
|
||||
end
|
||||
|
||||
nameplate.castbar:Show()
|
||||
|
||||
if texture then
|
||||
nameplate.castbar.icon.tex:SetTexture(texture)
|
||||
nameplate.castbar.icon.tex:SetTexCoord(.1,.9,.1,.9)
|
||||
end
|
||||
nameplate.castbar:Hide()
|
||||
end
|
||||
end
|
||||
else
|
||||
|
||||
Reference in New Issue
Block a user