Mercurial > wow > askmrrobot
diff Junk.lua @ 161:35612aee8e15
Added junk list.
author | yellowfive |
---|---|
date | Mon, 06 May 2019 14:08:03 -0700 |
parents | |
children | f66108c1f674 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Junk.lua Mon May 06 14:08:03 2019 -0700 @@ -0,0 +1,656 @@ +local Amr = LibStub("AceAddon-3.0"):GetAddon("AskMrRobot") +local L = LibStub("AceLocale-3.0"):GetLocale("AskMrRobot", true) +local AceGUI = LibStub("AceGUI-3.0") + +local _frameJunk +local _lblAction +local _lblBank +local _btnBank +local _panelContent + +local _canDisenchant = false +local _isScrapOpen = false +local _isMerchantOpen = false +local _isBankOpen = false + +-- +-- Scan a bag for the specified item, returning the first exact match found +-- +local function scanBag(bagId, matchItem, usedItems) + + local numSlots = GetContainerNumSlots(bagId) + local loc = ItemLocation.CreateEmpty() + + if not usedItems[bagId] then + usedItems[bagId] = {} + end + + for slotId = 1, numSlots do + if not usedItems[bagId][slotId] then + local _, itemCount, _, _, _, _, itemLink = GetContainerItemInfo(bagId, slotId) + if itemLink ~= nil then + local itemData = Amr.Serializer.ParseItemLink(itemLink) + if itemData ~= nil then + -- see if this is an azerite item and read azerite power ids + loc:SetBagAndSlot(bagId, slotId) + if C_AzeriteEmpoweredItem.IsAzeriteEmpoweredItem(loc) then + local powers = Amr.ReadAzeritePowers(loc) + if powers then + itemData.azerite = powers + end + end + + -- see if it matches + if Amr.CountItemDifferences(matchItem, itemData) == 0 then + usedItems[bagId][slotId] = true + itemData.bagId = bagId + itemData.slotId = slotId + return itemData + end + end + end + end + end + + return nil +end + +-- +-- Find a matching item in the player's bags +-- +local function findMatchingBagItem(item, usedItems) + + local matchItem = scanBag(BACKPACK_CONTAINER, item, usedItems) -- backpack + if not matchItem then + for bagId = 1, NUM_BAG_SLOTS do + matchItem = scanBag(bagId, item, usedItems) + if matchItem then break end + end + end + + return matchItem +end + + +-- +-- item actions +-- +local _deSpellName = GetSpellInfo(13262); +local _deMacro = "/stopmacro [combat][btn:2]\n/stopcasting\n/cast %s\n/use %s %s"; + +local function onItemPreClick(widget) + + local item = widget:GetUserData("itemData") + + if item and _canDisenchant and (not _isScrapOpen and not _isMerchantOpen) then + -- only way i can find to disenchant and item on click is to call a macro, gross but works + local matchItem = findMatchingBagItem(item, {}) + if matchItem then + widget:SetMacroText(_deMacro:format(_deSpellName, matchItem.bagId, matchItem.slotId)) + else + widget:SetMacroText(nil) + Amr:Print(L.JunkItemNotFound) + end + else + widget:SetMacroText(nil) + end +end + +local function onItemClick(widget) + + local item = widget:GetUserData("itemData") + if not item then return end + + local action = nil + if _isScrapOpen then + action = "scrap" + elseif _isMerchantOpen then + action = "sell" + elseif _canDisenchant then + action = "disenchant" + end + + if not action then return end + + local matchItem = findMatchingBagItem(item, {}) + if matchItem then + if action == "scrap" then + UseContainerItem(matchItem.bagId, matchItem.slotId) + elseif action == "sell" then + UseContainerItem(matchItem.bagId, matchItem.slotId) + end + + -- note for disenchant, the macro has handled the action, this will simply remove the item from the list + + -- re-render the list with this item removed; + -- AceGUI doesn't give a good way to remove a ui element from a container and re-render, + -- so we sort of hack it and modify the collection of children directly, + -- avoids the expensive logic of finding and matching all the items when the list changes as a user sells stuff + local scroll = widget.parent.parent + local newChildren = {} + for i = 1, #scroll.children do + local child = scroll.children[i] + if child ~= widget.parent then + table.insert(newChildren, child) + end + end + scroll.children = newChildren + + -- dispose the item just removed, then re-render the list + widget.parent:Release() + widget.parent.parent:DoLayout() + else + Amr:Print(L.JunkItemNotFound) + end +end + + +-- +-- bank withdraw stuff +-- +local _bankUsedBagSlots = nil +local finishBankWithdraw +local doBankWithdraw + +finishBankWithdraw = function() + + local done = true + + if _isBankOpen and _bankUsedBagSlots then + for bagId,v in pairs(_bankUsedBagSlots) do + for slotId,v in pairs(v) do + local _, _, _, _, _, _, itemLink = GetContainerItemInfo(bagId, slotId) + if not itemLink then + done = false + break + end + end + if not done then break end + end + end + + if not done then + -- wait a second and try again + Amr.Wait(1, function() + doBankWithdraw() + end) + else + + print("bank withdraw done") + + -- reset state + _bankUsedBagSlots = nil + _btnBank:SetDisabled(not _isBankOpen) + + -- re-render the junk list + Amr:RefreshJunkUi() + end +end + +doBankWithdraw = function() + if not _isBankOpen then return end + + local data = Amr.db.char.JunkData + if not data.Junk then return end + + print("doing bank withdraw...") + + -- disable button while processing + _btnBank:SetDisabled(true) + + local bagList = {} + table.insert(bagList, BANK_CONTAINER) + for bagId = NUM_BAG_SLOTS + 1, NUM_BAG_SLOTS + NUM_BANKBAGSLOTS do + table.insert(bagList, bagId) + end + + local usedItems = {} + _bankUsedBagSlots = {} + + for i,item in ipairs(data.Junk) do + -- stop immediately if the bank is closed + if not _isBankOpen then + finishBankWithdraw() + return + end + + -- check if already in bags + local matchItem = findMatchingBagItem(item, usedItems) + if not matchItem then + -- find it in the bank + for j = 1, #bagList do + matchItem = scanBag(bagList[j], item, usedItems) + if matchItem then break end + end + else + matchItem = nil + end + + if matchItem then + -- move it to the player's bags if there is space + local bagId, slotId = Amr.FindFirstEmptyBagSlot(_bankUsedBagSlots) + if bagId then + UseContainerItem(matchItem.bagId, matchItem.slotId) + else + -- no more empty bag slots + break + end + end + end + + -- wait a second and see if all the moves actually finished + Amr.Wait(1, function() + finishBankWithdraw() + end) +end + +local function onBankClick() + if not _frameJunk or not _isBankOpen then return end + + doBankWithdraw() +end + + +local function onJunkFrameClose(widget) + AceGUI:Release(widget) + _frameJunk = nil + _lblAction = nil + _lblBank = nil + _btnBank = nil + _panelContent = nil +end + +function Amr:HideJunkWindow() + if not _frameJunk then return end + _frameJunk:Hide() +end + +function Amr:ShowJunkWindow() + + if not _frameJunk then + _frameJunk = AceGUI:Create("AmrUiFrame") + _frameJunk:SetStatusTable(Amr.db.profile.junkWindow) -- window position is remembered in db + _frameJunk:SetCallback("OnClose", onJunkFrameClose) + _frameJunk:SetLayout("None") + _frameJunk:SetWidth(400) + _frameJunk:SetHeight(700) + _frameJunk:SetBorderColor(Amr.Colors.BorderBlue) + _frameJunk:SetBackgroundColor(Amr.Colors.Bg) + + if Amr.db.profile.options.uiScale ~= 1 then + local scale = tonumber(Amr.db.profile.options.uiScale) + _frameJunk:SetScale(scale) + end + + local lbl = AceGUI:Create("AmrUiLabel") + _frameJunk:AddChild(lbl) + lbl:SetWidth(300) + lbl:SetFont(Amr.CreateFont("Bold", 28, Amr.Colors.White)) + lbl:SetText(L.JunkTitle) + lbl:SetWordWrap(false) + lbl:SetJustifyH("CENTER") + lbl:SetPoint("TOP", _frameJunk.content, "TOP", 0, 30) + lbl:SetCallback("OnMouseDown", function(widget) _frameJunk:StartMove() end) + lbl:SetCallback("OnMouseUp", function(widget) _frameJunk:EndMove() end) + + _lblAction = AceGUI:Create("AmrUiLabel") + _frameJunk:AddChild(_lblAction) + _lblAction:SetWidth(380) + _lblAction:SetFont(Amr.CreateFont("Regular", 14, Amr.Colors.TextTan)) + _lblAction:SetText(" ") + _lblAction:SetWordWrap(false) + _lblAction:SetPoint("TOPLEFT", _frameJunk.content, "TOPLEFT", 0, -10) + + _btnBank = AceGUI:Create("AmrUiButton") + _frameJunk:AddChild(_btnBank) + _btnBank:SetText(L.JunkButtonBank) + _btnBank:SetBackgroundColor(Amr.Colors.Green) + _btnBank:SetFont(Amr.CreateFont("Bold", 14, Amr.Colors.White)) + _btnBank:SetWidth(180) + _btnBank:SetHeight(26) + _btnBank:SetDisabled(true) + _btnBank:SetCallback("OnClick", onBankClick) + _btnBank:SetPoint("BOTTOMLEFT", _frameJunk.content, "BOTTOMLEFT") + + _lblBank = AceGUI:Create("AmrUiLabel") + _frameJunk:AddChild(_lblBank) + _lblBank:SetWidth(380) + _lblBank:SetFont(Amr.CreateFont("Bold", 15, Amr.Colors.TextHeaderActive)) + _lblBank:SetText(L.JunkBankText(0)) + _lblBank:SetPoint("BOTTOMLEFT", _btnBank.frame, "TOPLEFT", 0, 10) + + local line = AceGUI:Create("AmrUiPanel") + _frameJunk:AddChild(line) + line:SetHeight(1) + line:SetBackgroundColor(Amr.Colors.White) + line:SetPoint("TOPLEFT", _frameJunk.content, "TOPLEFT", 0, -30) + line:SetPoint("TOPRIGHT", _frameJunk.content, "TOPRIGHT", 0, -30) + + line = AceGUI:Create("AmrUiPanel") + _frameJunk:AddChild(line) + line:SetHeight(1) + line:SetBackgroundColor(Amr.Colors.White) + line:SetPoint("TOPLEFT", _frameJunk.content, "BOTTOMLEFT", 0, 60) + line:SetPoint("TOPRIGHT", _frameJunk.content, "BOTTOMRIGHT", 0, 60) + + _panelContent = AceGUI:Create("AmrUiPanel") + _panelContent:SetLayout("None") + _panelContent:SetTransparent() + _frameJunk:AddChild(_panelContent) + _panelContent:SetPoint("TOPLEFT", _frameJunk.content, "TOPLEFT", 0, -31) + _panelContent:SetPoint("BOTTOMRIGHT", _frameJunk.content, "BOTTOMRIGHT", 0, 60) + + + Amr:RefreshJunkUi() + else + _frameJunk:Show() + Amr:RefreshJunkUi() + end + + _frameJunk:Raise() +end + +local function canDisenchant() + + local prof1, prof2 = GetProfessions(); + local profs = {} + table.insert(profs, prof1) + table.insert(profs, prof2) + for i,prof in ipairs(profs) do + if prof then + local _, _, skillLevel, _, _, _, skillLine = GetProfessionInfo(prof); + if Amr.ProfessionSkillLineToName[skillLine] == "Enchanting" and skillLevel > 0 then + return true + end + end + end + + return false +end + +-- +-- Find a matching item that is not in the player's inventory (bank or equipped) +-- +local function findMatchingNonBagItem(matchItem, usedItems) + + local loc = ItemLocation.CreateEmpty() + + -- check equipped + local equippedBagId = -1000 + if not usedItems[equippedBagId] then + usedItems[equippedBagId] = {} + end + + for slotNum = 1, #Amr.SlotIds do + local slotId = Amr.SlotIds[slotNum] + if not usedItems[equippedBagId][slotId] then + local itemLink = GetInventoryItemLink("player", slotId) + if itemLink then + local itemData = Amr.ParseItemLink(itemLink) + if itemData then + -- see if this is an azerite item and read azerite power ids + loc:SetEquipmentSlot(slotId) + if C_AzeriteEmpoweredItem.IsAzeriteEmpoweredItem(loc) then + local powers = Amr.ReadAzeritePowers(loc) + if powers then + itemData.azerite = powers + end + end + + -- see if it matches + if Amr.CountItemDifferences(matchItem, itemData) == 0 then + usedItems[equippedBagId][slotId] = true + itemData.bagId = bagId + itemData.slotId = slotId + return itemData + end + end + end + end + end + + -- check bank data + if Amr.db.char.BankItems then + for bagId, v in pairs(Amr.db.char.BankItems) do + if not usedItems[bagId] then + usedItems[bagId] = {} + end + + for i, itemData in ipairs(v) do + if itemData and not usedItems[bagId][i] then + -- see if it matches + if Amr.CountItemDifferences(matchItem, itemData) == 0 then + usedItems[bagId][i] = true + itemData.bagId = bagId + itemData.slotId = i + return itemData + end + end + end + end + end + + return nil +end + +local function renderItem(item, itemLink, scroll) + + local panel = AceGUI:Create("AmrUiPanel") + scroll:AddChild(panel) + panel:SetLayout("None") + panel:SetTransparent() + panel:SetWidth(380) + panel:SetHeight(40) + + -- ilvl label + local lblIlvl = AceGUI:Create("AmrUiLabel") + panel:AddChild(lblIlvl) + lblIlvl:SetPoint("LEFT", panel.content, "LEFT", 0, 0) + lblIlvl:SetWidth(35) + lblIlvl:SetFont(Amr.CreateFont("Italic", 13, Amr.Colors.TextTan)) + + -- icon + local icon = AceGUI:Create("AmrUiIcon") + panel:AddChild(icon) + icon:SetBorderWidth(1) + icon:SetIconBorderColor(Amr.Colors.White) + icon:SetWidth(28) + icon:SetHeight(28) + icon:SetPoint("LEFT", lblIlvl.frame, "RIGHT", 0, 0) + + -- item name/link label + local lblItem = AceGUI:Create("AmrUiTextButton") + panel:AddChild(lblItem) + lblItem:SetPoint("LEFT", icon.frame, "RIGHT", 0, 0) + lblItem:SetWordWrap(false) + lblItem:SetJustifyH("LEFT") + lblItem:SetWidth(300) + lblItem:SetHeight(28) + lblItem:SetFont(Amr.CreateFont("Regular", 13, Amr.Colors.White)) + lblItem:SetHoverBackgroundColor(Amr.Colors.Black, 0.3) + lblItem:SetTextPadding(0, 0, 0, 5) + lblItem:SetCallback("PreClick", onItemPreClick) + lblItem:SetCallback("OnClick", onItemClick) + lblItem:SetUserData("itemData", item) + + -- fill the name/ilvl labels, which may require asynchronous loading of item information + if itemLink then + local gameItem = Item:CreateFromItemLink(itemLink) + if gameItem then + local q = gameItem:GetItemQuality() + lblItem:SetFont(Amr.CreateFont("Regular", 13, Amr.Colors.Qualities[q] or Amr.Colors.White)) + lblItem:SetHoverFont(Amr.CreateFont("Regular", 13, Amr.Colors.Qualities[q] or Amr.Colors.White)) + lblItem:SetText(gameItem:GetItemName()) + lblIlvl:SetText(gameItem:GetCurrentItemLevel()) + icon:SetIconBorderColor(Amr.Colors.Qualities[q] or Amr.Colors.White) + icon:SetIcon(gameItem:GetItemIcon()) + Amr:SetItemTooltip(lblItem, gameItem:GetItemLink(), "ANCHOR_BOTTOMRIGHT", 0, 30) + end + end + +end + +-- +-- Just updates state without re-rendering the list of junk +-- +function Amr:SetJunkUiState() + + -- don't do anything if the window is not open + if not _frameJunk then return end + + -- cache whether the player can disenchant whenever the ui is refreshed + _canDisenchant = canDisenchant() + + -- update action label + if _isScrapOpen then + _lblAction:SetText(L.JunkScrap) + elseif _isMerchantOpen then + _lblAction:SetText(L.JunkVendor) + elseif _canDisenchant then + _lblAction:SetText(L.JunkDisenchant) + else + _lblAction:SetText(" ") + end + + -- update bank button state + _btnBank:SetDisabled(not _isBankOpen) +end + +-- +-- Refresh the entire UI, including re-rendering the junk list +-- +function Amr:RefreshJunkUi() + + -- don't do anything if the window is not open + if not _frameJunk then return end + + Amr:SetJunkUiState() + + -- clear out any previous data + _panelContent:ReleaseChildren() + + local data = Amr.db.char.JunkData + + if not data or not data.Junk or #data.Junk <= 0 then + local lbl = AceGUI:Create("AmrUiLabel") + _panelContent:AddChild(lbl) + lbl:SetFont(Amr.CreateFont("Italic", 16, Amr.Colors.TextGray)) + lbl:SetText(L.JunkEmpty) + lbl:SetPoint("TOPLEFT", _panelContent.content, "TOPLEFT", 0, -10) + _lblBank:SetVisible(false) + _btnBank:SetVisible(false) + else + + _panelContent:SetLayout("Fill") + + local scroll = AceGUI:Create("AmrUiScrollFrame") + scroll:SetLayout("List") + _panelContent:AddChild(scroll) + + -- render items currently in the player's inventory + local usedItems = {} + local bankCount = 0 + local missingCount = 0 + + -- if we have any "keep" items, those are exact duplicates of ones to be junked, + -- be sure to "reserve" those first + if data.Keep then + for uniqueId, item in pairs(data.Keep) do + -- check if an exact match is in the player's bank data or equipped + local matchItem = findMatchingNonBagItem(item, usedItems) + + -- if not, find one in the player's bags + if not matchItem then + matchItem = findMatchingBagItem(item, usedItems) + end + + if not matchItem then + -- abort, player's data must be out of sync + local lbl = AceGUI:Create("AmrUiLabel") + _panelContent:AddChild(lbl) + lbl:SetWidth(380) + lbl:SetFont(Amr.CreateFont("Italic", 13, Amr.Colors.TextGray)) + lbl:SetText(L.JunkOutOfSync) + lbl:SetPoint("TOPLEFT", _panelContent.content, "TOPLEFT", 0, -10) + + _lblBank:SetVisible(false) + _btnBank:SetVisible(false) + + return + end + end + end + + -- now render any junk items in the player's bags, and a count of how many are elsewhere (usually bank) + for i, item in ipairs(data.Junk) do + local matchItem = findMatchingBagItem(item, usedItems) + if matchItem then + local itemLink = Amr.CreateItemLink(matchItem) + renderItem(matchItem, itemLink, scroll) + else + -- see if it is in the bank or equipped + matchItem = findMatchingNonBagItem(item, usedItems) + if matchItem then + bankCount = bankCount + 1 + else + missingCount = missingCount + 1 + end + end + end + + _lblBank:SetText(L.JunkBankText(bankCount)) + _lblBank:SetVisible(bankCount > 0) + _btnBank:SetVisible(bankCount > 0) + + -- in most cases, this is because the user has sold/scrapped the items already + --if missingCount > 0 then + -- Amr:Print(L.JunkMissingText(missingCount)) + --end + end +end + +Amr:AddEventHandler("SCRAPPING_MACHINE_SHOW", function() + _isScrapOpen = true + 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 + Amr:ShowJunkWindow() + else + Amr:SetJunkUiState() + end +end) + +Amr:AddEventHandler("SCRAPPING_MACHINE_CLOSE", function() + _isScrapOpen = false + if Amr.db.profile.options.junkVendor then + Amr:HideJunkWindow() + else + Amr:SetJunkUiState() + end +end) + +Amr:AddEventHandler("MERCHANT_SHOW", function() + _isMerchantOpen = true + 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 + Amr:ShowJunkWindow() + else + Amr:SetJunkUiState() + end +end) + +Amr:AddEventHandler("MERCHANT_CLOSED", function() + _isMerchantOpen = false + if Amr.db.profile.options.junkVendor then + Amr:HideJunkWindow() + else + Amr:SetJunkUiState() + end +end) + +Amr:AddEventHandler("BANKFRAME_OPENED", function() + _isBankOpen = true + Amr:SetJunkUiState() +end) + +Amr:AddEventHandler("BANKFRAME_CLOSED", function() + _isBankOpen = false + Amr:SetJunkUiState() +end)