diff Shopping.lua @ 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 0515882856f1
children f1da233629be
line wrap: on
line diff
--- 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