338 lines
10 KiB
Lua
338 lines
10 KiB
Lua
--------------------------------------------------------------------------------
|
|
-- S-Frames: Offline DB for Bag Module (Bags/Offline.lua)
|
|
-- Tracks inventory across characters and realms
|
|
-- NOTE: This file defines SFrames.Bags first - must be loaded before other Bag files
|
|
--------------------------------------------------------------------------------
|
|
|
|
SFrames.Bags = SFrames.Bags or {}
|
|
SFrames.Bags.Offline = {}
|
|
|
|
local offlineFrame = CreateFrame("Frame")
|
|
offlineFrame:RegisterEvent("PLAYER_LOGIN")
|
|
offlineFrame:RegisterEvent("BAG_UPDATE")
|
|
offlineFrame:RegisterEvent("BANKFRAME_OPENED")
|
|
offlineFrame:RegisterEvent("BANKFRAME_CLOSED")
|
|
offlineFrame:RegisterEvent("PLAYERBANKSLOTS_CHANGED")
|
|
offlineFrame:RegisterEvent("PLAYERBANKBAGSLOTS_CHANGED")
|
|
|
|
local realmName = ""
|
|
local playerName = ""
|
|
local isBankOpen = false
|
|
|
|
local function QueueBankRefreshScans()
|
|
local elapsed = 0
|
|
local index = 1
|
|
local points = { 0.08, 0.25, 0.55, 0.95 }
|
|
local t = CreateFrame("Frame")
|
|
t:SetScript("OnUpdate", function()
|
|
elapsed = elapsed + (arg1 or 0)
|
|
if index <= table.getn(points) and elapsed >= points[index] then
|
|
if isBankOpen then
|
|
SFrames.Bags.Offline:ScanBags()
|
|
end
|
|
index = index + 1
|
|
end
|
|
if index > table.getn(points) then
|
|
this:SetScript("OnUpdate", nil)
|
|
end
|
|
end)
|
|
end
|
|
|
|
local function InitDB()
|
|
if not SFramesGlobalDB then SFramesGlobalDB = {} end
|
|
if not SFramesGlobalDB[realmName] then SFramesGlobalDB[realmName] = {} end
|
|
if not SFramesGlobalDB[realmName][playerName] then
|
|
SFramesGlobalDB[realmName][playerName] = {
|
|
bags = {},
|
|
bank = {},
|
|
bankSlots = 0,
|
|
bankBags = {},
|
|
equippedBags = {},
|
|
money = 0
|
|
}
|
|
end
|
|
end
|
|
|
|
local function IsBankBagInvSlot(slotID)
|
|
return type(slotID) == "number" and slotID > 23
|
|
end
|
|
|
|
local function IsBagItemLink(link)
|
|
if type(link) ~= "string" or link == "" then
|
|
return false
|
|
end
|
|
local _, _, _, _, _, _, _, equipLoc = GetItemInfo(link)
|
|
return equipLoc == "INVTYPE_BAG"
|
|
end
|
|
|
|
local function GetBagLinkState(link)
|
|
if type(link) ~= "string" or link == "" then
|
|
return "invalid"
|
|
end
|
|
local _, _, _, _, _, _, _, equipLoc = GetItemInfo(link)
|
|
if equipLoc == "INVTYPE_BAG" then
|
|
return "bag"
|
|
end
|
|
if equipLoc == nil or equipLoc == "" then
|
|
return "unknown"
|
|
end
|
|
return "invalid"
|
|
end
|
|
|
|
local function SafeGetInventorySlotByName(slotName)
|
|
if not GetInventorySlotInfo then return nil end
|
|
local ok, slotID = pcall(function() return GetInventorySlotInfo(slotName) end)
|
|
if ok and IsBankBagInvSlot(slotID) then
|
|
return slotID
|
|
end
|
|
return nil
|
|
end
|
|
|
|
local function ResolvePlayerBagInvSlot(index)
|
|
if type(index) ~= "number" or index < 1 or index > 4 then
|
|
return nil
|
|
end
|
|
|
|
local liveBtn = _G["CharacterBag" .. (index - 1) .. "Slot"]
|
|
if liveBtn and liveBtn.GetID then
|
|
local id = liveBtn:GetID()
|
|
if type(id) == "number" and id > 0 then
|
|
return id
|
|
end
|
|
end
|
|
|
|
if ContainerIDToInventoryID then
|
|
local ok, slotID = pcall(function() return ContainerIDToInventoryID(index) end)
|
|
if ok and type(slotID) == "number" and slotID > 0 then
|
|
return slotID
|
|
end
|
|
end
|
|
|
|
local slotNames = {
|
|
"Bag" .. (index - 1) .. "Slot",
|
|
"Bag" .. index .. "Slot",
|
|
"CharacterBag" .. (index - 1) .. "Slot",
|
|
}
|
|
for _, slotName in ipairs(slotNames) do
|
|
local ok, slotID = pcall(function() return GetInventorySlotInfo(slotName) end)
|
|
if ok and type(slotID) == "number" and slotID > 0 then
|
|
return slotID
|
|
end
|
|
end
|
|
|
|
-- Vanilla fallback.
|
|
local fallback = { [1] = 20, [2] = 21, [3] = 22, [4] = 23 }
|
|
return fallback[index]
|
|
end
|
|
|
|
local function CaptureEquippedBagMeta(db)
|
|
if not db then return end
|
|
|
|
db.equippedBags = {}
|
|
for index = 1, 4 do
|
|
local invSlot = ResolvePlayerBagInvSlot(index)
|
|
local link = nil
|
|
local texture = nil
|
|
if invSlot then
|
|
link = GetInventoryItemLink("player", invSlot)
|
|
texture = GetInventoryItemTexture("player", invSlot)
|
|
end
|
|
|
|
local size = GetContainerNumSlots(index) or 0
|
|
db.equippedBags[index] = {
|
|
link = link,
|
|
texture = texture,
|
|
size = tonumber(size) or 0,
|
|
}
|
|
end
|
|
end
|
|
|
|
local function ResolveBankBagInvSlot(index)
|
|
if type(index) ~= "number" or index <= 0 then return nil end
|
|
local bagID = index + 4
|
|
local function TryBankButtonID(buttonID, isBank)
|
|
if not BankButtonIDToInvSlotID then return nil end
|
|
local ok, slotID = pcall(function() return BankButtonIDToInvSlotID(buttonID, isBank) end)
|
|
if ok and IsBankBagInvSlot(slotID) then
|
|
return slotID
|
|
end
|
|
return nil
|
|
end
|
|
|
|
local function AcceptIfBag(slotID)
|
|
if not IsBankBagInvSlot(slotID) then return nil end
|
|
local link = GetInventoryItemLink("player", slotID)
|
|
if link and IsBagItemLink(link) then
|
|
return slotID
|
|
end
|
|
return nil
|
|
end
|
|
|
|
-- Guda-style primary mapping
|
|
local slot = TryBankButtonID(bagID, 1)
|
|
if slot then return slot end
|
|
slot = TryBankButtonID(index, 1)
|
|
if slot then return slot end
|
|
|
|
-- Fallbacks: only when they are confirmed bag items
|
|
slot = TryBankButtonID(bagID, nil)
|
|
slot = AcceptIfBag(slot)
|
|
if slot then return slot end
|
|
|
|
slot = TryBankButtonID(index, nil)
|
|
slot = AcceptIfBag(slot)
|
|
if slot then return slot end
|
|
|
|
if ContainerIDToInventoryID then
|
|
local ok, s = pcall(function() return ContainerIDToInventoryID(bagID) end)
|
|
if ok then
|
|
slot = AcceptIfBag(s)
|
|
if slot then return slot end
|
|
end
|
|
end
|
|
|
|
slot = AcceptIfBag(SafeGetInventorySlotByName("BankBagSlot" .. index))
|
|
if slot then return slot end
|
|
slot = AcceptIfBag(SafeGetInventorySlotByName("BankSlot" .. index))
|
|
if slot then return slot end
|
|
|
|
local liveBtn = _G["BankFrameBag" .. index] or _G["BankFrameBag" .. index .. "Slot"]
|
|
if liveBtn and liveBtn.GetID then
|
|
slot = AcceptIfBag(liveBtn:GetID())
|
|
if slot then return slot end
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
local function CaptureBankBagMeta(db)
|
|
if not db then return end
|
|
|
|
local purchased = (GetNumBankSlots and GetNumBankSlots()) or 0
|
|
db.bankSlots = tonumber(purchased) or 0
|
|
db.bankBags = {}
|
|
|
|
for index = 1, 7 do
|
|
local bagID = index + 4
|
|
local size = GetContainerNumSlots(bagID) or 0
|
|
local info = {
|
|
unlocked = (index <= db.bankSlots),
|
|
size = tonumber(size) or 0,
|
|
link = nil,
|
|
texture = nil,
|
|
}
|
|
|
|
if info.unlocked then
|
|
local invSlot = ResolveBankBagInvSlot(index)
|
|
if invSlot then
|
|
local link = GetInventoryItemLink("player", invSlot)
|
|
local state = GetBagLinkState(link)
|
|
if (state == "bag") or (state == "unknown" and info.size > 0) then
|
|
info.link = link
|
|
info.texture = GetInventoryItemTexture("player", invSlot)
|
|
end
|
|
end
|
|
end
|
|
|
|
db.bankBags[index] = info
|
|
end
|
|
end
|
|
|
|
local function ScanContainer(containerID, isBank)
|
|
if not SFramesGlobalDB or realmName == "" or playerName == "" then return end
|
|
local db = SFramesGlobalDB[realmName][playerName]
|
|
local targetDB = isBank and db.bank or db.bags
|
|
local size = GetContainerNumSlots(containerID)
|
|
targetDB[containerID] = { size = size, items = {} }
|
|
for slot = 1, size do
|
|
local link = GetContainerItemLink(containerID, slot)
|
|
if link then
|
|
local texture, itemCount, locked, quality = GetContainerItemInfo(containerID, slot)
|
|
local _, _, idStr = string.find(link, "item:(%d+):")
|
|
local itemID = idStr and tonumber(idStr) or nil
|
|
targetDB[containerID].items[slot] = { id = itemID,
|
|
link = link,
|
|
count = itemCount,
|
|
texture = texture,
|
|
quality = quality
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
function SFrames.Bags.Offline:ScanBags()
|
|
if realmName == "" or playerName == "" then return end
|
|
InitDB()
|
|
local db = SFramesGlobalDB[realmName] and SFramesGlobalDB[realmName][playerName]
|
|
if not db then return end
|
|
|
|
for bag = 0, 4 do
|
|
ScanContainer(bag, false)
|
|
end
|
|
CaptureEquippedBagMeta(db)
|
|
if isBankOpen then
|
|
ScanContainer(-1, true)
|
|
for bag = 5, 11 do
|
|
ScanContainer(bag, true)
|
|
end
|
|
CaptureBankBagMeta(db)
|
|
end
|
|
db.money = GetMoney()
|
|
end
|
|
|
|
local scanPending = false
|
|
local scanTimer = CreateFrame("Frame")
|
|
scanTimer:Hide()
|
|
local function RequestBagScan()
|
|
if scanPending then return end
|
|
scanPending = true
|
|
scanTimer.elapsed = 0
|
|
scanTimer:Show()
|
|
scanTimer:SetScript("OnUpdate", function()
|
|
this.elapsed = this.elapsed + arg1
|
|
if this.elapsed > 0.5 then
|
|
this:SetScript("OnUpdate", nil)
|
|
this:Hide()
|
|
scanPending = false
|
|
SFrames.Bags.Offline:ScanBags()
|
|
end
|
|
end)
|
|
end
|
|
|
|
offlineFrame:SetScript("OnEvent", function()
|
|
if event == "PLAYER_LOGIN" then
|
|
realmName = GetRealmName() or ""
|
|
playerName = UnitName("player") or ""
|
|
InitDB()
|
|
SFrames.Bags.Offline:ScanBags()
|
|
elseif event == "BAG_UPDATE" or event == "PLAYERBANKSLOTS_CHANGED" or event == "PLAYERBANKBAGSLOTS_CHANGED" then
|
|
RequestBagScan()
|
|
elseif event == "BANKFRAME_OPENED" then
|
|
isBankOpen = true
|
|
RequestBagScan()
|
|
QueueBankRefreshScans()
|
|
elseif event == "BANKFRAME_CLOSED" then
|
|
isBankOpen = false
|
|
end
|
|
end)
|
|
|
|
function SFrames.Bags.Offline:GetCharacterList()
|
|
if not SFramesGlobalDB or realmName == "" then return {} end
|
|
local chars = {}
|
|
if SFramesGlobalDB[realmName] then
|
|
for name in pairs(SFramesGlobalDB[realmName]) do
|
|
table.insert(chars, name)
|
|
end
|
|
end
|
|
return chars
|
|
end
|
|
|
|
function SFrames.Bags.Offline:GetCharacterData(name)
|
|
if not SFramesGlobalDB or realmName == "" then return nil end
|
|
return SFramesGlobalDB[realmName] and SFramesGlobalDB[realmName][name]
|
|
end
|
|
|
|
function SFrames.Bags.Offline:GetCurrentPlayerName()
|
|
return playerName
|
|
end
|