Repository navigation
Expand file tree
/
Copy pathScanner.lua
More file actions
202 lines (185 loc) · 7.09 KB
/
Copy pathScanner.lua
File metadata and controls
202 lines (185 loc) · 7.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
-- Alts Forever scanner: keeps the current character's bags, bank and equipped items up to date.
-- Stores itemID -> count for tooltips, and each bag's and bank tab's slots for the bags
-- window (Bags.lua), refilling the same tables so scans allocate nothing.
local _, ns = ...
if ns.disabled then return end -- another copy of Alts Forever is running (Core.lua)
local wipe, pcall, time = wipe, pcall, time
local issecretvalue = issecretvalue or function() return false end
local C_Container, C_Item = C_Container, C_Item
local GetContainerNumSlots = C_Container.GetContainerNumSlots
local GetContainerItemID = C_Container.GetContainerItemID
local GetInventoryItemID = GetInventoryItemID
-- Returns itemID, stackCount for a bag slot. Prefers one reused ItemLocation over
-- GetContainerItemInfo, which builds a new table for every slot.
local ReadSlot
if ItemLocation and ItemLocation.CreateEmpty and C_Item.GetStackCount then
local loc = ItemLocation:CreateEmpty()
local GetStackCount = C_Item.GetStackCount
ReadSlot = function(bag, slot)
local id = GetContainerItemID(bag, slot)
if id then
loc:SetBagAndSlot(bag, slot)
return id, GetStackCount(loc)
end
end
else
local GetContainerItemInfo = C_Container.GetContainerItemInfo
ReadSlot = function(bag, slot)
local info = GetContainerItemInfo(bag, slot)
if info then return info.itemID, info.stackCount end
end
end
-- Bags carried on the character. Keyring and ReagentBag only if this client has them.
local BAGS, BAG_SET = {}, {}
local BagIndex = Enum.BagIndex
for _, name in ipairs({ "Backpack", "Bag_1", "Bag_2", "Bag_3", "Bag_4", "ReagentBag", "Keyring" }) do
local bag = BagIndex[name]
if bag then
BAGS[#BAGS + 1] = bag
BAG_SET[bag] = true
end
end
-- Character bank tabs. Unpurchased tabs report 0 slots, so listing all of them is safe.
local BANK_BAGS, BANK_SET = {}, {}
for i = 1, 9 do
local bag = BagIndex["CharacterBankTab_" .. i]
if bag then
BANK_BAGS[#BANK_BAGS + 1] = bag
BANK_SET[bag] = true
end
end
-- Equipped gear, plus the bags themselves (they sit in inventory slots too).
local EQUIP_SLOTS = {}
local BAG_INV = {} -- bag -> the inventory slot its bag item sits in
for slot = INVSLOT_FIRST_EQUIPPED or 1, INVSLOT_LAST_EQUIPPED or 19 do
EQUIP_SLOTS[#EQUIP_SLOTS + 1] = slot
end
for _, name in ipairs({ "Bag_1", "Bag_2", "Bag_3", "Bag_4", "ReagentBag" }) do
local bag = BagIndex[name]
local ok, invSlot = pcall(C_Container.ContainerIDToInventoryID, bag)
if bag and ok and invSlot then
EQUIP_SLOTS[#EQUIP_SLOTS + 1] = invSlot
BAG_INV[bag] = invSlot
end
end
-- A slot in the saved layout: one number, itemID * 1000 + stack size (Classic stacks
-- stay under 1000), or 0 when empty. Numbers only, so a layout costs no strings.
local STACK = 1000
function ns.SlotValue(id, count)
if count >= STACK then count = STACK - 1 end
return id * STACK + count
end
function ns.SlotItem(value)
if not value or value == 0 then return nil end
return math.floor(value / STACK), value % STACK
end
-- Counts every item in the bags into out; with layout, also records each bag's slots
-- in layout[bag] (an array as long as the bag; a bag with no slots is removed), and
-- at index 0 the bag item itself, for its name in the bags window.
function ns.ScanContainers(bags, out, layout)
wipe(out)
for i = 1, #bags do
local bag = bags[i]
local size = GetContainerNumSlots(bag) or 0
local slots
if layout then
slots = layout[bag]
if size == 0 then
layout[bag] = nil
elseif not slots then
slots = {}
layout[bag] = slots
end
end
for slot = 1, size do
local id, count = ReadSlot(bag, slot)
-- A secret value would break the whole SavedVariables write, and even
-- testing one for truth throws, so check before anything else.
if id and not issecretvalue(count) and count then
out[id] = (out[id] or 0) + count
if slots then slots[slot] = ns.SlotValue(id, count) end
elseif slots then
slots[slot] = 0
end
end
-- The bag got smaller (swapped for a smaller one).
if slots then
local inv = BAG_INV[bag]
if inv then slots[0] = GetInventoryItemID("player", inv) or slots[0] end
for slot = #slots, size + 1, -1 do slots[slot] = nil end
end
end
end
function ns.ScanEquip(out)
wipe(out)
for i = 1, #EQUIP_SLOTS do
local id = GetInventoryItemID("player", EQUIP_SLOTS[i])
if id then out[id] = (out[id] or 0) + 1 end
end
end
-- The bags window's lists, in display order (Bags.lua).
ns.CARRIED_BAGS, ns.BANK_TABS = BAGS, BANK_BAGS
function ns.StartScanner()
local char = ns.char
local bagsDirty, bankDirty, bankOpen = false, false, false
local function ScanBags()
bagsDirty = false
char.layout = char.layout or {}
ns.ScanContainers(BAGS, char.bags, char.layout)
ns.version = ns.version + 1
ns.BagsWindowChanged()
end
-- Bank contents can only be read while the bank is open; outside that the tabs
-- can report as empty, which must not overwrite what we saw last time.
local function ScanBank()
bankDirty = false
char.bank = char.bank or {}
char.layout = char.layout or {}
ns.ScanContainers(BANK_BAGS, char.bank, char.layout)
char.bankAt = time()
ns.version = ns.version + 1
ns.BagsWindowChanged()
end
local function BankOpened()
if bankOpen then return end
bankOpen = true
ScanBank()
end
ns.On("BANKFRAME_OPENED", BankOpened)
ns.On("BANKFRAME_CLOSED", function() bankOpen = false end)
-- Newer clients announce the banker through the interaction manager too.
local Banker = Enum.PlayerInteractionType and Enum.PlayerInteractionType.Banker
if Banker then
ns.On("PLAYER_INTERACTION_MANAGER_FRAME_SHOW", function(kind)
if kind == Banker then BankOpened() end
end)
ns.On("PLAYER_INTERACTION_MANAGER_FRAME_HIDE", function(kind)
if kind == Banker then bankOpen = false end
end)
end
local function ScanEquip()
ns.ScanEquip(char.equip)
ns.version = ns.version + 1
end
-- BAG_UPDATE fires per bag, often many times at once; BAG_UPDATE_DELAYED fires
-- once after the batch. Only rescan when one of our carried bags changed.
ns.On("BAG_UPDATE", function(bag)
if BAG_SET[bag] then
bagsDirty = true
elseif bankOpen and BANK_SET[bag] then
bankDirty = true
end
end)
ns.On("BAG_UPDATE_DELAYED", function()
if bagsDirty then ScanBags() end
if bankDirty and bankOpen then ScanBank() end
end)
ns.On("PLAYER_EQUIPMENT_CHANGED", ScanEquip)
-- A bag was added, removed or swapped.
ns.On("BAG_CONTAINER_UPDATE", function()
ScanEquip()
ScanBags()
end)
ScanBags()
ScanEquip()
end