diff --git a/libs/libunitscan.lua b/libs/libunitscan.lua index fed17d81..9133bdc6 100644 --- a/libs/libunitscan.lua +++ b/libs/libunitscan.lua @@ -2,37 +2,26 @@ setfenv(1, pfUI:GetEnvironment()) --[[ libunitscan ]]-- --- A pfUI library that detects and saves all kind of unit related informations. --- Such as level, class, elite-state and playertype. Each query causes the library --- to automatically scan for the target if not already existing. Player-data is --- persisted within the pfUI_playerDB where the mob data is a throw-away table. --- The automatic target scanner is only working for vanilla due to client limitations --- on further expansions. +-- A pfUI library that remembers the class, level and guild of every player it +-- sees: in the friends, guild, raid and party rosters, in /who results, and as +-- target, mouseover or nameplate. The data is persisted within pfUI_playerDB, +-- for the places that only have a player's name, such as chat links and +-- offline friends. Anything with a unit reads the unit itself instead. -- -- External functions: --- GetUnitInfo(name, active, isPlayer) --- Returns information of the given unitname. Returns nil if no match is found. --- When nothing is found and the active flag is set, the autoscanner will --- automatically pick it up and try to fill the missing entry by targetting the unit. --- Pass isPlayer=true/false to restrict the lookup to the players or mobs table --- (avoids name-collision false positives, e.g. a player and an NPC named Chromie). +-- GetUnitInfo(name) +-- Returns information of the given player name. Returns nil if the player +-- was never seen. -- --- class[String] - The class of the unit --- level[Number] - The level of the unit --- elite[String] - The elite state of the unit (See UnitClassification()) --- player[Boolean] - Returns true if unit is a player --- guild[String] - Returns guild name of unit is a player --- --- Internal functions: --- libunitscan:AddData(db, name, class, level, elite) --- Adds unit data to a given db. Where db should be either "players" or "mobs" +-- class[String] - The class of the player +-- level[Number] - The level of the player +-- guild[String] - The guild name of the player -- -- return instantly when another libunitscan is already active if pfUI.api.libunitscan then return end -local units = { players = {}, mobs = {} } -local queue = { } +local players = {} -- Feed (guid, name, classToken) into ClassicAPI's persistent name -- cache when the unit's GUID is resolvable. Roster-style packets @@ -46,28 +35,19 @@ local function RememberByUnit(unit, name, class) C_PlayerCache.RememberPlayer(guid, name, class, UnitRace(unit), UnitSex(unit)) end -function GetUnitInfo(name, active, isPlayer) - if isPlayer ~= false and units["players"][name] then - local ret = units["players"][name] - return ret.class, ret.level, ret.elite, true, ret.guild - elseif isPlayer ~= true and units["mobs"][name] then - local ret = units["mobs"][name] - return ret.class, ret.level, ret.elite, nil, ret.guild - elseif active then - queue[name] = true - libunitscan:Show() +function GetUnitInfo(name) + local ret = players[name] + if ret then + return ret.class, ret.level, ret.guild end end -local function AddData(db, name, class, level, elite, guild) - if not name or not db then return end - units[db] = units[db] or {} - units[db][name] = units[db][name] or {} - units[db][name].class = class or units[db][name].class - units[db][name].level = level or units[db][name].level - units[db][name].elite = elite or units[db][name].elite - units[db][name].guild = guild or units[db][name].guild - queue[name] = nil +local function AddData(name, class, level, guild) + if not name then return end + players[name] = players[name] or {} + players[name].class = class or players[name].class + players[name].level = level or players[name].level + players[name].guild = guild or players[name].guild end local libunitscan = CreateFrame("Frame", "pfUnitScan", UIParent) @@ -85,14 +65,14 @@ libunitscan:SetScript("OnEvent", function() if event == "PLAYER_ENTERING_WORLD" then -- load pfUI_playerDB - units.players = pfUI_playerDB + players = pfUI_playerDB -- update own character details local name = UnitName("player") local class = UnitClassBase("player") local level = UnitLevel("player") local guild = GetGuildInfo("player") - AddData("players", name, class, level, nil, guild) + AddData(name, class, level, guild) RememberByUnit("player", name, class) elseif event == "FRIENDLIST_UPDATE" then @@ -100,38 +80,38 @@ libunitscan:SetScript("OnEvent", function() local info = C_FriendList.GetFriendInfoByIndex(i) if info then local level = info.level > 0 and info.level or nil - AddData("players", info.name, info.classFilename, level) + AddData(info.name, info.classFilename, level) end end elseif event == "GUILD_ROSTER_UPDATE" then - local name, class, level, _, guild + local guild = GetGuildInfo("player") + local name, class, level, _ for i = 1, GetNumGuildMembers() do name, _, _, level, class = GetGuildRosterInfo(i) - guild = GetGuildInfo("player") class = L["class"][class] or nil - AddData("players", name, class, level, nil, guild) + AddData(name, class, level, guild) end elseif event == "RAID_ROSTER_UPDATE" then - local name, class, SubGroup, level, unit, _ + local name, class, level, unit, _ for i = 1, GetNumRaidMembers() do unit = "raid" .. i - name, _, SubGroup, level, class = GetRaidRosterInfo(i) + name, _, _, level, class = GetRaidRosterInfo(i) class = L["class"][class] or nil - AddData("players", name, class, level) + AddData(name, class, level) RememberByUnit(unit, name, class) end elseif event == "PARTY_MEMBERS_CHANGED" then - local name, class, level, unit, _, guild + local name, class, level, unit, guild for i = 1, GetNumPartyMembers() do unit = "party" .. i class = UnitClassBase(unit) name = UnitName(unit) level = UnitLevel(unit) guild = GetGuildInfo(unit) - AddData("players", name, class, level, nil, guild) + AddData(name, class, level, guild) RememberByUnit(unit, name, class) end @@ -139,7 +119,7 @@ libunitscan:SetScript("OnEvent", function() for i = 1, C_FriendList.GetNumWhoResults() do local info = C_FriendList.GetWhoInfo(i) if info then - AddData("players", info.fullName, info.filename, info.level, nil, info.fullGuildName) + AddData(info.fullName, info.filename, info.level, info.fullGuildName) end end @@ -147,55 +127,16 @@ libunitscan:SetScript("OnEvent", function() local scan = event == "PLAYER_TARGET_CHANGED" and "target" or event == "NAME_PLATE_UNIT_ADDED" and arg1 or "mouseover" - local name, class, level, elite, guild, _ - if UnitExists(scan) then - if UnitIsPlayer(scan) then - class = UnitClassBase(scan) - level = UnitLevel(scan) - -- UnitLevel returns -1 for unknown levels, don't overwrite known values - level = level > 0 and level or nil - name = UnitName(scan) - guild = GetGuildInfo(scan) - AddData("players", name, class, level, nil, guild) - RememberByUnit(scan, name, class) - else - class = UnitClassBase(scan) - elite = UnitClassification(scan) - level = UnitLevel(scan) - -- UnitLevel returns -1 for unknown levels, don't overwrite known values - level = level > 0 and level or nil - name = UnitName(scan) - guild = UnitSubName(scan) - AddData("mobs", name, class, level, elite, guild) - end + if UnitIsPlayer(scan) then + local name = UnitName(scan) + local class = UnitClassBase(scan) + -- UnitLevel returns -1 for unknown levels, don't overwrite known values + local level = UnitLevel(scan) + level = level > 0 and level or nil + AddData(name, class, level, GetGuildInfo(scan)) + RememberByUnit(scan, name, class) end end end) --- setup sound function switches -local SoundOn = PlaySound -local SoundOff = function() return end - -libunitscan:SetScript("OnUpdate", function() - -- don't scan when another unit is in target - if UnitExists("target") or UnitName("target") then return end - - local name = next(queue) - if name then - -- disable sound - _G.PlaySound = SoundOff - - -- try to target the unknown unit - TargetByName(name, true) - ClearTarget() - - -- enable sound again - _G.PlaySound = SoundOn - - queue[name] = nil - end - - this:Hide() -end) - pfUI.api.libunitscan = libunitscan diff --git a/modules/afkcam.lua b/modules/afkcam.lua index 72b1b6be..22948f2d 100644 --- a/modules/afkcam.lua +++ b/modules/afkcam.lua @@ -59,7 +59,10 @@ pfUI:RegisterModule("afkcam", function () if not this:IsVisible() then return end local info = _G.ChatTypeInfo[string.gsub(event,"CHAT_MSG_","")] if not info then return end - local class = GetUnitInfo(arg2 or "") + -- the engine name-queries a chat sender before the event fires, so the + -- sender's GUID (arg12) always resolves + local _, class + if arg12 and arg12 ~= "" then _, class = GetPlayerInfoByGUID(arg12) end local author, msg if class and class ~= UNKNOWN then local class_color = PFUI_CLASS_COLORS[class]:GenerateHexColorMarkup() diff --git a/modules/nameplates.lua b/modules/nameplates.lua index 4a6ce3b7..54deaa21 100644 --- a/modules/nameplates.lua +++ b/modules/nameplates.lua @@ -1040,9 +1040,7 @@ nameplates:RegisterUnitEvent("UNIT_SPELLCAST_NOT_INTERRUPTIBLE", "target", "focu local level = plate.original.level:IsShown() and plate.original.level:GetObjectType() == "FontString" and tonumber(plate.original.level:GetText()) or "??" -- reset per-unit cache when the plate is reassigned. Gate on GUID *and* - -- 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 - -- same name), which would leak a stale "PLAYER" hint into GetUnitInfo. + -- name — name alone misses pool reuse between same-named units. -- 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 @@ -1065,27 +1063,26 @@ nameplates:RegisterUnitEvent("UNIT_SPELLCAST_NOT_INTERRUPTIBLE", "target", "focu if event == "PLAYER_TARGET_CHANGED" then unitstr = nil end -- remove unitstr when it doesn't resolve to this plate's unit (stale istarget, - -- stale mouseover guid or a despawned unit). Must run before the cache fills - -- below -- an unrelated unitstr would poison cache.player/minion with the - -- *other* unit's answer, and the nil-gate would then never recompute it. + -- stale mouseover guid or a despawned unit) if unitstr and UnitName(unitstr) ~= name then unitstr = nil end - -- resolve player vs npc from plate's own unit so libunitscan can't return - -- a player record for an NPC sharing the same name (e.g. Chromie). Stored - -- as true/false/nil so it doubles as the GetUnitInfo hint. - if plate.cache.player == nil and unitstr then - plate.cache.player = UnitIsPlayer(unitstr) and true or false + -- the plate's own unit token says who it is; a player too high to show a + -- level keeps the one the player database last saw + local unit = plate.unit + local player, class, elite, guild, ulevel + if unit then + player = UnitIsPlayer(unit) + class = player and UnitClassBase(unit) + elite = UnitClassification(unit) + guild = player and GetGuildInfo(unit) or UnitSubName(unit) + ulevel = UnitLevel(unit) + if player and ulevel <= 0 then + local _ + _, ulevel = GetUnitInfo(name) + end end - if plate.cache.minion == nil and unitstr then - plate.cache.minion = UnitIsMinion(unitstr) and true or false - end - local class, ulevel, elite, player, guild - if not plate.cache.minion then - class, ulevel, elite, player, guild = GetUnitInfo(name, true, plate.cache.player) - end - if plate.cache.player ~= nil then player = plate.cache.player or nil end - -- Use database level ONLY if current level is ?? (fixes ?? after reload, but doesn't override visible levels) + -- Use the unit's level ONLY if current level is ?? (fixes ?? after reload, but doesn't override visible levels) local levelFromDB = false if level == "??" and ulevel and ulevel > 0 then level = ulevel @@ -1095,13 +1092,9 @@ nameplates:RegisterUnitEvent("UNIT_SPELLCAST_NOT_INTERRUPTIBLE", "target", "focu local unittype = GetUnitType(plate) local font_size = C.nameplates.use_unitfonts == "1" and C.global.font_unit_size or C.global.font_size - -- ignore players with npc names if plate level is lower than player level - if ulevel and ulevel > (level == "??" and -1 or level) then player = nil end - if player and unittype == "ENEMY_NPC" then unittype = "ENEMY_PLAYER" end if player and unittype == "FRIENDLY_NPC" then unittype = "FRIENDLY_PLAYER" end elite = plate.original.levelicon:IsShown() and not player and "boss" or elite - if not class and not plate.cache.minion then plate.wait_for_scan = true end -- skip data updates on invisible frames if not visible then return end @@ -1219,7 +1212,6 @@ nameplates:RegisterUnitEvent("UNIT_SPELLCAST_NOT_INTERRUPTIBLE", "target", "focu if cfg.showhp then local rhp, rhpmax, estimated - local unit = plate.unit if unit then local npHp = UnitHealth(unit) local npMaxHp = UnitHealthMax(unit) @@ -1491,7 +1483,6 @@ nameplates:RegisterUnitEvent("UNIT_SPELLCAST_NOT_INTERRUPTIBLE", "target", "focu local update local original = nameplate.original - local name = original.name:GetText() local mouseover = nameplate.cachedGuid and nameplate.cachedGuid == frameState.mouseoverGuid or nil -- trigger queued event update @@ -1575,16 +1566,6 @@ nameplates:RegisterUnitEvent("UNIT_SPELLCAST_NOT_INTERRUPTIBLE", "target", "focu update = true end - -- trigger update when unit was found. Pass the cached player hint so the - -- retry probes the same table OnDataChanged will read on the next pass — - -- otherwise an NPC sharing a name with a known player flips wait_for_scan - -- off here, then OnDataChanged re-sets it, every frame until the mob scan - -- lands. - if nameplate.wait_for_scan and not nameplate.cache.minion and GetUnitInfo(name, true, nameplate.cache.player) then - nameplate.wait_for_scan = nil - update = true - end - -- trigger update when name color changed (includes combat state check). -- Skipped once OnDataChanged owns the name colour (class-coloured friendly -- players), so the two writers cannot overwrite each other.