changeset 89:6bbe64d587b4 v42

Improved artifact/relic reading, gear display. Now creates equipment manager sets when you use button or command to equip a set.
author yellowfive
date Sun, 18 Sep 2016 21:24:08 -0700
parents b3ff336fad77
children db63475ef5d4
files AskMrRobot-Serializer/AskMrRobot-Serializer.lua AskMrRobot.toc CombatLog.lua Constants.lua Export.lua Gear.lua Import.lua Shopping.lua ui/Ui.lua
diffstat 9 files changed, 405 insertions(+), 243 deletions(-) [+]
line wrap: on
line diff
--- a/AskMrRobot-Serializer/AskMrRobot-Serializer.lua	Fri Sep 02 16:22:12 2016 -0700
+++ b/AskMrRobot-Serializer/AskMrRobot-Serializer.lua	Sun Sep 18 21:24:08 2016 -0700
@@ -1,7 +1,7 @@
 -- AskMrRobot-Serializer will serialize and communicate character data between users.
 -- This is used primarily to associate character information to logs uploaded to askmrrobot.com.
 
-local MAJOR, MINOR = "AskMrRobot-Serializer", 41
+local MAJOR, MINOR = "AskMrRobot-Serializer", 42
 local Amr, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
 
 if not Amr then return end -- already loaded by something else
@@ -175,6 +175,65 @@
 	[1448] = true
 }
 
