97e38d5c3d
release / release (push) Successful in 5s
- Extract AddCharacterTooltipHeader() + SetCharacterRowNameLevel() (Altoholic.lua);
nil-guards centralized, callers in AccountSummary/Activity/BagUsage/Skills/tooltip.
- Fix sites the manual sweep missed: Skills.lua (row + skill ranks), Keys.lua x3,
ShowClassIcons sort (Altoholic.lua:705, getters bypass their own or-0 via the wrapper).
- Restore login scan: OnPlayerAlive was ghost-only (fdcb25a) so iLvl never populated;
now scans once per session. Removed dated DEBUG leftovers.
380 lines
12 KiB
Lua
380 lines
12 KiB
Lua
--[[ *** DataStore_Inventory ***
|
|
Written by : Thaoky, EU-Marécages de Zangar
|
|
July 13th, 2009
|
|
--]]
|
|
if not DataStore then return end
|
|
|
|
local addonName = "DataStore_Inventory"
|
|
|
|
_G[addonName] = LibStub("AceAddon-3.0"):NewAddon(addonName, "AceConsole-3.0", "AceEvent-3.0", "AceComm-3.0", "AceSerializer-3.0")
|
|
|
|
local addon = _G[addonName]
|
|
|
|
local THIS_ACCOUNT = "Default"
|
|
local commPrefix = "DS_Inv" -- let's keep it a bit shorter than the addon name, this goes on a comm channel, a byte is a byte ffs :p
|
|
local L = LibStub("AceLocale-3.0"):GetLocale("DataStore_Inventory")
|
|
|
|
-- Message types
|
|
local MSG_SEND_AIL = 1 -- Send AIL at login
|
|
local MSG_AIL_REPLY = 2 -- reply
|
|
local MSG_EQUIPMENT_REQUEST = 3 -- request equipment ..
|
|
local MSG_EQUIPMENT_TRANSFER = 4 -- .. and send the data
|
|
|
|
local AddonDB_Defaults = {
|
|
global = {
|
|
Options = {
|
|
AutoClearGuildInventory = 0, -- Automatically clear guild members' inventory at login
|
|
BroadcastAiL = 1, -- Broadcast professions at login or not
|
|
EquipmentRequestNotification = 0, -- Get a warning when someone requests my equipment
|
|
},
|
|
Guilds = {
|
|
['*'] = { -- ["Account.Realm.Name"]
|
|
Members = {
|
|
['*'] = { -- ["MemberName"]
|
|
lastUpdate = nil,
|
|
averageItemLvl = 0,
|
|
Inventory = {}, -- 19 inventory slots, a simple table containing item id's or full item string if enchanted
|
|
}
|
|
}
|
|
},
|
|
},
|
|
Characters = {
|
|
['*'] = { -- ["Account.Realm.Name"]
|
|
lastUpdate = nil,
|
|
averageItemLvl = 0,
|
|
Inventory = {}, -- 19 inventory slots, a simple table containing item id's or full item string if enchanted
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
-- *** Utility functions ***
|
|
local function GetOption(option)
|
|
return addon.db.global.Options[option]
|
|
end
|
|
|
|
local function IsEnchanted(link)
|
|
if not link then return end
|
|
|
|
if not string.find(link, "0:0:0:0:0:0:0") then
|
|
-- enchants/jewels store values instead of zeroes in the link, if this string can't be found, there's at least one enchant/jewel
|
|
return true
|
|
end
|
|
end
|
|
|
|
local function GetThisGuild()
|
|
local guild = GetGuildInfo("player")
|
|
if guild then
|
|
local key = format("%s.%s.%s", THIS_ACCOUNT, GetRealmName(), guild)
|
|
return addon.db.global.Guilds[key]
|
|
end
|
|
end
|
|
|
|
local function GetMemberKey(guild, member)
|
|
-- returns the appropriate key to address a guild member.
|
|
-- Either it's a known alt ==> point to the characters table
|
|
-- Or it's a guild member ==> point to the guild table
|
|
local main = DataStore:GetNameOfMain(member)
|
|
if main and main == UnitName("player") then
|
|
local key = format("%s.%s.%s", THIS_ACCOUNT, GetRealmName(), member)
|
|
return addon.db.global.Characters[key]
|
|
end
|
|
return guild.Members[member]
|
|
end
|
|
|
|
local function GetAIL(alts)
|
|
-- alts = list of alts in the same guild, same realm, same account, pipe-delimited : "alt1|alt2|alt3..."
|
|
-- usually provided by the main datastore module, but can also be built manually
|
|
local out = {}
|
|
|
|
local character = DataStore:GetCharacter() -- this character
|
|
local ail = DataStore:GetAverageItemLevel(character) or 0 -- CoA: guard nil so the login AIL broadcast can't crash (mirrors the alt loop below)
|
|
table.insert(out, format("%s:%d", UnitName("player"), ail))
|
|
|
|
if strlen(alts) > 0 then
|
|
for _, name in pairs( { strsplit("|", alts) }) do -- then all his alts
|
|
character = DataStore:GetCharacter(name)
|
|
if character then
|
|
ail = DataStore:GetAverageItemLevel(character)
|
|
if ail then
|
|
table.insert(out, format("%s:%d", name, ail))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return table.concat(out, "|")
|
|
end
|
|
|
|
local function SaveAIL(sender, ailList)
|
|
local thisGuild = GetThisGuild()
|
|
if not thisGuild then return end
|
|
|
|
for _, ailChar in pairs( { strsplit("|", ailList) }) do -- "char:ail | char:ail | ..."
|
|
local name, ail = strsplit(":", ailChar)
|
|
if name and ail then
|
|
thisGuild.Members[name].averageItemLvl = tonumber(ail)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function GuildBroadcast(messageType, ...)
|
|
local serializedData = addon:Serialize(messageType, ...)
|
|
addon:SendCommMessage(commPrefix, serializedData, "GUILD")
|
|
end
|
|
|
|
local function GuildWhisper(player, messageType, ...)
|
|
if DataStore:IsGuildMemberOnline(player) then
|
|
local serializedData = addon:Serialize(messageType, ...)
|
|
addon:SendCommMessage(commPrefix, serializedData, "WHISPER", player)
|
|
end
|
|
end
|
|
|
|
local function ClearGuildInventories()
|
|
local thisGuild = GetThisGuild()
|
|
if thisGuild then
|
|
wipe(thisGuild.Members)
|
|
end
|
|
end
|
|
|
|
-- *** Scanning functions ***
|
|
local NUM_EQUIPMENT_SLOTS = 19
|
|
|
|
function ScanInventory()
|
|
local totalItemLevel = 0
|
|
local itemCount = 0
|
|
|
|
local inventory = addon.ThisCharacter.Inventory
|
|
wipe(inventory)
|
|
|
|
for i = 1, NUM_EQUIPMENT_SLOTS do
|
|
local link = GetInventoryItemLink("player", i)
|
|
if link then
|
|
if IsEnchanted(link) then -- if there's an enchant, save the full link
|
|
inventory[i] = link
|
|
else -- .. otherwise, only save the id
|
|
inventory[i] = tonumber(link:match("item:(%d+)"))
|
|
end
|
|
|
|
if (i ~= 4) and (i ~= 19) then -- InventorySlotId 4 = shirt, 19 = tabard, skip them
|
|
itemCount = itemCount + 1
|
|
totalItemLevel = totalItemLevel + tonumber(((select(4, GetItemInfo(link))) or 0))
|
|
end
|
|
end
|
|
end
|
|
|
|
addon.ThisCharacter.averageItemLvl = (itemCount > 0) and (totalItemLevel / itemCount) or 0 -- CoA: ungeared char => itemCount 0; avoid 0/0 nan
|
|
addon.ThisCharacter.lastUpdate = time()
|
|
end
|
|
|
|
-- *** Event Handlers ***
|
|
local hasScannedThisSession
|
|
local function OnPlayerAlive()
|
|
-- CoA: scan once at login. PLAYER_ALIVE also fires on resurrect / Feign-Death cancel where gear
|
|
-- is unchanged, so skip those. (The previous "only when ghost" gate also skipped login, so iLvl
|
|
-- never populated and UNIT_INVENTORY_CHANGED was the only scan path - see commit fdcb25a.)
|
|
if hasScannedThisSession then return end
|
|
hasScannedThisSession = true
|
|
ScanInventory()
|
|
end
|
|
|
|
local function OnUnitInventoryChanged()
|
|
ScanInventory()
|
|
end
|
|
|
|
-- ** Mixins **
|
|
local function _GetInventory(character)
|
|
return character.Inventory
|
|
end
|
|
|
|
local function _GetInventoryItem(character, slotID)
|
|
return character.Inventory[slotID]
|
|
end
|
|
|
|
local function _GetInventoryItemCount(character, searchedID)
|
|
local count = 0
|
|
for _, item in pairs(character.Inventory) do
|
|
if type(item) == "number" then -- saved as a number ? this is the itemID
|
|
if (item == searchedID) then
|
|
count = count + 1
|
|
end
|
|
elseif tonumber(item:match("item:(%d+)")) == searchedID then -- otherwise it's the item link
|
|
count = count + 1
|
|
end
|
|
end
|
|
return count
|
|
end
|
|
|
|
local function _GetAverageItemLevel(character)
|
|
return character.averageItemLvl or 0 -- CoA: chars known only via guild comm / not yet scanned have no iLvl; callers do arithmetic on it (Altoholic sort, AccountSummary format)
|
|
end
|
|
|
|
local sentRequests -- recently sent requests
|
|
|
|
local function _RequestGuildMemberEquipment(member)
|
|
-- requests the equipment of a given character (alt or main)
|
|
local player = UnitName("player")
|
|
local main = DataStore:GetNameOfMain(member)
|
|
if not main then -- player is offline, check if his equipment is in the DB
|
|
local thisGuild = GetThisGuild()
|
|
if thisGuild and thisGuild.Members[member] then -- player found
|
|
if thisGuild.Members[member].Inventory then -- equipment found
|
|
addon:SendMessage("DATASTORE_PLAYER_EQUIPMENT_RECEIVED", player, member)
|
|
return
|
|
end
|
|
end
|
|
end
|
|
|
|
if main == player then -- if player requests the equipment of one of own alts, process the request locally, using the network works fine, but let's save the traffic.
|
|
-- trigger the same event, _GetGuildMemberInventoryItem will take care of picking the data in the right place
|
|
addon:SendMessage("DATASTORE_PLAYER_EQUIPMENT_RECEIVED", player, member)
|
|
return
|
|
end
|
|
|
|
-- prevent spamming remote players with too many requests
|
|
sentRequests = sentRequests or {}
|
|
|
|
if sentRequests[main] and ((time() - sentRequests[main]) < 5) then -- if there's a known timestamp , and it was sent less than 5 seconds ago .. exit
|
|
return
|
|
end
|
|
|
|
sentRequests[main] = time() -- timestamp of the last request sent to this player
|
|
GuildWhisper(main, MSG_EQUIPMENT_REQUEST, member)
|
|
end
|
|
|
|
local function _GetGuildMemberInventoryItem(guild, member, slotID)
|
|
local character = GetMemberKey(guild, member)
|
|
|
|
if character then
|
|
return character.Inventory[slotID]
|
|
end
|
|
end
|
|
|
|
local function _GetGuildMemberAverageItemLevel(guild, member)
|
|
local character = GetMemberKey(guild, member)
|
|
|
|
if character then
|
|
return character.averageItemLvl
|
|
end
|
|
end
|
|
|
|
local PublicMethods = {
|
|
GetInventory = _GetInventory,
|
|
GetInventoryItem = _GetInventoryItem,
|
|
GetInventoryItemCount = _GetInventoryItemCount,
|
|
GetAverageItemLevel = _GetAverageItemLevel,
|
|
RequestGuildMemberEquipment = _RequestGuildMemberEquipment,
|
|
GetGuildMemberInventoryItem = _GetGuildMemberInventoryItem,
|
|
GetGuildMemberAverageItemLevel = _GetGuildMemberAverageItemLevel,
|
|
}
|
|
|
|
-- *** Guild Comm ***
|
|
local function OnGuildAltsReceived(self, sender, alts)
|
|
if sender == UnitName("player") and GetOption("BroadcastAiL") == 1 then -- if I receive my own list of alts in the same guild, same realm, same account..
|
|
GuildBroadcast(MSG_SEND_AIL, GetAIL(alts)) -- ..then broacast AIL
|
|
end
|
|
end
|
|
|
|
local GuildCommCallbacks = {
|
|
[MSG_SEND_AIL] = function(sender, ail)
|
|
local player = UnitName("player")
|
|
if sender ~= player then -- don't send back to self
|
|
local alts = DataStore:GetGuildMemberAlts(player) -- get my own alts
|
|
if alts and GetOption("BroadcastAiL") == 1 then
|
|
GuildWhisper(sender, MSG_AIL_REPLY, GetAIL(alts)) -- .. and send them back
|
|
end
|
|
end
|
|
SaveAIL(sender, ail)
|
|
end,
|
|
[MSG_AIL_REPLY] = function(sender, ail)
|
|
SaveAIL(sender, ail)
|
|
end,
|
|
[MSG_EQUIPMENT_REQUEST] = function(sender, character)
|
|
if GetOption("EquipmentRequestNotification") == 1 then
|
|
addon:Print(format(L["%s is inspecting %s"], sender, character))
|
|
end
|
|
|
|
local key = DataStore:GetCharacter(character) -- this realm, this account
|
|
if key then
|
|
GuildWhisper(sender, MSG_EQUIPMENT_TRANSFER, character, DataStore:GetInventory(key))
|
|
end
|
|
end,
|
|
[MSG_EQUIPMENT_TRANSFER] = function(sender, character, equipment)
|
|
local thisGuild = GetThisGuild()
|
|
if thisGuild then
|
|
thisGuild.Members[character].Inventory = equipment
|
|
thisGuild.Members[character].lastUpdate = time()
|
|
addon:SendMessage("DATASTORE_PLAYER_EQUIPMENT_RECEIVED", sender, character)
|
|
end
|
|
end,
|
|
}
|
|
|
|
function addon:OnInitialize()
|
|
addon.db = LibStub("AceDB-3.0"):New(addonName .. "DB", AddonDB_Defaults)
|
|
|
|
DataStore:RegisterModule(addonName, addon, PublicMethods)
|
|
DataStore:SetGuildCommCallbacks(commPrefix, GuildCommCallbacks)
|
|
|
|
DataStore:SetCharacterBasedMethod("GetInventory")
|
|
DataStore:SetCharacterBasedMethod("GetInventoryItem")
|
|
DataStore:SetCharacterBasedMethod("GetInventoryItemCount")
|
|
DataStore:SetCharacterBasedMethod("GetAverageItemLevel")
|
|
DataStore:SetGuildBasedMethod("GetGuildMemberInventoryItem")
|
|
DataStore:SetGuildBasedMethod("GetGuildMemberAverageItemLevel")
|
|
|
|
addon:RegisterMessage("DATASTORE_GUILD_ALTS_RECEIVED", OnGuildAltsReceived)
|
|
addon:RegisterComm(commPrefix, DataStore:GetGuildCommHandler())
|
|
end
|
|
|
|
function addon:OnEnable()
|
|
addon:RegisterEvent("PLAYER_ALIVE", OnPlayerAlive)
|
|
addon:RegisterEvent("UNIT_INVENTORY_CHANGED", OnUnitInventoryChanged)
|
|
addon:SetupOptions()
|
|
|
|
if GetOption("AutoClearGuildInventory") == 1 then
|
|
ClearGuildInventories()
|
|
end
|
|
end
|
|
|
|
function addon:OnDisable()
|
|
addon:UnregisterEvent("PLAYER_ALIVE")
|
|
addon:UnregisterEvent("UNIT_INVENTORY_CHANGED")
|
|
end
|
|
|
|
|
|
local PT = LibStub("LibPeriodicTable-3.1")
|
|
local BZ = LibStub("LibBabble-Zone-3.0"):GetUnstrictLookupTable()
|
|
local BB = LibStub("LibBabble-Boss-3.0"):GetUnstrictLookupTable()
|
|
|
|
local DataSources = {
|
|
"InstanceLoot",
|
|
"InstanceLootHeroic",
|
|
"CurrencyItems",
|
|
}
|
|
|
|
-- stays out of public methods for now
|
|
function addon:GetSource(searchedID)
|
|
local info, source
|
|
for _, v in pairs(DataSources) do
|
|
info, source = PT:ItemInSet(searchedID, v)
|
|
if source then
|
|
local _, instance, boss = strsplit(".", source) -- ex: "InstanceLoot.Gnomeregan.Techbot"
|
|
|
|
instance = BZ[instance] or instance
|
|
if v == "InstanceLootHeroic" then
|
|
instance = format("%s (%s)", instance, L["Heroic"])
|
|
elseif v == "CurrencyItems" then
|
|
-- for currency items, there will be no "boss" value, let's return the quantity instead
|
|
boss = info.."x"
|
|
end
|
|
|
|
if boss == "Trash Mobs" then
|
|
boss = L["Trash Mobs"]
|
|
else
|
|
boss = BB[boss] or boss
|
|
end
|
|
|
|
return instance, boss
|
|
end
|
|
end
|
|
end
|