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