comparison Gear.lua @ 124:e31b02b24488

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