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