comparison Junk.lua @ 161:35612aee8e15

Added junk list.
author yellowfive
date Mon, 06 May 2019 14:08:03 -0700
parents
children f66108c1f674
comparison
equal deleted inserted replaced
160:d670336e8c89 161:35612aee8e15
1 local Amr = LibStub("AceAddon-3.0"):GetAddon("AskMrRobot")
2 local L = LibStub("AceLocale-3.0"):GetLocale("AskMrRobot", true)
3 local AceGUI = LibStub("AceGUI-3.0")
4
5 local _frameJunk
6 local _lblAction
7 local _lblBank
8 local _btnBank
9 local _panelContent
10
11 local _canDisenchant = false
12 local _isScrapOpen = false
13 local _isMerchantOpen = false
14 local _isBankOpen = false
15
16 --
17 -- Scan a bag for the specified item, returning the first exact match found
18 --
19 local function scanBag(bagId, matchItem, usedItems)
20
21 local numSlots = GetContainerNumSlots(bagId)
22 local loc = ItemLocation.CreateEmpty()
23
24 if not usedItems[bagId] then
25 usedItems[bagId] = {}
26 end
27
28 for slotId = 1, numSlots do
29 if not usedItems[bagId][slotId] then
30 local _, itemCount, _, _, _, _, itemLink = GetContainerItemInfo(bagId, slotId)
31 if itemLink ~= nil then
32 local itemData = Amr.Serializer.ParseItemLink(itemLink)
33 if itemData ~= nil then
34 -- see if this is an azerite item and read azerite power ids
35 loc:SetBagAndSlot(bagId, slotId)
36 if C_AzeriteEmpoweredItem.IsAzeriteEmpoweredItem(loc) then
37 local powers = Amr.ReadAzeritePowers(loc)
38 if powers then
39 itemData.azerite = powers
40 end
41 end
42
43 -- see if it matches
44 if Amr.CountItemDifferences(matchItem, itemData) == 0 then
45 usedItems[bagId][slotId] = true
46 itemData.bagId = bagId
47 itemData.slotId = slotId
48 return itemData
49 end
50 end
51 end
52 end
53 end
54
55 return nil
56 end
57
58 --
59 -- Find a matching item in the player's bags
60 --
61 local function findMatchingBagItem(item, usedItems)
62
63 local matchItem = scanBag(BACKPACK_CONTAINER, item, usedItems) -- backpack
64 if not matchItem then
65 for bagId = 1, NUM_BAG_SLOTS do
66 matchItem = scanBag(bagId, item, usedItems)
67 if matchItem then break end
68 end
69 end
70
71 return matchItem
72 end
73
74
75 --
76 -- item actions
77 --
78 local _deSpellName = GetSpellInfo(13262);
79 local _deMacro = "/stopmacro [combat][btn:2]\n/stopcasting\n/cast %s\n/use %s %s";
80
81 local function onItemPreClick(widget)
82
83 local item = widget:GetUserData("itemData")
84
85 if item and _canDisenchant and (not _isScrapOpen and not _isMerchantOpen) then
86 -- only way i can find to disenchant and item on click is to call a macro, gross but works
87 local matchItem = findMatchingBagItem(item, {})
88 if matchItem then
89 widget:SetMacroText(_deMacro:format(_deSpellName, matchItem.bagId, matchItem.slotId))
90 else
91 widget:SetMacroText(nil)
92 Amr:Print(L.JunkItemNotFound)
93 end
94 else
95 widget:SetMacroText(nil)
96 end
97 end
98
99 local function onItemClick(widget)
100
101 local item = widget:GetUserData("itemData")
102 if not item then return end
103
104 local action = nil
105 if _isScrapOpen then
106 action = "scrap"
107 elseif _isMerchantOpen then
108 action = "sell"
109 elseif _canDisenchant then
110 action = "disenchant"
111 end
112
113 if not action then return end
114
115 local matchItem = findMatchingBagItem(item, {})
116 if matchItem then
117 if action == "scrap" then
118 UseContainerItem(matchItem.bagId, matchItem.slotId)
119 elseif action == "sell" then
120 UseContainerItem(matchItem.bagId, matchItem.slotId)
121 end
122
123 -- note for disenchant, the macro has handled the action, this will simply remove the item from the list
124
125 -- re-render the list with this item removed;
126 -- AceGUI doesn't give a good way to remove a ui element from a container and re-render,
127 -- so we sort of hack it and modify the collection of children directly,
128 -- avoids the expensive logic of finding and matching all the items when the list changes as a user sells stuff
129 local scroll = widget.parent.parent
130 local newChildren = {}
131 for i = 1, #scroll.children do
132 local child = scroll.children[i]
133 if child ~= widget.parent then
134 table.insert(newChildren, child)
135 end
136 end
137 scroll.children = newChildren
138
139 -- dispose the item just removed, then re-render the list
140 widget.parent:Release()
141 widget.parent.parent:DoLayout()
142 else
143 Amr:Print(L.JunkItemNotFound)
144 end
145 end
146
147
148 --
149 -- bank withdraw stuff
150 --
151 local _bankUsedBagSlots = nil
152 local finishBankWithdraw
153 local doBankWithdraw
154
155 finishBankWithdraw = function()
156
157 local done = true
158
159 if _isBankOpen and _bankUsedBagSlots then
160 for bagId,v in pairs(_bankUsedBagSlots) do
161 for slotId,v in pairs(v) do
162 local _, _, _, _, _, _, itemLink = GetContainerItemInfo(bagId, slotId)
163 if not itemLink then
164 done = false
165 break
166 end
167 end
168 if not done then break end
169 end
170 end
171
172 if not done then
173 -- wait a second and try again
174 Amr.Wait(1, function()
175 doBankWithdraw()
176 end)
177 else
178
179 print("bank withdraw done")
180
181 -- reset state
182 _bankUsedBagSlots = nil
183 _btnBank:SetDisabled(not _isBankOpen)
184
185 -- re-render the junk list
186 Amr:RefreshJunkUi()
187 end
188 end
189
190 doBankWithdraw = function()
191 if not _isBankOpen then return end
192
193 local data = Amr.db.char.JunkData
194 if not data.Junk then return end
195
196 print("doing bank withdraw...")
197
198 -- disable button while processing
199 _btnBank:SetDisabled(true)
200
201 local bagList = {}
202 table.insert(bagList, BANK_CONTAINER)
203 for bagId = NUM_BAG_SLOTS + 1, NUM_BAG_SLOTS + NUM_BANKBAGSLOTS do
204 table.insert(bagList, bagId)
205 end
206
207 local usedItems = {}
208 _bankUsedBagSlots = {}
209
210 for i,item in ipairs(data.Junk) do
211 -- stop immediately if the bank is closed
212 if not _isBankOpen then
213 finishBankWithdraw()
214 return
215 end
216
217 -- check if already in bags
218 local matchItem = findMatchingBagItem(item, usedItems)
219 if not matchItem then
220 -- find it in the bank
221 for j = 1, #bagList do
222 matchItem = scanBag(bagList[j], item, usedItems)
223 if matchItem then break end
224 end
225 else
226 matchItem = nil
227 end
228
229 if matchItem then
230 -- move it to the player's bags if there is space
231 local bagId, slotId = Amr.FindFirstEmptyBagSlot(_bankUsedBagSlots)
232 if bagId then
233 UseContainerItem(matchItem.bagId, matchItem.slotId)
234 else
235 -- no more empty bag slots
236 break
237 end
238 end
239 end
240
241 -- wait a second and see if all the moves actually finished
242 Amr.Wait(1, function()
243 finishBankWithdraw()
244 end)
245 end
246
247 local function onBankClick()
248 if not _frameJunk or not _isBankOpen then return end
249
250 doBankWithdraw()
251 end
252
253
254 local function onJunkFrameClose(widget)
255 AceGUI:Release(widget)
256 _frameJunk = nil
257 _lblAction = nil
258 _lblBank = nil
259 _btnBank = nil
260 _panelContent = nil
261 end
262
263 function Amr:HideJunkWindow()
264 if not _frameJunk then return end
265 _frameJunk:Hide()
266 end
267
268 function Amr:ShowJunkWindow()
269
270 if not _frameJunk then
271 _frameJunk = AceGUI:Create("AmrUiFrame")
272 _frameJunk:SetStatusTable(Amr.db.profile.junkWindow) -- window position is remembered in db
273 _frameJunk:SetCallback("OnClose", onJunkFrameClose)
274 _frameJunk:SetLayout("None")
275 _frameJunk:SetWidth(400)
276 _frameJunk:SetHeight(700)
277 _frameJunk:SetBorderColor(Amr.Colors.BorderBlue)
278 _frameJunk:SetBackgroundColor(Amr.Colors.Bg)
279
280 if Amr.db.profile.options.uiScale ~= 1 then
281 local scale = tonumber(Amr.db.profile.options.uiScale)
282 _frameJunk:SetScale(scale)
283 end
284
285 local lbl = AceGUI:Create("AmrUiLabel")
286 _frameJunk:AddChild(lbl)
287 lbl:SetWidth(300)
288 lbl:SetFont(Amr.CreateFont("Bold", 28, Amr.Colors.White))
289 lbl:SetText(L.JunkTitle)
290 lbl:SetWordWrap(false)
291 lbl:SetJustifyH("CENTER")
292 lbl:SetPoint("TOP", _frameJunk.content, "TOP", 0, 30)
293 lbl:SetCallback("OnMouseDown", function(widget) _frameJunk:StartMove() end)
294 lbl:SetCallback("OnMouseUp", function(widget) _frameJunk:EndMove() end)
295
296 _lblAction = AceGUI:Create("AmrUiLabel")
297 _frameJunk:AddChild(_lblAction)
298 _lblAction:SetWidth(380)
299 _lblAction:SetFont(Amr.CreateFont("Regular", 14, Amr.Colors.TextTan))
300 _lblAction:SetText(" ")
301 _lblAction:SetWordWrap(false)
302 _lblAction:SetPoint("TOPLEFT", _frameJunk.content, "TOPLEFT", 0, -10)
303
304 _btnBank = AceGUI:Create("AmrUiButton")
305 _frameJunk:AddChild(_btnBank)
306 _btnBank:SetText(L.JunkButtonBank)
307 _btnBank:SetBackgroundColor(Amr.Colors.Green)
308 _btnBank:SetFont(Amr.CreateFont("Bold", 14, Amr.Colors.White))
309 _btnBank:SetWidth(180)
310 _btnBank:SetHeight(26)
311 _btnBank:SetDisabled(true)
312 _btnBank:SetCallback("OnClick", onBankClick)
313 _btnBank:SetPoint("BOTTOMLEFT", _frameJunk.content, "BOTTOMLEFT")
314
315 _lblBank = AceGUI:Create("AmrUiLabel")
316 _frameJunk:AddChild(_lblBank)
317 _lblBank:SetWidth(380)
318 _lblBank:SetFont(Amr.CreateFont("Bold", 15, Amr.Colors.TextHeaderActive))
319 _lblBank:SetText(L.JunkBankText(0))
320 _lblBank:SetPoint("BOTTOMLEFT", _btnBank.frame, "TOPLEFT", 0, 10)
321
322 local line = AceGUI:Create("AmrUiPanel")
323 _frameJunk:AddChild(line)
324 line:SetHeight(1)
325 line:SetBackgroundColor(Amr.Colors.White)
326 line:SetPoint("TOPLEFT", _frameJunk.content, "TOPLEFT", 0, -30)
327 line:SetPoint("TOPRIGHT", _frameJunk.content, "TOPRIGHT", 0, -30)
328
329 line = AceGUI:Create("AmrUiPanel")
330 _frameJunk:AddChild(line)
331 line:SetHeight(1)
332 line:SetBackgroundColor(Amr.Colors.White)
333 line:SetPoint("TOPLEFT", _frameJunk.content, "BOTTOMLEFT", 0, 60)
334 line:SetPoint("TOPRIGHT", _frameJunk.content, "BOTTOMRIGHT", 0, 60)
335
336 _panelContent = AceGUI:Create("AmrUiPanel")
337 _panelContent:SetLayout("None")
338 _panelContent:SetTransparent()
339 _frameJunk:AddChild(_panelContent)
340 _panelContent:SetPoint("TOPLEFT", _frameJunk.content, "TOPLEFT", 0, -31)
341 _panelContent:SetPoint("BOTTOMRIGHT", _frameJunk.content, "BOTTOMRIGHT", 0, 60)
342
343
344 Amr:RefreshJunkUi()
345 else
346 _frameJunk:Show()
347 Amr:RefreshJunkUi()
348 end
349
350 _frameJunk:Raise()
351 end
352
353 local function canDisenchant()
354
355 local prof1, prof2 = GetProfessions();
356 local profs = {}
357 table.insert(profs, prof1)
358 table.insert(profs, prof2)
359 for i,prof in ipairs(profs) do
360 if prof then
361 local _, _, skillLevel, _, _, _, skillLine = GetProfessionInfo(prof);
362 if Amr.ProfessionSkillLineToName[skillLine] == "Enchanting" and skillLevel > 0 then
363 return true
364 end
365 end
366 end
367
368 return false
369 end
370
371 --
372 -- Find a matching item that is not in the player's inventory (bank or equipped)
373 --
374 local function findMatchingNonBagItem(matchItem, usedItems)
375
376 local loc = ItemLocation.CreateEmpty()
377
378 -- check equipped
379 local equippedBagId = -1000
380 if not usedItems[equippedBagId] then
381 usedItems[equippedBagId] = {}
382 end
383
384 for slotNum = 1, #Amr.SlotIds do
385 local slotId = Amr.SlotIds[slotNum]
386 if not usedItems[equippedBagId][slotId] then
387 local itemLink = GetInventoryItemLink("player", slotId)
388 if itemLink then
389 local itemData = Amr.ParseItemLink(itemLink)
390 if itemData then
391 -- see if this is an azerite item and read azerite power ids
392 loc:SetEquipmentSlot(slotId)
393 if C_AzeriteEmpoweredItem.IsAzeriteEmpoweredItem(loc) then
394 local powers = Amr.ReadAzeritePowers(loc)
395 if powers then
396 itemData.azerite = powers
397 end
398 end
399
400 -- see if it matches
401 if Amr.CountItemDifferences(matchItem, itemData) == 0 then
402 usedItems[equippedBagId][slotId] = true
403 itemData.bagId = bagId
404 itemData.slotId = slotId
405 return itemData
406 end
407 end
408 end
409 end
410 end
411
412 -- check bank data
413 if Amr.db.char.BankItems then
414 for bagId, v in pairs(Amr.db.char.BankItems) do
415 if not usedItems[bagId] then
416 usedItems[bagId] = {}
417 end
418
419 for i, itemData in ipairs(v) do
420 if itemData and not usedItems[bagId][i] then
421 -- see if it matches
422 if Amr.CountItemDifferences(matchItem, itemData) == 0 then
423 usedItems[bagId][i] = true
424 itemData.bagId = bagId
425 itemData.slotId = i
426 return itemData
427 end
428 end
429 end
430 end
431 end
432
433 return nil
434 end
435
436 local function renderItem(item, itemLink, scroll)
437
438 local panel = AceGUI:Create("AmrUiPanel")
439 scroll:AddChild(panel)
440 panel:SetLayout("None")
441 panel:SetTransparent()
442 panel:SetWidth(380)
443 panel:SetHeight(40)
444
445 -- ilvl label
446 local lblIlvl = AceGUI:Create("AmrUiLabel")
447 panel:AddChild(lblIlvl)
448 lblIlvl:SetPoint("LEFT", panel.content, "LEFT", 0, 0)
449 lblIlvl:SetWidth(35)
450 lblIlvl:SetFont(Amr.CreateFont("Italic", 13, Amr.Colors.TextTan))
451
452 -- icon
453 local icon = AceGUI:Create("AmrUiIcon")
454 panel:AddChild(icon)
455 icon:SetBorderWidth(1)
456 icon:SetIconBorderColor(Amr.Colors.White)
457 icon:SetWidth(28)
458 icon:SetHeight(28)
459 icon:SetPoint("LEFT", lblIlvl.frame, "RIGHT", 0, 0)
460
461 -- item name/link label
462 local lblItem = AceGUI:Create("AmrUiTextButton")
463 panel:AddChild(lblItem)
464 lblItem:SetPoint("LEFT", icon.frame, "RIGHT", 0, 0)
465 lblItem:SetWordWrap(false)
466 lblItem:SetJustifyH("LEFT")
467 lblItem:SetWidth(300)
468 lblItem:SetHeight(28)
469 lblItem:SetFont(Amr.CreateFont("Regular", 13, Amr.Colors.White))
470 lblItem:SetHoverBackgroundColor(Amr.Colors.Black, 0.3)
471 lblItem:SetTextPadding(0, 0, 0, 5)
472 lblItem:SetCallback("PreClick", onItemPreClick)
473 lblItem:SetCallback("OnClick", onItemClick)
474 lblItem:SetUserData("itemData", item)
475
476 -- fill the name/ilvl labels, which may require asynchronous loading of item information
477 if itemLink then
478 local gameItem = Item:CreateFromItemLink(itemLink)
479 if gameItem then
480 local q = gameItem:GetItemQuality()
481 lblItem:SetFont(Amr.CreateFont("Regular", 13, Amr.Colors.Qualities[q] or Amr.Colors.White))
482 lblItem:SetHoverFont(Amr.CreateFont("Regular", 13, Amr.Colors.Qualities[q] or Amr.Colors.White))
483 lblItem:SetText(gameItem:GetItemName())
484 lblIlvl:SetText(gameItem:GetCurrentItemLevel())
485 icon:SetIconBorderColor(Amr.Colors.Qualities[q] or Amr.Colors.White)
486 icon:SetIcon(gameItem:GetItemIcon())
487 Amr:SetItemTooltip(lblItem, gameItem:GetItemLink(), "ANCHOR_BOTTOMRIGHT", 0, 30)
488 end
489 end
490
491 end
492
493 --
494 -- Just updates state without re-rendering the list of junk
495 --
496 function Amr:SetJunkUiState()
497
498 -- don't do anything if the window is not open
499 if not _frameJunk then return end
500
501 -- cache whether the player can disenchant whenever the ui is refreshed
502 _canDisenchant = canDisenchant()
503
504 -- update action label
505 if _isScrapOpen then
506 _lblAction:SetText(L.JunkScrap)
507 elseif _isMerchantOpen then
508 _lblAction:SetText(L.JunkVendor)
509 elseif _canDisenchant then
510 _lblAction:SetText(L.JunkDisenchant)
511 else
512 _lblAction:SetText(" ")
513 end
514
515 -- update bank button state
516 _btnBank:SetDisabled(not _isBankOpen)
517 end
518
519 --
520 -- Refresh the entire UI, including re-rendering the junk list
521 --
522 function Amr:RefreshJunkUi()
523
524 -- don't do anything if the window is not open
525 if not _frameJunk then return end
526
527 Amr:SetJunkUiState()
528
529 -- clear out any previous data
530 _panelContent:ReleaseChildren()
531
532 local data = Amr.db.char.JunkData
533
534 if not data or not data.Junk or #data.Junk <= 0 then
535 local lbl = AceGUI:Create("AmrUiLabel")
536 _panelContent:AddChild(lbl)
537 lbl:SetFont(Amr.CreateFont("Italic", 16, Amr.Colors.TextGray))
538 lbl:SetText(L.JunkEmpty)
539 lbl:SetPoint("TOPLEFT", _panelContent.content, "TOPLEFT", 0, -10)
540 _lblBank:SetVisible(false)
541 _btnBank:SetVisible(false)
542 else
543
544 _panelContent:SetLayout("Fill")
545
546 local scroll = AceGUI:Create("AmrUiScrollFrame")
547 scroll:SetLayout("List")
548 _panelContent:AddChild(scroll)
549
550 -- render items currently in the player's inventory
551 local usedItems = {}
552 local bankCount = 0
553 local missingCount = 0
554
555 -- if we have any "keep" items, those are exact duplicates of ones to be junked,
556 -- be sure to "reserve" those first
557 if data.Keep then
558 for uniqueId, item in pairs(data.Keep) do
559 -- check if an exact match is in the player's bank data or equipped
560 local matchItem = findMatchingNonBagItem(item, usedItems)
561
562 -- if not, find one in the player's bags
563 if not matchItem then
564 matchItem = findMatchingBagItem(item, usedItems)
565 end
566
567 if not matchItem then
568 -- abort, player's data must be out of sync
569 local lbl = AceGUI:Create("AmrUiLabel")
570 _panelContent:AddChild(lbl)
571 lbl:SetWidth(380)
572 lbl:SetFont(Amr.CreateFont("Italic", 13, Amr.Colors.TextGray))
573 lbl:SetText(L.JunkOutOfSync)
574 lbl:SetPoint("TOPLEFT", _panelContent.content, "TOPLEFT", 0, -10)
575
576 _lblBank:SetVisible(false)
577 _btnBank:SetVisible(false)
578
579 return
580 end
581 end
582 end
583
584 -- now render any junk items in the player's bags, and a count of how many are elsewhere (usually bank)
585 for i, item in ipairs(data.Junk) do
586 local matchItem = findMatchingBagItem(item, usedItems)
587 if matchItem then
588 local itemLink = Amr.CreateItemLink(matchItem)
589 renderItem(matchItem, itemLink, scroll)
590 else
591 -- see if it is in the bank or equipped
592 matchItem = findMatchingNonBagItem(item, usedItems)
593 if matchItem then
594 bankCount = bankCount + 1
595 else
596 missingCount = missingCount + 1
597 end
598 end
599 end
600
601 _lblBank:SetText(L.JunkBankText(bankCount))
602 _lblBank:SetVisible(bankCount > 0)
603 _btnBank:SetVisible(bankCount > 0)
604
605 -- in most cases, this is because the user has sold/scrapped the items already
606 --if missingCount > 0 then
607 -- Amr:Print(L.JunkMissingText(missingCount))
608 --end
609 end
610 end
611
612 Amr:AddEventHandler("SCRAPPING_MACHINE_SHOW", function()
613 _isScrapOpen = true
614 if Amr.db.profile.options.junkVendor and Amr.db.char.JunkData and Amr.db.char.JunkData.Junk and #Amr.db.char.JunkData.Junk > 0 then
615 Amr:ShowJunkWindow()
616 else
617 Amr:SetJunkUiState()
618 end
619 end)
620
621 Amr:AddEventHandler("SCRAPPING_MACHINE_CLOSE", function()
622 _isScrapOpen = false
623 if Amr.db.profile.options.junkVendor then
624 Amr:HideJunkWindow()
625 else
626 Amr:SetJunkUiState()
627 end
628 end)
629
630 Amr:AddEventHandler("MERCHANT_SHOW", function()
631 _isMerchantOpen = true
632 if Amr.db.profile.options.junkVendor and Amr.db.char.JunkData and Amr.db.char.JunkData.Junk and #Amr.db.char.JunkData.Junk > 0 then
633 Amr:ShowJunkWindow()
634 else
635 Amr:SetJunkUiState()
636 end
637 end)
638
639 Amr:AddEventHandler("MERCHANT_CLOSED", function()
640 _isMerchantOpen = false
641 if Amr.db.profile.options.junkVendor then
642 Amr:HideJunkWindow()
643 else
644 Amr:SetJunkUiState()
645 end
646 end)
647
648 Amr:AddEventHandler("BANKFRAME_OPENED", function()
649 _isBankOpen = true
650 Amr:SetJunkUiState()
651 end)
652
653 Amr:AddEventHandler("BANKFRAME_CLOSED", function()
654 _isBankOpen = false
655 Amr:SetJunkUiState()
656 end)