From 2ef9c5ee1ea52deadd2fce4f7bdd23003f1cf003 Mon Sep 17 00:00:00 2001 From: Meow <30401521+me0wg4ming@users.noreply.github.com> Date: Mon, 23 Mar 2026 10:47:42 +0100 Subject: [PATCH] Refactor nameplate cast tracking to be fully GUID-based MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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`. --- libs/libcast.lua | 18 +++-- libs/libpredict.lua | 10 +-- modules/afkcam.lua | 4 +- modules/castbar.lua | 14 ++-- modules/nameplates.lua | 173 ++++++++++++++--------------------------- 5 files changed, 84 insertions(+), 135 deletions(-) diff --git a/libs/libcast.lua b/libs/libcast.lua index 0b87df2a..d909f2aa 100644 --- a/libs/libcast.lua +++ b/libs/libcast.lua @@ -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 diff --git a/libs/libpredict.lua b/libs/libpredict.lua index fe18d157..da298a59 100644 --- a/libs/libpredict.lua +++ b/libs/libpredict.lua @@ -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 diff --git a/modules/afkcam.lua b/modules/afkcam.lua index a9d55d0d..25f651c4 100644 --- a/modules/afkcam.lua +++ b/modules/afkcam.lua @@ -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 diff --git a/modules/castbar.lua b/modules/castbar.lua index 7d22376d..1fb7fe2f 100644 --- a/modules/castbar.lua +++ b/modules/castbar.lua @@ -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 diff --git a/modules/nameplates.lua b/modules/nameplates.lua index b134ad56..b4567c03 100644 --- a/modules/nameplates.lua +++ b/modules/nameplates.lua @@ -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