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@57: local _gearTabs yellowfive@57: local _activeTab 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@57: if item1 == nil and item2 == nil then return 0 end yellowfive@57: yellowfive@57: -- different items (id + bonus ids + suffix, constitutes a different physical drop) yellowfive@57: if Amr.GetItemUniqueId(item1, true) ~= Amr.GetItemUniqueId(item2, true) then yellowfive@57: return 1000 yellowfive@57: end yellowfive@57: yellowfive@57: -- different upgrade levels of the same item (only for older gear, player has control over upgrade level) yellowfive@57: if item1.upgradeId ~= item2.upgradeId then yellowfive@57: return 100 yellowfive@57: 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@57: gemDiffs = gemDiffs + 1 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@57: enchantDiff = 1 yellowfive@57: end yellowfive@57: yellowfive@57: return gemDiffs + enchantDiff 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@57: local function findMatchingItemFromTable(item, list, bestLink, bestItem, bestDiff, bestLoc, usedItems, tableType) yellowfive@57: if not list then return nil end yellowfive@57: yellowfive@57: for k,v in pairs(list) do yellowfive@57: local listItem = Amr.ParseItemLink(v) yellowfive@57: if listItem then yellowfive@57: local diff = countItemDifferences(item, listItem) 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: bestLink = v yellowfive@57: bestItem = listItem yellowfive@57: bestDiff = diff yellowfive@57: bestLoc = string.format("%s_%s", tableType, k) yellowfive@57: end yellowfive@57: end yellowfive@57: if diff == 0 then break end yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@57: return bestLink, bestItem, bestDiff, bestLoc yellowfive@57: end yellowfive@57: yellowfive@57: -- search the player's equipped gear, bag, bank, and void storage 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@57: local bestLink, bestItem, bestDiff, bestLoc = findMatchingItemFromTable(item, equipped, nil, nil, 1000, nil, usedItems, "equip") yellowfive@57: bestLink, bestItem, bestDiff, bestLoc = findMatchingItemFromTable(item, player.BagItems, bestLink, bestItem, bestDiff, bestLoc, usedItems, "bag") yellowfive@57: bestLink, bestItem, bestDiff, bestLoc = findMatchingItemFromTable(item, player.BankItems, bestLink, bestItem, bestDiff, bestLoc, usedItems, "bank") yellowfive@57: bestLink, bestItem, bestDiff, bestLoc = findMatchingItemFromTable(item, player.VoidItems, bestLink, bestItem, bestDiff, bestLoc, usedItems, "void") yellowfive@57: yellowfive@57: if bestDiff >= 1000 then yellowfive@57: return nil, nil, 1000 yellowfive@57: else yellowfive@57: usedItems[bestLoc] = true yellowfive@57: return bestLink, 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@57: panelBlank:SetPoint("TOPLEFT", container.content, "TOPLEFT", 6, 0) yellowfive@57: panelBlank:SetPoint("BOTTOMRIGHT", container.content, "BOTTOMRIGHT") yellowfive@57: container:AddChild(panelBlank) yellowfive@57: yellowfive@57: local lbl = AceGUI:Create("AmrUiLabel") 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: panelBlank:AddChild(lbl) yellowfive@57: yellowfive@57: local lbl2 = AceGUI:Create("AmrUiLabel") 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@57: panelBlank:AddChild(lbl2) yellowfive@57: end yellowfive@57: yellowfive@57: local function renderGear(spec, container) yellowfive@57: yellowfive@57: local player = Amr:ExportCharacter() yellowfive@57: local gear = Amr.db.char.GearSets[spec] yellowfive@57: local equipped = player.Equipped[player.ActiveSpec] yellowfive@57: 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@57: panelGear:SetPoint("TOPLEFT", container.content, "TOPLEFT", 6, 0) yellowfive@57: panelGear:SetPoint("BOTTOMRIGHT", container.content, "BOTTOMRIGHT", -300, 0) yellowfive@57: container:AddChild(panelGear) yellowfive@57: yellowfive@57: local panelMods = AceGUI:Create("AmrUiPanel") yellowfive@57: panelMods:SetLayout("None") yellowfive@57: panelMods:SetPoint("TOPLEFT", panelGear.frame, "TOPRIGHT", 15, 0) yellowfive@57: panelMods:SetPoint("BOTTOMRIGHT", container.content, "BOTTOMRIGHT") yellowfive@57: panelMods:SetBackgroundColor(Amr.Colors.Black, 0.3) yellowfive@57: container:AddChild(panelMods) 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@57: if 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@57: icon:SetPoint("TOPLEFT", panelGear.content, "TOPLEFT", 10, -10) yellowfive@57: panelGear:AddChild(icon) yellowfive@57: yellowfive@57: local btnEquip = AceGUI:Create("AmrUiButton") yellowfive@57: btnEquip:SetText(L.GearButtonEquip(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:SetPoint("LEFT", icon.frame, "RIGHT", 40, 0) yellowfive@57: btnEquip:SetPoint("RIGHT", panelGear.content, "RIGHT", -40, 0) yellowfive@57: btnEquip:SetCallback("OnClick", function(widget) yellowfive@57: Amr:EquipGearSet(spec) yellowfive@57: end) yellowfive@57: panelGear:AddChild(btnEquip) yellowfive@57: yellowfive@61: --[[local btnShop = AceGUI:Create("AmrUiButton") yellowfive@57: btnShop:SetText(L.GearButtonShop) yellowfive@57: btnShop:SetBackgroundColor(Amr.Colors.Blue) yellowfive@57: btnShop:SetFont(Amr.CreateFont("Regular", 14, Amr.Colors.White)) yellowfive@57: btnShop:SetWidth(300) yellowfive@57: btnShop:SetHeight(26) yellowfive@57: btnShop:SetPoint("LEFT", btnEquip.frame, "RIGHT", 75, 0) yellowfive@57: btnShop:SetPoint("RIGHT", panelMods.content, "RIGHT", -20, 0) yellowfive@57: btnShop:SetCallback("OnClick", function(widget) Amr:ShowShopWindow() end) yellowfive@61: panelMods:AddChild(btnShop)]] 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@57: local equippedItemLink = equipped and equipped[slotId] or nil yellowfive@57: local equippedItem = Amr.ParseItemLink(equippedItemLink) 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@57: if equippedItem and optimalItem and Amr.GetItemUniqueId(equippedItem) == Amr.GetItemUniqueId(optimalItem) then yellowfive@57: isEquipped = true yellowfive@57: end yellowfive@57: yellowfive@57: -- find the item in the player's inventory that best matches what the optimization wants to use yellowfive@57: local matchItemLink, matchItem = Amr:FindMatchingItem(optimalItem, player, usedItems) yellowfive@57: yellowfive@57: -- slot label yellowfive@57: local lbl = AceGUI:Create("AmrUiLabel") 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: lbl:SetPoint("TOPLEFT", prevElem.frame, "BOTTOMLEFT", 0, -12) yellowfive@57: panelGear:AddChild(lbl) yellowfive@57: prevElem = lbl yellowfive@57: yellowfive@57: -- ilvl label yellowfive@57: local lblIlvl = AceGUI:Create("AmrUiLabel") yellowfive@57: lblIlvl:SetWidth(45) yellowfive@57: lblIlvl:SetFont(Amr.CreateFont("Italic", 14, Amr.Colors.TextTan)) yellowfive@57: lblIlvl:SetPoint("TOPLEFT", lbl.frame, "TOPRIGHT", 0, 0) yellowfive@57: panelGear:AddChild(lblIlvl) yellowfive@57: yellowfive@57: -- equipped label yellowfive@57: local lblEquipped = AceGUI:Create("AmrUiLabel") yellowfive@57: lblEquipped:SetWidth(20) yellowfive@57: lblEquipped:SetFont(Amr.CreateFont("Regular", 14, Amr.Colors.White)) yellowfive@57: lblEquipped:SetPoint("TOPLEFT", lblIlvl.frame, "TOPRIGHT", 0, 0) yellowfive@57: lblEquipped:SetText(isEquipped and "E" or "") yellowfive@57: panelGear:AddChild(lblEquipped) yellowfive@57: yellowfive@57: -- item name/link label yellowfive@57: local lblItem = AceGUI:Create("AmrUiLabel") 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: lblItem:SetPoint("TOPLEFT", lblEquipped.frame, "TOPRIGHT", 0, 0) yellowfive@57: panelGear:AddChild(lblItem) yellowfive@57: yellowfive@57: -- fill the name/ilvl labels, which may require asynchronous loading of item information yellowfive@57: if optimalItemLink then yellowfive@57: Amr.GetItemInfo(optimalItemLink, function(obj, name, link, quality, iLevel) yellowfive@57: -- set item name, tooltip, and ilvl yellowfive@57: obj.nameLabel:SetText(link:gsub("%[", ""):gsub("%]", "")) yellowfive@57: Amr:SetItemTooltip(obj.nameLabel, link) yellowfive@57: obj.ilvlLabel:SetText(iLevel) yellowfive@57: end, { ilvlLabel = lblIlvl, nameLabel = lblItem }) yellowfive@57: end yellowfive@57: yellowfive@57: -- modifications yellowfive@57: if optimalItem then yellowfive@57: local itemInfo = Amr.db.char.ExtraItemData[spec][optimalItem.id] yellowfive@57: yellowfive@57: -- gems yellowfive@57: if itemInfo and itemInfo.socketColors then yellowfive@57: for i = 1, #itemInfo.socketColors do yellowfive@57: local g = optimalItem.gemIds[i] yellowfive@57: local isGemEquipped = g ~= 0 and matchItem and matchItem.gemIds and matchItem.gemIds[i] == g yellowfive@57: yellowfive@57: -- highlight for gem that doesn't match yellowfive@57: local socketBorder = AceGUI:Create("AmrUiPanel") yellowfive@57: socketBorder:SetLayout("None") yellowfive@57: socketBorder:SetBackgroundColor(Amr.Colors.Black, isGemEquipped and 0 or 1) yellowfive@57: socketBorder:SetWidth(26) yellowfive@57: socketBorder:SetHeight(26) yellowfive@57: socketBorder:SetPoint("LEFT", lblItem.frame, "RIGHT", 30, 0) yellowfive@57: if isGemEquipped then yellowfive@57: socketBorder:SetAlpha(0.3) yellowfive@57: end yellowfive@57: panelMods:AddChild(socketBorder) yellowfive@57: yellowfive@57: local socketBg = AceGUI:Create("AmrUiIcon") yellowfive@57: socketBg:SetLayout("None") yellowfive@57: socketBg:SetBorderWidth(2) yellowfive@57: socketBg:SetIconBorderColor(Amr.Colors.Green, isGemEquipped and 0 or 1) yellowfive@57: socketBg:SetWidth(24) yellowfive@57: socketBg:SetHeight(24) yellowfive@57: socketBg:SetPoint("TOPLEFT", socketBorder.content, "TOPLEFT", 1, -1) yellowfive@57: socketBorder:AddChild(socketBg) yellowfive@57: yellowfive@57: local socketIcon = AceGUI:Create("AmrUiIcon") yellowfive@57: socketIcon:SetBorderWidth(1) yellowfive@57: socketIcon:SetIconBorderColor(Amr.Colors.White) yellowfive@57: socketIcon:SetWidth(18) yellowfive@57: socketIcon:SetHeight(18) yellowfive@57: socketIcon:SetPoint("CENTER", socketBg.content, "CENTER") yellowfive@57: socketBg:AddChild(socketIcon) yellowfive@57: yellowfive@57: -- get icon for optimized gem yellowfive@57: if g ~= 0 then yellowfive@57: local gemInfo = Amr.db.char.ExtraGemData[spec][g] yellowfive@57: if gemInfo then yellowfive@57: Amr.GetItemInfo(gemInfo.id, function(obj, name, link, quality, iLevel, reqLevel, class, subclass, maxStack, equipSlot, texture) yellowfive@57: -- set icon and a tooltip yellowfive@57: obj:SetIcon(texture) yellowfive@57: Amr:SetItemTooltip(obj, link) yellowfive@57: end, socketIcon) yellowfive@57: end yellowfive@57: end yellowfive@57: end yellowfive@57: end yellowfive@57: 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@57: yellowfive@57: local lblEnchant = AceGUI:Create("AmrUiLabel") 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: lblEnchant:SetPoint("TOPLEFT", lblItem.frame, "TOPRIGHT", 130, 0) yellowfive@57: yellowfive@57: local enchInfo = Amr.db.char.ExtraEnchantData[spec][optimalItem.enchantId] yellowfive@57: if enchInfo then yellowfive@57: lblEnchant:SetText(enchInfo.text) yellowfive@57: yellowfive@57: Amr.GetItemInfo(enchInfo.itemId, function(obj, name, link) yellowfive@57: Amr:SetItemTooltip(obj, link) yellowfive@57: end, lblEnchant) yellowfive@57: --Amr:SetSpellTooltip(lblEnchant, enchInfo.spellId) yellowfive@57: end yellowfive@57: panelMods:AddChild(lblEnchant) yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@57: prevElem = lbl yellowfive@57: end yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@57: local function onGearTabSelected(container, event, group) yellowfive@57: container:ReleaseChildren() yellowfive@57: _activeTab = group yellowfive@57: renderGear(tonumber(group), container) yellowfive@57: end yellowfive@57: yellowfive@57: local function onImportClick(widget) yellowfive@57: Amr:ShowImportWindow() yellowfive@57: end yellowfive@57: 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:SetPoint("TOPLEFT", container.content, "TOPLEFT", 0, -81) yellowfive@57: btnImport:SetCallback("OnClick", onImportClick) yellowfive@57: container:AddChild(btnImport) yellowfive@57: yellowfive@57: local lbl = AceGUI:Create("AmrUiLabel") 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: container:AddChild(lbl) yellowfive@57: yellowfive@57: local lbl2 = AceGUI:Create("AmrUiLabel") 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: container:AddChild(lbl2) yellowfive@57: yellowfive@57: lbl = AceGUI:Create("AmrUiLabel") 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: container:AddChild(lbl) yellowfive@57: yellowfive@57: lbl2 = AceGUI:Create("AmrUiLabel") 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: container:AddChild(lbl2) yellowfive@57: yellowfive@57: --[[ yellowfive@57: local btnClean = AceGUI:Create("AmrUiButton") yellowfive@57: btnClean:SetText(L.GearButtonCleanText) yellowfive@57: btnClean:SetBackgroundColor(Amr.Colors.Orange) yellowfive@57: btnClean:SetFont(Amr.CreateFont("Bold", 16, Amr.Colors.White)) yellowfive@57: btnClean:SetWidth(120) yellowfive@57: btnClean:SetHeight(26) yellowfive@57: btnClean:SetPoint("BOTTOMLEFT", container.content, "BOTTOMLEFT", 0, 5) yellowfive@57: btnClean:SetCallback("OnClick", function(widget) Amr:CleanBags() end) yellowfive@57: container:AddChild(btnClean) yellowfive@57: ]] yellowfive@57: yellowfive@57: local t = AceGUI:Create("AmrUiTabGroup") yellowfive@57: t:SetLayout("None") yellowfive@57: t:SetTabs({ yellowfive@57: {text=L.GearTabPrimary, value="1", style="bold"}, yellowfive@57: {text=L.GearTabSecondary, value="2", style="bold"} yellowfive@57: }) yellowfive@57: t:SetCallback("OnGroupSelected", onGearTabSelected) yellowfive@57: t:SetPoint("TOPLEFT", container.content, "TOPLEFT", 144, -30) yellowfive@57: t:SetPoint("BOTTOMRIGHT", container.content, "BOTTOMRIGHT") yellowfive@57: container:AddChild(t) yellowfive@57: _gearTabs = t; yellowfive@57: yellowfive@61: local btnShop = AceGUI:Create("AmrUiButton") 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@61: btnShop:SetWidth(245) yellowfive@61: btnShop:SetHeight(26) yellowfive@61: btnShop:SetPoint("TOPRIGHT", container.content, "TOPRIGHT", -20, -25) yellowfive@61: btnShop:SetCallback("OnClick", function(widget) Amr:ShowShopWindow() end) yellowfive@61: container:AddChild(btnShop) yellowfive@61: yellowfive@61: yellowfive@57: if not _activeTab then yellowfive@57: _activeTab = tostring(GetActiveSpecGroup()) yellowfive@57: end yellowfive@57: yellowfive@57: t:SelectTab(_activeTab) yellowfive@57: end yellowfive@57: yellowfive@57: -- do cleanup when the gear tab is released yellowfive@57: function Amr:ReleaseTabGear() yellowfive@57: _gearTabs = nil yellowfive@57: end yellowfive@57: yellowfive@57: -- show and update the gear tab for the specified spec yellowfive@57: function Amr:ShowGearTab(spec) yellowfive@57: if not _gearTabs then return end yellowfive@57: yellowfive@57: _activeTab = tostring(spec) yellowfive@57: _gearTabs:SelectTab(_activeTab) yellowfive@57: end yellowfive@57: yellowfive@57: -- refresh display of the current gear tab yellowfive@57: function Amr:RefreshGearTab() yellowfive@57: if not _gearTabs then return end yellowfive@57: _gearTabs:SelectTab(_activeTab) yellowfive@57: end yellowfive@57: yellowfive@57: yellowfive@57: ------------------------------------------------------------------------------------------------ yellowfive@57: -- Gear Set Management yellowfive@57: ------------------------------------------------------------------------------------------------ yellowfive@57: local _waitingForSpec = 0 yellowfive@57: local _waitingForItemLock = nil yellowfive@57: local _pendingEquip = nil yellowfive@57: yellowfive@57: -- scan a bag for the best matching item yellowfive@57: local function scanBagForItem(item, bagId, bestItem, bestDiff, bestLink) yellowfive@57: local numSlots = GetContainerNumSlots(bagId) yellowfive@57: for slotId = 1, numSlots do yellowfive@57: local _, _, _, _, _, _, itemLink = GetContainerItemInfo(bagId, slotId) yellowfive@57: -- we skip any stackable item, as far as we know, there is no equippable gear that can be stacked yellowfive@57: if itemLink then yellowfive@57: local bagItem = Amr.ParseItemLink(itemLink) yellowfive@57: if bagItem ~= nil then yellowfive@57: local diff = countItemDifferences(item, bagItem) yellowfive@57: if diff < bestDiff then yellowfive@57: bestItem = { bag = bagId, slot = slotId } yellowfive@57: bestDiff = diff yellowfive@57: bestLink = itemLink yellowfive@57: end yellowfive@57: end yellowfive@57: end yellowfive@57: end yellowfive@57: return bestItem, bestDiff, bestLink yellowfive@57: end yellowfive@57: yellowfive@57: -- find the first empty slot in the player's backpack+bags yellowfive@57: local function findFirstEmptyBagSlot() 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@57: local _, _, _, _, _, _, itemLink = GetContainerItemInfo(bagId, slotId) yellowfive@57: if not itemLink then yellowfive@57: return bagId, slotId yellowfive@57: end yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@57: return nil, nil yellowfive@57: end yellowfive@57: yellowfive@57: local function finishEquipGearSet() yellowfive@57: if not _pendingEquip then return end yellowfive@57: yellowfive@57: _pendingEquip.tries = _pendingEquip.tries + 1 yellowfive@57: if _pendingEquip.tries > 16 then yellowfive@61: -- too many tries, just give up (shouldn't happen but just to be safe) yellowfive@57: _pendingEquip = nil yellowfive@57: else yellowfive@57: -- start over again, trying any items that could not be equipped in the previous pass (unique constraints) yellowfive@57: Amr:EquipGearSet(_pendingEquip.spec) yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@57: -- equip the next slot in a pending equip yellowfive@57: local function tryEquipNextItem() yellowfive@57: if not _pendingEquip then return end yellowfive@57: yellowfive@57: local item = _pendingEquip.itemsToEquip[_pendingEquip.nextSlot] yellowfive@57: yellowfive@57: local bestItem = nil yellowfive@57: local bestLink = nil yellowfive@57: local bestDiff = 1000 yellowfive@57: yellowfive@57: -- find the best matching item yellowfive@57: 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@61: if not _pendingEquip.doneSlots[slotId] then yellowfive@61: local itemLink = GetInventoryItemLink("player", slotId) yellowfive@61: if itemLink then yellowfive@61: local invItem = Amr.ParseItemLink(itemLink) yellowfive@61: if invItem ~= nil 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@57: yellowfive@57: -- inventory yellowfive@57: bestItem, bestDiff, bestLink = scanBagForItem(item, BACKPACK_CONTAINER, bestItem, bestDiff, bestLink) yellowfive@57: for bagId = 1, NUM_BAG_SLOTS do yellowfive@57: bestItem, bestDiff, bestLink = scanBagForItem(item, bagId, bestItem, bestDiff, bestLink) yellowfive@57: end yellowfive@57: yellowfive@57: -- bank yellowfive@67: bestItem, bestDiff, bestLink = scanBagForItem(item, BANK_CONTAINER, bestItem, bestDiff, bestLink) yellowfive@57: for bagId = NUM_BAG_SLOTS + 1, NUM_BAG_SLOTS + NUM_BANKBAGSLOTS do yellowfive@67: bestItem, bestDiff, bestLink = scanBagForItem(item, bagId, bestItem, bestDiff, bestLink) yellowfive@57: end yellowfive@57: yellowfive@57: ClearCursor() yellowfive@57: yellowfive@57: if not bestItem then yellowfive@57: -- stop if we can't find an item yellowfive@57: Amr:Print(L.GearEquipErrorNotFound) yellowfive@57: Amr:Print(L.GearEquipErrorNotFound2) yellowfive@57: _pendingEquip = nil yellowfive@57: return yellowfive@57: yellowfive@71: 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@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@57: _pendingEquip = nil yellowfive@57: return yellowfive@57: end yellowfive@57: yellowfive@57: -- move from bank to bag yellowfive@57: PickupContainerItem(bestItem.bag, bestItem.slot) yellowfive@57: PickupContainerItem(invBag, invSlot) yellowfive@57: yellowfive@57: -- set flag so that when we clear cursor and release the item lock, we can respond to the event and continue yellowfive@57: _waitingForItemLock = { yellowfive@57: bagId = invBag, yellowfive@57: slotId = invSlot yellowfive@57: } yellowfive@57: yellowfive@57: ClearCursor() yellowfive@57: yellowfive@57: -- now we need to wait for game event to continue and try this item again after it is in our bag yellowfive@57: return yellowfive@57: else yellowfive@59: if 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@57: _pendingEquip = nil yellowfive@57: return yellowfive@57: else yellowfive@57: local slotId = _pendingEquip.nextSlot yellowfive@57: yellowfive@57: -- an item in the player's bags or already equipped, equip it yellowfive@57: _pendingEquip.bag = bestItem.bag yellowfive@57: _pendingEquip.slot = bestItem.slot yellowfive@57: _pendingEquip.destSlot = slotId yellowfive@57: yellowfive@57: if bestItem.bag then yellowfive@57: PickupContainerItem(bestItem.bag, bestItem.slot) yellowfive@57: else yellowfive@57: PickupInventoryItem(bestItem.slot) yellowfive@57: end yellowfive@57: PickupInventoryItem(slotId) yellowfive@57: ClearCursor() yellowfive@61: yellowfive@61: -- wait for game events to continue yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@57: end yellowfive@57: yellowfive@57: local function onItemUnlocked(bagId, slotId) yellowfive@57: yellowfive@57: if _waitingForItemLock then yellowfive@57: -- waiting on a move from bank to bags to complete, just continue as normal afterwards yellowfive@57: if bagId == _waitingForItemLock.bagId and slotId == _waitingForItemLock.slotId then yellowfive@57: _waitingForItemLock = nil yellowfive@57: tryEquipNextItem() yellowfive@57: end yellowfive@57: yellowfive@57: elseif _pendingEquip and _pendingEquip.destSlot then yellowfive@57: -- waiting on an item swap to complete successfully so that we can go on to the next item yellowfive@57: yellowfive@57: -- inventory slot we're swapping to is still locked, can't continue yet yellowfive@57: if IsInventoryItemLocked(_pendingEquip.destSlot) then return end yellowfive@57: yellowfive@57: if _pendingEquip.bag then yellowfive@57: local _, _, locked = GetContainerItemInfo(_pendingEquip.bag, _pendingEquip.slot) yellowfive@57: -- the bag slot we're swapping from is still locked, can't continue yet yellowfive@57: if locked then return end yellowfive@57: else yellowfive@57: -- inventory slot we're swapping from is still locked, can't continue yet yellowfive@57: if IsInventoryItemLocked(_pendingEquip.slot) then return end yellowfive@57: end yellowfive@57: yellowfive@57: -- move on to the next item, this item is done yellowfive@61: _pendingEquip.doneSlots[_pendingEquip.destSlot] = true yellowfive@57: _pendingEquip.itemsToEquip[_pendingEquip.destSlot] = nil yellowfive@57: _pendingEquip.destSlot = nil yellowfive@57: _pendingEquip.bag = nil yellowfive@57: _pendingEquip.slot = nil yellowfive@57: yellowfive@57: _pendingEquip.remaining = _pendingEquip.remaining - 1 yellowfive@57: if _pendingEquip.remaining > 0 then yellowfive@57: for slotId, item in pairs(_pendingEquip.itemsToEquip) do yellowfive@57: _pendingEquip.nextSlot = slotId yellowfive@57: break yellowfive@57: end yellowfive@57: tryEquipNextItem() yellowfive@57: else yellowfive@57: finishEquipGearSet() yellowfive@57: end yellowfive@57: yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@57: local function startEquipGearSet(spec) yellowfive@57: yellowfive@57: local gear = Amr.db.char.GearSets[spec] yellowfive@57: if not gear then yellowfive@57: Amr:Print(L.GearEquipErrorEmpty) yellowfive@57: return yellowfive@57: end yellowfive@57: yellowfive@57: local player = Amr:ExportCharacter() yellowfive@57: yellowfive@57: local itemsToEquip = {} yellowfive@57: local remaining = 0 yellowfive@57: local usedItems = {} yellowfive@57: local firstSlot = nil yellowfive@57: yellowfive@57: -- check for items that need to be equipped yellowfive@57: for slotNum = 1, #Amr.SlotIds do yellowfive@57: local slotId = Amr.SlotIds[slotNum] yellowfive@57: yellowfive@57: local old = player.Equipped[spec][slotId] yellowfive@57: old = Amr.ParseItemLink(old) yellowfive@57: yellowfive@57: local new = gear[slotId] yellowfive@69: if new then yellowfive@69: local diff = countItemDifferences(old, new) yellowfive@69: if diff < 1000 then yellowfive@69: -- same item, see if inventory has one that is closer (e.g. a duplicate item with correct enchants/gems) yellowfive@69: local bestLink, bestItem, bestDiff = Amr:FindMatchingItem(new, player, usedItems) yellowfive@69: if bestDiff and bestDiff < diff then yellowfive@69: itemsToEquip[slotId] = new yellowfive@69: remaining = remaining + 1 yellowfive@69: end yellowfive@69: else yellowfive@57: itemsToEquip[slotId] = new yellowfive@57: remaining = remaining + 1 yellowfive@57: end yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@57: if remaining > 0 then yellowfive@57: _pendingEquip = { yellowfive@57: tries = _pendingEquip and _pendingEquip.spec == spec and _pendingEquip.tries or 0, yellowfive@57: spec = spec, yellowfive@57: itemsToEquip = itemsToEquip, yellowfive@57: remaining = remaining, yellowfive@61: doneSlots = {}, yellowfive@57: nextSlot = firstSlot yellowfive@57: } yellowfive@57: yellowfive@57: -- starting item yellowfive@57: for slotId, item in pairs(_pendingEquip.itemsToEquip) do yellowfive@57: _pendingEquip.nextSlot = slotId yellowfive@57: break yellowfive@57: end yellowfive@57: yellowfive@57: tryEquipNextItem() yellowfive@57: else yellowfive@57: _pendingEquip = nil yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@57: local function onActiveTalentGroupChanged() yellowfive@57: local auto = Amr.db.profile.options.autoGear yellowfive@57: local currentSpec = GetActiveSpecGroup() yellowfive@57: yellowfive@57: if currentSpec == _waitingForSpec or auto then yellowfive@57: -- spec is what we want, now equip the gear yellowfive@57: startEquipGearSet(currentSpec) yellowfive@57: end yellowfive@57: yellowfive@57: _waitingForSpec = 0 yellowfive@57: end yellowfive@57: yellowfive@57: -- activate the specified spec and then equip the saved gear set for either primary (1) or secondary (2) spec yellowfive@57: function Amr:EquipGearSet(spec) yellowfive@57: yellowfive@57: -- if no argument, then toggle spec yellowfive@57: if not spec then yellowfive@57: spec = GetActiveSpecGroup() == 1 and 2 or 1 yellowfive@57: end yellowfive@57: yellowfive@57: -- allow some flexibility in the arguments yellowfive@57: if spec == "primary" or spec == "Primary" then spec = 1 end yellowfive@57: if spec == "secondary" or spec == "Secondary" then spec = 2 end yellowfive@57: if spec == "1" or spec == "2" then spec = tonumber(spec) end yellowfive@57: yellowfive@57: -- only spec 1 or 2 are valid yellowfive@57: if spec ~= 1 and spec ~= 2 then return end yellowfive@57: yellowfive@57: if UnitAffectingCombat("player") then yellowfive@57: Amr:Print(L.GearEquipErrorCombat) yellowfive@57: return yellowfive@57: end yellowfive@57: yellowfive@57: _waitingForSpec = spec yellowfive@57: yellowfive@57: local currentSpec = GetActiveSpecGroup() yellowfive@57: if currentSpec ~= spec then yellowfive@57: SetActiveSpecGroup(spec) yellowfive@57: else yellowfive@57: onActiveTalentGroupChanged() yellowfive@57: end yellowfive@57: end yellowfive@57: yellowfive@57: -- moves any gear in bags to the bank if not part of main or off spec gear set yellowfive@57: function Amr:CleanBags() yellowfive@57: -- TODO: implement yellowfive@57: end yellowfive@57: yellowfive@57: function Amr:InitializeGear() yellowfive@57: Amr:AddEventHandler("ACTIVE_TALENT_GROUP_CHANGED", onActiveTalentGroupChanged) yellowfive@57: yellowfive@57: Amr:AddEventHandler("UNIT_INVENTORY_CHANGED", function(unitID) yellowfive@57: if unitID and unitID ~= "player" then return end yellowfive@57: Amr:RefreshGearTab() yellowfive@57: end) yellowfive@57: yellowfive@57: Amr:AddEventHandler("ITEM_UNLOCKED", onItemUnlocked) yellowfive@57: end