From 2d3b90a4dc5f8618b68e36f220221ee851d68637 Mon Sep 17 00:00:00 2001 From: Peter Eliasson Date: Sat, 30 Jul 2016 19:55:47 +0200 Subject: [PATCH] Rewrite of inspect module to use LibGroupInSpecT. Using a proven lib is likely better than creating a new one from scratch. LGIST also provides some nice features, such as syncing inspect data among raid members. --- src/inspect.lua | 347 ++++++++++++------------------------------------------- 1 file changed, 75 insertions(+), 272 deletions(-) diff --git a/src/inspect.lua b/src/inspect.lua index 002d331..ef93ae3 100644 --- a/src/inspect.lua +++ b/src/inspect.lua @@ -4,24 +4,27 @@ -- Contains the inspect module. The inspect module -- is used for getting additional information about -- players that can only be gotten by doing an inspection --- of them. These datas are item level and spec. +-- of them. -- --- The module has an inspectCache that is used to reduce --- the number of inspects required, as inspects are quite --- slow to perform. This cache is automatically added to --- for every successful inspect. --- --- The module tries to inspect the raid whenever the player --- enters a raid instance and clears the inspect cache when --- the player leaves the group. +-- The module uses LibGroupInSpecT for keeping track of +-- the group members and performing the inspects. A local +-- playerInfo table is kept, as we are also interested in +-- the item levels of the group members. +-- +-- The local playerInfo table is index by player guid and +-- may have a value for the following keys: +-- * itemLevel: Avg equiped item level of the player +-- * specName: Name of player's current spec +-- * specRole: Role of the player's current spec -- local addonName, addonTable = ... -- Global functions for faster access -local tinsert = tinsert; local floor = floor; -local wipe = wipe; + +-- LibGroupInSpecT, lib handling inspection of group members +local LGIST = LibStub:GetLibrary("LibGroupInSpecT-1.1") -- ItemUpgradeInfo, lib for information about item upgrades applied to items. local ItemUpgradeInfo = LibStub("LibItemUpgradeInfo-1.0") @@ -31,63 +34,19 @@ local addon = addonTable[1]; local inspect = addon:NewModule("inspect", "AceEvent-3.0", "AceTimer-3.0") addon.inspect = inspect; --- How many seconds before a cached inspect is considered invalid. -local INSPECT_CACHE_TIMEOUT = 60*60; - --- How many seconds before an attempted inspect is canceled. -local INSPECT_CANCEL_TIMEOUT = 1; - +-- Slots used for calculating item level of a player local INVENTORY_SLOT_NAMES = { "HeadSlot","NeckSlot","ShoulderSlot","BackSlot","ChestSlot","WristSlot", "HandsSlot","WaistSlot","LegsSlot","FeetSlot","Finger0Slot","Finger1Slot", "Trinket0Slot","Trinket1Slot","MainHandSlot","SecondaryHandSlot" } -local NOOP = function() end - --- UnitGUID("player") seems to not always be available here, --- so PLAYER_GUID is set in OnEnable. -local PLAYER_GUID = nil; - - --- A map from player GUID to an object: {itemLevel, specName, time} -local inspectCache = {}; - - --- Function returning true if the player is currently in a --- PVE instance. -local function isInPVEInstance() - local isInstance, instanceType = IsInInstance(); - return inInstance and (instanceType == "party" or instanceType == "raid") -end - --- Attempts to get the talent spec name of the provided unitName. --- The unitName must either be "player" or a unit currently being --- inspected. -local function getTalentSpec(unitName) - if unitName == "player" then - local spec = GetSpecialization(); - if spec and spec > 0 then - local _, name = GetSpecializationInfo(spec); - return name; - end - else - local spec = GetInspectSpecialization(unitName) - if spec and spec > 0 then - local role = GetSpecializationRoleByID(spec); - if role then - local _, name = GetSpecializationInfoByID(spec); - return name - end - end - end -end -- Attempts to get the item level of the provided unitName. -- The unitName must either be "player" or a unit currently being -- inspected. local function getItemLevel(unitName) - if unitName == "player" then + if unitName == "player" or UnitIsUnit(unitName, "player") then local _, equipped = GetAverageItemLevel(); return floor(equipped); else @@ -96,7 +55,6 @@ local function getItemLevel(unitName) local slotName = INVENTORY_SLOT_NAMES[i]; local slotId = GetInventorySlotInfo(slotName); local itemLink = GetInventoryItemLink(unitName, slotId); - if itemLink then itemLevel = ItemUpgradeInfo:GetUpgradedItemLevel(itemLink) if itemLevel and itemLevel > 0 then @@ -113,114 +71,35 @@ local function getItemLevel(unitName) end end --- Returns true if the inspectCache has an entry for the playerId --- specified. If the optional ignoreExpired flag is true, then --- this function will not check the time of the stored entry. -local function hasPlayerInspectCache(playerId, ignoreExpired) - if inspectCache[playerId] and ignoreExpired then - return true -- We have a cache, might be expired - elseif inspectCache[playerId] and inspectCache[playerId].time then - local isExpired = (GetTime() - inspectCache[playerId].time) > INSPECT_CACHE_TIMEOUT - local hasAllAttributes = inspectCache[playerId].specName and inspectCache[playerId].itemLevel; - - return (not isExpired and hasAllAttributes) - else - return false; -- No entry for player id - end -end - --- Removes any entry for the specified playerId from the --- inspectCache. -local function unsetPlayerInspectCache(playerId) - inspectCache[playerId] = nil; -end - --- Sets the entry for a player with the specified playerId to match --- the provided values. -local function setPlayerInspectCache(playerId, specName, itemLevel) - inspectCache[playerId] = {}; - inspectCache[playerId].specName = specName; - inspectCache[playerId].itemLevel = itemLevel; - if specName and itemLevel then - -- Dont set time if not all values are provided, as that would - -- make this entry valid and prevent inspections of the player - -- for the duration of the INSPECT_CACHE_TIMEOUT. - inspectCache[playerId].time = GetTime(); - end -end - --- Starts the timer for doing inspects, if not already started. -function inspect:StartNotifyInspectTimer() - if not self.notifyInspectTimer then - self:Debug("Starting notifyInspectTimer"); - - self.notifyInspectTimer = self:ScheduleRepeatingTimer(function() - self:OnNotifyInspectTimerDone() - end, INSPECT_CANCEL_TIMEOUT); - end -end - --- Stops the NotifyInspect timer, if not already stopped. -function inspect:StopNotifyInspectTimer() - if self.notifyInspectTimer then - self:Debug("Stopping notifyInspectTimer"); - - self:CancelTimer(self.notifyInspectTimer); - self.notifyInspectTimer = nil; - end -end - --- Adds a player object to the queue of players to inspect. --- The provided callback will be called with the result of the --- inspect when done. -function inspect:QueueInspect(player, callback) - self:Debug("QueueInspect", player.name) - - if not self.inspectQueue[player.id] then - self.inspectQueue[player.id] = {player = player, callbacks = {}}; - end - tinsert(self.inspectQueue[player.id].callbacks, callback); - - self:StartNotifyInspectTimer(); -end - --- Attempts to set fields on the player object by using --- cached data from the inspectCache. Returns true if data --- was available and not expired. False if no data could be --- found or the data was expired. Note that data will be added --- to the player object even if the data is expired. -function inspect:SetCachedInspectDataForPlayer(player) - if player.id == PLAYER_GUID then - player.specName = getTalentSpec("player") - player.itemLevel = getItemLevel("player"); - return true - elseif hasPlayerInspectCache(player.id, false) then - player.specName = inspectCache[player.id].specName; - player.itemLevel = inspectCache[player.id].itemLevel; - return true - elseif hasPlayerInspectCache(player.id, true) then - -- Expired cache, but better than nothing - player.specName = inspectCache[player.id].specName; - player.itemLevel = inspectCache[player.id].itemLevel; - return false - else - return false - end -end - -- Helper method for getting inspect data for a single player, -- calling the provided callback on success/error. function inspect:GetInspectDataForPlayer(player, callback) - -- Make sure we always have a callback - callback = callback and callback or NOOP; + local playerId = player.id; + local playerInfo = self.playerInfo[playerId] + + if playerInfo then + -- If the playerInfo object is missing expected values, + -- make sure that that player is queued for a re-inspect. + if not (playerInfo["specName"] and playerInfo["itemLevel"] and playerInfo["specRole"]) then + LGIST:Rescan(playerId) + end + + -- Set inspect-only data + player["specName"] = playerInfo["specName"]; + player["itemLevel"] = playerInfo["itemLevel"]; - if self:SetCachedInspectDataForPlayer(player) then - return callback() - elseif not CanInspect(player.name, false) then - return callback() + -- Add role from spec if role is not already a valid role + if not (role == "TANK" or role == "HEALER" or role == "DAMAGER") then + player["role"] = playerInfo["specRole"]; + end + + self:Debug("inspect:GetInspectDataForPlayer", "success", + player["specName"], player["itemLevel"], player["role"]); else - self:QueueInspect(player, callback); + self:Debug("inspect:GetInspectDataForPlayer", "fail"); end + + callback() end -- Takes a list of player objects and tries to get inspect data @@ -241,131 +120,55 @@ function inspect:GetInspectDataForPlayers(players, callback) end end --- Tries to pre-inspect all guild members of a raid group --- to populate the inspectCache for the players. -function inspect:PreInspectGroup() - self:Debug("PreInspectGroup") - - for i=1, GetNumGroupMembers() do - - local playerName = GetRaidRosterInfo(i); - if playerName and addon:IsInMyGuild(playerName) then - - local playerId = UnitGUID(playerName) - if playerId and playerId ~= PLAYER_GUID and not hasPlayerInspectCache(playerId) then - local player = {name = playerName, id = playerId} - self:QueueInspect(player, NOOP); - end - end - end -end - --- Resolves a pending inspect for playerId. If successful --- sets the player object's data to the new values. Calls --- all callbacks registered for this player id and removes --- the player id from the queue of inspects. -function inspect:ResolveInspect(playerId, success) - if not self.inspectQueue[playerId] then - return - end - - local player = self.inspectQueue[playerId].player; - local callbacks = self.inspectQueue[playerId].callbacks; - self.inspectQueue[playerId] = nil; - - self:Debug("ResolveInspect", player.name, (success and "success" or "fail")) - - if success then - if not hasPlayerInspectCache(player.id, false) then - local specName = getTalentSpec(player.name); - local itemLevel = getItemLevel(player.name); - setPlayerInspectCache(player.id, specName, itemLevel); - end - - player.specName = inspectCache[player.id].specName; - player.itemLevel = inspectCache[player.id].itemLevel; - - ClearInspectPlayer(); - end - - for _,callback in ipairs(callbacks) do - callback(); - end -end - --- Called once every INSPECT_CANCEL_TIMEOUT seconds. If a --- sent inspect request is currently pending this request will --- be canceld and considered failed. --- If any inspects are queued a request for a new inspect is --- sent. -function inspect:OnNotifyInspectTimerDone() - -- Timeout any current inspection - if self.currentInspectPlayerId then - self:ResolveInspect(self.currentInspectPlayerId, false); - self.currentInspectPlayerId = nil; - end +-- LGIST event for INSPECT_READY where we can perform our own +-- inspection handling. In our case, we need to try and grab +-- the item level here, as that is not provided by LGIST. +function inspect:GroupInSpecT_InspectReady(evt, guid, unit) + self.playerInfo[guid] = self.playerInfo[guid] or {}; + local playerInfo = self.playerInfo[guid]; - local playerId, inspectData = next(self.inspectQueue); - if playerId then - NotifyInspect(inspectData.player.name); - self.currentInspectPlayerId = playerId; - else - self:StopNotifyInspectTimer(); + -- Getting the item level is unreliable, as it requires + -- proximity to the target (among other things?). Because + -- of that we keep the old itemLevel if we can not get a + -- new one. This might mean that we don't pick up on all + -- item changes. However, as LGIST might re-inspect players + -- frequently, we instead optimize for the case where players + -- do not change their item levels. + local itemLevel = getItemLevel(unit); + if itemLevel then + playerInfo["itemLevel"] = itemLevel; end -end -function inspect:INSPECT_READY(evt, GUID) - if self.currentInspectPlayerId == GUID then - self:ResolveInspect(self.currentInspectPlayerId, true) - self.currentInspectPlayerId = nil; - end + self:Debug("inspect:GroupInSpecT_InspectReady", unit, itemLevel, playerInfo["itemLevel"]) end -function inspect:GROUP_ROSTER_UPDATE(evt) - if not IsInGroup() then - self:Debug("Left group, wiping inspect cache"); - wipe(inspectCache); - wipe(inspect.inspectQueue); - inspect.currentInspectPlayerId = nil; - end -end +-- LGIST event for when info for a player is ready or has been modified. +function inspect:GroupInSpecT_Update(evt, guid, unit, info) + self.playerInfo[guid] = self.playerInfo[guid] or {}; + local playerInfo = self.playerInfo[guid]; -function inspect:ZONE_CHANGED_NEW_AREA(evt) - if isInPVEInstance() then - -- We just zoned into an instance, try pre-inspecting the group - self:PreInspectGroup(); - end + -- Copy data we are interested in + playerInfo["specName"] = info.spec_name_localized; + playerInfo["specRole"] = info.spec_role; end -function inspect:PLAYER_SPECIALIZATION_CHANGED(evt, unitId) - if unitId and unitId ~= "player" then - local playerId = UnitGUID(unitId); - unsetPlayerInspectCache(playerId); - end +-- LGIST event for when a member leaves the group. +function inspect:GroupInSpecT_Remove(evt, guid) + self.playerInfo[guid] = nil; end function inspect:OnEnable() - PLAYER_GUID = UnitGUID("player"); + self.playerInfo = {}; - inspect.inspectQueue = {}; - inspect.notifyInspectTimer = nil; - inspect.currentInspectPlayerId = nil; - - self:RegisterEvent("INSPECT_READY"); - self:RegisterEvent("GROUP_ROSTER_UPDATE"); - self:RegisterEvent("ZONE_CHANGED_NEW_AREA"); - self:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED"); + LGIST.RegisterCallback(self, "GroupInSpecT_Update"); + LGIST.RegisterCallback(self, "GroupInSpecT_Remove"); + LGIST.RegisterCallback(self, "GroupInSpecT_InspectReady"); end function inspect:OnDisable() - self:UnregisterEvent("INSPECT_READY"); - self:UnregisterEvent("GROUP_ROSTER_UPDATE"); - self:UnregisterEvent("ZONE_CHANGED_NEW_AREA"); - self:UnregisterEvent("PLAYER_SPECIALIZATION_CHANGED"); - - self:StopNotifyInspectTimer(); + LGIST.UnregisterCallback(self, "GroupInSpecT_Update"); + LGIST.UnregisterCallback(self, "GroupInSpecT_Remove"); + LGIST.UnregisterCallback(self, "GroupInSpecT_InspectReady"); - wipe(inspectCache); - wipe(inspect.inspectQueue); - inspect.currentInspectPlayerId = nil; -end \ No newline at end of file + self.playerInfo = nil; +end -- 1.7.9.5