You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

360 lines
15 KiB

local _,rematch = ...
local L = rematch.localization
local C = rematch.constants
rematch.targetInfo = {}
--[[
This gets information about npcIDs, mostly for notable targets, but some support for all npcIDs
Only three functions work for all npcIDs:
GetUnicNpcID(unit) -- returns a numeric npcID for the unit, either "target" or "mouseover" usually
GetNpcName(npcID) -- returns the name of the npcID (see comment on function; it returns C.CACHE_RETRIEVING
if the name is not returned and the call needs to happen again!)
GetTargetHistory() -- returns an ordered list of the last 3 npcIDs the player targeted
The rest of the functions are built around the 325+ notable targets:
AllTargets() -- iterator function to iterate over all npcIDs in the order they list
IsNotable(npcID) -- returns true if the npcID (or its redirected target) is in targetData
GetNpcInfo(npcID) -- returns the headerID,mapID,expansionID,questID for the given notable npcID
GetNpcPets(npcID) -- returns an ordered table of petInfo-usable battlepet:etc strings for the notable npcID
GetHeaderName(npcID) -- returns the name of the header (generally name of map used to group) for notable npcID
GetExpansionName(npcID) -- returns the name of the expansion for the notable npcID
GetQuestName(npcID) -- returns the name of the npcID's quest (nil if no quest or C.CACHE_RETRIEVING if not cached)
GetLocations(npcID) -- returns a \n-delimited list of map names associated with the notable npcID
]]
local targetIndexes = {} -- indexed by npcID, the index into targetData for that npcID
local targetNameCache = {} -- indexed by npcID, the localized name of the npcID
local targetsToCache = {} -- indexed by npcID, the number of cache attempts for this npcID
local reusedPets = {} -- reused table of pets to reduce garbage creation
local targetHistory = {}
local targetHistoryLookup = {} -- reusable table for target history cleanup
local wildPets = {} -- lookup by npcID, whether this is a wild pet
local testModel = CreateFrame("PlayerModel") -- used to get displayIDs, a hidden model to SetCreature(npcID) and GetDisplayInfo()
testModel:Hide()
rematch.targetInfo.recentTarget = nil -- the last npcID targeted (can be nil but is generally not nil'ed by dropping target)
rematch.targetInfo.currentTarget = nil -- the current npcID targeted (or nil if a player or no current target)
-- on login, populate targetIndexes with indexes into notableTargets for each npcID
rematch.events:Register(rematch.targetInfo,"PLAYER_LOGIN",function(self)
for index,info in ipairs(rematch.targetData.notableTargets) do
targetIndexes[info[2]] = index
end
-- if sometehing targeted while logging in, capture target
if UnitExists("target") then
self:PLAYER_TARGET_CHANGED()
end
end)
-- does maintenance on the targetHistory list of targeted npcIDs
local function cleanupTargetHistory()
-- first remove any duplicates, keeping only the topmost (most recent) distinct copy
wipe(targetHistoryLookup)
for i=#targetHistory,1,-1 do
local npcID = targetHistory[i]
if targetHistoryLookup[npcID] then
tremove(targetHistory,i)
else
targetHistoryLookup[npcID] = true
end
end
-- then if there's more than 3 (C.TARGET_HISTORY_SIZE) remove earlier ones so at most 3 remain
for i=1,(#targetHistory-C.TARGET_HISTORY_SIZE) do
tremove(targetHistory,1)
end
end
function rematch.targetInfo:PLAYER_TARGET_CHANGED()
if UnitExists("target") then
local npcID = rematch.targetInfo:GetUnitNpcID("target")
if npcID then
self.recentTarget = npcID
rematch.loadedTargetPanel.teamMode = C.ENEMY_TEAM
-- add npcID to history and come back in a while to do cleanup so list doesn't get huge
-- and we don't waste time doing maintenance in a PLAYER_TARGET_CHANGED
tinsert(targetHistory,npcID)
rematch.timer:Start(30,cleanupTargetHistory)
-- if the target is a wild pet that hasn't been saved to the wildPets lookup yet, add it
if not wildPets[npcID] and UnitIsWildBattlePet("target") then
wildPets[npcID] = UnitBattlePetSpeciesID("target")
end
end
self.currentTarget = npcID
else
self.currentTarget = nil
end
rematch.events:Fire("REMATCH_TARGET_CHANGED")
end
-- rematch.targetInfo.recentTarget should be registered first (before PLAYER_LOGIN) so it can define recentTarget
-- before anything else hears the target has changed
rematch.events:Register(rematch.targetInfo,"PLAYER_TARGET_CHANGED",rematch.targetInfo.PLAYER_TARGET_CHANGED)
-- sometimes this addon "targets" something via loadedTargetPanel:SetTarget(npcID); these should show in history also
function rematch.targetInfo:SetRecentTarget(npcID)
if not npcID then
self.recentTarget = nil
else
if type(npcID)=="string" then
npcID = rematch.targetInfo:GetNpcID(npcID)
end
if type(npcID)=="number" then
self.recentTarget = npcID
tinsert(targetHistory,npcID)
end
end
end
-- returns an ordered list of the 3 (C.TARGET_HISTORY_SIZE) most recent targets, with the most recent at the end of the list
function rematch.targetInfo:GetTargetHistory()
cleanupTargetHistory()
return targetHistory
end
-- returns the npcID of the given unit ("target"/"mouseover"), or nil if unit doesn't exist/is a player
function rematch.targetInfo:GetUnitNpcID(unit)
if UnitExists(unit) then
local npcID = tonumber((UnitGUID(unit) or ""):match(".-%-%d+%-%d+%-%d+%-%d+%-(%d+)"))
if npcID and npcID~=0 then
if rematch.targetData.redirects[npcID] then -- targeting a redirected target
return rematch.targetData.redirects[npcID] -- return redirected npcID
else
return npcID -- otherwise return the npcID
end
end
end
end
-- gets the localized name of an npcID from a tooltip scan. tooltip scans are computationally expensive so
-- it will cache the result and use that in future calls. if the npcID didn't get a name the npcID will be
-- added to targetsToCache and return "Retrieving name..." to display. in this case, the calling function
-- can wait a second and re-update to try for the name again. once the number of attempts are exceeded, it
-- will return "NPC <npcID>". (the goal here is to not cache all 300+ npcs on login. the calling function
-- should do a delayed update if the name returned is C.CACHE_RETRIEVING)
function rematch.targetInfo:GetNpcName(npcID,noDisplay)
if type(npcID)=="string" then
npcID = tonumber(npcID:match("target:(%d+)"))
end
-- if target has a subname like (Legendary) then it will be appended to name
local subname = ""
if rematch.targetData.subnames[npcID] then
subname = format(" (%s)",rematch.targetData.subnames[npcID])
end
if type(npcID)~="number" then
return L["No Target"]
elseif targetNameCache[npcID] then -- if name cached, return it
return targetNameCache[npcID]..subname
else
local tooltip = RematchTooltipScan or CreateFrame("GameTooltip","RematchTooltipScan",nil,"GameTooltipTemplate")
tooltip:SetOwner(UIParent,"ANCHOR_NONE")
tooltip:SetHyperlink(format("unit:Creature-0-0-0-0-%d-0000000000",npcID))
if tooltip:NumLines()>0 then
local name = RematchTooltipScanTextLeft1:GetText()
if name and name:len()>0 then
targetNameCache[npcID] = name
targetsToCache[npcID] = nil
return name..subname
end
end
-- if we reached here, then this npcID is still not cached
if not targetsToCache[npcID] then
targetsToCache[npcID] = GetTime()
end
if GetTime()-targetsToCache[npcID] < C.CACHE_TIMEOUT then -- haven't exceeded timeout duration, return temp name
if not noDisplay then -- if name wasn't cached and we're displaying it, come back in a bit and update UI (could be team or target list or elsewhere that needs update)
rematch.timer:Start(C.CACHE_WAIT,rematch.frame.Update)
end
return C.CACHE_RETRIEVING
else -- exceeded retry attempts, cache it as NPC <npcID> and give up trying
local name = format(L["%s (npc id %d)"],UNKNOWN,npcID)
targetNameCache[npcID] = name
targetsToCache[npcID] = nil
return name..subname
end
end
end
-- gets the displayID for the given npcID, by setting a model to that npcID and then getting its displayID from that
function rematch.targetInfo:GetNpcDisplayID(npcID)
if type(npcID)=="number" then
testModel:SetCreature(npcID)
return testModel:GetDisplayInfo()
end
end
--[[ notable npcs: the following only apply to the 300+ npcs in targetData ]]
-- iterator function to iterate over all npcIDs in order in targetData
-- usage: for npcID in rematch.targetInfo:AllTargets() do print(npcID) end
function rematch.targetInfo:AllTargets()
local i = 0
return function()
local targetData = rematch.targetData.notableTargets
i = i + 1
if i <= #targetData then
return targetData[i][2]
end
end
end
-- returns true if the npcID is in the notableTargets table (with redirect too)
function rematch.targetInfo:IsNotable(npcID)
return rematch.targetInfo:GetNpcInfo(npcID) and true
end
function rematch.targetInfo:IsWildPet(npcID)
return wildPets[npcID] and true or false
end
-- returns the headerID,mapID,expansionID,questID for the given notable npcID
function rematch.targetInfo:GetNpcInfo(npcID)
if type(npcID)=="string" then
npcID = tonumber(npcID:match("target:(%d+)"))
end
if not npcID then
return
end
if rematch.targetData.redirects[npcID] then
npcID = rematch.targetData.redirects[npcID]
end
if targetIndexes[npcID] then
local info = rematch.targetData.notableTargets[targetIndexes[npcID]]
--info[1] = "header:"..info[1] -- make header into a headerID usable in lists
return "header:"..info[1],info[3],info[4],info[5]
end
end
-- converts target:12345 to numeric 12345
function rematch.targetInfo:GetNpcID(targetID)
return type(targetID)=="string" and tonumber(targetID:match("target:(.+)")) or targetID
end
-- returns an ordered table of petInfo-usable battlepet:etc strings for the notable npc
-- if numSlots is defined, the table is padded with empty slots before the pet(s)
-- returns a single unnotable pet if the npc is not notable (use GetNumPets to get a real count)
function rematch.targetInfo:GetNpcPets(npcID,numSlots)
if type(npcID)=="string" then
npcID = tonumber(npcID:match("target:(%d+)"))
end
if not npcID then
return
end
if rematch.targetData.redirects[npcID] then
npcID = rematch.targetData.redirects[npcID]
end
wipe(reusedPets)
if targetIndexes[npcID] then -- if this is a notable npc, pets are known
local info = rematch.targetData.notableTargets[targetIndexes[npcID]]
for i=6,8 do
if info[i] then
tinsert(reusedPets,info[i])
end
end
elseif wildPets[npcID] then -- if not a notable npc but it is a seen wild pet, add a speciesID for the pet
tinsert(reusedPets,wildPets[npcID])
else -- otherwise add unobtainable:npcID petID
tinsert(reusedPets,"unnotable:"..npcID)
end
if numSlots then
for i=#reusedPets+1,(numSlots or 3) do
tinsert(reusedPets,1,"empty")
end
end
return reusedPets
end
-- returns the number of pets this npcID is known to have, 0 if not notable or no pets
function rematch.targetInfo:GetNumPets(npcID)
if type(npcID)=="string" then
npcID = tonumber(npcID:match("target:(%d+)"))
end
if not npcID then
return 0
end
if rematch.targetData.redirects[npcID] then
npcID = rematch.targetData.redirects[npcID]
end
if targetIndexes[npcID] then
return #rematch.targetData.notableTargets[targetIndexes[npcID]]-5
elseif wildPets[npcID] then
return 1 -- wild pets have 1 pet, the speciesID
else
return 0
end
end
-- gets the headerID of the given npcID
function rematch.targetInfo:GetHeaderID(npcID)
return rematch.targetInfo:GetNpcInfo(npcID)
end
-- returns the name of the header from the headerID (first return of GetNpcInfo)
function rematch.targetInfo:GetHeaderName(headerID)
local name = headerID:match("header:(.+)")
local mapID = tonumber(name)
if mapID then
local mapInfo = C_Map.GetMapInfo(mapID)
return mapInfo and mapInfo.name or UNKNOWN
else
return L[name]
end
end
-- returns the expansionID of the header
function rematch.targetInfo:GetHeaderExpansionID(headerID)
return headerID and rematch.targetData.headerExpansions[headerID]
end
-- returns the name of the expansion associated with the notable npcID
function rematch.targetInfo:GetExpansionName(npcID)
local _,_,expansionID = rematch.targetInfo:GetNpcInfo(npcID)
return _G["EXPANSION_NAME"..expansionID]
end
-- returns the name of the quest associated with the notable npcID, if any. (if a quest name hasn't been
-- cached yet, it will return nothing; todo: make it return CACHE_RETRIEVING with a timeout like name?)
function rematch.targetInfo:GetQuestName(npcID)
local _,_,_,questID = rematch.targetInfo:GetNpcInfo(npcID)
if questID then
local name = C_TaskQuest.GetQuestInfoByQuestID(questID) or C_QuestLog.GetTitleForQuestID(questID)
return name --or C.CACHE_RETRIEVING
end
end
function rematch.targetInfo:GetExpansionID(npcID)
local _,_,expansionID = rematch.targetInfo:GetNpcInfo(npcID)
return expansionID
end
-- for GetLocations(), a small recursive function to get a list of map names where mapID is
local exploredIDs = {} -- reused ordered list of map names in the following
local function exploreMapID(mapID)
local mapInfo = C_Map.GetMapInfo(mapID)
if mapInfo and mapInfo.name and mapID~=946 and mapID~=947 then
local prefix = #exploredIDs>0 and "\124cffa0a0a0" or ""
tinsert(exploredIDs,prefix..mapInfo.name)
exploreMapID(mapInfo.parentMapID)
end
end
-- returns a string of the map a notable npcID belongs to and its parent maps, up to but not
-- including cosmic/azeroth
function rematch.targetInfo:GetLocations(npcID)
local _,mapID = rematch.targetInfo:GetNpcInfo(npcID)
wipe(exploredIDs)
exploreMapID(mapID)
if #exploredIDs>0 then
return table.concat(exploredIDs,"\n")
else
return UNKNOWN
end
end