yellowfive@57: local Amr = LibStub("AceAddon-3.0"):GetAddon("AskMrRobot") yellowfive@57: local L = LibStub("AceLocale-3.0"):GetLocale("AskMrRobot", true) yellowfive@57: local AceGUI = LibStub("AceGUI-3.0") yellowfive@57: yellowfive@139: local _cboSetups yellowfive@139: local _panelGear yellowfive@139: local _activeSetupId yellowfive@139: yellowfive@139: local function getSetupById(id) yellowfive@139: if not id then yellowfive@139: id = _activeSetupId yellowfive@139: end yellowfive@139: local setup yellowfive@139: for i,s in ipairs(Amr.db.char.GearSetups) do yellowfive@139: if s.Id == id then yellowfive@139: setup = s yellowfive@139: break yellowfive@139: end yellowfive@139: end yellowfive@139: return setup yellowfive@139: end yellowfive@57: yellowfive@57: -- Returns a number indicating how different two items are (0 means the same, higher means more different) yellowfive@57: local function countItemDifferences(item1, item2) yellowfive@124: -- both nil, the same yellowfive@124: if not item1 and not item2 then yellowfive@124: return 0 yellowfive@124: end yellowfive@124: yellowfive@124: -- one nil and other not, or different id, totally different yellowfive@124: if (not item1 and item2) or (item1 and not item2) or item1.id ~= item2.id then yellowfive@191: return 1000000 yellowfive@124: end yellowfive@124: Yellowfive@200: --if item1.guid and item2.guid and item1.guid == item2.guid then Yellowfive@200: -- -- these have the same guid, so even if bonus id or something doesn't match for some reason, they are identical items Yellowfive@200: --else Yellowfive@200: -- different versions of same item (id + bonus ids + suffix + drop level, constitutes a different physical drop) Yellowfive@200: if Amr.GetItemUniqueId(item1, true, true) ~= Amr.GetItemUniqueId(item2, true, true) then Yellowfive@200: return 100000 Yellowfive@200: end Yellowfive@200: Yellowfive@200: -- different upgrade levels of the same item Yellowfive@200: if item1.upgradeId ~= item2.upgradeId then Yellowfive@200: return 10000 Yellowfive@200: end Yellowfive@200: --end Yellowfive@200: yellowfive@149: -- a change that requires reforging is considered more different than a change that does not; yellowfive@149: -- it is assumed that item1 is how we want the item to be in the end, and item2 is how it currently is yellowfive@149: local aztReforges = 0 yellowfive@149: local aztSelects = 0 yellowfive@149: yellowfive@149: if item1.id == item2.id and (item1.azerite or item2.azerite) then yellowfive@149: -- azerite that needs to be reforged yellowfive@149: if item2.azerite and not item1.azerite then yellowfive@149: -- kind of a dumb case... but we would need to blank all azerite on item2 to match item1 yellowfive@191: aztReforges = #item2.azerite * 1000 yellowfive@149: elseif item2.azerite then yellowfive@149: -- count up azerite on item2 but not on item1, these would need to be reforged yellowfive@149: for i = 1, #item2.azerite do yellowfive@149: local missing = true yellowfive@149: for j = 1, #item1.azerite do yellowfive@149: if item1.azerite[j] == item2.azerite[i] then yellowfive@149: missing = false yellowfive@149: end yellowfive@149: end yellowfive@149: if missing then yellowfive@191: aztReforges = aztReforges + 1000 yellowfive@149: end yellowfive@149: end yellowfive@149: end yellowfive@149: yellowfive@149: -- azerite that needs to be selected yellowfive@124: if item1.azerite and not item2.azerite then yellowfive@149: -- item2 is blank, so just need to choose all the right ones yellowfive@191: aztSelects = #item1.azerite * 100 yellowfive@149: elseif item1.azerite then yellowfive@149: -- count up azerite on item1 but not on item2, these would need to be selected yellowfive@145: for i = 1, #item1.azerite do yellowfive@145: local missing = true yellowfive@124: for j = 1, #item2.azerite do yellowfive@145: if item2.azerite[j] == item1.azerite[i] then yellowfive@124: missing = false yellowfive@124: end yellowfive@124: end yellowfive@124: if missing then yellowfive@191: aztSelects = aztSelects + 100 yellowfive@124: end yellowfive@124: end yellowfive@149: end yellowfive@124: end yellowfive@57: yellowfive@57: -- different gems yellowfive@57: local gemDiffs = 0 yellowfive@57: for i = 1, 3 do yellowfive@57: if item1.gemIds[i] ~= item2.gemIds[i] then yellowfive@191: gemDiffs = gemDiffs + 10 yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@57: -- different enchants yellowfive@57: local enchantDiff = 0 yellowfive@57: if item1.enchantId ~= item2.enchantId then yellowfive@191: enchantDiff = 10 yellowfive@57: end yellowfive@191: yellowfive@191: -- different guid yellowfive@191: local guidDiff = 0 yellowfive@191: if item1.guid and item2.guid and item1.guid ~= item2.guid then yellowfive@191: guidDiff = 1 yellowfive@191: end yellowfive@191: yellowfive@191: return aztReforges + aztSelects + gemDiffs + enchantDiff + guidDiff yellowfive@57: end yellowfive@57: yellowfive@57: -- given a table of items (keyed or indexed doesn't matter) find closest match to item, or nil if none are a match yellowfive@124: local function findMatchingItemFromTable(item, list, bestItem, bestDiff, bestLoc, usedItems, tableType) yellowfive@57: if not list then return nil end yellowfive@57: yellowfive@73: local found = false yellowfive@129: for k,listItem in pairs(list) do yellowfive@57: if listItem then yellowfive@57: local diff = countItemDifferences(item, listItem) Yellowfive@200: yellowfive@57: if diff < bestDiff then yellowfive@57: -- each physical item can only be used once, the usedItems table has items we can't use in this search yellowfive@57: local key = string.format("%s_%s", tableType, k) yellowfive@57: if not usedItems[key] then yellowfive@57: bestItem = listItem yellowfive@57: bestDiff = diff yellowfive@124: bestLoc = key yellowfive@57: end yellowfive@57: end Yellowfive@200: Yellowfive@200: if bestDiff == 0 then break end yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@124: return bestItem, bestDiff, bestLoc yellowfive@57: end yellowfive@57: yellowfive@124: -- search the player's equipped gear, bag, and bank for an item that best matches the specified item yellowfive@57: function Amr:FindMatchingItem(item, player, usedItems) yellowfive@57: if not item then return nil end yellowfive@57: yellowfive@57: local equipped = player.Equipped and player.Equipped[player.ActiveSpec] or nil yellowfive@191: local bestItem, bestDiff, bestLoc = findMatchingItemFromTable(item, equipped, nil, 1000000, nil, usedItems, "equip") yellowfive@124: bestItem, bestDiff, bestLoc = findMatchingItemFromTable(item, player.BagItems, bestItem, bestDiff, bestLoc, usedItems, "bag") yellowfive@124: if player.BankItems then yellowfive@129: bestItem, bestDiff, bestLoc = findMatchingItemFromTable(item, player.BankItems, bestItem, bestDiff, bestLoc, usedItems, "bank") yellowfive@124: end yellowfive@57: yellowfive@191: if bestDiff >= 1000000 then yellowfive@191: return nil, 1000000 yellowfive@57: else yellowfive@57: usedItems[bestLoc] = true yellowfive@124: return bestItem, bestDiff yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@57: local function renderEmptyGear(container) yellowfive@57: yellowfive@57: local panelBlank = AceGUI:Create("AmrUiPanel") yellowfive@57: panelBlank:SetLayout("None") yellowfive@57: panelBlank:SetBackgroundColor(Amr.Colors.Black, 0.4) yellowfive@124: container:AddChild(panelBlank) yellowfive@57: panelBlank:SetPoint("TOPLEFT", container.content, "TOPLEFT", 6, 0) yellowfive@57: panelBlank:SetPoint("BOTTOMRIGHT", container.content, "BOTTOMRIGHT") yellowfive@57: yellowfive@57: local lbl = AceGUI:Create("AmrUiLabel") yellowfive@124: panelBlank:AddChild(lbl) yellowfive@57: lbl:SetText(L.GearBlank) yellowfive@57: lbl:SetWidth(700) yellowfive@57: lbl:SetJustifyH("MIDDLE") yellowfive@57: lbl:SetFont(Amr.CreateFont("Italic", 16, Amr.Colors.TextTan)) yellowfive@57: lbl:SetPoint("BOTTOM", panelBlank.content, "CENTER", 0, 20) yellowfive@57: yellowfive@57: local lbl2 = AceGUI:Create("AmrUiLabel") yellowfive@124: panelBlank:AddChild(lbl2) yellowfive@57: lbl2:SetText(L.GearBlank2) yellowfive@57: lbl2:SetWidth(700) yellowfive@57: lbl2:SetJustifyH("MIDDLE") yellowfive@57: lbl2:SetFont(Amr.CreateFont("Italic", 16, Amr.Colors.TextTan)) yellowfive@57: lbl2:SetPoint("TOP", lbl.frame, "CENTER", 0, -20) yellowfive@124: end yellowfive@124: yellowfive@124: -- helper to create a widget for showing a socket or azerite power yellowfive@124: local function createSocketWidget(panelMods, prevWidget, prevIsSocket, isEquipped) yellowfive@124: yellowfive@124: -- highlight for socket that doesn't match yellowfive@124: local socketBorder = AceGUI:Create("AmrUiPanel") yellowfive@124: panelMods:AddChild(socketBorder) yellowfive@124: if not prevIsSocket then yellowfive@124: socketBorder:SetPoint("LEFT", prevWidget.frame, "RIGHT", 30, 0) yellowfive@124: else yellowfive@124: socketBorder:SetPoint("LEFT", prevWidget.frame, "RIGHT", 2, 0) yellowfive@124: end yellowfive@124: socketBorder:SetLayout("None") yellowfive@124: socketBorder:SetBackgroundColor(Amr.Colors.Black, isEquipped and 0 or 1) yellowfive@124: socketBorder:SetWidth(26) yellowfive@124: socketBorder:SetHeight(26) yellowfive@124: if isEquipped then yellowfive@124: socketBorder:SetAlpha(0.3) yellowfive@124: end yellowfive@124: yellowfive@124: local socketBg = AceGUI:Create("AmrUiIcon") yellowfive@124: socketBorder:AddChild(socketBg) yellowfive@124: socketBg:SetPoint("TOPLEFT", socketBorder.content, "TOPLEFT", 1, -1) yellowfive@124: socketBg:SetLayout("None") yellowfive@124: socketBg:SetBorderWidth(2) yellowfive@124: socketBg:SetIconBorderColor(Amr.Colors.Green, isEquipped and 0 or 1) yellowfive@124: socketBg:SetWidth(24) yellowfive@124: socketBg:SetHeight(24) yellowfive@124: yellowfive@124: local socketIcon = AceGUI:Create("AmrUiIcon") yellowfive@124: socketBg:AddChild(socketIcon) yellowfive@124: socketIcon:SetPoint("CENTER", socketBg.content, "CENTER") yellowfive@124: socketIcon:SetBorderWidth(1) yellowfive@124: socketIcon:SetIconBorderColor(Amr.Colors.White) yellowfive@124: socketIcon:SetWidth(18) yellowfive@124: socketIcon:SetHeight(18) yellowfive@124: yellowfive@124: return socketBorder, socketIcon yellowfive@57: end yellowfive@57: yellowfive@139: local function renderGear(setupId, container) yellowfive@139: yellowfive@139: -- release all children that were previously rendered, we gonna redo it now yellowfive@139: container:ReleaseChildren() yellowfive@57: yellowfive@57: local player = Amr:ExportCharacter() yellowfive@139: yellowfive@139: local gear yellowfive@139: local spec yellowfive@139: local setupIndex yellowfive@181: local essences yellowfive@139: for i, setup in ipairs(Amr.db.char.GearSetups) do yellowfive@139: if setup.Id == setupId then yellowfive@139: setupIndex = i yellowfive@139: gear = setup.Gear yellowfive@139: spec = setup.SpecSlot yellowfive@165: essences = setup.Essences yellowfive@139: break yellowfive@139: end yellowfive@139: end yellowfive@139: yellowfive@57: local equipped = player.Equipped[player.ActiveSpec] yellowfive@185: --local equippedEssences = player.Essences[player.ActiveSpec] yellowfive@181: yellowfive@57: if not gear then yellowfive@57: -- no gear has been imported for this spec so show a message yellowfive@57: renderEmptyGear(container) yellowfive@57: else yellowfive@57: local panelGear = AceGUI:Create("AmrUiPanel") yellowfive@57: panelGear:SetLayout("None") yellowfive@57: panelGear:SetBackgroundColor(Amr.Colors.Black, 0.3) yellowfive@124: container:AddChild(panelGear) yellowfive@57: panelGear:SetPoint("TOPLEFT", container.content, "TOPLEFT", 6, 0) yellowfive@57: panelGear:SetPoint("BOTTOMRIGHT", container.content, "BOTTOMRIGHT", -300, 0) yellowfive@57: yellowfive@57: local panelMods = AceGUI:Create("AmrUiPanel") yellowfive@57: panelMods:SetLayout("None") yellowfive@124: panelMods:SetBackgroundColor(Amr.Colors.Black, 0.3) yellowfive@124: container:AddChild(panelMods) yellowfive@57: panelMods:SetPoint("TOPLEFT", panelGear.frame, "TOPRIGHT", 15, 0) yellowfive@57: panelMods:SetPoint("BOTTOMRIGHT", container.content, "BOTTOMRIGHT") yellowfive@57: yellowfive@57: -- spec icon yellowfive@57: local icon = AceGUI:Create("AmrUiIcon") yellowfive@57: icon:SetIconBorderColor(Amr.Colors.Classes[player.Class]) yellowfive@57: icon:SetWidth(48) yellowfive@57: icon:SetHeight(48) yellowfive@57: yellowfive@57: local iconSpec yellowfive@81: if player.SubSpecs and player.SubSpecs[spec] then yellowfive@57: iconSpec = player.SubSpecs[spec] yellowfive@57: else yellowfive@57: iconSpec = player.Specs[spec] yellowfive@57: end yellowfive@57: yellowfive@57: icon:SetIcon("Interface\\Icons\\" .. Amr.SpecIcons[iconSpec]) yellowfive@124: panelGear:AddChild(icon) yellowfive@57: icon:SetPoint("TOPLEFT", panelGear.content, "TOPLEFT", 10, -10) yellowfive@57: yellowfive@57: local btnEquip = AceGUI:Create("AmrUiButton") yellowfive@81: btnEquip:SetText(L.GearButtonEquip(L.SpecsShort[player.Specs[spec]])) yellowfive@57: btnEquip:SetBackgroundColor(Amr.Colors.Green) yellowfive@57: btnEquip:SetFont(Amr.CreateFont("Regular", 14, Amr.Colors.White)) yellowfive@57: btnEquip:SetWidth(300) yellowfive@57: btnEquip:SetHeight(26) yellowfive@57: btnEquip:SetCallback("OnClick", function(widget) yellowfive@139: Amr:EquipGearSet(setupIndex) yellowfive@57: end) yellowfive@57: panelGear:AddChild(btnEquip) yellowfive@124: btnEquip:SetPoint("LEFT", icon.frame, "RIGHT", 40, 0) yellowfive@124: btnEquip:SetPoint("RIGHT", panelGear.content, "RIGHT", -40, 0) yellowfive@57: yellowfive@57: -- each physical item can only be used once, this tracks ones we have already used yellowfive@57: local usedItems = {} yellowfive@57: yellowfive@57: -- gear list yellowfive@57: local prevElem = icon yellowfive@57: for slotNum = 1, #Amr.SlotIds do yellowfive@57: local slotId = Amr.SlotIds[slotNum] yellowfive@57: yellowfive@124: local equippedItem = equipped and equipped[slotId] or nil yellowfive@133: --local equippedItemLink = equipped and equipped.link or nil yellowfive@57: local optimalItem = gear[slotId] yellowfive@57: local optimalItemLink = Amr.CreateItemLink(optimalItem) yellowfive@57: yellowfive@57: -- see if item is currently equipped, is false if don't have any item for that slot (e.g. OH for a 2-hander) yellowfive@57: local isEquipped = false yellowfive@135: if equippedItem and optimalItem and Amr.GetItemUniqueId(equippedItem, false, true) == Amr.GetItemUniqueId(optimalItem, false, true) then yellowfive@145: yellowfive@191: if optimalItem.guid then yellowfive@191: isEquipped = optimalItem.guid == equippedItem.guid yellowfive@191: else yellowfive@191: --[[if slotId == 1 or slotId == 3 or slotId == 5 then yellowfive@191: -- show the item as not equipped if azerite doesn't match... might mean they have to switch to another version of same item yellowfive@191: local aztDiff = countItemDifferences(optimalItem, equippedItem) yellowfive@191: if aztDiff < 100 then yellowfive@191: isEquipped = true yellowfive@191: end yellowfive@191: else]] yellowfive@145: isEquipped = true yellowfive@191: --end yellowfive@145: end yellowfive@57: end yellowfive@124: yellowfive@185: --local isAzerite = optimalItem and C_AzeriteEmpoweredItem.IsAzeriteEmpoweredItemByID(optimalItem.id) yellowfive@185: --local isEssence = essences and optimalItem and optimalItem.id == 158075 yellowfive@185: local isAzerite = false yellowfive@185: local isEssence = false yellowfive@57: yellowfive@57: -- find the item in the player's inventory that best matches what the optimization wants to use yellowfive@124: local matchItem = Amr:FindMatchingItem(optimalItem, player, usedItems) yellowfive@57: yellowfive@57: -- slot label yellowfive@57: local lbl = AceGUI:Create("AmrUiLabel") yellowfive@124: panelGear:AddChild(lbl) yellowfive@124: lbl:SetPoint("TOPLEFT", prevElem.frame, "BOTTOMLEFT", 0, -12) yellowfive@57: lbl:SetText(Amr.SlotDisplayText[slotId]) yellowfive@57: lbl:SetWidth(85) yellowfive@57: lbl:SetFont(Amr.CreateFont("Regular", 14, Amr.Colors.White)) yellowfive@57: prevElem = lbl yellowfive@57: yellowfive@57: -- ilvl label yellowfive@57: local lblIlvl = AceGUI:Create("AmrUiLabel") yellowfive@124: panelGear:AddChild(lblIlvl) yellowfive@124: lblIlvl:SetPoint("TOPLEFT", lbl.frame, "TOPRIGHT", 0, 0) yellowfive@57: lblIlvl:SetWidth(45) yellowfive@57: lblIlvl:SetFont(Amr.CreateFont("Italic", 14, Amr.Colors.TextTan)) yellowfive@57: yellowfive@57: -- equipped label yellowfive@57: local lblEquipped = AceGUI:Create("AmrUiLabel") yellowfive@124: panelGear:AddChild(lblEquipped) yellowfive@124: lblEquipped:SetPoint("TOPLEFT", lblIlvl.frame, "TOPRIGHT", 0, 0) yellowfive@57: lblEquipped:SetWidth(20) yellowfive@57: lblEquipped:SetFont(Amr.CreateFont("Regular", 14, Amr.Colors.White)) yellowfive@57: lblEquipped:SetText(isEquipped and "E" or "") yellowfive@57: yellowfive@57: -- item name/link label yellowfive@57: local lblItem = AceGUI:Create("AmrUiLabel") yellowfive@124: panelGear:AddChild(lblItem) yellowfive@124: lblItem:SetPoint("TOPLEFT", lblEquipped.frame, "TOPRIGHT", 0, 0) yellowfive@57: lblItem:SetWordWrap(false) yellowfive@57: lblItem:SetWidth(345) yellowfive@57: lblItem:SetFont(Amr.CreateFont(isEquipped and "Regular" or "Bold", isEquipped and 14 or 15, Amr.Colors.White)) yellowfive@57: yellowfive@133: -- fill the name/ilvl labels, which may require asynchronous loading of item information yellowfive@57: if optimalItemLink then yellowfive@133: local gameItem = Item:CreateFromItemLink(optimalItemLink) yellowfive@133: if gameItem then yellowfive@133: local q = gameItem:GetItemQuality() yellowfive@133: if q == 6 then yellowfive@133: -- for artifacts, we consider it equipped if the item id alone matches yellowfive@133: if equippedItem and equippedItem.id == optimalItem.id then yellowfive@133: isEquipped = true yellowfive@133: end yellowfive@133: lblEquipped:SetText(isEquipped and "E" or "") yellowfive@133: end yellowfive@124: yellowfive@137: lblItem:SetFont(Amr.CreateFont(isEquipped and "Regular" or "Bold", isEquipped and 14 or 15, Amr.Colors.Qualities[q] or Amr.Colors.White)) yellowfive@133: lblItem:SetText(gameItem:GetItemName()) yellowfive@133: lblIlvl:SetText(gameItem:GetCurrentItemLevel()) yellowfive@133: Amr:SetItemTooltip(lblItem, gameItem:GetItemLink(), "ANCHOR_TOPRIGHT") yellowfive@133: end yellowfive@57: end yellowfive@57: yellowfive@57: -- modifications yellowfive@57: if optimalItem then yellowfive@57: yellowfive@165: -- gems or azerite powers or essences yellowfive@124: local prevSocket = nil yellowfive@124: yellowfive@124: if isAzerite then yellowfive@124: local azt = optimalItem.azerite or {} yellowfive@124: for i,spellId in ipairs(azt) do yellowfive@124: if spellId and spellId ~= 0 then yellowfive@135: local equippedAzt = matchItem and matchItem.azerite or {} yellowfive@124: local isPowerActive = Amr.Contains(equippedAzt, spellId) yellowfive@124: yellowfive@124: local socketBorder, socketIcon = createSocketWidget(panelMods, prevSocket or lblItem, prevSocket, isPowerActive) yellowfive@124: yellowfive@124: -- set icon and tooltip yellowfive@133: local _, _, spellIcon = GetSpellInfo(spellId) yellowfive@124: socketIcon:SetIcon(spellIcon) yellowfive@124: Amr:SetSpellTooltip(socketIcon, spellId, "ANCHOR_TOPRIGHT") yellowfive@124: yellowfive@124: prevSocket = socketBorder yellowfive@124: end yellowfive@124: end yellowfive@165: elseif isEssence then yellowfive@181: for i = 1, 4 do yellowfive@181: if essences and #essences >= i then yellowfive@181: local essence = essences[i] yellowfive@181: local equippedEssence = equippedEssences and #equippedEssences >= i and equippedEssences[i] or nil yellowfive@181: if essence then yellowfive@181: local essenceInfo = C_AzeriteEssence.GetEssenceInfo(essence[2]) yellowfive@181: if essenceInfo then yellowfive@181: local isEssenceActive = equippedEssence and equippedEssence[2] == essence[2] yellowfive@181: yellowfive@181: local socketBorder, socketIcon = createSocketWidget(panelMods, prevSocket or lblItem, prevSocket, isEssenceActive) yellowfive@181: yellowfive@181: -- set icon and tooltip yellowfive@181: socketIcon:SetIcon(essenceInfo.icon) yellowfive@181: Amr:SetEssenceTooltip(socketIcon, string.format("azessence:%d:%d", essence[2], essence[3]) , "ANCHOR_TOPRIGHT") yellowfive@181: yellowfive@181: --[[ yellowfive@181: if essence[1] and essence[1] > 4 then yellowfive@181: Amr:SetSpellTooltip(socketIcon, essence[1], "ANCHOR_TOPRIGHT") yellowfive@181: end]] yellowfive@181: yellowfive@181: prevSocket = socketBorder yellowfive@181: end yellowfive@181: end yellowfive@181: end yellowfive@181: end yellowfive@124: else yellowfive@124: for i = 1, #optimalItem.gemIds do yellowfive@124: -- we rely on the fact that the gear sets coming back from the site will almost always have all sockets filled, yellowfive@124: -- because it's a pain to get the actual number of sockets on an item from within the game yellowfive@57: local g = optimalItem.gemIds[i] yellowfive@124: if g == 0 then break end yellowfive@124: yellowfive@124: local isGemEquipped = matchItem and matchItem.gemIds and matchItem.gemIds[i] == g yellowfive@57: yellowfive@124: local socketBorder, socketIcon = createSocketWidget(panelMods, prevSocket or lblItem, prevSocket, isGemEquipped) yellowfive@57: yellowfive@57: -- get icon for optimized gem yellowfive@133: local gameItem = Item:CreateFromItemID(g) yellowfive@133: if gameItem then yellowfive@133: socketIcon:SetIcon(gameItem:GetItemIcon()) yellowfive@133: Amr:SetItemTooltip(socketIcon, gameItem:GetItemLink(), "ANCHOR_TOPRIGHT") yellowfive@133: end yellowfive@89: yellowfive@89: prevSocket = socketBorder yellowfive@57: end yellowfive@57: end yellowfive@124: yellowfive@57: -- enchant yellowfive@57: if optimalItem.enchantId and optimalItem.enchantId ~= 0 then yellowfive@57: local isEnchantEquipped = matchItem and matchItem.enchantId and matchItem.enchantId == optimalItem.enchantId yellowfive@135: yellowfive@57: local lblEnchant = AceGUI:Create("AmrUiLabel") yellowfive@124: panelMods:AddChild(lblEnchant) yellowfive@124: lblEnchant:SetPoint("TOPLEFT", lblItem.frame, "TOPRIGHT", 130, 0) yellowfive@57: lblEnchant:SetWordWrap(false) yellowfive@57: lblEnchant:SetWidth(170) yellowfive@57: lblEnchant:SetFont(Amr.CreateFont(isEnchantEquipped and "Regular" or "Bold", 14, isEnchantEquipped and Amr.Colors.TextGray or Amr.Colors.White)) yellowfive@57: yellowfive@124: local enchInfo = Amr.db.char.ExtraEnchantData[optimalItem.enchantId] yellowfive@57: if enchInfo then yellowfive@57: lblEnchant:SetText(enchInfo.text) yellowfive@57: yellowfive@133: local gameItem = Item:CreateFromItemID(enchInfo.itemId) yellowfive@133: if gameItem then yellowfive@133: Amr:SetItemTooltip(lblEnchant, gameItem:GetItemLink(), "ANCHOR_TOPRIGHT") yellowfive@133: end yellowfive@57: end yellowfive@124: yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@57: prevElem = lbl yellowfive@57: end yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@139: local function onSetupChange(widget, eventName, value) yellowfive@139: _activeSetupId = value yellowfive@139: renderGear(_activeSetupId, _panelGear) yellowfive@57: end yellowfive@57: yellowfive@57: local function onImportClick(widget) yellowfive@57: Amr:ShowImportWindow() yellowfive@57: end yellowfive@57: yellowfive@139: function Amr:PickFirstSetupForSpec() yellowfive@139: local specSlot = GetSpecialization() yellowfive@139: for i, setup in ipairs(Amr.db.char.GearSetups) do yellowfive@139: if setup.SpecSlot == specSlot then yellowfive@139: _activeSetupId = setup.Id yellowfive@139: break yellowfive@139: end yellowfive@139: end yellowfive@139: end yellowfive@139: yellowfive@139: function Amr:GetActiveSetupId() yellowfive@139: return _activeSetupId yellowfive@139: end yellowfive@139: yellowfive@139: function Amr:SetActiveSetupId(setupId) yellowfive@139: _activeSetupId = setupId yellowfive@139: end yellowfive@139: yellowfive@139: function Amr:GetActiveSetupLabel() yellowfive@139: if not _activeSetupId then yellowfive@139: return nil yellowfive@139: end yellowfive@139: local setup = getSetupById(_activeSetupId) yellowfive@139: if not setup then yellowfive@139: return nil yellowfive@139: else yellowfive@139: return setup.Label yellowfive@139: end yellowfive@139: end yellowfive@139: yellowfive@57: -- renders the main UI for the Gear tab yellowfive@57: function Amr:RenderTabGear(container) yellowfive@57: yellowfive@57: local btnImport = AceGUI:Create("AmrUiButton") yellowfive@57: btnImport:SetText(L.GearButtonImportText) yellowfive@57: btnImport:SetBackgroundColor(Amr.Colors.Orange) yellowfive@57: btnImport:SetFont(Amr.CreateFont("Bold", 16, Amr.Colors.White)) yellowfive@57: btnImport:SetWidth(120) yellowfive@57: btnImport:SetHeight(26) yellowfive@57: btnImport:SetCallback("OnClick", onImportClick) yellowfive@57: container:AddChild(btnImport) yellowfive@124: btnImport:SetPoint("TOPLEFT", container.content, "TOPLEFT", 0, -81) yellowfive@57: yellowfive@57: local lbl = AceGUI:Create("AmrUiLabel") yellowfive@124: container:AddChild(lbl) yellowfive@57: lbl:SetText(L.GearImportNote) yellowfive@57: lbl:SetWidth(100) yellowfive@57: lbl:SetFont(Amr.CreateFont("Italic", 12, Amr.Colors.TextTan)) yellowfive@57: lbl:SetJustifyH("MIDDLE") yellowfive@57: lbl:SetPoint("TOP", btnImport.frame, "BOTTOM", 0, -5) yellowfive@57: yellowfive@57: local lbl2 = AceGUI:Create("AmrUiLabel") yellowfive@124: container:AddChild(lbl2) yellowfive@57: lbl2:SetText(L.GearTipTitle) yellowfive@57: lbl2:SetWidth(140) yellowfive@57: lbl2:SetFont(Amr.CreateFont("Italic", 20, Amr.Colors.Text)) yellowfive@57: lbl2:SetJustifyH("MIDDLE") yellowfive@57: lbl2:SetPoint("TOP", lbl.frame, "BOTTOM", 0, -50) yellowfive@57: yellowfive@57: lbl = AceGUI:Create("AmrUiLabel") yellowfive@124: container:AddChild(lbl) yellowfive@57: lbl:SetText(L.GearTipText) yellowfive@57: lbl:SetWidth(140) yellowfive@57: lbl:SetFont(Amr.CreateFont("Italic", 12, Amr.Colors.Text)) yellowfive@57: lbl:SetJustifyH("MIDDLE") yellowfive@57: lbl:SetPoint("TOP", lbl2.frame, "BOTTOM", 0, -5) yellowfive@57: yellowfive@57: lbl2 = AceGUI:Create("AmrUiLabel") yellowfive@124: container:AddChild(lbl2) yellowfive@57: lbl2:SetText(L.GearTipCommands) yellowfive@57: lbl2:SetWidth(130) yellowfive@57: lbl2:SetFont(Amr.CreateFont("Italic", 12, Amr.Colors.Text)) yellowfive@57: lbl2:SetPoint("TOP", lbl.frame, "BOTTOM", 10, -5) yellowfive@57: yellowfive@139: _cboSetups = AceGUI:Create("AmrUiDropDown") yellowfive@139: _cboSetups:SetWidth(300) yellowfive@139: container:AddChild(_cboSetups) yellowfive@139: _cboSetups:SetPoint("TOPLEFT", container.content, "TOPLEFT", 150, -27.5) yellowfive@81: yellowfive@139: _panelGear = AceGUI:Create("AmrUiPanel") yellowfive@139: _panelGear:SetLayout("None") yellowfive@139: _panelGear:SetBackgroundColor(Amr.Colors.Bg) yellowfive@139: container:AddChild(_panelGear) yellowfive@139: _panelGear:SetPoint("TOPLEFT", container.content, "TOPLEFT", 144, -58) yellowfive@139: _panelGear:SetPoint("BOTTOMRIGHT", container.content, "BOTTOMRIGHT") yellowfive@57: yellowfive@61: local btnShop = AceGUI:Create("AmrUiButton") yellowfive@161: container:AddChild(btnShop) yellowfive@61: btnShop:SetText(L.GearButtonShop) yellowfive@61: btnShop:SetBackgroundColor(Amr.Colors.Blue) yellowfive@61: btnShop:SetFont(Amr.CreateFont("Regular", 14, Amr.Colors.White)) yellowfive@161: btnShop:SetWidth(200) yellowfive@61: btnShop:SetHeight(26) yellowfive@61: btnShop:SetCallback("OnClick", function(widget) Amr:ShowShopWindow() end) yellowfive@161: btnShop:SetPoint("TOPRIGHT", container.content, "TOPRIGHT", -42, -25) yellowfive@161: yellowfive@161: local btnJunk = AceGUI:Create("AmrUiButton") yellowfive@161: container:AddChild(btnJunk) yellowfive@161: btnJunk:SetText(L.GearButtonJunk) yellowfive@161: btnJunk:SetBackgroundColor(Amr.Colors.Blue) yellowfive@161: btnJunk:SetFont(Amr.CreateFont("Regular", 14, Amr.Colors.White)) yellowfive@161: btnJunk:SetWidth(200) yellowfive@161: btnJunk:SetHeight(26) yellowfive@161: btnJunk:SetCallback("OnClick", function(widget) Amr:ShowJunkWindow() end) yellowfive@161: btnJunk:SetPoint("CENTER", btnShop.frame, "CENTER", 0, 36) yellowfive@139: yellowfive@139: -- pick a default tab based on player's current spec if none is already specified yellowfive@139: if not _activeSetupId then yellowfive@139: Amr:PickFirstSetupForSpec() yellowfive@57: end yellowfive@139: yellowfive@139: Amr:RefreshGearDisplay() yellowfive@139: yellowfive@139: -- set event on dropdown after UI has been initially rendered yellowfive@139: _cboSetups:SetCallback("OnChange", onSetupChange) yellowfive@57: end yellowfive@57: yellowfive@57: function Amr:ReleaseTabGear() yellowfive@139: _cboSetups = nil yellowfive@139: _panelGear = nil yellowfive@57: end yellowfive@57: yellowfive@57: -- refresh display of the current gear tab yellowfive@139: function Amr:RefreshGearDisplay() yellowfive@139: yellowfive@139: if not _panelGear then yellowfive@139: return yellowfive@139: end yellowfive@139: yellowfive@139: -- fill the gear setup picker yellowfive@139: local setupList = {} yellowfive@139: for i, setup in ipairs(Amr.db.char.GearSetups) do yellowfive@139: table.insert(setupList, { text = setup.Label, value = setup.Id }) yellowfive@139: end yellowfive@139: _cboSetups:SetItems(setupList) yellowfive@139: yellowfive@139: -- set selected value yellowfive@139: local prev = _activeSetupId yellowfive@139: _cboSetups:SelectItem(_activeSetupId) yellowfive@139: yellowfive@139: if prev == _activeSetupId then yellowfive@139: -- selecting will trigger the change event if it changed; if it didn't change, do a render now yellowfive@139: renderGear(_activeSetupId, _panelGear) yellowfive@139: end yellowfive@57: end yellowfive@57: yellowfive@57: yellowfive@57: ------------------------------------------------------------------------------------------------ yellowfive@57: -- Gear Set Management yellowfive@57: ------------------------------------------------------------------------------------------------ yellowfive@57: local _waitingForSpec = 0 yellowfive@124: local _pendingGearOps = nil yellowfive@124: local _currentGearOp = nil yellowfive@124: local _itemLockAction = nil yellowfive@124: local _gearOpPasses = 0 yellowfive@124: local _gearOpWaiting = nil yellowfive@57: yellowfive@124: local beginEquipGearSet, processCurrentGearOp, nextGearOp yellowfive@89: yellowfive@57: -- find the first empty slot in the player's backpack+bags yellowfive@161: local function findFirstEmptyBagSlot(usedBagSlots) yellowfive@57: yellowfive@57: local bagIds = {} yellowfive@57: table.insert(bagIds, BACKPACK_CONTAINER) yellowfive@57: for bagId = 1, NUM_BAG_SLOTS do yellowfive@57: table.insert(bagIds, bagId) yellowfive@57: end yellowfive@57: yellowfive@57: for i, bagId in ipairs(bagIds) do yellowfive@57: local numSlots = GetContainerNumSlots(bagId) yellowfive@57: for slotId = 1, numSlots do yellowfive@161: if not usedBagSlots or not usedBagSlots[bagId] or not usedBagSlots[bagId][slotId] then yellowfive@161: local _, _, _, _, _, _, itemLink = GetContainerItemInfo(bagId, slotId) yellowfive@161: if not itemLink then yellowfive@161: -- this prevents repeated calls to this from returning the same bag slot if desired yellowfive@161: if usedBagSlots then yellowfive@161: if not usedBagSlots[bagId] then yellowfive@161: usedBagSlots[bagId] = {} yellowfive@161: end yellowfive@161: usedBagSlots[bagId][slotId] = true yellowfive@161: end yellowfive@161: yellowfive@161: return bagId, slotId yellowfive@161: end yellowfive@57: end yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@57: return nil, nil yellowfive@57: end yellowfive@57: yellowfive@161: yellowfive@161: yellowfive@124: -- scan a bag for the best matching item yellowfive@124: local function scanBagForItem(item, bagId, bestItem, bestDiff, bestLink) yellowfive@124: local numSlots = GetContainerNumSlots(bagId) Yellowfive@195: --local loc = ItemLocation.CreateEmpty() Yellowfive@195: local blizzItem yellowfive@124: for slotId = 1, numSlots do yellowfive@124: local _, _, _, _, _, _, itemLink = GetContainerItemInfo(bagId, slotId) yellowfive@124: -- we skip any stackable item, as far as we know, there is no equippable gear that can be stacked yellowfive@124: if itemLink then yellowfive@124: local bagItem = Amr.ParseItemLink(itemLink) yellowfive@124: if bagItem ~= nil then Yellowfive@195: Yellowfive@195: blizzItem = Item:CreateFromBagAndSlot(bagId, slotId) Yellowfive@195: Yellowfive@195: -- seems to be of the form Item-1147-0-4000000XXXXXXXXX, so we take just the last 9 digits Yellowfive@195: bagItem.guid = blizzItem:GetItemGUID() Yellowfive@195: if bagItem.guid and strlen(bagItem.guid) > 9 then Yellowfive@195: bagItem.guid = strsub(bagItem.guid, -9) Yellowfive@195: end Yellowfive@195: yellowfive@149: -- see if this is an azerite item and read azerite power ids yellowfive@185: --[[loc:SetBagAndSlot(bagId, slotId) yellowfive@149: if C_AzeriteEmpoweredItem.IsAzeriteEmpoweredItem(loc) then yellowfive@149: local powers = Amr.ReadAzeritePowers(loc) yellowfive@149: if powers then yellowfive@149: bagItem.azerite = powers yellowfive@149: end yellowfive@185: end]] yellowfive@149: yellowfive@124: local diff = countItemDifferences(item, bagItem) yellowfive@124: if diff < bestDiff then yellowfive@124: bestItem = { bag = bagId, slot = slotId } yellowfive@124: bestDiff = diff yellowfive@124: bestLink = itemLink yellowfive@124: end yellowfive@124: end yellowfive@124: end yellowfive@57: end yellowfive@124: return bestItem, bestDiff, bestLink yellowfive@57: end yellowfive@57: yellowfive@124: -- find the item in the player's inventory that best matches the current gear op item, favoring stuff already equipped, then in bags, then in bank yellowfive@124: local function findCurrentGearOpItem() yellowfive@124: yellowfive@124: local item = _currentGearOp.items[_currentGearOp.nextSlot] yellowfive@124: yellowfive@57: local bestItem = nil yellowfive@57: local bestLink = nil yellowfive@124: local bestDiff = 10000 yellowfive@57: yellowfive@73: -- inventory yellowfive@73: bestItem, bestDiff, bestLink = scanBagForItem(item, BACKPACK_CONTAINER, bestItem, bestDiff, bestLink) yellowfive@73: for bagId = 1, NUM_BAG_SLOTS do yellowfive@73: bestItem, bestDiff, bestLink = scanBagForItem(item, bagId, bestItem, bestDiff, bestLink) yellowfive@73: end yellowfive@73: yellowfive@129: -- with new approach, the item to use should never be equipped, should be in bags at this point yellowfive@129: --[[ yellowfive@61: -- equipped items, but skip slots we have just equipped (to avoid e.g. just moving 2 of the same item back and forth between mh oh weapon slots) yellowfive@57: for slotNum = 1, #Amr.SlotIds do yellowfive@57: local slotId = Amr.SlotIds[slotNum] yellowfive@124: if _currentGearOp.slotsRemaining[slotId] then yellowfive@61: local itemLink = GetInventoryItemLink("player", slotId) yellowfive@61: if itemLink then yellowfive@61: local invItem = Amr.ParseItemLink(itemLink) yellowfive@124: if invItem then yellowfive@61: local diff = countItemDifferences(item, invItem) yellowfive@61: if diff < bestDiff then yellowfive@61: bestItem = { slot = slotId } yellowfive@61: bestDiff = diff yellowfive@61: bestLink = itemLink yellowfive@61: end yellowfive@57: end yellowfive@57: end yellowfive@57: end yellowfive@57: end yellowfive@129: ]] yellowfive@129: yellowfive@57: -- bank yellowfive@124: if bestDiff > 0 then yellowfive@124: bestItem, bestDiff, bestLink = scanBagForItem(item, BANK_CONTAINER, bestItem, bestDiff, bestLink) yellowfive@124: for bagId = NUM_BAG_SLOTS + 1, NUM_BAG_SLOTS + NUM_BANKBAGSLOTS do yellowfive@124: bestItem, bestDiff, bestLink = scanBagForItem(item, bagId, bestItem, bestDiff, bestLink) yellowfive@124: end yellowfive@57: end yellowfive@124: yellowfive@124: return bestItem, bestDiff, bestLink yellowfive@124: end yellowfive@124: yellowfive@143: local function createAmrEquipmentSet() yellowfive@133: yellowfive@143: -- clear any currently ignored slots, ignore shirt and tabard yellowfive@143: C_EquipmentSet.ClearIgnoredSlotsForSave() yellowfive@133: C_EquipmentSet.IgnoreSlotForSave(INVSLOT_BODY) -- shirt yellowfive@133: C_EquipmentSet.IgnoreSlotForSave(INVSLOT_TABARD) yellowfive@133: yellowfive@133: -- for now use icon of the spec yellowfive@133: local _, specName, _, setIcon = GetSpecializationInfo(GetSpecialization()) yellowfive@57: yellowfive@133: --[[ yellowfive@124: local item = Amr.ParseItemLink(GetInventoryItemLink("player", INVSLOT_MAINHAND)) yellowfive@124: if not item then yellowfive@124: item = Amr.ParseItemLink(GetInventoryItemLink("player", INVSLOT_OFFHAND)) yellowfive@124: end yellowfive@124: if item then yellowfive@133: local itemObj = Item:CreateFromItemID(item.id) yellowfive@133: if itemObj then yellowfive@133: setIcon = itemObj:GetItemIcon() yellowfive@133: end yellowfive@133: end yellowfive@133: ]] yellowfive@133: yellowfive@139: local setup = getSetupById(_activeSetupId) yellowfive@139: local setname = setup.Label -- "AMR " .. specName yellowfive@133: local setid = C_EquipmentSet.GetEquipmentSetID(setname) yellowfive@133: if setid then yellowfive@145: local oldName, oldIcon = C_EquipmentSet.GetEquipmentSetInfo(setid) yellowfive@145: setIcon = oldIcon yellowfive@133: C_EquipmentSet.SaveEquipmentSet(setid, setIcon) yellowfive@133: else yellowfive@133: C_EquipmentSet.CreateEquipmentSet(setname, setIcon) yellowfive@124: end yellowfive@124: end yellowfive@124: yellowfive@143: -- on completion, create an equipment manager set if desired yellowfive@143: local function onEquipGearSetComplete() yellowfive@143: if Amr.db.profile.options.disableEm then return end yellowfive@143: yellowfive@143: -- create an equipment manager set yellowfive@143: createAmrEquipmentSet() yellowfive@143: yellowfive@143: -- need to call it twice because on first load the WoW equipment manager just doesn't work yellowfive@143: Amr.Wait(1, function() yellowfive@143: createAmrEquipmentSet() yellowfive@143: end) yellowfive@143: end yellowfive@143: yellowfive@124: -- stop any currently in-progress gear swapping operation and clean up yellowfive@124: local function disposeGearOp() yellowfive@124: _pendingGearOps = nil yellowfive@124: _currentGearOp = nil yellowfive@124: _itemLockAction = nil yellowfive@124: _gearOpPasses = 0 yellowfive@124: _gearOpWaiting = nil yellowfive@124: yellowfive@124: -- make sure the gear tab is still in sync yellowfive@139: Amr:RefreshGearDisplay() yellowfive@124: end yellowfive@124: yellowfive@124: -- initialize a gear op to start running it yellowfive@139: local function initializeGearOp(op, setupId, pos) yellowfive@124: op.pos = pos yellowfive@139: op.setupId = setupId yellowfive@124: yellowfive@124: -- fill the remaining slot list and set the starting slot yellowfive@124: op.nextSlot = nil yellowfive@124: op.slotsRemaining = {} yellowfive@124: op.isWaiting = false yellowfive@124: for slotId, item in pairs(op.items) do yellowfive@124: op.slotsRemaining[slotId] = true yellowfive@124: if not op.nextSlot then yellowfive@124: op.nextSlot = slotId yellowfive@124: end yellowfive@124: end yellowfive@124: end yellowfive@124: yellowfive@124: function processCurrentGearOp() yellowfive@124: if not _currentGearOp then return end yellowfive@124: yellowfive@124: if _currentGearOp.remove then yellowfive@124: -- remove the next item yellowfive@124: yellowfive@124: -- check if the slot is already empty yellowfive@124: local itemLink = GetInventoryItemLink("player", _currentGearOp.nextSlot) yellowfive@124: if not itemLink then yellowfive@124: nextGearOp() yellowfive@124: return yellowfive@124: end yellowfive@124: yellowfive@57: -- find first empty bag slot yellowfive@57: local invBag, invSlot = findFirstEmptyBagSlot() yellowfive@57: if not invBag then yellowfive@57: -- stop if bags are too full yellowfive@57: Amr:Print(L.GearEquipErrorBagFull) yellowfive@124: disposeGearOp() yellowfive@57: return yellowfive@57: end yellowfive@57: yellowfive@124: PickupInventoryItem(_currentGearOp.nextSlot) yellowfive@57: PickupContainerItem(invBag, invSlot) yellowfive@57: yellowfive@124: -- set an action to happen on ITEM_UNLOCKED, triggered by ClearCursor yellowfive@124: _itemLockAction = { yellowfive@57: bagId = invBag, yellowfive@124: slotId = invSlot, yellowfive@124: isRemove = true yellowfive@57: } yellowfive@124: yellowfive@124: ClearCursor() yellowfive@124: -- wait for remove to complete yellowfive@124: else yellowfive@124: -- equip the next item yellowfive@57: yellowfive@124: local bestItem, bestDiff, bestLink = findCurrentGearOpItem() yellowfive@124: yellowfive@124: _itemLockAction = nil yellowfive@57: ClearCursor() yellowfive@124: yellowfive@124: if not bestItem then yellowfive@124: -- stop if we can't find an item yellowfive@124: Amr:Print(L.GearEquipErrorNotFound) yellowfive@124: Amr:Print(L.GearEquipErrorNotFound2) yellowfive@124: disposeGearOp() yellowfive@124: yellowfive@124: elseif bestItem and bestItem.bag and (bestItem.bag == BANK_CONTAINER or bestItem.bag >= NUM_BAG_SLOTS + 1 and bestItem.bag <= NUM_BAG_SLOTS + NUM_BANKBAGSLOTS) then yellowfive@124: -- find first empty bag slot yellowfive@124: local invBag, invSlot = findFirstEmptyBagSlot() yellowfive@124: if not invBag then yellowfive@124: -- stop if bags are too full yellowfive@124: Amr:Print(L.GearEquipErrorBagFull) yellowfive@124: disposeGearOp() yellowfive@124: return yellowfive@124: end yellowfive@124: yellowfive@124: -- move from bank to bag yellowfive@124: PickupContainerItem(bestItem.bag, bestItem.slot) yellowfive@124: PickupContainerItem(invBag, invSlot) yellowfive@124: yellowfive@124: -- set an action to happen on ITEM_UNLOCKED, triggered by ClearCursor yellowfive@124: _itemLockAction = { yellowfive@124: bagId = invBag, yellowfive@124: slotId = invSlot, yellowfive@124: isBank = true yellowfive@124: } yellowfive@124: yellowfive@124: ClearCursor() yellowfive@124: -- now we need to wait for game event to continue and try this item again after it is in our bag and unlocked yellowfive@124: yellowfive@124: elseif (bestItem.bag or bestItem.bag == 0) and not Amr:CanEquip(bestItem.bag, bestItem.slot) then yellowfive@57: -- if an item is not soulbound, then warn the user and quit yellowfive@57: Amr:Print(L.GearEquipErrorSoulbound(bestLink)) yellowfive@124: disposeGearOp() yellowfive@124: yellowfive@57: else yellowfive@124: yellowfive@129: --print("equipping " .. bestLink .. " in slot " .. _currentGearOp.nextSlot) yellowfive@129: yellowfive@57: -- an item in the player's bags or already equipped, equip it yellowfive@57: if bestItem.bag then yellowfive@57: PickupContainerItem(bestItem.bag, bestItem.slot) yellowfive@57: else yellowfive@124: _gearOpWaiting.inventory[bestItem.slot] = true yellowfive@57: PickupInventoryItem(bestItem.slot) yellowfive@57: end yellowfive@124: _gearOpWaiting.inventory[_currentGearOp.nextSlot] = true yellowfive@124: PickupInventoryItem(_currentGearOp.nextSlot) yellowfive@124: yellowfive@124: -- don't wait for now, do all equips at once yellowfive@124: --[[ yellowfive@124: -- set an action to happen on ITEM_UNLOCKED, triggered by ClearCursor yellowfive@124: _itemLockAction = { yellowfive@124: bagId = bestItem.bag, yellowfive@124: slotId = bestItem.slot, yellowfive@124: invSlot = _currentGearOp.nextSlot, yellowfive@124: isEquip = true yellowfive@124: } yellowfive@124: ]] yellowfive@124: yellowfive@124: ClearCursor() yellowfive@124: nextGearOp() yellowfive@124: end yellowfive@124: yellowfive@124: end yellowfive@124: end yellowfive@124: yellowfive@124: -- when a gear op completes successfully, this will advance to the next op or finish yellowfive@124: function nextGearOp() yellowfive@124: if not _currentGearOp then return end yellowfive@124: yellowfive@139: local setupId = _currentGearOp.setupId yellowfive@124: local pos = _currentGearOp.pos yellowfive@124: local passes = _gearOpPasses yellowfive@124: yellowfive@124: -- mark the slot as done and move to the next yellowfive@124: if _currentGearOp.nextSlot then yellowfive@124: _currentGearOp.slotsRemaining[_currentGearOp.nextSlot] = nil yellowfive@124: _currentGearOp.nextSlot = nil yellowfive@124: for slotId, item in pairs(_currentGearOp.items) do yellowfive@124: if _currentGearOp.slotsRemaining[slotId] then yellowfive@124: _currentGearOp.nextSlot = slotId yellowfive@124: break yellowfive@124: end yellowfive@124: end yellowfive@124: end yellowfive@124: yellowfive@124: if not _currentGearOp.nextSlot then yellowfive@124: -- see if anything is still in progress and we want to wait for it before continuing yellowfive@124: local inProgress = not Amr.IsEmpty(_gearOpWaiting.inventory) yellowfive@124: yellowfive@124: if (_currentGearOp.wait or _currentGearOp.remove) and inProgress then yellowfive@124: -- this will cause the item unlock handler to call nextGearOp again when all in-progress swaps have unlocked related slots yellowfive@124: _currentGearOp.isWaiting = true yellowfive@124: else yellowfive@124: _currentGearOp = _pendingGearOps[pos + 1] yellowfive@124: if _currentGearOp then yellowfive@124: -- we have another op, do it yellowfive@139: initializeGearOp(_currentGearOp, setupId, pos + 1) yellowfive@124: processCurrentGearOp() yellowfive@124: else yellowfive@124: -- we are done yellowfive@124: disposeGearOp() yellowfive@124: yellowfive@124: -- this will check if not all items were swapped, and either finish up, try again, or abort if have tried too many times yellowfive@139: beginEquipGearSet(setupId, passes + 1) yellowfive@124: end yellowfive@124: end yellowfive@124: else yellowfive@124: -- do the next item yellowfive@124: processCurrentGearOp() yellowfive@124: end yellowfive@124: yellowfive@124: end yellowfive@124: yellowfive@124: local function handleItemUnlocked(bagId, slotId) yellowfive@124: yellowfive@124: -- mark anything that is waiting as unlocked if it is no longer locked yellowfive@124: if _currentGearOp and _gearOpWaiting then yellowfive@124: for i,s in ipairs(Amr.SlotIds) do yellowfive@124: if not IsInventoryItemLocked(s) then yellowfive@124: _gearOpWaiting.inventory[s] = nil yellowfive@124: end yellowfive@124: end yellowfive@124: end yellowfive@124: yellowfive@124: if _itemLockAction then yellowfive@124: if _itemLockAction.isRemove then yellowfive@124: -- waiting for a specific remove op to finish before continuing yellowfive@124: if bagId == _itemLockAction.bagId and slotId == _itemLockAction.slotId then yellowfive@124: _itemLockAction = nil yellowfive@124: nextGearOp() yellowfive@124: end yellowfive@124: elseif _itemLockAction.isBank then yellowfive@124: -- waiting for an item to move from bank into inventory, then reprocess the current op yellowfive@124: if bagId == _itemLockAction.bagId and slotId == _itemLockAction.slotId then yellowfive@124: _itemLockAction = nil yellowfive@124: processCurrentGearOp() yellowfive@124: end yellowfive@124: yellowfive@124: elseif _itemLockAction.isEquip then yellowfive@124: -- this is not currently used... we do all equips at once usually, but could go back to this if it causes problems yellowfive@124: yellowfive@124: -- waiting for a specific equip op to finish yellowfive@61: yellowfive@124: -- inventory slot we're swapping to is still locked, can't continue yet yellowfive@124: if IsInventoryItemLocked(_itemLockAction.invSlot) then return end yellowfive@124: yellowfive@124: if _itemLockAction.bagId then yellowfive@124: local _, _, locked = GetContainerItemInfo(_itemLockAction.bagId, _itemLockAction.slotId) yellowfive@124: -- the bag slot we're swapping from is still locked, can't continue yet yellowfive@124: if locked then return end yellowfive@124: else yellowfive@124: -- inventory slot we're swapping from is still locked, can't continue yet yellowfive@124: if IsInventoryItemLocked(_itemLockAction.slotId) then return end yellowfive@124: end yellowfive@124: yellowfive@124: _itemLockAction = nil yellowfive@124: nextGearOp() yellowfive@124: else yellowfive@124: -- unknown... shouldn't happen yellowfive@124: _itemLockAction = nil yellowfive@57: end yellowfive@124: else yellowfive@124: yellowfive@124: -- not waiting on a specific action, check if we are waiting for all locked slots to open up and they are done yellowfive@124: if _currentGearOp and _gearOpWaiting and _currentGearOp.isWaiting and Amr.IsEmpty(_gearOpWaiting.inventory) then yellowfive@124: nextGearOp() yellowfive@124: end yellowfive@57: end yellowfive@57: yellowfive@57: end yellowfive@57: yellowfive@124: local function shuffle(tbl) yellowfive@124: local size = #tbl yellowfive@124: for i = size, 1, -1 do yellowfive@124: local rand = math.random(size) yellowfive@124: tbl[i], tbl[rand] = tbl[rand], tbl[i] yellowfive@89: end yellowfive@124: return tbl yellowfive@89: end yellowfive@89: yellowfive@129: local _ohFirst = { yellowfive@129: [20] = true, -- PaladinProtection yellowfive@129: [32] = true, -- WarlockDemonology yellowfive@129: [36] = true -- WarriorProtection yellowfive@129: } yellowfive@129: yellowfive@139: function beginEquipGearSet(setupId, passes) yellowfive@57: yellowfive@139: local setup = getSetupById(setupId) yellowfive@139: yellowfive@139: if not setup or not setup.Gear then yellowfive@57: Amr:Print(L.GearEquipErrorEmpty) yellowfive@57: return yellowfive@57: end yellowfive@124: yellowfive@139: local gear = setup.Gear yellowfive@139: local spec = setup.SpecSlot yellowfive@139: yellowfive@139: -- ensure all our stored data is up to date yellowfive@57: local player = Amr:ExportCharacter() yellowfive@129: local doOhFirst = _ohFirst[player.Specs[spec]] yellowfive@57: yellowfive@124: local itemsToEquip = { yellowfive@124: legendaries = {}, yellowfive@124: weapons = {}, yellowfive@129: mh = {}, yellowfive@129: oh = {}, yellowfive@124: rings = {}, yellowfive@124: trinkets = {}, yellowfive@124: others = {}, yellowfive@124: blanks = {} yellowfive@124: } yellowfive@57: local remaining = 0 yellowfive@129: local usedItems = {} yellowfive@124: yellowfive@124: -- check for items that need to be equipped, do in a random order to try and defeat any unique constraint issues we might hit yellowfive@124: local slots = {} yellowfive@124: for i,s in ipairs(Amr.SlotIds) do yellowfive@124: table.insert(slots, s) yellowfive@124: end yellowfive@124: shuffle(slots) yellowfive@124: yellowfive@124: for i,slotId in ipairs(slots) do yellowfive@124: yellowfive@124: -- we do stuff in batches that avoids most unique conflicts yellowfive@124: local list = itemsToEquip.others yellowfive@129: if slotId == 16 then yellowfive@129: list = itemsToEquip.mh yellowfive@129: elseif slotId == 17 then yellowfive@129: list = itemsToEquip.oh yellowfive@124: elseif slotId == 11 or slotId == 12 then yellowfive@124: list = itemsToEquip.rings yellowfive@124: elseif slotId == 13 or slotId == 14 then yellowfive@124: list = itemsToEquip.trinkets yellowfive@124: end yellowfive@124: yellowfive@57: local old = player.Equipped[spec][slotId] yellowfive@57: local new = gear[slotId] yellowfive@124: local prevRemaining = remaining yellowfive@69: if new then yellowfive@124: -- if the new thing is an artifact, only match the item id yellowfive@124: local newItem = Item:CreateFromItemID(new.id) yellowfive@124: local quality = newItem and newItem:GetItemQuality() or 0 yellowfive@124: if quality == 6 then yellowfive@124: if not old or new.id ~= old.id then yellowfive@124: list[slotId] = new yellowfive@129: if list == itemsToEquip.mh or list == itemsToEquip.oh then yellowfive@129: itemsToEquip.weapons[slotId] = {} yellowfive@129: end yellowfive@69: remaining = remaining + 1 yellowfive@69: end yellowfive@129: else yellowfive@145: yellowfive@129: -- find the best matching item anywhere in the player's gear yellowfive@129: local bestItem, bestDiff = Amr:FindMatchingItem(new, player, usedItems) Yellowfive@195: yellowfive@129: new = bestItem yellowfive@149: local diff = countItemDifferences(new, old) yellowfive@124: Yellowfive@195: if diff > 0 then Yellowfive@200: yellowfive@124: list[slotId] = new yellowfive@129: if list == itemsToEquip.mh or list == itemsToEquip.oh then yellowfive@129: itemsToEquip.weapons[slotId] = {} yellowfive@129: end yellowfive@124: remaining = remaining + 1 yellowfive@124: end yellowfive@124: end yellowfive@129: elseif old then yellowfive@124: -- need to remove this item yellowfive@124: itemsToEquip.blanks[slotId] = {} yellowfive@124: remaining = remaining + 1 yellowfive@124: end yellowfive@124: yellowfive@124: if remaining > prevRemaining then yellowfive@124: -- if we need to swap this slot, see if the old item is a legendary, add a step to remove those first to avoid conflicts yellowfive@124: if old then yellowfive@124: local oldItem = Item:CreateFromItemID(old.id) yellowfive@124: if oldItem and oldItem:GetItemQuality() == 5 then yellowfive@124: itemsToEquip.legendaries[slotId] = {} yellowfive@124: end yellowfive@57: end yellowfive@57: end yellowfive@57: end yellowfive@124: yellowfive@124: if remaining > 0 then yellowfive@57: yellowfive@124: if passes < 5 then yellowfive@124: _pendingGearOps = {} yellowfive@124: yellowfive@129: if not Amr.IsEmpty(itemsToEquip.blanks) then yellowfive@124: -- if gear set wants slots to be blank, do that first yellowfive@124: table.insert(_pendingGearOps, { items = itemsToEquip.blanks, remove = true, label = "blanks" }) yellowfive@129: end yellowfive@124: if not Amr.IsEmpty(itemsToEquip.weapons) then yellowfive@129: -- change weapons first: remove both, wait, then equip each weapon one by one, waiting after each yellowfive@129: table.insert(_pendingGearOps, { items = itemsToEquip.weapons, remove = true, label = "remove weapons" }) yellowfive@129: local thisWeapon = doOhFirst and itemsToEquip.oh or itemsToEquip.mh yellowfive@129: if not Amr.IsEmpty(thisWeapon) then yellowfive@129: table.insert(_pendingGearOps, { items = thisWeapon, wait = true, label = "equip weapon 1" }) yellowfive@129: end yellowfive@129: thisWeapon = doOhFirst and itemsToEquip.mh or itemsToEquip.oh yellowfive@129: if not Amr.IsEmpty(thisWeapon) then yellowfive@129: table.insert(_pendingGearOps, { items = thisWeapon, wait = true, label = "equip weapon 2" }) yellowfive@129: end yellowfive@124: end yellowfive@124: if not Amr.IsEmpty(itemsToEquip.legendaries) then yellowfive@124: -- remove any legendaries, wait yellowfive@124: table.insert(_pendingGearOps, { items = itemsToEquip.legendaries, remove = true, label = "remove legendaries" }) yellowfive@124: end yellowfive@124: if not Amr.IsEmpty(itemsToEquip.rings) then yellowfive@124: -- remove both rings, wait, then equip new ones yellowfive@124: table.insert(_pendingGearOps, { items = itemsToEquip.rings, remove = true, label = "remove rings" }) yellowfive@124: table.insert(_pendingGearOps, { items = itemsToEquip.rings, wait = true, label = "equip rings" }) yellowfive@124: end yellowfive@124: if not Amr.IsEmpty(itemsToEquip.trinkets) then yellowfive@124: -- remove both trinkets, wait, then equip new ones yellowfive@124: table.insert(_pendingGearOps, { items = itemsToEquip.trinkets, remove = true, label = "remove trinkets" }) yellowfive@124: table.insert(_pendingGearOps, { items = itemsToEquip.trinkets, wait = true, label = "equip trinkets" }) yellowfive@124: end yellowfive@124: if not Amr.IsEmpty(itemsToEquip.others) then yellowfive@124: -- equip all other items, wait for completion yellowfive@124: table.insert(_pendingGearOps, { items = itemsToEquip.others, wait = true, label = "equip others" }) yellowfive@124: end yellowfive@124: yellowfive@169: if #_pendingGearOps > 0 then yellowfive@169: -- make the last operation wait no matter what, before this gets called again to check if everything succeeded yellowfive@169: _pendingGearOps[#_pendingGearOps].wait = true yellowfive@124: yellowfive@169: if not _gearOpWaiting then yellowfive@169: _gearOpWaiting = { inventory = {} } yellowfive@169: end yellowfive@169: yellowfive@169: _gearOpPasses = passes yellowfive@169: _currentGearOp = _pendingGearOps[1] yellowfive@169: initializeGearOp(_currentGearOp, setupId, 1) yellowfive@169: yellowfive@169: processCurrentGearOp() yellowfive@169: else yellowfive@169: -- TODO: print message that gear set couldn't be equipped yellowfive@124: end yellowfive@169: yellowfive@124: else yellowfive@124: -- TODO: print message that gear set couldn't be equipped yellowfive@89: end yellowfive@57: yellowfive@57: else yellowfive@89: onEquipGearSetComplete() yellowfive@124: end yellowfive@57: end yellowfive@57: yellowfive@57: local function onActiveTalentGroupChanged() yellowfive@81: yellowfive@57: local auto = Amr.db.profile.options.autoGear yellowfive@81: local currentSpec = GetSpecialization() yellowfive@129: local waitingSpec = _waitingForSpec yellowfive@129: _waitingForSpec = 0 yellowfive@57: yellowfive@139: -- when spec changes, change active setup to first one for this spec (does nothing if they have no setups for this spec) yellowfive@139: if _activeSetupId then yellowfive@139: local currentSetup = getSetupById(_activeSetupId) yellowfive@139: if currentSetup.SpecSlot ~= currentSpec then yellowfive@139: Amr:PickFirstSetupForSpec() yellowfive@139: end yellowfive@139: end yellowfive@139: yellowfive@129: if currentSpec == waitingSpec or auto then yellowfive@129: -- spec is what we want, now equip the gear but after a short delay because the game auto-swaps artifact weapons yellowfive@129: Amr.Wait(2, function() yellowfive@139: beginEquipGearSet(_activeSetupId, 0) yellowfive@129: end) yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@81: -- activate the specified spec and then equip the saved gear set yellowfive@139: function Amr:EquipGearSet(setupIndex) yellowfive@57: yellowfive@139: -- if no argument, then cycle yellowfive@139: if not setupIndex then yellowfive@139: if not _activeSetupId then yellowfive@139: Amr:PickFirstSetupForSpec() yellowfive@139: end yellowfive@139: for i,setup in ipairs(Amr.db.char.GearSetups) do yellowfive@139: if setup.Id == _activeSetupId then yellowfive@139: setupIndex = i yellowfive@139: break yellowfive@139: end yellowfive@139: end yellowfive@139: if not setupIndex then yellowfive@139: setupIndex = 1 yellowfive@139: else yellowfive@139: setupIndex = setupIndex + 1 yellowfive@139: end yellowfive@57: end yellowfive@81: yellowfive@139: setupIndex = tonumber(setupIndex) yellowfive@81: yellowfive@139: if setupIndex > #Amr.db.char.GearSetups then yellowfive@139: setupIndex = 1 yellowfive@139: end yellowfive@139: yellowfive@57: if UnitAffectingCombat("player") then yellowfive@57: Amr:Print(L.GearEquipErrorCombat) yellowfive@57: return yellowfive@57: end yellowfive@57: yellowfive@139: _activeSetupId = Amr.db.char.GearSetups[setupIndex].Id yellowfive@139: Amr:RefreshGearDisplay() yellowfive@139: yellowfive@139: local setup = Amr.db.char.GearSetups[setupIndex] yellowfive@81: local currentSpec = GetSpecialization() yellowfive@139: if currentSpec ~= setup.SpecSlot then yellowfive@139: _waitingForSpec = setup.SpecSlot yellowfive@139: SetSpecialization(setup.SpecSlot) yellowfive@57: else yellowfive@129: -- spec is what we want, now equip the gear yellowfive@139: beginEquipGearSet(_activeSetupId, 0) yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@124: -- moves any gear in bags to the bank if not part of a gear set yellowfive@57: function Amr:CleanBags() yellowfive@57: -- TODO: implement yellowfive@57: end yellowfive@57: yellowfive@183: --[[ yellowfive@81: local function testfunc(message) yellowfive@81: print(strsub(message, 13)) yellowfive@81: end yellowfive@183: ]] yellowfive@81: yellowfive@57: function Amr:InitializeGear() yellowfive@87: Amr:AddEventHandler("ACTIVE_TALENT_GROUP_CHANGED", onActiveTalentGroupChanged) yellowfive@57: yellowfive@183: --Amr:AddEventHandler("CHAT_MSG_CHANNEL", testfunc) yellowfive@81: yellowfive@57: Amr:AddEventHandler("UNIT_INVENTORY_CHANGED", function(unitID) yellowfive@57: if unitID and unitID ~= "player" then return end yellowfive@124: yellowfive@124: -- don't update during a gear operation, wait until it is totally finished yellowfive@124: if _pendingGearOps then return end yellowfive@124: yellowfive@139: Amr:RefreshGearDisplay() yellowfive@57: end) yellowfive@57: yellowfive@124: Amr:AddEventHandler("ITEM_UNLOCKED", handleItemUnlocked) yellowfive@122: end yellowfive@161: yellowfive@161: yellowfive@161: -- export some local methods we need elsewhere yellowfive@161: Amr.CountItemDifferences = countItemDifferences yellowfive@161: Amr.FindFirstEmptyBagSlot = findFirstEmptyBagSlot