comparison Gear.lua @ 57:01b63b8ed811 v21

total rewrite to version 21
author yellowfive
date Fri, 05 Jun 2015 11:05:15 -0700
parents
children ee701ce45354
comparison
equal deleted inserted replaced
56:75431c084aa0 57:01b63b8ed811
1 local Amr = LibStub("AceAddon-3.0"):GetAddon("AskMrRobot")
2 local L = LibStub("AceLocale-3.0"):GetLocale("AskMrRobot", true)
3 local AceGUI = LibStub("AceGUI-3.0")
4
5 local _gearTabs
6 local _activeTab
7
8 -- Returns a number indicating how different two items are (0 means the same, higher means more different)
9 local function countItemDifferences(item1, item2)
10 if item1 == nil and item2 == nil then return 0 end
11
12 -- different items (id + bonus ids + suffix, constitutes a different physical drop)
13 if Amr.GetItemUniqueId(item1, true) ~= Amr.GetItemUniqueId(item2, true) then
14 return 1000
15 end
16
17 -- different upgrade levels of the same item (only for older gear, player has control over upgrade level)
18 if item1.upgradeId ~= item2.upgradeId then
19 return 100
20 end
21
22 -- different gems
23 local gemDiffs = 0
24 for i = 1, 3 do
25 if item1.gemIds[i] ~= item2.gemIds[i] then
26 gemDiffs = gemDiffs + 1
27 end
28 end
29
30 -- different enchants
31 local enchantDiff = 0
32 if item1.enchantId ~= item2.enchantId then
33 enchantDiff = 1
34 end
35
36 return gemDiffs + enchantDiff
37 end
38
39 -- 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)
41 if not list then return nil end
42
43 for k,v in pairs(list) do
44 local listItem = Amr.ParseItemLink(v)
45 if listItem then
46 local diff = countItemDifferences(item, listItem)
47 if diff < bestDiff then
48 -- each physical item can only be used once, the usedItems table has items we can't use in this search
49 local key = string.format("%s_%s", tableType, k)
50 if not usedItems[key] then
51 bestLink = v
52 bestItem = listItem
53 bestDiff = diff
54 bestLoc = string.format("%s_%s", tableType, k)
55 end
56 end
57 if diff == 0 then break end
58 end
59 end
60
61 return bestLink, bestItem, bestDiff, bestLoc
62 end
63
64 -- search the player's equipped gear, bag, bank, and void storage for an item that best matches the specified item
65 function Amr:FindMatchingItem(item, player, usedItems)
66 if not item then return nil end
67
68 local equipped = player.Equipped and player.Equipped[player.ActiveSpec] or nil
69 local bestLink, bestItem, bestDiff, bestLoc = findMatchingItemFromTable(item, equipped, nil, nil, 1000, nil, usedItems, "equip")
70 bestLink, bestItem, bestDiff, bestLoc = findMatchingItemFromTable(item, player.BagItems, bestLink, bestItem, bestDiff, bestLoc, usedItems, "bag")
71 bestLink, bestItem, bestDiff, bestLoc = findMatchingItemFromTable(item, player.BankItems, bestLink, bestItem, bestDiff, bestLoc, usedItems, "bank")
72 bestLink, bestItem, bestDiff, bestLoc = findMatchingItemFromTable(item, player.VoidItems, bestLink, bestItem, bestDiff, bestLoc, usedItems, "void")
73
74 if bestDiff >= 1000 then
75 return nil, nil, 1000
76 else
77 usedItems[bestLoc] = true
78 return bestLink, bestItem, bestDiff
79 end
80 end
81
82 local function renderEmptyGear(container)
83
84 local panelBlank = AceGUI:Create("AmrUiPanel")
85 panelBlank:SetLayout("None")
86 panelBlank:SetBackgroundColor(Amr.Colors.Black, 0.4)
87 panelBlank:SetPoint("TOPLEFT", container.content, "TOPLEFT", 6, 0)
88 panelBlank:SetPoint("BOTTOMRIGHT", container.content, "BOTTOMRIGHT")
89 container:AddChild(panelBlank)
90
91 local lbl = AceGUI:Create("AmrUiLabel")
92 lbl:SetText(L.GearBlank)
93 lbl:SetWidth(700)
94 lbl:SetJustifyH("MIDDLE")
95 lbl:SetFont(Amr.CreateFont("Italic", 16, Amr.Colors.TextTan))
96 lbl:SetPoint("BOTTOM", panelBlank.content, "CENTER", 0, 20)
97 panelBlank:AddChild(lbl)
98
99 local lbl2 = AceGUI:Create("AmrUiLabel")
100 lbl2:SetText(L.GearBlank2)
101 lbl2:SetWidth(700)
102 lbl2:SetJustifyH("MIDDLE")
103 lbl2:SetFont(Amr.CreateFont("Italic", 16, Amr.Colors.TextTan))
104 lbl2:SetPoint("TOP", lbl.frame, "CENTER", 0, -20)
105 panelBlank:AddChild(lbl2)
106 end
107
108 local function renderGear(spec, container)
109
110 local player = Amr:ExportCharacter()
111 local gear = Amr.db.char.GearSets[spec]
112 local equipped = player.Equipped[player.ActiveSpec]
113
114 if not gear then
115 -- no gear has been imported for this spec so show a message
116 renderEmptyGear(container)
117 else
118 local panelGear = AceGUI:Create("AmrUiPanel")
119 panelGear:SetLayout("None")
120 panelGear:SetBackgroundColor(Amr.Colors.Black, 0.3)
121 panelGear:SetPoint("TOPLEFT", container.content, "TOPLEFT", 6, 0)
122 panelGear:SetPoint("BOTTOMRIGHT", container.content, "BOTTOMRIGHT", -300, 0)
123 container:AddChild(panelGear)
124
125 local panelMods = AceGUI:Create("AmrUiPanel")
126 panelMods:SetLayout("None")
127 panelMods:SetPoint("TOPLEFT", panelGear.frame, "TOPRIGHT", 15, 0)
128 panelMods:SetPoint("BOTTOMRIGHT", container.content, "BOTTOMRIGHT")
129 panelMods:SetBackgroundColor(Amr.Colors.Black, 0.3)
130 container:AddChild(panelMods)
131
132 -- spec icon
133 local icon = AceGUI:Create("AmrUiIcon")
134 icon:SetIconBorderColor(Amr.Colors.Classes[player.Class])
135 icon:SetWidth(48)
136 icon:SetHeight(48)
137
138 local iconSpec
139 if player.SubSpecs[spec] then
140 iconSpec = player.SubSpecs[spec]
141 else
142 iconSpec = player.Specs[spec]
143 end
144
145 icon:SetIcon("Interface\\Icons\\" .. Amr.SpecIcons[iconSpec])
146 icon:SetPoint("TOPLEFT", panelGear.content, "TOPLEFT", 10, -10)
147 panelGear:AddChild(icon)
148
149 local btnEquip = AceGUI:Create("AmrUiButton")
150 btnEquip:SetText(L.GearButtonEquip(spec))
151 btnEquip:SetBackgroundColor(Amr.Colors.Green)
152 btnEquip:SetFont(Amr.CreateFont("Regular", 14, Amr.Colors.White))
153 btnEquip:SetWidth(300)
154 btnEquip:SetHeight(26)
155 btnEquip:SetPoint("LEFT", icon.frame, "RIGHT", 40, 0)
156 btnEquip:SetPoint("RIGHT", panelGear.content, "RIGHT", -40, 0)
157 btnEquip:SetCallback("OnClick", function(widget)
158 Amr:EquipGearSet(spec)
159 end)
160 panelGear:AddChild(btnEquip)
161
162 local btnShop = AceGUI:Create("AmrUiButton")
163 btnShop:SetText(L.GearButtonShop)
164 btnShop:SetBackgroundColor(Amr.Colors.Blue)
165 btnShop:SetFont(Amr.CreateFont("Regular", 14, Amr.Colors.White))
166 btnShop:SetWidth(300)
167 btnShop:SetHeight(26)
168 btnShop:SetPoint("LEFT", btnEquip.frame, "RIGHT", 75, 0)
169 btnShop:SetPoint("RIGHT", panelMods.content, "RIGHT", -20, 0)
170 btnShop:SetCallback("OnClick", function(widget) Amr:ShowShopWindow() end)
171 panelMods:AddChild(btnShop)
172
173 -- each physical item can only be used once, this tracks ones we have already used
174 local usedItems = {}
175
176 -- gear list
177 local prevElem = icon
178 for slotNum = 1, #Amr.SlotIds do
179 local slotId = Amr.SlotIds[slotNum]
180
181 local equippedItemLink = equipped and equipped[slotId] or nil
182 local equippedItem = Amr.ParseItemLink(equippedItemLink)
183 local optimalItem = gear[slotId]
184 local optimalItemLink = Amr.CreateItemLink(optimalItem)
185
186 -- see if item is currently equipped, is false if don't have any item for that slot (e.g. OH for a 2-hander)
187 local isEquipped = false
188 if equippedItem and optimalItem and Amr.GetItemUniqueId(equippedItem) == Amr.GetItemUniqueId(optimalItem) then
189 isEquipped = true
190 end
191
192 -- find the item in the player's inventory that best matches what the optimization wants to use
193 local matchItemLink, matchItem = Amr:FindMatchingItem(optimalItem, player, usedItems)
194
195 -- slot label
196 local lbl = AceGUI:Create("AmrUiLabel")
197 lbl:SetText(Amr.SlotDisplayText[slotId])
198 lbl:SetWidth(85)
199 lbl:SetFont(Amr.CreateFont("Regular", 14, Amr.Colors.White))
200 lbl:SetPoint("TOPLEFT", prevElem.frame, "BOTTOMLEFT", 0, -12)
201 panelGear:AddChild(lbl)
202 prevElem = lbl
203
204 -- ilvl label
205 local lblIlvl = AceGUI:Create("AmrUiLabel")
206 lblIlvl:SetWidth(45)
207 lblIlvl:SetFont(Amr.CreateFont("Italic", 14, Amr.Colors.TextTan))
208 lblIlvl:SetPoint("TOPLEFT", lbl.frame, "TOPRIGHT", 0, 0)
209 panelGear:AddChild(lblIlvl)
210
211 -- equipped label
212 local lblEquipped = AceGUI:Create("AmrUiLabel")
213 lblEquipped:SetWidth(20)
214 lblEquipped:SetFont(Amr.CreateFont("Regular", 14, Amr.Colors.White))
215 lblEquipped:SetPoint("TOPLEFT", lblIlvl.frame, "TOPRIGHT", 0, 0)
216 lblEquipped:SetText(isEquipped and "E" or "")
217 panelGear:AddChild(lblEquipped)
218
219 -- item name/link label
220 local lblItem = AceGUI:Create("AmrUiLabel")
221 lblItem:SetWordWrap(false)
222 lblItem:SetWidth(345)
223 lblItem:SetFont(Amr.CreateFont(isEquipped and "Regular" or "Bold", isEquipped and 14 or 15, Amr.Colors.White))
224 lblItem:SetPoint("TOPLEFT", lblEquipped.frame, "TOPRIGHT", 0, 0)
225 panelGear:AddChild(lblItem)
226
227 -- fill the name/ilvl labels, which may require asynchronous loading of item information
228 if optimalItemLink then
229 Amr.GetItemInfo(optimalItemLink, function(obj, name, link, quality, iLevel)
230 -- set item name, tooltip, and ilvl
231 obj.nameLabel:SetText(link:gsub("%[", ""):gsub("%]", ""))
232 Amr:SetItemTooltip(obj.nameLabel, link)
233 obj.ilvlLabel:SetText(iLevel)
234 end, { ilvlLabel = lblIlvl, nameLabel = lblItem })
235 end
236
237 -- modifications
238 if optimalItem then
239 local itemInfo = Amr.db.char.ExtraItemData[spec][optimalItem.id]
240
241 -- gems
242 if itemInfo and itemInfo.socketColors then
243 for i = 1, #itemInfo.socketColors do
244 local g = optimalItem.gemIds[i]
245 local isGemEquipped = g ~= 0 and matchItem and matchItem.gemIds and matchItem.gemIds[i] == g
246
247 -- highlight for gem that doesn't match
248 local socketBorder = AceGUI:Create("AmrUiPanel")
249 socketBorder:SetLayout("None")
250 socketBorder:SetBackgroundColor(Amr.Colors.Black, isGemEquipped and 0 or 1)
251 socketBorder:SetWidth(26)
252 socketBorder:SetHeight(26)
253 socketBorder:SetPoint("LEFT", lblItem.frame, "RIGHT", 30, 0)
254 if isGemEquipped then
255 socketBorder:SetAlpha(0.3)
256 end
257 panelMods:AddChild(socketBorder)
258
259 local socketBg = AceGUI:Create("AmrUiIcon")
260 socketBg:SetLayout("None")
261 socketBg:SetBorderWidth(2)
262 socketBg:SetIconBorderColor(Amr.Colors.Green, isGemEquipped and 0 or 1)
263 socketBg:SetWidth(24)
264 socketBg:SetHeight(24)
265 socketBg:SetPoint("TOPLEFT", socketBorder.content, "TOPLEFT", 1, -1)
266 socketBorder:AddChild(socketBg)
267
268 local socketIcon = AceGUI:Create("AmrUiIcon")
269 socketIcon:SetBorderWidth(1)
270 socketIcon:SetIconBorderColor(Amr.Colors.White)
271 socketIcon:SetWidth(18)
272 socketIcon:SetHeight(18)
273 socketIcon:SetPoint("CENTER", socketBg.content, "CENTER")
274 socketBg:AddChild(socketIcon)
275
276 -- get icon for optimized gem
277 if g ~= 0 then
278 local gemInfo = Amr.db.char.ExtraGemData[spec][g]
279 if gemInfo then
280 Amr.GetItemInfo(gemInfo.id, function(obj, name, link, quality, iLevel, reqLevel, class, subclass, maxStack, equipSlot, texture)
281 -- set icon and a tooltip
282 obj:SetIcon(texture)
283 Amr:SetItemTooltip(obj, link)
284 end, socketIcon)
285 end
286 end
287 end
288 end
289
290 -- enchant
291 if optimalItem.enchantId and optimalItem.enchantId ~= 0 then
292 local isEnchantEquipped = matchItem and matchItem.enchantId and matchItem.enchantId == optimalItem.enchantId
293
294 local lblEnchant = AceGUI:Create("AmrUiLabel")
295 lblEnchant:SetWordWrap(false)
296 lblEnchant:SetWidth(170)
297 lblEnchant:SetFont(Amr.CreateFont(isEnchantEquipped and "Regular" or "Bold", 14, isEnchantEquipped and Amr.Colors.TextGray or Amr.Colors.White))
298 lblEnchant:SetPoint("TOPLEFT", lblItem.frame, "TOPRIGHT", 130, 0)
299
300 local enchInfo = Amr.db.char.ExtraEnchantData[spec][optimalItem.enchantId]
301 if enchInfo then
302 lblEnchant:SetText(enchInfo.text)
303
304 Amr.GetItemInfo(enchInfo.itemId, function(obj, name, link)
305 Amr:SetItemTooltip(obj, link)
306 end, lblEnchant)
307 --Amr:SetSpellTooltip(lblEnchant, enchInfo.spellId)
308 end
309 panelMods:AddChild(lblEnchant)
310 end
311 end
312
313 prevElem = lbl
314 end
315 end
316 end
317
318 local function onGearTabSelected(container, event, group)
319 container:ReleaseChildren()
320 _activeTab = group
321 renderGear(tonumber(group), container)
322 end
323
324 local function onImportClick(widget)
325 Amr:ShowImportWindow()
326 end
327
328 -- renders the main UI for the Gear tab
329 function Amr:RenderTabGear(container)
330
331 local btnImport = AceGUI:Create("AmrUiButton")
332 btnImport:SetText(L.GearButtonImportText)
333 btnImport:SetBackgroundColor(Amr.Colors.Orange)
334 btnImport:SetFont(Amr.CreateFont("Bold", 16, Amr.Colors.White))
335 btnImport:SetWidth(120)
336 btnImport:SetHeight(26)
337 btnImport:SetPoint("TOPLEFT", container.content, "TOPLEFT", 0, -81)
338 btnImport:SetCallback("OnClick", onImportClick)
339 container:AddChild(btnImport)
340
341 local lbl = AceGUI:Create("AmrUiLabel")
342 lbl:SetText(L.GearImportNote)
343 lbl:SetWidth(100)
344 lbl:SetFont(Amr.CreateFont("Italic", 12, Amr.Colors.TextTan))
345 lbl:SetJustifyH("MIDDLE")
346 lbl:SetPoint("TOP", btnImport.frame, "BOTTOM", 0, -5)
347 container:AddChild(lbl)
348
349 local lbl2 = AceGUI:Create("AmrUiLabel")
350 lbl2:SetText(L.GearTipTitle)
351 lbl2:SetWidth(140)
352 lbl2:SetFont(Amr.CreateFont("Italic", 20, Amr.Colors.Text))
353 lbl2:SetJustifyH("MIDDLE")
354 lbl2:SetPoint("TOP", lbl.frame, "BOTTOM", 0, -50)
355 container:AddChild(lbl2)
356
357 lbl = AceGUI:Create("AmrUiLabel")
358 lbl:SetText(L.GearTipText)
359 lbl:SetWidth(140)
360 lbl:SetFont(Amr.CreateFont("Italic", 12, Amr.Colors.Text))
361 lbl:SetJustifyH("MIDDLE")
362 lbl:SetPoint("TOP", lbl2.frame, "BOTTOM", 0, -5)
363 container:AddChild(lbl)
364
365 lbl2 = AceGUI:Create("AmrUiLabel")
366 lbl2:SetText(L.GearTipCommands)
367 lbl2:SetWidth(130)
368 lbl2:SetFont(Amr.CreateFont("Italic", 12, Amr.Colors.Text))
369 lbl2:SetPoint("TOP", lbl.frame, "BOTTOM", 10, -5)
370 container:AddChild(lbl2)
371
372 --[[
373 local btnClean = AceGUI:Create("AmrUiButton")
374 btnClean:SetText(L.GearButtonCleanText)
375 btnClean:SetBackgroundColor(Amr.Colors.Orange)
376 btnClean:SetFont(Amr.CreateFont("Bold", 16, Amr.Colors.White))
377 btnClean:SetWidth(120)
378 btnClean:SetHeight(26)
379 btnClean:SetPoint("BOTTOMLEFT", container.content, "BOTTOMLEFT", 0, 5)
380 btnClean:SetCallback("OnClick", function(widget) Amr:CleanBags() end)
381 container:AddChild(btnClean)
382 ]]
383
384 local t = AceGUI:Create("AmrUiTabGroup")
385 t:SetLayout("None")
386 t:SetTabs({
387 {text=L.GearTabPrimary, value="1", style="bold"},
388 {text=L.GearTabSecondary, value="2", style="bold"}
389 })
390 t:SetCallback("OnGroupSelected", onGearTabSelected)
391 t:SetPoint("TOPLEFT", container.content, "TOPLEFT", 144, -30)
392 t:SetPoint("BOTTOMRIGHT", container.content, "BOTTOMRIGHT")
393 container:AddChild(t)
394 _gearTabs = t;
395
396 if not _activeTab then
397 _activeTab = tostring(GetActiveSpecGroup())
398 end
399
400 t:SelectTab(_activeTab)
401 end
402
403 -- do cleanup when the gear tab is released
404 function Amr:ReleaseTabGear()
405 _gearTabs = nil
406 end
407
408 -- show and update the gear tab for the specified spec
409 function Amr:ShowGearTab(spec)
410 if not _gearTabs then return end
411
412 _activeTab = tostring(spec)
413 _gearTabs:SelectTab(_activeTab)
414 end
415
416 -- refresh display of the current gear tab
417 function Amr:RefreshGearTab()
418 if not _gearTabs then return end
419 _gearTabs:SelectTab(_activeTab)
420 end
421
422
423 ------------------------------------------------------------------------------------------------
424 -- Gear Set Management
425 ------------------------------------------------------------------------------------------------
426 local _waitingForSpec = 0
427 local _waitingForItemLock = nil
428 local _pendingEquip = nil
429
430 -- scan a bag for the best matching item
431 local function scanBagForItem(item, bagId, bestItem, bestDiff, bestLink)
432 local numSlots = GetContainerNumSlots(bagId)
433 for slotId = 1, numSlots do
434 local _, _, _, _, _, _, itemLink = GetContainerItemInfo(bagId, slotId)
435 -- we skip any stackable item, as far as we know, there is no equippable gear that can be stacked
436 if itemLink then
437 local bagItem = Amr.ParseItemLink(itemLink)
438 if bagItem ~= nil then
439 local diff = countItemDifferences(item, bagItem)
440 if diff < bestDiff then
441 bestItem = { bag = bagId, slot = slotId }
442 bestDiff = diff
443 bestLink = itemLink
444 end
445 end
446 end
447 end
448 return bestItem, bestDiff, bestLink
449 end
450
451 -- find the first empty slot in the player's backpack+bags
452 local function findFirstEmptyBagSlot()
453
454 local bagIds = {}
455 table.insert(bagIds, BACKPACK_CONTAINER)
456 for bagId = 1, NUM_BAG_SLOTS do
457 table.insert(bagIds, bagId)
458 end
459
460 for i, bagId in ipairs(bagIds) do
461 local numSlots = GetContainerNumSlots(bagId)
462 for slotId = 1, numSlots do
463 local _, _, _, _, _, _, itemLink = GetContainerItemInfo(bagId, slotId)
464 if not itemLink then
465 return bagId, slotId
466 end
467 end
468 end
469
470 return nil, nil
471 end
472
473 local function finishEquipGearSet()
474 if not _pendingEquip then return end
475
476 _pendingEquip.tries = _pendingEquip.tries + 1
477 if _pendingEquip.tries > 16 then
478 _pendingEquip = nil
479 else
480 -- start over again, trying any items that could not be equipped in the previous pass (unique constraints)
481 Amr:EquipGearSet(_pendingEquip.spec)
482 end
483 end
484
485 -- equip the next slot in a pending equip
486 local function tryEquipNextItem()
487 if not _pendingEquip then return end
488
489 local item = _pendingEquip.itemsToEquip[_pendingEquip.nextSlot]
490
491 local bestItem = nil
492 local bestLink = nil
493 local bestDiff = 1000
494
495 -- find the best matching item
496
497 -- equipped items
498 for slotNum = 1, #Amr.SlotIds do
499 local slotId = Amr.SlotIds[slotNum]
500 local itemLink = GetInventoryItemLink("player", slotId)
501 if itemLink then
502 local invItem = Amr.ParseItemLink(itemLink)
503 if invItem ~= nil then
504 local diff = countItemDifferences(item, invItem)
505 if diff < bestDiff then
506 bestItem = { slot = slotId }
507 bestDiff = diff
508 bestLink = itemLink
509 end
510 end
511 end
512 end
513
514 -- inventory
515 bestItem, bestDiff, bestLink = scanBagForItem(item, BACKPACK_CONTAINER, bestItem, bestDiff, bestLink)
516 for bagId = 1, NUM_BAG_SLOTS do
517 bestItem, bestDiff, bestLink = scanBagForItem(item, bagId, bestItem, bestDiff, bestLink)
518 end
519
520 -- bank
521 bestItem, bestDiff = scanBagForItem(item, BANK_CONTAINER, bestItem, bestDiff, bestLink)
522 for bagId = NUM_BAG_SLOTS + 1, NUM_BAG_SLOTS + NUM_BANKBAGSLOTS do
523 bestItem, bestDiff = scanBagForItem(item, bagId, bestItem, bestDiff, bestLink)
524 end
525
526 ClearCursor()
527
528 if not bestItem then
529 -- stop if we can't find an item
530 Amr:Print(L.GearEquipErrorNotFound)
531 Amr:Print(L.GearEquipErrorNotFound2)
532 _pendingEquip = nil
533 return
534
535 elseif bestItem and bestItem.bag and bestItem.bag >= NUM_BAG_SLOTS + 1 and bestItem.bag <= NUM_BAG_SLOTS + NUM_BANKBAGSLOTS then
536 -- find first empty bag slot
537 local invBag, invSlot = findFirstEmptyBagSlot()
538 if not invBag then
539 -- stop if bags are too full
540 Amr:Print(L.GearEquipErrorBagFull)
541 _pendingEquip = nil
542 return
543 end
544
545 -- move from bank to bag
546 PickupContainerItem(bestItem.bag, bestItem.slot)
547 PickupContainerItem(invBag, invSlot)
548
549 -- set flag so that when we clear cursor and release the item lock, we can respond to the event and continue
550 _waitingForItemLock = {
551 bagId = invBag,
552 slotId = invSlot
553 }
554
555 ClearCursor()
556
557 -- now we need to wait for game event to continue and try this item again after it is in our bag
558 return
559 else
560 if not Amr:IsSoulbound(bestItem.bag, bestItem.slot) then
561 -- if an item is not soulbound, then warn the user and quit
562 Amr:Print(L.GearEquipErrorSoulbound(bestLink))
563 _pendingEquip = nil
564 return
565 else
566 local slotId = _pendingEquip.nextSlot
567
568 -- an item in the player's bags or already equipped, equip it
569 _pendingEquip.bag = bestItem.bag
570 _pendingEquip.slot = bestItem.slot
571 _pendingEquip.destSlot = slotId
572
573 if bestItem.bag then
574 PickupContainerItem(bestItem.bag, bestItem.slot)
575 else
576 PickupInventoryItem(bestItem.slot)
577 end
578 PickupInventoryItem(slotId)
579 ClearCursor()
580 end
581 end
582
583 end
584
585 local function onItemUnlocked(bagId, slotId)
586
587 if _waitingForItemLock then
588 -- waiting on a move from bank to bags to complete, just continue as normal afterwards
589 if bagId == _waitingForItemLock.bagId and slotId == _waitingForItemLock.slotId then
590 _waitingForItemLock = nil
591 tryEquipNextItem()
592 end
593
594 elseif _pendingEquip and _pendingEquip.destSlot then
595 -- waiting on an item swap to complete successfully so that we can go on to the next item
596
597 -- inventory slot we're swapping to is still locked, can't continue yet
598 if IsInventoryItemLocked(_pendingEquip.destSlot) then return end
599
600 if _pendingEquip.bag then
601 local _, _, locked = GetContainerItemInfo(_pendingEquip.bag, _pendingEquip.slot)
602 -- the bag slot we're swapping from is still locked, can't continue yet
603 if locked then return end
604 else
605 -- inventory slot we're swapping from is still locked, can't continue yet
606 if IsInventoryItemLocked(_pendingEquip.slot) then return end
607 end
608
609 -- move on to the next item, this item is done
610 _pendingEquip.itemsToEquip[_pendingEquip.destSlot] = nil
611 _pendingEquip.destSlot = nil
612 _pendingEquip.bag = nil
613 _pendingEquip.slot = nil
614
615 _pendingEquip.remaining = _pendingEquip.remaining - 1
616 if _pendingEquip.remaining > 0 then
617 for slotId, item in pairs(_pendingEquip.itemsToEquip) do
618 _pendingEquip.nextSlot = slotId
619 break
620 end
621 tryEquipNextItem()
622 else
623 finishEquipGearSet()
624 end
625
626 end
627 end
628
629 local function startEquipGearSet(spec)
630
631 local gear = Amr.db.char.GearSets[spec]
632 if not gear then
633 Amr:Print(L.GearEquipErrorEmpty)
634 return
635 end
636
637 local player = Amr:ExportCharacter()
638
639 local itemsToEquip = {}
640 local remaining = 0
641 local usedItems = {}
642 local firstSlot = nil
643
644 -- check for items that need to be equipped
645 for slotNum = 1, #Amr.SlotIds do
646 local slotId = Amr.SlotIds[slotNum]
647
648 local old = player.Equipped[spec][slotId]
649 old = Amr.ParseItemLink(old)
650
651 local new = gear[slotId]
652
653 local diff = countItemDifferences(old, new)
654 if diff < 1000 then
655 -- same item, see if inventory has one that is closer (e.g. a duplicate item with correct enchants/gems)
656 local bestLink, bestItem, bestDiff = Amr:FindMatchingItem(new, player, usedItems)
657 if bestDiff and bestDiff < diff then
658 itemsToEquip[slotId] = new
659 remaining = remaining + 1
660 end
661 else
662 itemsToEquip[slotId] = new
663 remaining = remaining + 1
664 end
665 end
666
667 if remaining > 0 then
668 _pendingEquip = {
669 tries = _pendingEquip and _pendingEquip.spec == spec and _pendingEquip.tries or 0,
670 spec = spec,
671 itemsToEquip = itemsToEquip,
672 remaining = remaining,
673 nextSlot = firstSlot
674 }
675
676 -- starting item
677 for slotId, item in pairs(_pendingEquip.itemsToEquip) do
678 _pendingEquip.nextSlot = slotId
679 break
680 end
681
682 tryEquipNextItem()
683 else
684 _pendingEquip = nil
685 end
686 end
687
688 local function onActiveTalentGroupChanged()
689 local auto = Amr.db.profile.options.autoGear
690 local currentSpec = GetActiveSpecGroup()
691
692 if currentSpec == _waitingForSpec or auto then
693 -- spec is what we want, now equip the gear
694 startEquipGearSet(currentSpec)
695 end
696
697 _waitingForSpec = 0
698 end
699
700 -- activate the specified spec and then equip the saved gear set for either primary (1) or secondary (2) spec
701 function Amr:EquipGearSet(spec)
702
703 -- if no argument, then toggle spec
704 if not spec then
705 spec = GetActiveSpecGroup() == 1 and 2 or 1
706 end
707
708 -- allow some flexibility in the arguments
709 if spec == "primary" or spec == "Primary" then spec = 1 end
710 if spec == "secondary" or spec == "Secondary" then spec = 2 end
711 if spec == "1" or spec == "2" then spec = tonumber(spec) end
712
713 -- only spec 1 or 2 are valid
714 if spec ~= 1 and spec ~= 2 then return end
715
716 if UnitAffectingCombat("player") then
717 Amr:Print(L.GearEquipErrorCombat)
718 return
719 end
720
721 _waitingForSpec = spec
722
723 local currentSpec = GetActiveSpecGroup()
724 if currentSpec ~= spec then
725 SetActiveSpecGroup(spec)
726 else
727 onActiveTalentGroupChanged()
728 end
729 end
730
731 -- moves any gear in bags to the bank if not part of main or off spec gear set
732 function Amr:CleanBags()
733 -- TODO: implement
734 end
735
736 function Amr:InitializeGear()
737 Amr:AddEventHandler("ACTIVE_TALENT_GROUP_CHANGED", onActiveTalentGroupChanged)
738
739 Amr:AddEventHandler("UNIT_INVENTORY_CHANGED", function(unitID)
740 if unitID and unitID ~= "player" then return end
741 Amr:RefreshGearTab()
742 end)
743
744 Amr:AddEventHandler("ITEM_UNLOCKED", onItemUnlocked)
745 end