forked from kemayo/wow-bankstack
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsort.lua
More file actions
400 lines (364 loc) · 12.4 KB
/
sort.lua
File metadata and controls
400 lines (364 loc) · 12.4 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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
local core = BankStack
local L = core.L
local Debug = core.Debug
local encode_bagslot = core.encode_bagslot
local decode_bagslot = core.decode_bagslot
local encode_move = core.encode_move
local moves = core.moves
local bagcache = {}
local bag_groups = {}
function core.SortBags(...)
local start = 1
local sorter
if type(...) == "function" then
start = 2
sorter = ...
end
for i = start, select("#", ...) do
local bags = select(i, ...)
for _, bag in ipairs(bags) do
Debug("Considering bag", bag)
local bagtype = core.IsSpecialtyBag(bag)
if not bagtype then bagtype = 'Normal' end
if not bagcache[bagtype] then bagcache[bagtype] = {} end
table.insert(bagcache[bagtype], bag)
Debug(" went with", bag, bagtype)
end
for bagtype, sorted_bags in pairs(bagcache) do
if bagtype ~= 'Normal' then
Debug("Moving to normal from", bagtype)
core.Stack(sorted_bags, sorted_bags, core.is_partial)
if bagcache['Normal'] then
core.Stack(bagcache['Normal'], sorted_bags)
core.Fill(bagcache['Normal'], sorted_bags, core.db.reverse)
end
core.Sort(sorted_bags, sorter)
wipe(sorted_bags)
end
end
if bagcache['Normal'] then
core.Stack(bagcache['Normal'], bagcache['Normal'], core.is_partial)
core.Sort(bagcache['Normal'], sorter)
wipe(bagcache['Normal'])
end
wipe(bagcache)
end
end
-- Sorting:
local inventory_slots = {
INVTYPE_AMMO = 0,
INVTYPE_HEAD = 1,
INVTYPE_NECK = 2,
INVTYPE_SHOULDER = 3,
INVTYPE_BODY = 4,
INVTYPE_CHEST = 5,
INVTYPE_ROBE = 5,
INVTYPE_WAIST = 6,
INVTYPE_LEGS = 7,
INVTYPE_FEET = 8,
INVTYPE_WRIST = 9,
INVTYPE_HAND = 10,
INVTYPE_FINGER = 11,
INVTYPE_TRINKET = 12,
INVTYPE_CLOAK = 13,
INVTYPE_WEAPON = 14,
INVTYPE_SHIELD = 15,
INVTYPE_2HWEAPON = 16,
INVTYPE_WEAPONMAINHAND = 18,
INVTYPE_WEAPONOFFHAND = 19,
INVTYPE_HOLDABLE = 20,
INVTYPE_RANGED = 21,
INVTYPE_THROWN = 22,
INVTYPE_RANGEDRIGHT = 23,
INVTYPE_RELIC = 24,
INVTYPE_TABARD = 25,
}
-- Pet Cages normally sort by "Pet Cage", we need to dig to sort by their actual names
local PET_CAGE_ID = 82800
local function GetSortablePetName(link)
if link and string.match(link, "Hbattlepet") then
local _, _, _, name = string.split("|", link)
return name
end
return ""
end
-- Classic compat:
local GetAuctionItemSubClasses = C_AuctionHouse and C_AuctionHouse.GetAuctionItemSubClasses or function(...) return {_G.GetAuctionItemSubClasses(...)} end
-- Sorting
local item_types
local item_subtypes
local empty = {}
local function build_sort_order()
local auction_classes = {
-- Enum.ItemClass.WoWToken,
Enum.ItemClass.Weapon,
Enum.ItemClass.Armor,
Enum.ItemClass.Quiver,
Enum.ItemClass.Container,
Enum.ItemClass.Gem,
Enum.ItemClass.ItemEnhancement,
Enum.ItemClass.Consumable,
Enum.ItemClass.Reagent,
Enum.ItemClass.Glyph,
Enum.ItemClass.Tradegoods,
Enum.ItemClass.Profession,
Enum.ItemClass.Recipe,
Enum.ItemClass.Battlepet,
Enum.ItemClass.Key,
Enum.ItemClass.Miscellaneous,
Enum.ItemClass.Projectile,
Enum.ItemClass.CurrencyTokenObsolete,
Enum.ItemClass.PermanentObsolete,
Enum.ItemClass.Questitem,
}
item_types = {}
item_subtypes = {}
for i, itype in ipairs(auction_classes) do
-- local itype_name = GetItemClassInfo(itype)
item_types[itype] = i
item_subtypes[itype] = {}
for ii, istype in ipairs(GetAuctionItemSubClasses(itype)) do
item_subtypes[itype][istype] = ii
end
end
end
local bag_ids = core.bag_ids
local bag_stacks = core.bag_stacks
local bag_maxstacks = core.bag_maxstacks
local bag_links = core.bag_links
local bag_soulbound = core.bag_soulbound
local bag_conjured = core.bag_conjured
-- Avoid a *lot* of calls to GetItemInfo...
local item_name, item_rarity, item_level, item_equipLoc, item_price, item_class, item_subClass = {}, {}, {}, {}, {}, {}, {}
local iteminfo_metatable = {__index = function(self, itemid)
local name, _, rarity, level, _, _, _, _, equipLoc, _, price, class, subClass = C_Item.GetItemInfo(itemid)
if not name then
return false
end
item_name[itemid] = name
item_rarity[itemid] = rarity
item_level[itemid] = level
item_equipLoc[itemid] = equipLoc
item_price[itemid] = price
item_class[itemid] = class
item_subClass[itemid] = subClass
return self[itemid]
end,}
item_name = setmetatable(item_name, iteminfo_metatable)
item_rarity = setmetatable(item_rarity, iteminfo_metatable)
item_level = setmetatable(item_level, iteminfo_metatable)
item_equipLoc = setmetatable(item_equipLoc, iteminfo_metatable)
item_price = setmetatable(item_price, iteminfo_metatable)
item_class = setmetatable(item_class, iteminfo_metatable)
item_subClass = setmetatable(item_subClass, iteminfo_metatable)
local function prime_sort(a, b)
local a_id = bag_ids[a]
local b_id = bag_ids[b]
if item_level[a_id] ~= item_level[b_id] then
return item_level[a_id] > item_level[b_id]
end
if item_price[a_id] ~= item_price[b_id] then
return item_price[a_id] > item_price[b_id]
end
if item_name[a_id] ~= item_name[b_id] then
return item_name[a_id] < item_name[b_id]
end
return a_id < b_id
end
local initial_order = {}
local function default_sorter(a, b)
-- a and b are from encode_bagslot
-- note that "return initial_order[a] < initial_order[b]" would maintain the bag's state
-- I'm certain this could be made to be more efficient
-- return: whether a should be in front of b
local a_id = bag_ids[a]
local b_id = bag_ids[b]
-- is either slot empty? If so, move it to the back.
if (not a_id) or (not b_id) then
if core.db.junk == 2 then
if a_id and item_rarity[a_id] == 0 then return false end
if b_id and item_rarity[b_id] == 0 then return true end
end
if a_id then return true end
if b_id then return false end
end
-- is only one slot a pet? If so, move it to the back.
if a_id == PET_CAGE_ID and b_id ~= PET_CAGE_ID then return false end
if a_id ~= PET_CAGE_ID and b_id == PET_CAGE_ID then return true end
local a_order, b_order = initial_order[a], initial_order[b]
local a_link, b_link = bag_links[a], bag_links[b]
-- are they the same item?
if a_id == b_id then
-- pets will sort by their pet link
if a_id == PET_CAGE_ID then
local a_name = GetSortablePetName(a_link)
local b_name = GetSortablePetName(b_link)
if a_name and b_name then return a_name < b_name end
end
local a_count = bag_stacks[a]
local b_count = bag_stacks[b]
if a_count == b_count then
-- maintain the original order
return a_order < b_order
else
-- emptier stacks to the front
return a_count < b_count
end
end
-- Conjured items to the back?
if core.db.conjured and bag_conjured[a] ~= bag_conjured[b] then
if bag_conjured[a] then return false end
if bag_conjured[b] then return true end
end
-- Quick sanity-check to make sure we correctly fetched information about the items
if not (item_name[a_link] and item_name[b_link] and item_rarity[a_link] and item_rarity[b_link]) then
-- preserve the existing order in this case
return a_order < b_order
end
-- junk to the back?
if core.db.junk == 1 and item_rarity[a_link] ~= item_rarity[b_link] then
if item_rarity[a_link] == 0 then return false end
if item_rarity[b_link] == 0 then return true end
end
-- Soulbound items to the front?
if core.db.soulbound and bag_soulbound[a] ~= bag_soulbound[b] then
if bag_soulbound[a] then return true end
if bag_soulbound[b] then return false end
end
if item_rarity[a_link] ~= item_rarity[b_link] then
return item_rarity[a_link] > item_rarity[b_link]
end
if item_class[a_link] ~= item_class[b_link] then
return (item_types[item_class[a_link]] or 99) < (item_types[item_class[b_link]] or 99)
end
-- are they the same type?
if item_class[a_link] == Enum.ItemClass.Armor or item_class[a_link] == Enum.ItemClass.Weapon then
-- "or -1" because some things are classified as armor/weapon without being equipable; note Everlasting Underspore Frond
local a_equipLoc = inventory_slots[item_equipLoc[a_link]] or -1
local b_equipLoc = inventory_slots[item_equipLoc[b_link]] or -1
if a_equipLoc == b_equipLoc then
-- sort by level, then name
return prime_sort(a, b)
end
return a_equipLoc < b_equipLoc
end
if item_subClass[a_link] == item_subClass[b_link] then
return prime_sort(a, b)
end
if (item_subtypes[item_class[a_link]] or empty)[item_subClass[a_link]] ~= (item_subtypes[item_class[b_link]] or empty)[item_subClass[b_link]] then
return ((item_subtypes[item_class[a_link]] or empty)[item_subClass[a_link]] or 99) < ((item_subtypes[item_class[b_link]] or empty)[item_subClass[b_link]] or 99)
end
-- Utter fallback: initial order wins
return a_order < b_order
end
local function reverse_sort(a, b) return default_sorter(b, a) end
local bag_sorted = {}
local bag_locked = {}
local function update_sorted(source, destination)
for i,bs in pairs(bag_sorted) do
if bs == source then
bag_sorted[i] = destination
elseif bs == destination then
bag_sorted[i] = source
end
end
end
local function should_actually_move(source, destination)
-- work out whether a move from source to destination actually makes sense to do
-- skip it if...
-- source and destination are the same bag slot
if destination == source then return false end
-- nothing's in the source slot
if not bag_ids[source] then return false end
-- pets with the same name don't need to move
if bag_ids[source] == PET_CAGE_ID and bag_ids[destination] == PET_CAGE_ID then
local a_name = GetSortablePetName(bag_links[source])
local b_name = GetSortablePetName(bag_links[destination])
if a_name and b_name then
return a_name ~= b_name
end
end
-- slot contents are the same and stack sizes are the same
if bag_ids[source] == bag_ids[destination] and bag_stacks[source] == bag_stacks[destination] then return false end
-- go for it!
return true
end
function core.Sort(bags, sorter)
-- bags: table, e.g. {1,2,3,4}
-- sorter: function or nil. Passed to table.sort.
if not sorter then sorter = core.db.reverse and reverse_sort or default_sorter end
if not item_types then build_sort_order() end
for i, bag, slot in core.IterateBags(bags, nil, "both") do
--(you need withdraw *and* deposit permissions in the guild bank to move items within it)
local bagslot = encode_bagslot(bag, slot)
if not core.IsIgnored(bag, slot) then
initial_order[bagslot] = i
table.insert(bag_sorted, bagslot)
end
end
table.sort(bag_sorted, sorter)
-- for i,s in ipairs(bag_sorted) do Debug("SORTED", i, core.GetItemLink(decode_bagslot(s))) end
-- We now have bag_sorted, which is a table containing all slots that contain items, in the order
-- that they need to be moved into.
local another_pass_needed = true
local passes_tried = 0
while another_pass_needed do
-- Multiple "passes" are simulated here, for the purpose of fitting as many moves as possible
-- into a single run of the mover, which moves until it finds a locked item, then breaks until
-- the game removes the lock. By sequencing moves correctly, locks can be avoided as much as
-- possible.
another_pass_needed = false
local i = 1
for _, bag, slot in core.IterateBags(bags, nil, "both") do
-- Make sure the origin slot isn't empty; if so no move needs to be scheduled.
local destination = encode_bagslot(bag, slot) -- This is like i, increasing as we go on.
local source = bag_sorted[i]
-- If destination is ignored we skip everything here
-- Notably, i does not get incremented.
if not core.IsIgnored(bag, slot) then
if should_actually_move(source, destination) then
if not (bag_locked[source] or bag_locked[destination]) then
core.AddMove(source, destination)
update_sorted(source, destination)
bag_locked[source] = true
bag_locked[destination] = true
else
-- If we've moved to the destination or source slots before in this run
-- then we pass and request another run. This is to make sure as many
-- moves as possible run per pass.
another_pass_needed = true
end
end
i = i + 1
end
end
wipe(bag_locked)
passes_tried = passes_tried + 1
if passes_tried > 666 then
Debug("Broke out of passes because it took over 666 tries")
break
end
end
wipe(bag_soulbound)
wipe(bag_conjured)
wipe(bag_sorted)
wipe(initial_order)
end
SlashCmdList["SORT"] = core.CommandDecorator(core.SortBags, 'bags')
SLASH_SORT1 = "/sort"
SLASH_SORT2 = "/sortbags"
SlashCmdList["SHUFFLE"] = core.CommandDecorator(function(...)
local sort = {}
core.SortBags(function(a, b)
if not sort[a] then
sort[a] = math.random()
end
if not sort[b] then
sort[b] = math.random()
end
return sort[a] < sort[b]
end, ...)
wipe(sort)
end, 'bags')
SLASH_SHUFFLE1 = "/shuffle"
SLASH_SHUFFLE2 = "/shufflebags"