+-- just to make life easier, maps ID of each artifact weapon to the spec number (1-4)
+Amr.ArtifactIdToSpecNumber = {
+	[128402] = 1, -- DK
+	[128292] = 2,
+	[128403] = 3,
+	[127829] = 1, -- DH
+	[128832] = 2,
+	[128858] = 1, -- Druid
+	[128860] = 2,
+	[128821] = 3,
+	[128306] = 4,
+	[128861] = 1, -- Hunter
+	[128826] = 2,
+	[128808] = 3,
+	[127857] = 1, -- Mage
+	[128820] = 2,
+	[128862] = 3,
+	[128938] = 1, -- Monk
+	[128937] = 2,
+	[128940] = 3,
+	[128823] = 1, -- Paladin
+	[128866] = 2,
+	[120978] = 3,
+	[128868] = 1, -- Priest
+	[128825] = 2,
+	[128827] = 3,
+	[128870] = 1, -- Rogue
+	[128872] = 2,
+	[128476] = 3,
+	[128935] = 1, -- Shaman
+	[128819] = 2,
+	[128911] = 3,
+	[128942] = 1, -- Warlock
+	[128943] = 2,
+	[128941] = 3,
+	[128910] = 1, -- Warrior
+	[128908] = 2,
+	[128289] = 3,
+	
+	--[128293] = 2, -- Frost OH
+	--[127830] = 1, -- Havoc OH
+	--[128831] = 2, -- Vengeance OH
+	--[128859] = 2, -- Feral OH
+	--[128822] = 3, -- Guardian OH
+	--[133959] = 2, -- Fire OH
+	--[133948] = 3, -- Windwalker OH
+	--[128867] = 2, -- Prot MH
+	--[133958] = 3, -- Shadow OH
+	--[128869] = 1, -- Rogue OH's
+	--[134552] = 2,
+	--[128479] = 3,
+	--[128936] = 1, -- Shaman OH's
+	--[128873] = 2,
+	--[128934] = 3,
+	--[137246] = 2, -- Demo MH
+	--[134553] = 2, -- Fury OH
+	--[128288] = 3  -- Prot MH
+}
+
 -- IDs of set tokens that we would care about in a player's inventory
 Amr.SetTokenIds = {
 	[127970] = true,
--- a/AskMrRobot.toc	Fri Sep 02 16:22:12 2016 -0700
+++ b/AskMrRobot.toc	Sun Sep 18 21:24:08 2016 -0700
@@ -1,7 +1,7 @@
 ## Interface: 70000
 ## Title: Ask Mr. Robot
 ## Author: Team Robot, Inc.
-## Version: 41
+## Version: 42
 ## Notes: Gear import/export, combat logging, and more.
 ## URL: www.askmrrobot.com
 ## SavedVariables: AskMrRobotDb3
--- a/CombatLog.lua	Fri Sep 02 16:22:12 2016 -0700
+++ b/CombatLog.lua	Sun Sep 18 21:24:08 2016 -0700
@@ -599,10 +599,12 @@
 end
 
 function Amr:InitializeCombatLog()
-	updateAutoLogging()
+	--updateAutoLogging()
 end
 
+--[[
 Amr:AddEventHandler("UPDATE_INSTANCE_INFO", updateAutoLogging)
 Amr:AddEventHandler("PLAYER_DIFFICULTY_CHANGED", updateAutoLogging)
 Amr:AddEventHandler("ENCOUNTER_START", updateAutoLogging)
 Amr:AddEventHandler("PLAYER_REGEN_DISABLED", logPlayerExtraData)
+]]
--- a/Constants.lua	Fri Sep 02 16:22:12 2016 -0700
+++ b/Constants.lua	Sun Sep 18 21:24:08 2016 -0700
@@ -26,6 +26,7 @@
 Amr.GetItemTooltip = Amr.Serializer.GetItemTooltip
 Amr.GetItemLevel = Amr.Serializer.GetItemLevel
 Amr.GetItemUniqueId = Amr.Serializer.GetItemUniqueId
+Amr.ArtifactIdToSpecNumber = Amr.Serializer.ArtifactIdToSpecNumber
 
 -- map of slot ID to display text
 Amr.SlotDisplayText = {
@@ -160,6 +161,8 @@
 		table.insert(parts, 4)
 	elseif itemObj.level and itemObj.level ~= 0 then
 		table.insert(parts, 512)
+	elseif itemObj.relicBonusIds then
+		table.insert(parts, 256)
 	else
 		table.insert(parts, 0)
 	end
@@ -184,10 +187,24 @@
 		table.insert(parts, 0)
 	end
 	
-	-- technically relic stuff comes after this... but we ignore it for now, too much of a pain
-	table.insert(parts, 0)
-	table.insert(parts, 0)
-	table.insert(parts, 0)
+	-- sometimes we provide relic bonus IDs
+	if itemObj.relicBonusIds then
+		for i = 1,3 do
+			local bonusList = itemObj.relicBonusIds[i]
+			if bonusList and #bonusList > 0 then
+				table.insert(parts, #bonusList)
+				for bi, bv in ipairs(bonusList) do
+					table.insert(parts, bv)
+				end
+			else
+				table.insert(parts, 0)
+			end
+		end
+	else
+		table.insert(parts, 0)
+		table.insert(parts, 0)
+		table.insert(parts, 0)
+	end
     
     return table.concat(parts, ":")
 end
--- a/Export.lua	Fri Sep 02 16:22:12 2016 -0700
+++ b/Export.lua	Sun Sep 18 21:24:08 2016 -0700
@@ -270,7 +270,6 @@
 end
 
 local function scanArtifact()
-	-- TODO: when they put in a real API for this, switch to that instead of using UI methods directly
 	local powers = C_ArtifactUI.GetPowers()
 	if not powers then return end
 	
@@ -292,7 +291,11 @@
 	powers = C_ArtifactUI.GetPowers()
 	if not powers then return end
 	
-	local spec = GetSpecialization()
+	-- use the artifact item ID to figure out which spec this is for, since you can open your artifact on any spec
+	local itemID = C_ArtifactUI.GetArtifactInfo()
+	local spec = Amr.ArtifactIdToSpecNumber[itemID]	
+	--local spec = GetSpecialization()
+	
 	Amr.db.char.Artifacts[spec] = {
 		Powers = powerRanks,
 		Relics = relicInfo
--- a/Gear.lua	Fri Sep 02 16:22:12 2016 -0700
+++ b/Gear.lua	Sun Sep 18 21:24:08 2016 -0700
@@ -221,7 +221,11 @@
 					-- set item name, tooltip, and ilvl
 					obj.nameLabel:SetText(link:gsub("%[", ""):gsub("%]", ""))
 					Amr:SetItemTooltip(obj.nameLabel, link)
-					obj.ilvlLabel:SetText(iLevel)					
+					
+					-- the game's info gives the wrong item level, so we have to scan for it
+					iLevel = (quality ~= 6 or optimalItem.relicBonusIds) and Amr.GetItemLevel(nil, nil, link) or ""
+					obj.ilvlLabel:SetText(iLevel)			
+					
 				end, { ilvlLabel = lblIlvl, nameLabel = lblItem })
 			end
 						
@@ -231,6 +235,7 @@
 
 				-- gems
 				if itemInfo and itemInfo.socketColors then
+					local prevSocket = nil
 					for i = 1, #itemInfo.socketColors do
 						local g = optimalItem.gemIds[i]
 						local isGemEquipped = g ~= 0 and matchItem and matchItem.gemIds and matchItem.gemIds[i] == g
@@ -241,7 +246,11 @@
 						socketBorder:SetBackgroundColor(Amr.Colors.Black, isGemEquipped and 0 or 1)
 						socketBorder:SetWidth(26)
 						socketBorder:SetHeight(26)
-						socketBorder:SetPoint("LEFT", lblItem.frame, "RIGHT", 30, 0)
+						if not prevSocket then
+							socketBorder:SetPoint("LEFT", lblItem.frame, "RIGHT", 30, 0)
+						else
+							socketBorder:SetPoint("LEFT", prevSocket.frame, "RIGHT", 2, 0)
+						end
 						if isGemEquipped then
 							socketBorder:SetAlpha(0.3)
 						end
@@ -268,13 +277,19 @@
 						if g ~= 0 then
 							local gemInfo = Amr.db.char.ExtraGemData[spec][g]
 							if gemInfo then
-								Amr.GetItemInfo(gemInfo.id, function(obj, name, link, quality, iLevel, reqLevel, class, subclass, maxStack, equipSlot, texture)					
+								local gident = gemInfo.id
+								if optimalItem.relicBonusIds then
+									gident = Amr.CreateItemLink({ id = gemInfo.id, enchantId = 0, gemIds = {0,0,0,0}, suffixId = 0, bonusIds = optimalItem.relicBonusIds[i]})
+								end
+								Amr.GetItemInfo(gident, function(obj, name, link, quality, iLevel, reqLevel, class, subclass, maxStack, equipSlot, texture)					
 									-- set icon and a tooltip
 									obj:SetIcon(texture)
 									Amr:SetItemTooltip(obj, link)
 								end, socketIcon)
 							end
 						end
+						
+						prevSocket = socketBorder
 					end
 				end
 				
@@ -378,7 +393,6 @@
 	container:AddChild(t)	
 	_gearTabs = t;
 	
-	--[[
 	local btnShop = AceGUI:Create("AmrUiButton")
 	btnShop:SetText(L.GearButtonShop)
 	btnShop:SetBackgroundColor(Amr.Colors.Blue)
@@ -388,7 +402,6 @@
 	btnShop:SetPoint("TOPRIGHT", container.content, "TOPRIGHT", -20, -25)
 	btnShop:SetCallback("OnClick", function(widget) Amr:ShowShopWindow() end)
 	container:AddChild(btnShop)
-	]]
 	
 	if not _activeTab then
 		_activeTab = tostring(GetSpecialization())
@@ -423,6 +436,7 @@
 local _waitingForSpec = 0
 local _waitingForItemLock = nil
 local _pendingEquip = nil
+local _pendingRemove = nil
 
 -- scan a bag for the best matching item
 local function scanBagForItem(item, bagId, bestItem, bestDiff, bestLink)
@@ -445,6 +459,24 @@
 	return bestItem, bestDiff, bestLink
 end
 
+local function onEquipGearSetComplete()
+	-- create an equipment manager set
+	local specId, specName = GetSpecializationInfo(GetSpecialization())
+	
+	local item = Amr.ParseItemLink(GetInventoryItemLink("player", INVSLOT_MAINHAND))
+	if not item or not Amr.ArtifactIdToSpecNumber[item.id] then
+		item = Amr.ParseItemLink(GetInventoryItemLink("player", INVSLOT_OFFHAND))
+		if item and not Amr.ArtifactIdToSpecNumber[item.id] then
+			item = nil
+		end
+	end
+	if item then
+		Amr.GetItemInfo(item.id, function(customArg, name, link, quality, iLevel, reqLevel, class, subclass, maxStack, equipSlot, texture)
+			SaveEquipmentSet("AMR " .. specName, texture)
+		end)
+	end
+end
+
 -- find the first empty slot in the player's backpack+bags
 local function findFirstEmptyBagSlot()
 	
@@ -584,13 +616,55 @@
 	
 end
 
+local function removeNextItem()
+	if not _pendingRemove then return end
+	
+	local list = _pendingRemove.slotsToRemove
+	local slot = list[#list - _pendingRemove.remaining + 1]
+	
+	-- find first empty bag slot
+	local invBag, invSlot = findFirstEmptyBagSlot()
+	if not invBag then
+		-- stop if bags are too full
+		Amr:Print(L.GearEquipErrorBagFull)
+		_pendingRemove = nil
+		_pendingEquip = nil
+		return
+	end
+	
+	PickupInventoryItem(slot)
+	PickupContainerItem(invBag, invSlot)
+	
+	-- set flag so that when we clear cursor and release the item lock, we can respond to the event and continue
+	_waitingForItemLock = {
+		bagId = invBag,
+		slotId = invSlot,
+		isRemove = true
+	}
+	
+	ClearCursor()
+end
+
 local function onItemUnlocked(bagId, slotId)
-
+	
 	if _waitingForItemLock then
-		-- waiting on a move from bank to bags to complete, just continue as normal afterwards
+		-- waiting on a move from bank to bags to complete, or waiting on removing an item to complete, just continue as normal afterwards
 		if bagId == _waitingForItemLock.bagId and slotId == _waitingForItemLock.slotId then
+			local isremove = _waitingForItemLock.isRemove
 			_waitingForItemLock = nil
-			tryEquipNextItem()
+			
+			if isremove then
+				_pendingRemove.remaining = _pendingRemove.remaining - 1
+				if _pendingRemove.remaining > 0 then
+					removeNextItem()
+				else
+					-- we have removed all items that we want to remove, now do the equip
+					_pendingRemove = nil
+					tryEquipNextItem()
+				end
+			else
+				tryEquipNextItem()
+			end
 		end
 		
 	elseif _pendingEquip and _pendingEquip.destSlot then
@@ -681,6 +755,18 @@
 	end
 
 	if remaining > 0 then
+		-- if this is not our first try, then remove weapons before starting
+		local toRemove = {}
+		local removesRemaining = 0
+		if _pendingEquip and _pendingEquip.tries > 0 then
+			for slotId, item in pairs(itemsToEquip) do
+				if slotId == 16 or slotId == 17 then
+					table.insert(toRemove, slotId)
+					removesRemaining = removesRemaining + 1
+				end
+			end			
+		end
+		
 		_pendingEquip = {
 			tries = _pendingEquip and _pendingEquip.spec == spec and _pendingEquip.tries or 0,
 			spec = spec,
@@ -696,9 +782,18 @@
 			break
 		end
 		
-		tryEquipNextItem()
+		if removesRemaining > 0 then
+			_pendingRemove = {
+				slotsToRemove = toRemove,
+				remaining = removesRemaining
+			}
+			removeNextItem()
+		else
+			tryEquipNextItem()
+		end
 	else
 		_pendingEquip = nil
+		onEquipGearSetComplete()
 	end
 end
 
--- a/Import.lua	Fri Sep 02 16:22:12 2016 -0700
+++ b/Import.lua	Sun Sep 18 21:24:08 2016 -0700
@@ -221,6 +221,11 @@
         if itemString ~= "" and itemString ~= "_" then
             local tokens = {}
             local bonusIds = {}
+			local relicBonusIds = {}
+			table.insert(relicBonusIds, {})
+			table.insert(relicBonusIds, {})
+			table.insert(relicBonusIds, {})
+			local hasRelics = false
             local hasBonuses = false
             local token = ""
             local prop = "i"
@@ -258,6 +263,15 @@
                     if prop == "b" then
                         table.insert(bonusIds, val)
                         hasBonuses = true
+					elseif prop == "m" then
+						table.insert(relicBonusIds[1], val)
+						hasRelics = true
+					elseif prop == "n" then
+						table.insert(relicBonusIds[2], val)
+						hasRelics = true
+					elseif prop == "o" then
+						table.insert(relicBonusIds[3], val)
+						hasRelics = true
                     else
                         tokens[prop] = val
                     end
@@ -278,12 +292,17 @@
             obj.upgradeId = tokens["u"] or 0
 			obj.level = tokens["v"] or 0
             obj.enchantId = tokens["e"] or 0
+			obj.inventoryId = tokens["t"] or 0
             
             obj.gemIds = {}
             table.insert(obj.gemIds, tokens["x"] or 0)
             table.insert(obj.gemIds, tokens["y"] or 0)
             table.insert(obj.gemIds, tokens["z"] or 0)
             table.insert(obj.gemIds, 0)
+			
+			if hasRelics then
+				obj.relicBonusIds = relicBonusIds
+			end
             
             if hasBonuses then
                 obj.bonusIds = bonusIds
--- a/Shopping.lua	Fri Sep 02 16:22:12 2016 -0700
+++ b/Shopping.lua	Sun Sep 18 21:24:08 2016 -0700
@@ -20,6 +20,10 @@
 local _chk4
 local _isAhOpen = false
 
+local function incrementTableItem(tbl, key, inc)
+	tbl[key] = tbl[key] and tbl[key] + inc or inc
+end
+
 local function onShopFrameClose(widget)
 	AceGUI:Release(widget)
 	_frameShop = nil
@@ -179,7 +183,7 @@
 		scroll:AddChild(panel)
 		
 		lbl = AceGUI:Create("AmrUiLabel")
-		lbl:SetWidth(35)
+		lbl:SetWidth(40)
 		lbl:SetWordWrap(false)
 		lbl:SetFont(Amr.CreateFont("Bold", 20, Amr.Colors.White))
 		lbl:SetText(count .. "x")
@@ -215,125 +219,7 @@
 	
 end
 
-function Amr:RefreshShoppingUi()
-
-	_chk1:SetVisible(false)
-	_chk2:SetVisible(false)
-	_chk3:SetVisible(false)
-	_chk4:SetVisible(false)
-	
-	_chk1:SetChecked(false)
-	_chk2:SetChecked(false)
-	_chk3:SetChecked(false)
-	_chk4:SetChecked(false)
-	
-	-- clear out any previous data
-	_panelContent:ReleaseChildren()
-	
-	local data = Amr.db.global.Shopping[_selectedPlayer]
-	if not data then		
-		_panelContent:SetLayout("None")
-		
-		local lbl = AceGUI:Create("AmrUiLabel")
-		lbl:SetFont(Amr.CreateFont("Italic", 18, Amr.Colors.TextTan))
-		lbl:SetText(L.ShopEmpty)
-		lbl:SetJustifyH("CENTER")
-		lbl:SetPoint("TOP", _panelContent.content, "TOP", 0, -30)
-		_panelContent:AddChild(lbl)
-	else
-		-- set labels on checkboxes
-		if data.specs[1] and data.specs[1] ~= 0 then
-			_chk1:SetText(L.SpecsShort[data.specs[1]])
-			_chk1:SetVisible(true)
-			_chk1:SetChecked(_specs[1])
-		end
-		
-		if data.specs[2] and data.specs[2] ~= 0 then
-			_chk2:SetText(L.SpecsShort[data.specs[2]])
-			_chk2:SetVisible(true)
-			_chk2:SetChecked(_specs[2])
-		end
-		
-		if data.specs[3] and data.specs[3] ~= 0 then
-			_chk3:SetText(L.SpecsShort[data.specs[3]])
-			_chk3:SetVisible(true)
-			_chk3:SetChecked(_specs[3])
-		end
-		
-		if data.specs[4] and data.spes[4] ~= 0 then
-			_chk4:SetText(L.SpecsShort[data.specs[4]])
-			_chk4:SetVisible(true)
-			_chk4:SetChecked(_specs[4])
-		end
-		
-		local spec = 0
-		if not _specs[1] and not _specs[2] and not _specs[3] and not _specs[4] then
-			-- all unchecked, show nothing
-		else
-			-- both is 0, otherwise the one that is selected
-			if not _specs[1] or not _specs[2] then
-				spec = _specs[1] and 1 or 2
-			end
-			
-			_panelContent:SetLayout("Fill")
-			
-			local scroll = AceGUI:Create("AmrUiScrollFrame")
-			scroll:SetLayout("List")
-			_panelContent:AddChild(scroll)
-			
-			renderShopSection(data.gems[spec], scroll, L.ShopHeaderGems)
-			renderShopSection(data.enchants[spec], scroll, L.ShopHeaderEnchants)		
-			renderShopSection(data.materials[spec], scroll, L.ShopHeaderMaterials)
-		end
-	end
-				
-end
-
--- compare gear to everything the player owns, and return the minimum gems/enchants/materials needed to optimize
-local function getShoppingData(player, gear, spec)
-
-	local ret = {
-		gems = {},
-		enchants = {},
-		materials = {}
-	}
-	
-	-- used to prevent considering the same item twice
-	local usedItems = {}
-	
-	for slotId, optimalItem in pairs(gear) do
-		local matchItemLink, matchItem = Amr:FindMatchingItem(optimalItem, player, usedItems)
-		local itemInfo = Amr.db.char.ExtraItemData[spec][optimalItem.id]
-		
-		-- find gem/enchant differences on the best-matching item
-		
-		-- gems
-		if itemInfo and itemInfo.socketColors then
-			for i = 1, #itemInfo.socketColors do
-				local g = optimalItem.gemIds[i]
-				local isGemEquipped = g ~= 0 and matchItem and matchItem.gemIds and matchItem.gemIds[i] == g
-				
-				if not isGemEquipped then
-					ret.gems[g] = ret.gems[g] and ret.gems[g] + 1 or 1
-				end
-			end
-		end
-		
-		-- enchant
-		if optimalItem.enchantId and optimalItem.enchantId ~= 0 then
-			local e = optimalItem.enchantId
-			local isEnchantEquipped = matchItem and matchItem.enchantId and matchItem.enchantId == e
-			
-			if not isEnchantEquipped then
-				ret.enchants[e] = ret.enchants[e] and ret.enchants[e] + 1 or 1
-			end
-		end
-	end
-	
-	return ret
-end
-
--- get the number of a specified item that the player currently owns
+-- get the number of a specified gem/enchant/material that the player currently owns
 local function getOwnedCount(itemId)
 	local ret = 0
 	
@@ -350,126 +236,205 @@
 	return ret
 end
 
+local function removeOwned(list, owned)
+	
+	for itemId, count in pairs(list) do
+		-- load up how many of an item we have
+		if not owned.loaded[itemId] then
+			owned.counts[itemId] = getOwnedCount(itemId)
+			owned.loaded[itemId] = true
+		end
+		
+		-- see how many we can remove from the required count
+		local used = math.min(owned.counts[itemId], count)
+		
+		-- update owned count so we can't double-use something
+		owned.counts[itemId] = owned.counts[itemId] - used;
+		
+		-- reduce the requirement, removing entirely if we have it completely covered
+		list[itemId] = list[itemId] - used;
+		if list[itemId] == 0 then
+			list[itemId] = nil
+		end
+	end
+end
+
+function Amr:RefreshShoppingUi()
+
+	local posToCheck = { _chk1, _chk2, _chk3, _chk4 }
+	local chk
+	
+	-- reset spec checkboxes
+	for specPos = 1,4 do
+		chk = posToCheck[specPos]
+		chk:SetVisible(false)
+		chk:SetChecked(false)
+	end
+		
+	-- clear out any previous data
+	_panelContent:ReleaseChildren()
+	
+	local data = Amr.db.global.Shopping[_selectedPlayer]
+	if not data then		
+		_panelContent:SetLayout("None")
+		
+		local lbl = AceGUI:Create("AmrUiLabel")
+		lbl:SetFont(Amr.CreateFont("Italic", 18, Amr.Colors.TextTan))
+		lbl:SetText(L.ShopEmpty)
+		lbl:SetJustifyH("CENTER")
+		lbl:SetPoint("TOP", _panelContent.content, "TOP", 0, -30)
+		_panelContent:AddChild(lbl)
+	else
+		local allStuff = { gems = {}, enchants = {}, materials = {} }
+		local hasStuff = false
+		local visited = {}
+		
+		for specPos = 1,4 do
+			-- set labels on checkboxes
+			if data.specs[specPos] and data.specs[specPos] ~= 0 then
+				chk = posToCheck[specPos]
+				chk:SetText(L.SpecsShort[data.specs[specPos]])
+				chk:SetVisible(true)
+				chk:SetChecked(_specs[specPos])
+				
+				-- gather up all stuff for checked specs
+				if _specs[specPos] then
+					hasStuff = true
+					
+					for inventoryId, stuff in pairs(data.stuff[specPos]) do
+						if not visited[inventoryId] then
+							if stuff.gems then
+								for itemId, count in pairs(stuff.gems) do
+									incrementTableItem(allStuff.gems, itemId, count)
+								end
+							end
+							
+							if stuff.enchants then
+								for itemId, count in pairs(stuff.enchants) do
+									incrementTableItem(allStuff.enchants, itemId, count)
+								end
+							end
+							
+							if stuff.materials then
+								for itemId, count in pairs(stuff.materials) do
+									incrementTableItem(allStuff.materials, itemId, count)
+								end
+							end
+						
+							-- make sure not to count the same physical item twice
+							if inventoryId ~= -1 then
+								visited[inventoryId] = true
+							end
+						end
+					end
+				end
+				
+			end
+			
+		end
+		
+		if hasStuff then		
+			-- remove what we already own
+			local owned = { counts = {}, loaded = {} }
+			removeOwned(allStuff.gems, owned)
+			removeOwned(allStuff.enchants, owned)
+			removeOwned(allStuff.materials, owned)
+			
+			_panelContent:SetLayout("Fill")
+			
+			local scroll = AceGUI:Create("AmrUiScrollFrame")
+			scroll:SetLayout("List")
+			_panelContent:AddChild(scroll)
+			
+			renderShopSection(allStuff.gems, scroll, L.ShopHeaderGems)
+			renderShopSection(allStuff.enchants, scroll, L.ShopHeaderEnchants)		
+			renderShopSection(allStuff.materials, scroll, L.ShopHeaderMaterials)
+		end
+	end
+				
+end
+
+-- compare gear to everything the player owns, and return the minimum gems/enchants/materials needed to optimize, grouped by inventory ID so that we can combine multiple specs without double-counting
+local function getShoppingData(player, gear, spec)
+
+	local ret = {}
+	
+	-- used to prevent considering the same item twice
+	local usedItems = {}
+	
+	for slotId, optimalItem in pairs(gear) do
+		local matchItemLink, matchItem = Amr:FindMatchingItem(optimalItem, player, usedItems)
+		local itemInfo = Amr.db.char.ExtraItemData[spec][optimalItem.id]
+		local inventoryId = optimalItem.inventoryId or -1
+		
+		-- find gem/enchant differences on the best-matching item
+		
+		-- gems, but skip artifact relics (will have relicBonusIds set)
+		if not optimalItem.relicBonusIds and itemInfo and itemInfo.socketColors then
+			for i = 1, #itemInfo.socketColors do
+				local g = optimalItem.gemIds[i]
+				local isGemEquipped = g ~= 0 and matchItem and matchItem.gemIds and matchItem.gemIds[i] == g
+				
+				if not isGemEquipped then
+					if not ret[inventoryId] then
+						ret[inventoryId] = { gems = {}, enchants = {}, materials = {} }
+					end
+					incrementTableItem(ret[inventoryId].gems, g, 1)
+				end
+			end
+		end
+		
+		-- enchant
+		if optimalItem.enchantId and optimalItem.enchantId ~= 0 then
+			local e = optimalItem.enchantId
+			local isEnchantEquipped = matchItem and matchItem.enchantId and matchItem.enchantId == e
+			
+			if not isEnchantEquipped then
+				-- enchant info, look in all spec extra info cache
+				local enchInfo = nil
+				for specPos = 1,4 do
+					if Amr.db.char.ExtraEnchantData[specPos] then
+						enchInfo = Amr.db.char.ExtraEnchantData[specPos][e]
+						if enchInfo then break end
+					end
+				end
+
+				if enchInfo then
+					if not ret[inventoryId] then
+						ret[inventoryId] = { gems = {}, enchants = {}, materials = {} }
+					end
+					incrementTableItem(ret[inventoryId].enchants, enchInfo.itemId, 1)
+					
+					if enchInfo.materials then
+						for k, v in pairs(enchInfo.materials) do
+							incrementTableItem(ret[inventoryId].materials, k, v)
+						end
+					end
+				end
+			end
+		end
+	end
+	
+	return ret
+end
+
 -- look at both gear sets and find stuff that a player needs to acquire to gem/enchant their gear
 function Amr:UpdateShoppingData(player)
 
-	-- TODO: re-enable shopping list when Legion comes out
-	do return end
-	
-	-- 0 is combination of all specs
 	local required = {
-		gems = {
-			[0] = {},
-			[1] = {},
-			[2] = {},
-			[3] = {},
-			[4] = {}
-		},
-		enchants = {
-			[0] = {},
-			[1] = {},
-			[2] = {},
-			[3] = {},
-			[4] = {}
-		},
-		materials = {
-			[0] = {},
-			[1] = {},
-			[2] = {},
-			[3] = {},
-			[4] = {}
-		},
+		stuff = {},
 		specs = player.Specs
 	}
 	
 	local enchantItemIdToId = {}
 	
 	for spec, gear in pairs(Amr.db.char.GearSets) do
-		local obj = getShoppingData(player, gear, spec)
-		for k, v in pairs(obj.gems) do
-			local gemInfo = Amr.db.char.ExtraGemData[spec][k]
-			if gemInfo then
-				local prev = required.gems[spec][gemInfo.id]
-				required.gems[spec][gemInfo.id] = prev and prev + v or v
-				
-				prev = required.gems[0][gemInfo.id]
-				required.gems[0][gemInfo.id] = prev and prev + v or v
-			end
-		end
-		for k, v in pairs(obj.enchants) do
-			local enchInfo = Amr.db.char.ExtraEnchantData[spec][k]
-			if enchInfo then
-				enchantItemIdToId[enchInfo.itemId] = k
-				
-				local prev = required.enchants[spec][enchInfo.itemId]
-				required.enchants[spec][enchInfo.itemId] = prev and prev + v or v
-				
-				prev = required.enchants[0][enchInfo.itemId]
-				required.enchants[0][enchInfo.itemId] = prev and prev + v or v
-			end
-		end
-	end
-	
-	-- now subtract stuff the player already has, and generate a list of materials as well
-	for spec = 0, 4 do
-		local specId = spec == 0 and 1 or GetSpecializationInfo(spec)
-		if specId then
-			-- now check if the player has any of the gems or enchants in their inventory, and subtract those
-			for itemId, count in pairs(required.gems[spec]) do
-				required.gems[spec][itemId] = math.max(count - getOwnedCount(itemId), 0)
-				
-				if required.gems[spec][itemId] == 0 then
-					required.gems[spec][itemId] = nil
-				end
-			end
-			
-			for itemId, count in pairs(required.enchants[spec]) do
-				-- look in both spec extra info cache
-				local e = enchantItemIdToId[itemId]		
-				local enchInfo = nil
-				if Amr.db.char.ExtraEnchantData[1] then
-					enchInfo = Amr.db.char.ExtraEnchantData[1][e]
-				end
-				if not enchInfo then
-					if Amr.db.char.ExtraEnchantData[2] then
-						enchInfo = Amr.db.char.ExtraEnchantData[2][e]
-					end
-				end
-				
-				if enchInfo then
-					required.enchants[spec][itemId] = math.max(count - getOwnedCount(itemId), 0)
-
-					if required.enchants[spec][itemId] == 0 then
-						required.enchants[spec][itemId] = nil
-					else
-						-- count up required materials
-						if enchInfo.materials then
-							local c = required.enchants[spec][itemId]
-							for k, v in pairs(enchInfo.materials) do
-								local prev = required.materials[spec][k]
-								required.materials[spec][k] = prev and prev + (v * c) or (v * c)
-							end
-						end
-					end			
-				end	
-			end
-			
-			-- check if player has any of the materials already
-			for itemId, count in pairs(required.materials[spec]) do
-				required.materials[spec][itemId] = math.max(count - getOwnedCount(itemId), 0)
-				
-				if required.materials[spec][itemId] == 0 then
-					required.materials[spec][itemId] = nil
-				end
-			end
-		end
+		required.stuff[spec] = getShoppingData(player, gear, spec)
 	end
 	
 	Amr.db.global.Shopping[player.Name .. "-" .. player.Realm] = required
 end
 
--- TODO: re-enable shopping list with Legion
---[[
 Amr:AddEventHandler("AUCTION_HOUSE_SHOW", function() 
 	_isAhOpen = true
 	if Amr.db.profile.options.shopAh then
@@ -482,5 +447,4 @@
 	if Amr.db.profile.options.shopAh then
 		Amr:HideShopWindow()
 	end
-end)
-]]
\ No newline at end of file
+end)
\ No newline at end of file
--- a/ui/Ui.lua	Fri Sep 02 16:22:12 2016 -0700
+++ b/ui/Ui.lua	Sun Sep 18 21:24:08 2016 -0700
@@ -190,7 +190,7 @@
 	t:SetTabs({
 		{text=L.TabExportText, value="Export"}, 
 		{text=L.TabGearText, value="Gear"}, 
-		{text=L.TabLogText, value="Log"}, 
+		--{text=L.TabLogText, value="Log"}, 
 		{text=L.TabTeamText, value="Team"},
 		{text=L.TabOptionsText, value="Options"}
 	})