annotate Gear.lua @ 135:57be71eccc0a v63

Small azerite gear fix.
author yellowfive
date Sun, 12 Aug 2018 23:36:17 -0700
parents a0894ffebd15
children 6dc0e8e9f960
rev   line source
yellowfive@57 1 local Amr = LibStub("AceAddon-3.0"):GetAddon("AskMrRobot")
yellowfive@57 2 local L = LibStub("AceLocale-3.0"):GetLocale("AskMrRobot", true)
yellowfive@57 3 local AceGUI = LibStub("AceGUI-3.0")
yellowfive@57 4
yellowfive@57 5 local _gearTabs
yellowfive@57 6 local _activeTab
yellowfive@57 7
yellowfive@57 8 -- Returns a number indicating how different two items are (0 means the same, higher means more different)
yellowfive@57 9 local function countItemDifferences(item1, item2)
yellowfive@124 10 -- both nil, the same
yellowfive@124 11 if not item1 and not item2 then
yellowfive@124 12 return 0
yellowfive@124 13 end
yellowfive@124 14
yellowfive@124 15 -- one nil and other not, or different id, totally different
yellowfive@124 16 if (not item1 and item2) or (item1 and not item2) or item1.id ~= item2.id then
yellowfive@124 17 return 10000
yellowfive@124 18 end
yellowfive@124 19
yellowfive@124 20 -- different versions of same item (id + bonus ids + suffix + drop level, constitutes a different physical drop)
yellowfive@135 21 if Amr.GetItemUniqueId(item1, true, true) ~= Amr.GetItemUniqueId(item2, true, true) then
yellowfive@57 22 return 1000
yellowfive@57 23 end
yellowfive@57 24
yellowfive@81 25 -- different upgrade levels of the same item
yellowfive@57 26 if item1.upgradeId ~= item2.upgradeId then
yellowfive@57 27 return 100
yellowfive@124 28 end
yellowfive@124 29
yellowfive@124 30 -- different azerite powers
yellowfive@124 31 local aztDiffs = 0
yellowfive@124 32 if item1.azerite or item2.azerite then
yellowfive@124 33 if item1.azerite and not item2.azerite then
yellowfive@124 34 aztDiffs = #item1.azerite * 10
yellowfive@124 35 elseif item2.azerite and not item1.azerite then
yellowfive@124 36 aztDiffs = #item2.azerite * 10
yellowfive@124 37 else
yellowfive@124 38 -- count up number in item1 but missing from item2
yellowfive@124 39 for i = 1, #item1.azerite do
yellowfive@124 40 local missing = false
yellowfive@124 41 for j = 1, #item2.azerite do
yellowfive@124 42 if item2[j] == item1[i] then
yellowfive@124 43 missing = false
yellowfive@124 44 end
yellowfive@124 45 end
yellowfive@124 46 if missing then
yellowfive@124 47 aztDiffs = aztDiffs + 10
yellowfive@124 48 end
yellowfive@124 49 end
yellowfive@124 50 -- count up number in item2 but missing from item1
yellowfive@124 51 for i = 1, #item2.azerite do
yellowfive@124 52 local missing = false
yellowfive@124 53 for j = 1, #item1.azerite do
yellowfive@124 54 if item1[j] == item2[i] then
yellowfive@124 55 missing = false
yellowfive@124 56 end
yellowfive@124 57 end
yellowfive@124 58 if missing then
yellowfive@124 59 aztDiffs = aztDiffs + 10
yellowfive@124 60 end
yellowfive@124 61 end
yellowfive@124 62 end
yellowfive@124 63 end
yellowfive@57 64
yellowfive@57 65 -- different gems
yellowfive@57 66 local gemDiffs = 0
yellowfive@57 67 for i = 1, 3 do
yellowfive@57 68 if item1.gemIds[i] ~= item2.gemIds[i] then
yellowfive@57 69 gemDiffs = gemDiffs + 1
yellowfive@57 70 end
yellowfive@57 71 end
yellowfive@57 72
yellowfive@57 73 -- different enchants
yellowfive@57 74 local enchantDiff = 0
yellowfive@57 75 if item1.enchantId ~= item2.enchantId then
yellowfive@57 76 enchantDiff = 1
yellowfive@57 77 end
yellowfive@57 78
yellowfive@124 79 return aztDiffs + gemDiffs + enchantDiff
yellowfive@57 80 end
yellowfive@57 81
yellowfive@57 82 -- 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 83 local function findMatchingItemFromTable(item, list, bestItem, bestDiff, bestLoc, usedItems, tableType)
yellowfive@57 84 if not list then return nil end
yellowfive@57 85
yellowfive@73 86 local found = false
yellowfive@129 87 for k,listItem in pairs(list) do
yellowfive@57 88 if listItem then
yellowfive@57 89 local diff = countItemDifferences(item, listItem)
yellowfive@57 90 if diff < bestDiff then
yellowfive@57 91 -- each physical item can only be used once, the usedItems table has items we can't use in this search
yellowfive@57 92 local key = string.format("%s_%s", tableType, k)
yellowfive@57 93 if not usedItems[key] then
yellowfive@57 94 bestItem = listItem
yellowfive@57 95 bestDiff = diff
yellowfive@124 96 bestLoc = key
yellowfive@73 97 found = true
yellowfive@57 98 end
yellowfive@57 99 end
yellowfive@73 100 if found then break end
yellowfive@57 101 end
yellowfive@57 102 end
yellowfive@57 103
yellowfive@124 104 return bestItem, bestDiff, bestLoc
yellowfive@57 105 end
yellowfive@57 106
yellowfive@124 107 -- search the player's equipped gear, bag, and bank for an item that best matches the specified item
yellowfive@57 108 function Amr:FindMatchingItem(item, player, usedItems)
yellowfive@57 109 if not item then return nil end
yellowfive@57 110
yellowfive@57 111 local equipped = player.Equipped and player.Equipped[player.ActiveSpec] or nil
yellowfive@124 112 local bestItem, bestDiff, bestLoc = findMatchingItemFromTable(item, equipped, nil, 10000, nil, usedItems, "equip")
yellowfive@124 113 bestItem, bestDiff, bestLoc = findMatchingItemFromTable(item, player.BagItems, bestItem, bestDiff, bestLoc, usedItems, "bag")
yellowfive@124 114 if player.BankItems then
yellowfive@129 115 bestItem, bestDiff, bestLoc = findMatchingItemFromTable(item, player.BankItems, bestItem, bestDiff, bestLoc, usedItems, "bank")
yellowfive@124 116 end
yellowfive@57 117
yellowfive@124 118 if bestDiff >= 10000 then
yellowfive@124 119 return nil, 10000
yellowfive@57 120 else
yellowfive@57 121 usedItems[bestLoc] = true
yellowfive@124 122 return bestItem, bestDiff
yellowfive@57 123 end
yellowfive@57 124 end
yellowfive@57 125
yellowfive@57 126 local function renderEmptyGear(container)
yellowfive@57 127
yellowfive@57 128 local panelBlank = AceGUI:Create("AmrUiPanel")
yellowfive@57 129 panelBlank:SetLayout("None")
yellowfive@57 130 panelBlank:SetBackgroundColor(Amr.Colors.Black, 0.4)
yellowfive@124 131 container:AddChild(panelBlank)
yellowfive@57 132 panelBlank:SetPoint("TOPLEFT", container.content, "TOPLEFT", 6, 0)
yellowfive@57 133 panelBlank:SetPoint("BOTTOMRIGHT", container.content, "BOTTOMRIGHT")
yellowfive@57 134
yellowfive@57 135 local lbl = AceGUI:Create("AmrUiLabel")
yellowfive@124 136 panelBlank:AddChild(lbl)
yellowfive@57 137 lbl:SetText(L.GearBlank)
yellowfive@57 138 lbl:SetWidth(700)
yellowfive@57 139 lbl:SetJustifyH("MIDDLE")
yellowfive@57 140 lbl:SetFont(Amr.CreateFont("Italic", 16, Amr.Colors.TextTan))
yellowfive@57 141 lbl:SetPoint("BOTTOM", panelBlank.content, "CENTER", 0, 20)
yellowfive@57 142
yellowfive@57 143 local lbl2 = AceGUI:Create("AmrUiLabel")
yellowfive@124 144 panelBlank:AddChild(lbl2)
yellowfive@57 145 lbl2:SetText(L.GearBlank2)
yellowfive@57 146 lbl2:SetWidth(700)
yellowfive@57 147 lbl2:SetJustifyH("MIDDLE")
yellowfive@57 148 lbl2:SetFont(Amr.CreateFont("Italic", 16, Amr.Colors.TextTan))
yellowfive@57 149 lbl2:SetPoint("TOP", lbl.frame, "CENTER", 0, -20)
yellowfive@124 150 end
yellowfive@124 151
yellowfive@124 152 -- helper to create a widget for showing a socket or azerite power
yellowfive@124 153 local function createSocketWidget(panelMods, prevWidget, prevIsSocket, isEquipped)
yellowfive@124 154
yellowfive@124 155 -- highlight for socket that doesn't match
yellowfive@124 156 local socketBorder = AceGUI:Create("AmrUiPanel")
yellowfive@124 157 panelMods:AddChild(socketBorder)
yellowfive@124 158 if not prevIsSocket then
yellowfive@124 159 socketBorder:SetPoint("LEFT", prevWidget.frame, "RIGHT", 30, 0)
yellowfive@124 160 else
yellowfive@124 161 socketBorder:SetPoint("LEFT", prevWidget.frame, "RIGHT", 2, 0)
yellowfive@124 162 end
yellowfive@124 163 socketBorder:SetLayout("None")
yellowfive@124 164 socketBorder:SetBackgroundColor(Amr.Colors.Black, isEquipped and 0 or 1)
yellowfive@124 165 socketBorder:SetWidth(26)
yellowfive@124 166 socketBorder:SetHeight(26)
yellowfive@124 167 if isEquipped then
yellowfive@124 168 socketBorder:SetAlpha(0.3)
yellowfive@124 169 end
yellowfive@124 170
yellowfive@124 171 local socketBg = AceGUI:Create("AmrUiIcon")
yellowfive@124 172 socketBorder:AddChild(socketBg)
yellowfive@124 173 socketBg:SetPoint("TOPLEFT", socketBorder.content, "TOPLEFT", 1, -1)
yellowfive@124 174 socketBg:SetLayout("None")
yellowfive@124 175 socketBg:SetBorderWidth(2)
yellowfive@124 176 socketBg:SetIconBorderColor(Amr.Colors.Green, isEquipped and 0 or 1)
yellowfive@124 177 socketBg:SetWidth(24)
yellowfive@124 178 socketBg:SetHeight(24)
yellowfive@124 179
yellowfive@124 180 local socketIcon = AceGUI:Create("AmrUiIcon")
yellowfive@124 181 socketBg:AddChild(socketIcon)
yellowfive@124 182 socketIcon:SetPoint("CENTER", socketBg.content, "CENTER")
yellowfive@124 183 socketIcon:SetBorderWidth(1)
yellowfive@124 184 socketIcon:SetIconBorderColor(Amr.Colors.White)
yellowfive@124 185 socketIcon:SetWidth(18)
yellowfive@124 186 socketIcon:SetHeight(18)
yellowfive@124 187
yellowfive@124 188 return socketBorder, socketIcon
yellowfive@57 189 end
yellowfive@57 190
yellowfive@57 191 local function renderGear(spec, container)
yellowfive@57 192
yellowfive@57 193 local player = Amr:ExportCharacter()
yellowfive@57 194 local gear = Amr.db.char.GearSets[spec]
yellowfive@57 195 local equipped = player.Equipped[player.ActiveSpec]
yellowfive@57 196
yellowfive@57 197 if not gear then
yellowfive@57 198 -- no gear has been imported for this spec so show a message
yellowfive@57 199 renderEmptyGear(container)
yellowfive@57 200 else
yellowfive@57 201 local panelGear = AceGUI:Create("AmrUiPanel")
yellowfive@57 202 panelGear:SetLayout("None")
yellowfive@57 203 panelGear:SetBackgroundColor(Amr.Colors.Black, 0.3)
yellowfive@124 204 container:AddChild(panelGear)
yellowfive@57 205 panelGear:SetPoint("TOPLEFT", container.content, "TOPLEFT", 6, 0)
yellowfive@57 206 panelGear:SetPoint("BOTTOMRIGHT", container.content, "BOTTOMRIGHT", -300, 0)
yellowfive@57 207
yellowfive@57 208 local panelMods = AceGUI:Create("AmrUiPanel")
yellowfive@57 209 panelMods:SetLayout("None")
yellowfive@124 210 panelMods:SetBackgroundColor(Amr.Colors.Black, 0.3)
yellowfive@124 211 container:AddChild(panelMods)
yellowfive@57 212 panelMods:SetPoint("TOPLEFT", panelGear.frame, "TOPRIGHT", 15, 0)
yellowfive@57 213 panelMods:SetPoint("BOTTOMRIGHT", container.content, "BOTTOMRIGHT")
yellowfive@57 214
yellowfive@57 215 -- spec icon
yellowfive@57 216 local icon = AceGUI:Create("AmrUiIcon")
yellowfive@57 217 icon:SetIconBorderColor(Amr.Colors.Classes[player.Class])
yellowfive@57 218 icon:SetWidth(48)
yellowfive@57 219 icon:SetHeight(48)
yellowfive@57 220
yellowfive@57 221 local iconSpec
yellowfive@81 222 if player.SubSpecs and player.SubSpecs[spec] then
yellowfive@57 223 iconSpec = player.SubSpecs[spec]
yellowfive@57 224 else
yellowfive@57 225 iconSpec = player.Specs[spec]
yellowfive@57 226 end
yellowfive@57 227
yellowfive@57 228 icon:SetIcon("Interface\\Icons\\" .. Amr.SpecIcons[iconSpec])
yellowfive@124 229 panelGear:AddChild(icon)
yellowfive@57 230 icon:SetPoint("TOPLEFT", panelGear.content, "TOPLEFT", 10, -10)
yellowfive@57 231
yellowfive@57 232 local btnEquip = AceGUI:Create("AmrUiButton")
yellowfive@81 233 btnEquip:SetText(L.GearButtonEquip(L.SpecsShort[player.Specs[spec]]))
yellowfive@57 234 btnEquip:SetBackgroundColor(Amr.Colors.Green)
yellowfive@57 235 btnEquip:SetFont(Amr.CreateFont("Regular", 14, Amr.Colors.White))
yellowfive@57 236 btnEquip:SetWidth(300)
yellowfive@57 237 btnEquip:SetHeight(26)
yellowfive@57 238 btnEquip:SetCallback("OnClick", function(widget)
yellowfive@57 239 Amr:EquipGearSet(spec)
yellowfive@57 240 end)
yellowfive@57 241 panelGear:AddChild(btnEquip)
yellowfive@124 242 btnEquip:SetPoint("LEFT", icon.frame, "RIGHT", 40, 0)
yellowfive@124 243 btnEquip:SetPoint("RIGHT", panelGear.content, "RIGHT", -40, 0)
yellowfive@57 244
yellowfive@57 245 -- each physical item can only be used once, this tracks ones we have already used
yellowfive@57 246 local usedItems = {}
yellowfive@57 247
yellowfive@57 248 -- gear list
yellowfive@57 249 local prevElem = icon
yellowfive@57 250 for slotNum = 1, #Amr.SlotIds do
yellowfive@57 251 local slotId = Amr.SlotIds[slotNum]
yellowfive@57 252
yellowfive@124 253 local equippedItem = equipped and equipped[slotId] or nil
yellowfive@133 254 --local equippedItemLink = equipped and equipped.link or nil
yellowfive@57 255 local optimalItem = gear[slotId]
yellowfive@57 256 local optimalItemLink = Amr.CreateItemLink(optimalItem)
yellowfive@57 257
yellowfive@57 258 -- 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 259 local isEquipped = false
yellowfive@135 260 if equippedItem and optimalItem and Amr.GetItemUniqueId(equippedItem, false, true) == Amr.GetItemUniqueId(optimalItem, false, true) then
yellowfive@57 261 isEquipped = true
yellowfive@57 262 end
yellowfive@124 263
yellowfive@124 264 local isAzerite = optimalItem and C_AzeriteEmpoweredItem.IsAzeriteEmpoweredItemByID(optimalItem.id)
yellowfive@57 265
yellowfive@57 266 -- find the item in the player's inventory that best matches what the optimization wants to use
yellowfive@124 267 local matchItem = Amr:FindMatchingItem(optimalItem, player, usedItems)
yellowfive@57 268
yellowfive@57 269 -- slot label
yellowfive@57 270 local lbl = AceGUI:Create("AmrUiLabel")
yellowfive@124 271 panelGear:AddChild(lbl)
yellowfive@124 272 lbl:SetPoint("TOPLEFT", prevElem.frame, "BOTTOMLEFT", 0, -12)
yellowfive@57 273 lbl:SetText(Amr.SlotDisplayText[slotId])
yellowfive@57 274 lbl:SetWidth(85)
yellowfive@57 275 lbl:SetFont(Amr.CreateFont("Regular", 14, Amr.Colors.White))
yellowfive@57 276 prevElem = lbl
yellowfive@57 277
yellowfive@57 278 -- ilvl label
yellowfive@57 279 local lblIlvl = AceGUI:Create("AmrUiLabel")
yellowfive@124 280 panelGear:AddChild(lblIlvl)
yellowfive@124 281 lblIlvl:SetPoint("TOPLEFT", lbl.frame, "TOPRIGHT", 0, 0)
yellowfive@57 282 lblIlvl:SetWidth(45)
yellowfive@57 283 lblIlvl:SetFont(Amr.CreateFont("Italic", 14, Amr.Colors.TextTan))
yellowfive@57 284
yellowfive@57 285 -- equipped label
yellowfive@57 286 local lblEquipped = AceGUI:Create("AmrUiLabel")
yellowfive@124 287 panelGear:AddChild(lblEquipped)
yellowfive@124 288 lblEquipped:SetPoint("TOPLEFT", lblIlvl.frame, "TOPRIGHT", 0, 0)
yellowfive@57 289 lblEquipped:SetWidth(20)
yellowfive@57 290 lblEquipped:SetFont(Amr.CreateFont("Regular", 14, Amr.Colors.White))
yellowfive@57 291 lblEquipped:SetText(isEquipped and "E" or "")
yellowfive@57 292
yellowfive@57 293 -- item name/link label
yellowfive@57 294 local lblItem = AceGUI:Create("AmrUiLabel")
yellowfive@124 295 panelGear:AddChild(lblItem)
yellowfive@124 296 lblItem:SetPoint("TOPLEFT", lblEquipped.frame, "TOPRIGHT", 0, 0)
yellowfive@57 297 lblItem:SetWordWrap(false)
yellowfive@57 298 lblItem:SetWidth(345)
yellowfive@57 299 lblItem:SetFont(Amr.CreateFont(isEquipped and "Regular" or "Bold", isEquipped and 14 or 15, Amr.Colors.White))
yellowfive@57 300
yellowfive@133 301 -- fill the name/ilvl labels, which may require asynchronous loading of item information
yellowfive@57 302 if optimalItemLink then
yellowfive@133 303 local gameItem = Item:CreateFromItemLink(optimalItemLink)
yellowfive@133 304 if gameItem then
yellowfive@133 305 local q = gameItem:GetItemQuality()
yellowfive@133 306 if q == 6 then
yellowfive@133 307 -- for artifacts, we consider it equipped if the item id alone matches
yellowfive@133 308 if equippedItem and equippedItem.id == optimalItem.id then
yellowfive@133 309 isEquipped = true
yellowfive@133 310 end
yellowfive@133 311 lblEquipped:SetText(isEquipped and "E" or "")
yellowfive@133 312 end
yellowfive@124 313
yellowfive@133 314 lblItem:SetFont(Amr.CreateFont(isEquipped and "Regular" or "Bold", isEquipped and 14 or 15, Amr.Colors.Qualities[q]))
yellowfive@133 315 lblItem:SetText(gameItem:GetItemName())
yellowfive@133 316 lblIlvl:SetText(gameItem:GetCurrentItemLevel())
yellowfive@133 317 Amr:SetItemTooltip(lblItem, gameItem:GetItemLink(), "ANCHOR_TOPRIGHT")
yellowfive@133 318 end
yellowfive@57 319 end
yellowfive@57 320
yellowfive@57 321 -- modifications
yellowfive@57 322 if optimalItem then
yellowfive@57 323
yellowfive@124 324 -- gems or azerite powers
yellowfive@124 325 local prevSocket = nil
yellowfive@124 326
yellowfive@124 327 if isAzerite then
yellowfive@124 328 local azt = optimalItem.azerite or {}
yellowfive@124 329 for i,spellId in ipairs(azt) do
yellowfive@124 330 if spellId and spellId ~= 0 then
yellowfive@135 331 local equippedAzt = matchItem and matchItem.azerite or {}
yellowfive@124 332 local isPowerActive = Amr.Contains(equippedAzt, spellId)
yellowfive@124 333
yellowfive@124 334 local socketBorder, socketIcon = createSocketWidget(panelMods, prevSocket or lblItem, prevSocket, isPowerActive)
yellowfive@124 335
yellowfive@124 336 -- set icon and tooltip
yellowfive@133 337 local _, _, spellIcon = GetSpellInfo(spellId)
yellowfive@124 338 socketIcon:SetIcon(spellIcon)
yellowfive@124 339 Amr:SetSpellTooltip(socketIcon, spellId, "ANCHOR_TOPRIGHT")
yellowfive@124 340
yellowfive@124 341 prevSocket = socketBorder
yellowfive@124 342 end
yellowfive@124 343 end
yellowfive@124 344 else
yellowfive@124 345 for i = 1, #optimalItem.gemIds do
yellowfive@124 346 -- we rely on the fact that the gear sets coming back from the site will almost always have all sockets filled,
yellowfive@124 347 -- because it's a pain to get the actual number of sockets on an item from within the game
yellowfive@57 348 local g = optimalItem.gemIds[i]
yellowfive@124 349 if g == 0 then break end
yellowfive@124 350
yellowfive@124 351 local isGemEquipped = matchItem and matchItem.gemIds and matchItem.gemIds[i] == g
yellowfive@57 352
yellowfive@124 353 local socketBorder, socketIcon = createSocketWidget(panelMods, prevSocket or lblItem, prevSocket, isGemEquipped)
yellowfive@57 354
yellowfive@57 355 -- get icon for optimized gem
yellowfive@133 356 local gameItem = Item:CreateFromItemID(g)
yellowfive@133 357 if gameItem then
yellowfive@133 358 socketIcon:SetIcon(gameItem:GetItemIcon())
yellowfive@133 359 Amr:SetItemTooltip(socketIcon, gameItem:GetItemLink(), "ANCHOR_TOPRIGHT")
yellowfive@133 360 end
yellowfive@89 361
yellowfive@89 362 prevSocket = socketBorder
yellowfive@57 363 end
yellowfive@57 364 end
yellowfive@124 365
yellowfive@57 366 -- enchant
yellowfive@57 367 if optimalItem.enchantId and optimalItem.enchantId ~= 0 then
yellowfive@57 368 local isEnchantEquipped = matchItem and matchItem.enchantId and matchItem.enchantId == optimalItem.enchantId
yellowfive@135 369
yellowfive@57 370 local lblEnchant = AceGUI:Create("AmrUiLabel")
yellowfive@124 371 panelMods:AddChild(lblEnchant)
yellowfive@124 372 lblEnchant:SetPoint("TOPLEFT", lblItem.frame, "TOPRIGHT", 130, 0)
yellowfive@57 373 lblEnchant:SetWordWrap(false)
yellowfive@57 374 lblEnchant:SetWidth(170)
yellowfive@57 375 lblEnchant:SetFont(Amr.CreateFont(isEnchantEquipped and "Regular" or "Bold", 14, isEnchantEquipped and Amr.Colors.TextGray or Amr.Colors.White))
yellowfive@57 376
yellowfive@124 377 local enchInfo = Amr.db.char.ExtraEnchantData[optimalItem.enchantId]
yellowfive@57 378 if enchInfo then
yellowfive@57 379 lblEnchant:SetText(enchInfo.text)
yellowfive@57 380
yellowfive@133 381 local gameItem = Item:CreateFromItemID(enchInfo.itemId)
yellowfive@133 382 if gameItem then
yellowfive@133 383 Amr:SetItemTooltip(lblEnchant, gameItem:GetItemLink(), "ANCHOR_TOPRIGHT")
yellowfive@133 384 end
yellowfive@57 385 end
yellowfive@124 386
yellowfive@57 387 end
yellowfive@57 388 end
yellowfive@57 389
yellowfive@57 390 prevElem = lbl
yellowfive@57 391 end
yellowfive@57 392 end
yellowfive@57 393 end
yellowfive@57 394
yellowfive@57 395 local function onGearTabSelected(container, event, group)
yellowfive@57 396 container:ReleaseChildren()
yellowfive@57 397 _activeTab = group
yellowfive@57 398 renderGear(tonumber(group), container)
yellowfive@57 399 end
yellowfive@57 400
yellowfive@57 401 local function onImportClick(widget)
yellowfive@57 402 Amr:ShowImportWindow()
yellowfive@57 403 end
yellowfive@57 404
yellowfive@57 405 -- renders the main UI for the Gear tab
yellowfive@57 406 function Amr:RenderTabGear(container)
yellowfive@57 407
yellowfive@57 408 local btnImport = AceGUI:Create("AmrUiButton")
yellowfive@57 409 btnImport:SetText(L.GearButtonImportText)
yellowfive@57 410 btnImport:SetBackgroundColor(Amr.Colors.Orange)
yellowfive@57 411 btnImport:SetFont(Amr.CreateFont("Bold", 16, Amr.Colors.White))
yellowfive@57 412 btnImport:SetWidth(120)
yellowfive@57 413 btnImport:SetHeight(26)
yellowfive@57 414 btnImport:SetCallback("OnClick", onImportClick)
yellowfive@57 415 container:AddChild(btnImport)
yellowfive@124 416 btnImport:SetPoint("TOPLEFT", container.content, "TOPLEFT", 0, -81)
yellowfive@57 417
yellowfive@57 418 local lbl = AceGUI:Create("AmrUiLabel")
yellowfive@124 419 container:AddChild(lbl)
yellowfive@57 420 lbl:SetText(L.GearImportNote)
yellowfive@57 421 lbl:SetWidth(100)
yellowfive@57 422 lbl:SetFont(Amr.CreateFont("Italic", 12, Amr.Colors.TextTan))
yellowfive@57 423 lbl:SetJustifyH("MIDDLE")
yellowfive@57 424 lbl:SetPoint("TOP", btnImport.frame, "BOTTOM", 0, -5)
yellowfive@57 425
yellowfive@57 426 local lbl2 = AceGUI:Create("AmrUiLabel")
yellowfive@124 427 container:AddChild(lbl2)
yellowfive@57 428 lbl2:SetText(L.GearTipTitle)
yellowfive@57 429 lbl2:SetWidth(140)
yellowfive@57 430 lbl2:SetFont(Amr.CreateFont("Italic", 20, Amr.Colors.Text))
yellowfive@57 431 lbl2:SetJustifyH("MIDDLE")
yellowfive@57 432 lbl2:SetPoint("TOP", lbl.frame, "BOTTOM", 0, -50)
yellowfive@57 433
yellowfive@57 434 lbl = AceGUI:Create("AmrUiLabel")
yellowfive@124 435 container:AddChild(lbl)
yellowfive@57 436 lbl:SetText(L.GearTipText)
yellowfive@57 437 lbl:SetWidth(140)
yellowfive@57 438 lbl:SetFont(Amr.CreateFont("Italic", 12, Amr.Colors.Text))
yellowfive@57 439 lbl:SetJustifyH("MIDDLE")
yellowfive@57 440 lbl:SetPoint("TOP", lbl2.frame, "BOTTOM", 0, -5)
yellowfive@57 441
yellowfive@57 442 lbl2 = AceGUI:Create("AmrUiLabel")
yellowfive@124 443 container:AddChild(lbl2)
yellowfive@57 444 lbl2:SetText(L.GearTipCommands)
yellowfive@57 445 lbl2:SetWidth(130)
yellowfive@57 446 lbl2:SetFont(Amr.CreateFont("Italic", 12, Amr.Colors.Text))
yellowfive@57 447 lbl2:SetPoint("TOP", lbl.frame, "BOTTOM", 10, -5)
yellowfive@57 448
yellowfive@57 449 local t = AceGUI:Create("AmrUiTabGroup")
yellowfive@57 450 t:SetLayout("None")
yellowfive@81 451
yellowfive@81 452 local tabz = {}
yellowfive@81 453 for pos = 1, 4 do
yellowfive@81 454 local specId = GetSpecializationInfo(pos)
yellowfive@81 455 if specId then
yellowfive@81 456 table.insert(tabz, { text = L.SpecsShort[Amr.SpecIds[specId]], value = pos .. "", style = "bold" })
yellowfive@81 457 end
yellowfive@81 458 end
yellowfive@81 459
yellowfive@81 460 t:SetTabs(tabz)
yellowfive@57 461 t:SetCallback("OnGroupSelected", onGearTabSelected)
yellowfive@124 462 container:AddChild(t)
yellowfive@57 463 t:SetPoint("TOPLEFT", container.content, "TOPLEFT", 144, -30)
yellowfive@57 464 t:SetPoint("BOTTOMRIGHT", container.content, "BOTTOMRIGHT")
yellowfive@57 465 _gearTabs = t;
yellowfive@57 466
yellowfive@61 467 local btnShop = AceGUI:Create("AmrUiButton")
yellowfive@61 468 btnShop:SetText(L.GearButtonShop)
yellowfive@61 469 btnShop:SetBackgroundColor(Amr.Colors.Blue)
yellowfive@61 470 btnShop:SetFont(Amr.CreateFont("Regular", 14, Amr.Colors.White))
yellowfive@61 471 btnShop:SetWidth(245)
yellowfive@61 472 btnShop:SetHeight(26)
yellowfive@61 473 btnShop:SetCallback("OnClick", function(widget) Amr:ShowShopWindow() end)
yellowfive@61 474 container:AddChild(btnShop)
yellowfive@124 475 btnShop:SetPoint("TOPRIGHT", container.content, "TOPRIGHT", -20, -25)
yellowfive@61 476
yellowfive@57 477 if not _activeTab then
yellowfive@81 478 _activeTab = tostring(GetSpecialization())
yellowfive@57 479 end
yellowfive@57 480
yellowfive@57 481 t:SelectTab(_activeTab)
yellowfive@57 482 end
yellowfive@57 483
yellowfive@57 484 -- do cleanup when the gear tab is released
yellowfive@57 485 function Amr:ReleaseTabGear()
yellowfive@57 486 _gearTabs = nil
yellowfive@57 487 end
yellowfive@57 488
yellowfive@57 489 -- show and update the gear tab for the specified spec
yellowfive@57 490 function Amr:ShowGearTab(spec)
yellowfive@57 491 if not _gearTabs then return end
yellowfive@57 492
yellowfive@57 493 _activeTab = tostring(spec)
yellowfive@57 494 _gearTabs:SelectTab(_activeTab)
yellowfive@57 495 end
yellowfive@57 496
yellowfive@57 497 -- refresh display of the current gear tab
yellowfive@57 498 function Amr:RefreshGearTab()
yellowfive@57 499 if not _gearTabs then return end
yellowfive@57 500 _gearTabs:SelectTab(_activeTab)
yellowfive@57 501 end
yellowfive@57 502
yellowfive@57 503
yellowfive@57 504 ------------------------------------------------------------------------------------------------
yellowfive@57 505 -- Gear Set Management
yellowfive@57 506 ------------------------------------------------------------------------------------------------
yellowfive@57 507 local _waitingForSpec = 0
yellowfive@124 508 local _pendingGearOps = nil
yellowfive@124 509 local _currentGearOp = nil
yellowfive@124 510 local _itemLockAction = nil
yellowfive@124 511 local _gearOpPasses = 0
yellowfive@124 512 local _gearOpWaiting = nil
yellowfive@57 513
yellowfive@124 514 local beginEquipGearSet, processCurrentGearOp, nextGearOp
yellowfive@89 515
yellowfive@57 516 -- find the first empty slot in the player's backpack+bags
yellowfive@57 517 local function findFirstEmptyBagSlot()
yellowfive@57 518
yellowfive@57 519 local bagIds = {}
yellowfive@57 520 table.insert(bagIds, BACKPACK_CONTAINER)
yellowfive@57 521 for bagId = 1, NUM_BAG_SLOTS do
yellowfive@57 522 table.insert(bagIds, bagId)
yellowfive@57 523 end
yellowfive@57 524
yellowfive@57 525 for i, bagId in ipairs(bagIds) do
yellowfive@57 526 local numSlots = GetContainerNumSlots(bagId)
yellowfive@57 527 for slotId = 1, numSlots do
yellowfive@57 528 local _, _, _, _, _, _, itemLink = GetContainerItemInfo(bagId, slotId)
yellowfive@57 529 if not itemLink then
yellowfive@57 530 return bagId, slotId
yellowfive@57 531 end
yellowfive@57 532 end
yellowfive@57 533 end
yellowfive@57 534
yellowfive@57 535 return nil, nil
yellowfive@57 536 end
yellowfive@57 537
yellowfive@124 538 -- scan a bag for the best matching item
yellowfive@124 539 local function scanBagForItem(item, bagId, bestItem, bestDiff, bestLink)
yellowfive@124 540 local numSlots = GetContainerNumSlots(bagId)
yellowfive@124 541 for slotId = 1, numSlots do
yellowfive@124 542 local _, _, _, _, _, _, itemLink = GetContainerItemInfo(bagId, slotId)
yellowfive@124 543 -- we skip any stackable item, as far as we know, there is no equippable gear that can be stacked
yellowfive@124 544 if itemLink then
yellowfive@124 545 local bagItem = Amr.ParseItemLink(itemLink)
yellowfive@124 546 if bagItem ~= nil then
yellowfive@124 547 local diff = countItemDifferences(item, bagItem)
yellowfive@124 548 if diff < bestDiff then
yellowfive@124 549 bestItem = { bag = bagId, slot = slotId }
yellowfive@124 550 bestDiff = diff
yellowfive@124 551 bestLink = itemLink
yellowfive@124 552 end
yellowfive@124 553 end
yellowfive@124 554 end
yellowfive@57 555 end
yellowfive@124 556 return bestItem, bestDiff, bestLink
yellowfive@57 557 end
yellowfive@57 558
yellowfive@124 559 -- 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 560 local function findCurrentGearOpItem()
yellowfive@124 561
yellowfive@124 562 local item = _currentGearOp.items[_currentGearOp.nextSlot]
yellowfive@124 563
yellowfive@57 564 local bestItem = nil
yellowfive@57 565 local bestLink = nil
yellowfive@124 566 local bestDiff = 10000
yellowfive@57 567
yellowfive@73 568 -- inventory
yellowfive@73 569 bestItem, bestDiff, bestLink = scanBagForItem(item, BACKPACK_CONTAINER, bestItem, bestDiff, bestLink)
yellowfive@73 570 for bagId = 1, NUM_BAG_SLOTS do
yellowfive@73 571 bestItem, bestDiff, bestLink = scanBagForItem(item, bagId, bestItem, bestDiff, bestLink)
yellowfive@73 572 end
yellowfive@73 573
yellowfive@129 574 -- with new approach, the item to use should never be equipped, should be in bags at this point
yellowfive@129 575 --[[
yellowfive@61 576 -- 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 577 for slotNum = 1, #Amr.SlotIds do
yellowfive@57 578 local slotId = Amr.SlotIds[slotNum]
yellowfive@124 579 if _currentGearOp.slotsRemaining[slotId] then
yellowfive@61 580 local itemLink = GetInventoryItemLink("player", slotId)
yellowfive@61 581 if itemLink then
yellowfive@61 582 local invItem = Amr.ParseItemLink(itemLink)
yellowfive@124 583 if invItem then
yellowfive@61 584 local diff = countItemDifferences(item, invItem)
yellowfive@61 585 if diff < bestDiff then
yellowfive@61 586 bestItem = { slot = slotId }
yellowfive@61 587 bestDiff = diff
yellowfive@61 588 bestLink = itemLink
yellowfive@61 589 end
yellowfive@57 590 end
yellowfive@57 591 end
yellowfive@57 592 end
yellowfive@57 593 end
yellowfive@129 594 ]]
yellowfive@129 595
yellowfive@57 596 -- bank
yellowfive@124 597 if bestDiff > 0 then
yellowfive@124 598 bestItem, bestDiff, bestLink = scanBagForItem(item, BANK_CONTAINER, bestItem, bestDiff, bestLink)
yellowfive@124 599 for bagId = NUM_BAG_SLOTS + 1, NUM_BAG_SLOTS + NUM_BANKBAGSLOTS do
yellowfive@124 600 bestItem, bestDiff, bestLink = scanBagForItem(item, bagId, bestItem, bestDiff, bestLink)
yellowfive@124 601 end
yellowfive@57 602 end
yellowfive@124 603
yellowfive@124 604 return bestItem, bestDiff, bestLink
yellowfive@124 605 end
yellowfive@124 606
yellowfive@124 607 -- on completion, create an equipment manager set if desired
yellowfive@124 608 local function onEquipGearSetComplete()
yellowfive@124 609 if Amr.db.profile.options.disableEm then return end
yellowfive@57 610
yellowfive@124 611 -- create an equipment manager set
yellowfive@133 612
yellowfive@133 613 -- note: ignore slots and/or saveset need to be called twice
yellowfive@133 614 -- for some reason, the slot is treated as blank if you try to ignore once on the first load of the equipment manager
yellowfive@133 615
yellowfive@133 616 -- clear any currently ignored slots
yellowfive@133 617 C_EquipmentSet.ClearIgnoredSlotsForSave()
yellowfive@133 618 C_EquipmentSet.ClearIgnoredSlotsForSave()
yellowfive@133 619
yellowfive@133 620 -- ignore shirt and tabard
yellowfive@133 621 C_EquipmentSet.IgnoreSlotForSave(INVSLOT_BODY) -- shirt
yellowfive@133 622 C_EquipmentSet.IgnoreSlotForSave(INVSLOT_TABARD)
yellowfive@133 623 C_EquipmentSet.IgnoreSlotForSave(INVSLOT_BODY) -- shirt
yellowfive@133 624 C_EquipmentSet.IgnoreSlotForSave(INVSLOT_TABARD)
yellowfive@133 625
yellowfive@133 626 -- for now use icon of the spec
yellowfive@133 627 local _, specName, _, setIcon = GetSpecializationInfo(GetSpecialization())
yellowfive@57 628
yellowfive@133 629 --[[
yellowfive@124 630 local item = Amr.ParseItemLink(GetInventoryItemLink("player", INVSLOT_MAINHAND))
yellowfive@124 631 if not item then
yellowfive@124 632 item = Amr.ParseItemLink(GetInventoryItemLink("player", INVSLOT_OFFHAND))
yellowfive@124 633 end
yellowfive@124 634 if item then
yellowfive@133 635 local itemObj = Item:CreateFromItemID(item.id)
yellowfive@133 636 if itemObj then
yellowfive@133 637 setIcon = itemObj:GetItemIcon()
yellowfive@133 638 end
yellowfive@133 639 end
yellowfive@133 640 ]]
yellowfive@133 641
yellowfive@133 642 local setname = "AMR " .. specName
yellowfive@133 643 local setid = C_EquipmentSet.GetEquipmentSetID(setname)
yellowfive@133 644 if setid then
yellowfive@133 645 C_EquipmentSet.SaveEquipmentSet(setid, setIcon)
yellowfive@133 646 else
yellowfive@133 647 C_EquipmentSet.CreateEquipmentSet(setname, setIcon)
yellowfive@124 648 end
yellowfive@124 649 end
yellowfive@124 650
yellowfive@124 651 -- stop any currently in-progress gear swapping operation and clean up
yellowfive@124 652 local function disposeGearOp()
yellowfive@124 653 _pendingGearOps = nil
yellowfive@124 654 _currentGearOp = nil
yellowfive@124 655 _itemLockAction = nil
yellowfive@124 656 _gearOpPasses = 0
yellowfive@124 657 _gearOpWaiting = nil
yellowfive@124 658
yellowfive@124 659 -- make sure the gear tab is still in sync
yellowfive@124 660 Amr:RefreshGearTab()
yellowfive@124 661 end
yellowfive@124 662
yellowfive@124 663 -- initialize a gear op to start running it
yellowfive@124 664 local function initializeGearOp(op, spec, pos)
yellowfive@124 665 op.pos = pos
yellowfive@124 666 op.spec = spec
yellowfive@124 667
yellowfive@124 668 -- fill the remaining slot list and set the starting slot
yellowfive@124 669 op.nextSlot = nil
yellowfive@124 670 op.slotsRemaining = {}
yellowfive@124 671 op.isWaiting = false
yellowfive@124 672 for slotId, item in pairs(op.items) do
yellowfive@124 673 op.slotsRemaining[slotId] = true
yellowfive@124 674 if not op.nextSlot then
yellowfive@124 675 op.nextSlot = slotId
yellowfive@124 676 end
yellowfive@124 677 end
yellowfive@124 678 end
yellowfive@124 679
yellowfive@124 680 function processCurrentGearOp()
yellowfive@124 681 if not _currentGearOp then return end
yellowfive@124 682
yellowfive@124 683 if _currentGearOp.remove then
yellowfive@124 684 -- remove the next item
yellowfive@124 685
yellowfive@124 686 -- check if the slot is already empty
yellowfive@124 687 local itemLink = GetInventoryItemLink("player", _currentGearOp.nextSlot)
yellowfive@124 688 if not itemLink then
yellowfive@124 689 nextGearOp()
yellowfive@124 690 return
yellowfive@124 691 end
yellowfive@124 692
yellowfive@57 693 -- find first empty bag slot
yellowfive@57 694 local invBag, invSlot = findFirstEmptyBagSlot()
yellowfive@57 695 if not invBag then
yellowfive@57 696 -- stop if bags are too full
yellowfive@57 697 Amr:Print(L.GearEquipErrorBagFull)
yellowfive@124 698 disposeGearOp()
yellowfive@57 699 return
yellowfive@57 700 end
yellowfive@57 701
yellowfive@124 702 PickupInventoryItem(_currentGearOp.nextSlot)
yellowfive@57 703 PickupContainerItem(invBag, invSlot)
yellowfive@57 704
yellowfive@124 705 -- set an action to happen on ITEM_UNLOCKED, triggered by ClearCursor
yellowfive@124 706 _itemLockAction = {
yellowfive@57 707 bagId = invBag,
yellowfive@124 708 slotId = invSlot,
yellowfive@124 709 isRemove = true
yellowfive@57 710 }
yellowfive@124 711
yellowfive@124 712 ClearCursor()
yellowfive@124 713 -- wait for remove to complete
yellowfive@124 714 else
yellowfive@124 715 -- equip the next item
yellowfive@57 716
yellowfive@124 717 local bestItem, bestDiff, bestLink = findCurrentGearOpItem()
yellowfive@124 718
yellowfive@124 719 _itemLockAction = nil
yellowfive@57 720 ClearCursor()
yellowfive@124 721
yellowfive@124 722 if not bestItem then
yellowfive@124 723 -- stop if we can't find an item
yellowfive@124 724 Amr:Print(L.GearEquipErrorNotFound)
yellowfive@124 725 Amr:Print(L.GearEquipErrorNotFound2)
yellowfive@124 726 disposeGearOp()
yellowfive@124 727
yellowfive@124 728 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 729 -- find first empty bag slot
yellowfive@124 730 local invBag, invSlot = findFirstEmptyBagSlot()
yellowfive@124 731 if not invBag then
yellowfive@124 732 -- stop if bags are too full
yellowfive@124 733 Amr:Print(L.GearEquipErrorBagFull)
yellowfive@124 734 disposeGearOp()
yellowfive@124 735 return
yellowfive@124 736 end
yellowfive@124 737
yellowfive@124 738 -- move from bank to bag
yellowfive@124 739 PickupContainerItem(bestItem.bag, bestItem.slot)
yellowfive@124 740 PickupContainerItem(invBag, invSlot)
yellowfive@124 741
yellowfive@124 742 -- set an action to happen on ITEM_UNLOCKED, triggered by ClearCursor
yellowfive@124 743 _itemLockAction = {
yellowfive@124 744 bagId = invBag,
yellowfive@124 745 slotId = invSlot,
yellowfive@124 746 isBank = true
yellowfive@124 747 }
yellowfive@124 748
yellowfive@124 749 ClearCursor()
yellowfive@124 750 -- 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 751
yellowfive@124 752 elseif (bestItem.bag or bestItem.bag == 0) and not Amr:CanEquip(bestItem.bag, bestItem.slot) then
yellowfive@57 753 -- if an item is not soulbound, then warn the user and quit
yellowfive@57 754 Amr:Print(L.GearEquipErrorSoulbound(bestLink))
yellowfive@124 755 disposeGearOp()
yellowfive@124 756
yellowfive@57 757 else
yellowfive@124 758
yellowfive@129 759 --print("equipping " .. bestLink .. " in slot " .. _currentGearOp.nextSlot)
yellowfive@129 760
yellowfive@57 761 -- an item in the player's bags or already equipped, equip it
yellowfive@57 762 if bestItem.bag then
yellowfive@57 763 PickupContainerItem(bestItem.bag, bestItem.slot)
yellowfive@57 764 else
yellowfive@124 765 _gearOpWaiting.inventory[bestItem.slot] = true
yellowfive@57 766 PickupInventoryItem(bestItem.slot)
yellowfive@57 767 end
yellowfive@124 768 _gearOpWaiting.inventory[_currentGearOp.nextSlot] = true
yellowfive@124 769 PickupInventoryItem(_currentGearOp.nextSlot)
yellowfive@124 770
yellowfive@124 771 -- don't wait for now, do all equips at once
yellowfive@124 772 --[[
yellowfive@124 773 -- set an action to happen on ITEM_UNLOCKED, triggered by ClearCursor
yellowfive@124 774 _itemLockAction = {
yellowfive@124 775 bagId = bestItem.bag,
yellowfive@124 776 slotId = bestItem.slot,
yellowfive@124 777 invSlot = _currentGearOp.nextSlot,
yellowfive@124 778 isEquip = true
yellowfive@124 779 }
yellowfive@124 780 ]]
yellowfive@124 781
yellowfive@124 782 ClearCursor()
yellowfive@124 783 nextGearOp()
yellowfive@124 784 end
yellowfive@124 785
yellowfive@124 786 end
yellowfive@124 787 end
yellowfive@124 788
yellowfive@124 789 -- when a gear op completes successfully, this will advance to the next op or finish
yellowfive@124 790 function nextGearOp()
yellowfive@124 791 if not _currentGearOp then return end
yellowfive@124 792
yellowfive@124 793 local spec = _currentGearOp.spec
yellowfive@124 794 local pos = _currentGearOp.pos
yellowfive@124 795 local passes = _gearOpPasses
yellowfive@124 796
yellowfive@124 797 -- mark the slot as done and move to the next
yellowfive@124 798 if _currentGearOp.nextSlot then
yellowfive@124 799 _currentGearOp.slotsRemaining[_currentGearOp.nextSlot] = nil
yellowfive@124 800 _currentGearOp.nextSlot = nil
yellowfive@124 801 for slotId, item in pairs(_currentGearOp.items) do
yellowfive@124 802 if _currentGearOp.slotsRemaining[slotId] then
yellowfive@124 803 _currentGearOp.nextSlot = slotId
yellowfive@124 804 break
yellowfive@124 805 end
yellowfive@124 806 end
yellowfive@124 807 end
yellowfive@124 808
yellowfive@124 809 if not _currentGearOp.nextSlot then
yellowfive@124 810 -- see if anything is still in progress and we want to wait for it before continuing
yellowfive@124 811 local inProgress = not Amr.IsEmpty(_gearOpWaiting.inventory)
yellowfive@124 812
yellowfive@124 813 if (_currentGearOp.wait or _currentGearOp.remove) and inProgress then
yellowfive@124 814 -- this will cause the item unlock handler to call nextGearOp again when all in-progress swaps have unlocked related slots
yellowfive@124 815 _currentGearOp.isWaiting = true
yellowfive@124 816 else
yellowfive@124 817 _currentGearOp = _pendingGearOps[pos + 1]
yellowfive@124 818 if _currentGearOp then
yellowfive@124 819 -- we have another op, do it
yellowfive@124 820 initializeGearOp(_currentGearOp, spec, pos + 1)
yellowfive@124 821 processCurrentGearOp()
yellowfive@124 822 else
yellowfive@124 823 -- we are done
yellowfive@124 824 disposeGearOp()
yellowfive@124 825
yellowfive@124 826 -- this will check if not all items were swapped, and either finish up, try again, or abort if have tried too many times
yellowfive@124 827 beginEquipGearSet(spec, passes + 1)
yellowfive@124 828 end
yellowfive@124 829 end
yellowfive@124 830 else
yellowfive@124 831 -- do the next item
yellowfive@124 832 processCurrentGearOp()
yellowfive@124 833 end
yellowfive@124 834
yellowfive@124 835 end
yellowfive@124 836
yellowfive@124 837 local function handleItemUnlocked(bagId, slotId)
yellowfive@124 838
yellowfive@124 839 -- mark anything that is waiting as unlocked if it is no longer locked
yellowfive@124 840 if _currentGearOp and _gearOpWaiting then
yellowfive@124 841 for i,s in ipairs(Amr.SlotIds) do
yellowfive@124 842 if not IsInventoryItemLocked(s) then
yellowfive@124 843 _gearOpWaiting.inventory[s] = nil
yellowfive@124 844 end
yellowfive@124 845 end
yellowfive@124 846 end
yellowfive@124 847
yellowfive@124 848 if _itemLockAction then
yellowfive@124 849 if _itemLockAction.isRemove then
yellowfive@124 850 -- waiting for a specific remove op to finish before continuing
yellowfive@124 851 if bagId == _itemLockAction.bagId and slotId == _itemLockAction.slotId then
yellowfive@124 852 _itemLockAction = nil
yellowfive@124 853 nextGearOp()
yellowfive@124 854 end
yellowfive@124 855 elseif _itemLockAction.isBank then
yellowfive@124 856 -- waiting for an item to move from bank into inventory, then reprocess the current op
yellowfive@124 857 if bagId == _itemLockAction.bagId and slotId == _itemLockAction.slotId then
yellowfive@124 858 _itemLockAction = nil
yellowfive@124 859 processCurrentGearOp()
yellowfive@124 860 end
yellowfive@124 861
yellowfive@124 862 elseif _itemLockAction.isEquip then
yellowfive@124 863 -- this is not currently used... we do all equips at once usually, but could go back to this if it causes problems
yellowfive@124 864
yellowfive@124 865 -- waiting for a specific equip op to finish
yellowfive@61 866
yellowfive@124 867 -- inventory slot we're swapping to is still locked, can't continue yet
yellowfive@124 868 if IsInventoryItemLocked(_itemLockAction.invSlot) then return end
yellowfive@124 869
yellowfive@124 870 if _itemLockAction.bagId then
yellowfive@124 871 local _, _, locked = GetContainerItemInfo(_itemLockAction.bagId, _itemLockAction.slotId)
yellowfive@124 872 -- the bag slot we're swapping from is still locked, can't continue yet
yellowfive@124 873 if locked then return end
yellowfive@124 874 else
yellowfive@124 875 -- inventory slot we're swapping from is still locked, can't continue yet
yellowfive@124 876 if IsInventoryItemLocked(_itemLockAction.slotId) then return end
yellowfive@124 877 end
yellowfive@124 878
yellowfive@124 879 _itemLockAction = nil
yellowfive@124 880 nextGearOp()
yellowfive@124 881 else
yellowfive@124 882 -- unknown... shouldn't happen
yellowfive@124 883 _itemLockAction = nil
yellowfive@57 884 end
yellowfive@124 885 else
yellowfive@124 886
yellowfive@124 887 -- not waiting on a specific action, check if we are waiting for all locked slots to open up and they are done
yellowfive@124 888 if _currentGearOp and _gearOpWaiting and _currentGearOp.isWaiting and Amr.IsEmpty(_gearOpWaiting.inventory) then
yellowfive@124 889 nextGearOp()
yellowfive@124 890 end
yellowfive@57 891 end
yellowfive@57 892
yellowfive@57 893 end
yellowfive@57 894
yellowfive@124 895 local function shuffle(tbl)
yellowfive@124 896 local size = #tbl
yellowfive@124 897 for i = size, 1, -1 do
yellowfive@124 898 local rand = math.random(size)
yellowfive@124 899 tbl[i], tbl[rand] = tbl[rand], tbl[i]
yellowfive@89 900 end
yellowfive@124 901 return tbl
yellowfive@89 902 end
yellowfive@89 903
yellowfive@129 904 local _ohFirst = {
yellowfive@129 905 [20] = true, -- PaladinProtection
yellowfive@129 906 [32] = true, -- WarlockDemonology
yellowfive@129 907 [36] = true -- WarriorProtection
yellowfive@129 908 }
yellowfive@129 909
yellowfive@124 910 function beginEquipGearSet(spec, passes)
yellowfive@57 911
yellowfive@57 912 local gear = Amr.db.char.GearSets[spec]
yellowfive@57 913 if not gear then
yellowfive@57 914 Amr:Print(L.GearEquipErrorEmpty)
yellowfive@57 915 return
yellowfive@57 916 end
yellowfive@124 917
yellowfive@124 918 -- ensure all our stored data is up to date
yellowfive@57 919 local player = Amr:ExportCharacter()
yellowfive@129 920 local doOhFirst = _ohFirst[player.Specs[spec]]
yellowfive@57 921
yellowfive@124 922 local itemsToEquip = {
yellowfive@124 923 legendaries = {},
yellowfive@124 924 weapons = {},
yellowfive@129 925 mh = {},
yellowfive@129 926 oh = {},
yellowfive@124 927 rings = {},
yellowfive@124 928 trinkets = {},
yellowfive@124 929 others = {},
yellowfive@124 930 blanks = {}
yellowfive@124 931 }
yellowfive@57 932 local remaining = 0
yellowfive@129 933 local usedItems = {}
yellowfive@124 934
yellowfive@124 935 -- 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 936 local slots = {}
yellowfive@124 937 for i,s in ipairs(Amr.SlotIds) do
yellowfive@124 938 table.insert(slots, s)
yellowfive@124 939 end
yellowfive@124 940 shuffle(slots)
yellowfive@124 941
yellowfive@124 942 for i,slotId in ipairs(slots) do
yellowfive@124 943
yellowfive@124 944 -- we do stuff in batches that avoids most unique conflicts
yellowfive@124 945 local list = itemsToEquip.others
yellowfive@129 946 if slotId == 16 then
yellowfive@129 947 list = itemsToEquip.mh
yellowfive@129 948 elseif slotId == 17 then
yellowfive@129 949 list = itemsToEquip.oh
yellowfive@124 950 elseif slotId == 11 or slotId == 12 then
yellowfive@124 951 list = itemsToEquip.rings
yellowfive@124 952 elseif slotId == 13 or slotId == 14 then
yellowfive@124 953 list = itemsToEquip.trinkets
yellowfive@124 954 end
yellowfive@124 955
yellowfive@57 956 local old = player.Equipped[spec][slotId]
yellowfive@57 957 local new = gear[slotId]
yellowfive@124 958 local prevRemaining = remaining
yellowfive@69 959 if new then
yellowfive@124 960 -- if the new thing is an artifact, only match the item id
yellowfive@124 961 local newItem = Item:CreateFromItemID(new.id)
yellowfive@124 962 local quality = newItem and newItem:GetItemQuality() or 0
yellowfive@124 963 if quality == 6 then
yellowfive@124 964 if not old or new.id ~= old.id then
yellowfive@124 965 list[slotId] = new
yellowfive@129 966 if list == itemsToEquip.mh or list == itemsToEquip.oh then
yellowfive@129 967 itemsToEquip.weapons[slotId] = {}
yellowfive@129 968 end
yellowfive@69 969 remaining = remaining + 1
yellowfive@69 970 end
yellowfive@129 971 else
yellowfive@129 972 -- find the best matching item anywhere in the player's gear
yellowfive@129 973 local bestItem, bestDiff = Amr:FindMatchingItem(new, player, usedItems)
yellowfive@129 974 new = bestItem
yellowfive@129 975
yellowfive@124 976 local diff = countItemDifferences(old, new)
yellowfive@129 977
yellowfive@129 978 --[[
yellowfive@124 979 if diff > 0 and diff < 1000 then
yellowfive@124 980 -- same item, see if inventory has one that is closer (e.g. a duplicate item with correct enchants/gems)
yellowfive@124 981 local bestItem, bestDiff = Amr:FindMatchingItem(new, player, usedItems)
yellowfive@124 982 if bestDiff and bestDiff < diff then
yellowfive@124 983 new = bestItem
yellowfive@124 984 diff = bestDiff
yellowfive@124 985 end
yellowfive@124 986 end
yellowfive@129 987 ]]
yellowfive@124 988
yellowfive@129 989 if diff > 0 then
yellowfive@124 990 list[slotId] = new
yellowfive@129 991 if list == itemsToEquip.mh or list == itemsToEquip.oh then
yellowfive@129 992 itemsToEquip.weapons[slotId] = {}
yellowfive@129 993 end
yellowfive@124 994 remaining = remaining + 1
yellowfive@124 995 end
yellowfive@124 996 end
yellowfive@129 997 elseif old then
yellowfive@124 998 -- need to remove this item
yellowfive@124 999 itemsToEquip.blanks[slotId] = {}
yellowfive@124 1000 remaining = remaining + 1
yellowfive@124 1001 end
yellowfive@124 1002
yellowfive@124 1003 if remaining > prevRemaining then
yellowfive@124 1004 -- 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 1005 if old then
yellowfive@124 1006 local oldItem = Item:CreateFromItemID(old.id)
yellowfive@124 1007 if oldItem and oldItem:GetItemQuality() == 5 then
yellowfive@124 1008 itemsToEquip.legendaries[slotId] = {}
yellowfive@124 1009 end
yellowfive@57 1010 end
yellowfive@57 1011 end
yellowfive@57 1012 end
yellowfive@124 1013
yellowfive@124 1014 if remaining > 0 then
yellowfive@57 1015
yellowfive@124 1016 if passes < 5 then
yellowfive@124 1017 _pendingGearOps = {}
yellowfive@124 1018
yellowfive@129 1019 if not Amr.IsEmpty(itemsToEquip.blanks) then
yellowfive@124 1020 -- if gear set wants slots to be blank, do that first
yellowfive@124 1021 table.insert(_pendingGearOps, { items = itemsToEquip.blanks, remove = true, label = "blanks" })
yellowfive@129 1022 end
yellowfive@124 1023 if not Amr.IsEmpty(itemsToEquip.weapons) then
yellowfive@129 1024 -- change weapons first: remove both, wait, then equip each weapon one by one, waiting after each
yellowfive@129 1025 table.insert(_pendingGearOps, { items = itemsToEquip.weapons, remove = true, label = "remove weapons" })
yellowfive@129 1026 local thisWeapon = doOhFirst and itemsToEquip.oh or itemsToEquip.mh
yellowfive@129 1027 if not Amr.IsEmpty(thisWeapon) then
yellowfive@129 1028 table.insert(_pendingGearOps, { items = thisWeapon, wait = true, label = "equip weapon 1" })
yellowfive@129 1029 end
yellowfive@129 1030 thisWeapon = doOhFirst and itemsToEquip.mh or itemsToEquip.oh
yellowfive@129 1031 if not Amr.IsEmpty(thisWeapon) then
yellowfive@129 1032 table.insert(_pendingGearOps, { items = thisWeapon, wait = true, label = "equip weapon 2" })
yellowfive@129 1033 end
yellowfive@124 1034 end
yellowfive@124 1035 if not Amr.IsEmpty(itemsToEquip.legendaries) then
yellowfive@124 1036 -- remove any legendaries, wait
yellowfive@124 1037 table.insert(_pendingGearOps, { items = itemsToEquip.legendaries, remove = true, label = "remove legendaries" })
yellowfive@124 1038 end
yellowfive@124 1039 if not Amr.IsEmpty(itemsToEquip.rings) then
yellowfive@124 1040 -- remove both rings, wait, then equip new ones
yellowfive@124 1041 table.insert(_pendingGearOps, { items = itemsToEquip.rings, remove = true, label = "remove rings" })
yellowfive@124 1042 table.insert(_pendingGearOps, { items = itemsToEquip.rings, wait = true, label = "equip rings" })
yellowfive@124 1043 end
yellowfive@124 1044 if not Amr.IsEmpty(itemsToEquip.trinkets) then
yellowfive@124 1045 -- remove both trinkets, wait, then equip new ones
yellowfive@124 1046 table.insert(_pendingGearOps, { items = itemsToEquip.trinkets, remove = true, label = "remove trinkets" })
yellowfive@124 1047 table.insert(_pendingGearOps, { items = itemsToEquip.trinkets, wait = true, label = "equip trinkets" })
yellowfive@124 1048 end
yellowfive@124 1049 if not Amr.IsEmpty(itemsToEquip.others) then
yellowfive@124 1050 -- equip all other items, wait for completion
yellowfive@124 1051 table.insert(_pendingGearOps, { items = itemsToEquip.others, wait = true, label = "equip others" })
yellowfive@124 1052 end
yellowfive@124 1053
yellowfive@124 1054 -- make the last operation wait no matter what, before this gets called again to check if everything succeeded
yellowfive@124 1055 _pendingGearOps[#_pendingGearOps].wait = true
yellowfive@124 1056
yellowfive@124 1057 if not _gearOpWaiting then
yellowfive@124 1058 _gearOpWaiting = { inventory = {} }
yellowfive@124 1059 end
yellowfive@124 1060
yellowfive@124 1061 _gearOpPasses = passes
yellowfive@124 1062 _currentGearOp = _pendingGearOps[1]
yellowfive@124 1063 initializeGearOp(_currentGearOp, spec, 1)
yellowfive@124 1064
yellowfive@124 1065 processCurrentGearOp()
yellowfive@124 1066 else
yellowfive@124 1067 -- TODO: print message that gear set couldn't be equipped
yellowfive@89 1068 end
yellowfive@57 1069
yellowfive@57 1070 else
yellowfive@89 1071 onEquipGearSetComplete()
yellowfive@124 1072 end
yellowfive@57 1073 end
yellowfive@57 1074
yellowfive@57 1075 local function onActiveTalentGroupChanged()
yellowfive@81 1076
yellowfive@57 1077 local auto = Amr.db.profile.options.autoGear
yellowfive@81 1078 local currentSpec = GetSpecialization()
yellowfive@129 1079 local waitingSpec = _waitingForSpec
yellowfive@129 1080 _waitingForSpec = 0
yellowfive@57 1081
yellowfive@129 1082 if currentSpec == waitingSpec or auto then
yellowfive@129 1083 -- spec is what we want, now equip the gear but after a short delay because the game auto-swaps artifact weapons
yellowfive@129 1084 Amr.Wait(2, function()
yellowfive@129 1085 beginEquipGearSet(GetSpecialization(), 0)
yellowfive@129 1086 end)
yellowfive@57 1087 end
yellowfive@57 1088 end
yellowfive@57 1089
yellowfive@81 1090 -- activate the specified spec and then equip the saved gear set
yellowfive@57 1091 function Amr:EquipGearSet(spec)
yellowfive@57 1092
yellowfive@81 1093 -- if no argument, then cycle spec
yellowfive@57 1094 if not spec then
yellowfive@81 1095 spec = GetSpecialization() + 1
yellowfive@57 1096 end
yellowfive@81 1097
yellowfive@57 1098 -- allow some flexibility in the arguments
yellowfive@81 1099 if spec == "1" or spec == "2" or spec == "3" or spec == "4" then spec = tonumber(spec) end
yellowfive@81 1100
yellowfive@81 1101 local specId = GetSpecializationInfo(spec)
yellowfive@81 1102 if not specId then spec = 1 end
yellowfive@57 1103
yellowfive@57 1104 if UnitAffectingCombat("player") then
yellowfive@57 1105 Amr:Print(L.GearEquipErrorCombat)
yellowfive@57 1106 return
yellowfive@57 1107 end
yellowfive@57 1108
yellowfive@81 1109 local currentSpec = GetSpecialization()
yellowfive@57 1110 if currentSpec ~= spec then
yellowfive@129 1111 _waitingForSpec = spec
yellowfive@81 1112 SetSpecialization(spec)
yellowfive@57 1113 else
yellowfive@129 1114 -- spec is what we want, now equip the gear
yellowfive@129 1115 beginEquipGearSet(currentSpec, 0)
yellowfive@57 1116 end
yellowfive@57 1117 end
yellowfive@57 1118
yellowfive@124 1119 -- moves any gear in bags to the bank if not part of a gear set
yellowfive@57 1120 function Amr:CleanBags()
yellowfive@57 1121 -- TODO: implement
yellowfive@57 1122 end
yellowfive@57 1123
yellowfive@81 1124 --[[
yellowfive@81 1125 local function testfunc(message)
yellowfive@81 1126 print(strsub(message, 13))
yellowfive@81 1127 end
yellowfive@81 1128 ]]
yellowfive@81 1129
yellowfive@57 1130 function Amr:InitializeGear()
yellowfive@87 1131 Amr:AddEventHandler("ACTIVE_TALENT_GROUP_CHANGED", onActiveTalentGroupChanged)
yellowfive@57 1132
yellowfive@81 1133 --Amr:AddEventHandler("CHAT_MSG_CHANNEL", testfunc)
yellowfive@81 1134
yellowfive@57 1135 Amr:AddEventHandler("UNIT_INVENTORY_CHANGED", function(unitID)
yellowfive@57 1136 if unitID and unitID ~= "player" then return end
yellowfive@124 1137
yellowfive@124 1138 -- don't update during a gear operation, wait until it is totally finished
yellowfive@124 1139 if _pendingGearOps then return end
yellowfive@124 1140
yellowfive@57 1141 Amr:RefreshGearTab()
yellowfive@57 1142 end)
yellowfive@57 1143
yellowfive@124 1144 Amr:AddEventHandler("ITEM_UNLOCKED", handleItemUnlocked)
yellowfive@122 1145 end