Mercurial > wow > askmrrobot
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 |