changeset 139:c229c759a125 v65

Update to support multiple setups per spec.
author yellowfive
date Mon, 05 Nov 2018 16:06:00 -0800
parents 45e467335a1b
children f40295e81fe0
files AskMrRobot-Serializer/AskMrRobot-Serializer.lua AskMrRobot.toc Constants.lua Core.lua Gear.lua Import.lua Shopping.lua
diffstat 7 files changed, 350 insertions(+), 187 deletions(-) [+]
line wrap: on
line diff
--- a/AskMrRobot-Serializer/AskMrRobot-Serializer.lua	Mon Sep 03 19:20:00 2018 -0700
+++ b/AskMrRobot-Serializer/AskMrRobot-Serializer.lua	Mon Nov 05 16:06:00 2018 -0800
@@ -1,6 +1,6 @@
 -- AskMrRobot-Serializer will serialize and communicate character data between users.
 
-local MAJOR, MINOR = "AskMrRobot-Serializer", 64
+local MAJOR, MINOR = "AskMrRobot-Serializer", 65
 local Amr, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
 
 if not Amr then return end -- already loaded by something else
--- a/AskMrRobot.toc	Mon Sep 03 19:20:00 2018 -0700
+++ b/AskMrRobot.toc	Mon Nov 05 16:06:00 2018 -0800
@@ -1,7 +1,7 @@
 ## Interface: 80000
 ## Title: Ask Mr. Robot
 ## Author: Team Robot, Inc.
-## Version: 64
+## Version: 65
 ## Notes: Gear import/export, combat logging, and more.
 ## URL: www.askmrrobot.com
 ## SavedVariables: AskMrRobotDb4
--- a/Constants.lua	Mon Sep 03 19:20:00 2018 -0700
+++ b/Constants.lua	Mon Nov 05 16:06:00 2018 -0800
@@ -2,10 +2,10 @@
 local L = LibStub("AceLocale-3.0"):GetLocale("AskMrRobot", true)
 
 -- min import version that we will read from the website
-Amr.MIN_IMPORT_VERSION = 58
+Amr.MIN_IMPORT_VERSION = 65
 
 -- min addon version that we will support for inter-addon communication
-Amr.MIN_ADDON_VERSION = 58
+Amr.MIN_ADDON_VERSION = 65
 
 -- import some constants from the serializer for convenience
 Amr.ChatPrefix = Amr.Serializer.ChatPrefix
--- a/Core.lua	Mon Sep 03 19:20:00 2018 -0700
+++ b/Core.lua	Mon Nov 05 16:06:00 2018 -0800
@@ -42,7 +42,8 @@
 local function initializeDb()
 
 	local defaults = {
-		char = {			
+		char = {
+			LastVersion = 0,           -- used to clean out old stuff	
 			FirstUse = true,           -- true if this is first time use, gets cleared after seeing the export help splash window
 			Talents = {},              -- for each spec, selected talents
 			Equipped = {},             -- for each spec, slot id to item info
@@ -50,7 +51,7 @@
 			BankItems = {},            -- list of item info for bank
 			BagItemsAndCounts = {},    -- used mainly for the shopping list
 			BankItemsAndCounts = {},   -- used mainly for the shopping list			
-			GearSets = {},             -- imported gear sets
+			GearSetups = {},           -- imported gear sets
 			ExtraEnchantData = {},     -- enchant id to enchant display information and material information
 			Logging = {                -- character logging settings
 				Enabled = false,       -- whether logging is currently on or not
@@ -77,7 +78,7 @@
 		},
 		global = {
 			Region = nil,              -- region that this user is in, all characters on the same account should be the same region
-			Shopping = {},             -- shopping list data stored globally for access on any character
+			Shopping2 = {},            -- shopping list data stored globally for access on any character
 			Logging = {                -- a lot of log data is stored globally for simplicity, can only be raiding with one character at a time
 				Wipes = {},            -- times that a wipe was called
 				PlayerData = {},       -- player data gathered at fight start
@@ -122,6 +123,18 @@
 		end
 	end
 	
+	-- upgrade old gear set info to new format
+	if Amr.db.char.GearSets then
+		Amr.db.char.GearSets = nil
+	end
+
+	if not Amr.db.char.GearSetups then
+		Amr.db.char.GearSetups = {}
+	end
+
+	if Amr.db.global.Shopping then
+		Amr.db.global.Shopping = nil
+	end
 	
 	Amr.db.RegisterCallback(Amr, "OnProfileChanged", "RefreshConfig")
 	Amr.db.RegisterCallback(Amr, "OnProfileCopied", "RefreshConfig")
@@ -137,12 +150,32 @@
 	_icon:Register(Amr.ADDON_NAME, _amrLDB, self.db.profile.minimap)	
 
 	-- listen for inter-addon communication
-	self:RegisterComm(Amr.ChatPrefix, "OnCommReceived")	
+	self:RegisterComm(Amr.ChatPrefix, "OnCommReceived")
 end
 
 local _enteredWorld = false
 local _pendingInit = false
 
+-- upgrade some stuff from old to new formats
+local function upgradeFromOld()
+
+	local currentVersion = tonumber(GetAddOnMetadata(Amr.ADDON_NAME, "Version"))
+	if Amr.db.char.LastVersion < 65 then
+		for i = 1,GetNumSpecializations() do
+			local _, specName = GetSpecializationInfo(i)
+			if specName then
+				print("AMR " .. specName)
+				local setid = C_EquipmentSet.GetEquipmentSetID("AMR " .. specName)
+				if setid then
+					C_EquipmentSet.DeleteEquipmentSet(setid)
+				end
+			end
+		end
+	end
+	Amr.db.char.LastVersion = currentVersion
+
+end
+
 local function finishInitialize()
 
 	-- record region, the only thing that we still can't get from the log file
@@ -155,6 +188,8 @@
 		Amr:InitializeGear()
 		Amr:InitializeExport()
 		Amr:InitializeCombatLog()
+
+		upgradeFromOld()
 	end)
 end
 
--- a/Gear.lua	Mon Sep 03 19:20:00 2018 -0700
+++ b/Gear.lua	Mon Nov 05 16:06:00 2018 -0800
@@ -2,8 +2,23 @@
 local L = LibStub("AceLocale-3.0"):GetLocale("AskMrRobot", true)
 local AceGUI = LibStub("AceGUI-3.0")
 
-local _gearTabs
-local _activeTab
+local _cboSetups
+local _panelGear
+local _activeSetupId
+
+local function getSetupById(id)
+	if not id then
+		id = _activeSetupId
+	end
+	local setup
+	for i,s in ipairs(Amr.db.char.GearSetups) do
+		if s.Id == id then
+			setup = s
+			break
+		end
+	end
+	return setup
+end
 
 -- Returns a number indicating how different two items are (0 means the same, higher means more different)
 local function countItemDifferences(item1, item2)
@@ -188,10 +203,25 @@
 	return socketBorder, socketIcon
 end
 
-local function renderGear(spec, container)
+local function renderGear(setupId, container)
+
+	-- release all children that were previously rendered, we gonna redo it now
+	container:ReleaseChildren()
 
 	local player = Amr:ExportCharacter()
-	local gear = Amr.db.char.GearSets[spec]
+
+	local gear
+	local spec
+	local setupIndex
+	for i, setup in ipairs(Amr.db.char.GearSetups) do
+		if setup.Id == setupId then
+			setupIndex = i
+			gear = setup.Gear
+			spec = setup.SpecSlot
+			break
+		end
+	end
+
 	local equipped = player.Equipped[player.ActiveSpec]
 		
 	if not gear then
@@ -236,7 +266,7 @@
 		btnEquip:SetWidth(300)
 		btnEquip:SetHeight(26)
 		btnEquip:SetCallback("OnClick", function(widget)
-			Amr:EquipGearSet(spec)			
+			Amr:EquipGearSet(setupIndex)
 		end)
 		panelGear:AddChild(btnEquip)
 		btnEquip:SetPoint("LEFT", icon.frame, "RIGHT", 40, 0)
@@ -392,16 +422,45 @@
 	end
 end
 
-local function onGearTabSelected(container, event, group)
-	container:ReleaseChildren()
-	_activeTab = group
-	renderGear(tonumber(group), container)
+local function onSetupChange(widget, eventName, value)
+	_activeSetupId = value
+	renderGear(_activeSetupId, _panelGear)
 end
 
 local function onImportClick(widget)
 	Amr:ShowImportWindow()
 end
 
+function Amr:PickFirstSetupForSpec()
+	local specSlot = GetSpecialization()
+	for i, setup in ipairs(Amr.db.char.GearSetups) do
+		if setup.SpecSlot == specSlot then
+			_activeSetupId = setup.Id
+			break
+		end
+	end
+end
+
+function Amr:GetActiveSetupId()
+	return _activeSetupId
+end
+
+function Amr:SetActiveSetupId(setupId)
+	_activeSetupId = setupId
+end
+
+function Amr:GetActiveSetupLabel()
+	if not _activeSetupId then
+		return nil
+	end
+	local setup = getSetupById(_activeSetupId)
+	if not setup then
+		return nil
+	else
+		return setup.Label
+	end
+end
+
 -- renders the main UI for the Gear tab
 function Amr:RenderTabGear(container)
 
@@ -446,23 +505,17 @@
 	lbl2:SetFont(Amr.CreateFont("Italic", 12, Amr.Colors.Text))
 	lbl2:SetPoint("TOP", lbl.frame, "BOTTOM", 10, -5)
 	
-	local t =  AceGUI:Create("AmrUiTabGroup")
-	t:SetLayout("None")
+	_cboSetups = AceGUI:Create("AmrUiDropDown")
+	_cboSetups:SetWidth(300)	
+	container:AddChild(_cboSetups)
+	_cboSetups:SetPoint("TOPLEFT", container.content, "TOPLEFT", 150, -27.5)
 	
-	local tabz = {}
-	for pos = 1, 4 do
-        local specId = GetSpecializationInfo(pos)
-        if specId then
-            table.insert(tabz, { text = L.SpecsShort[Amr.SpecIds[specId]], value = pos .. "", style = "bold" })
-        end
-	end
-	
-	t:SetTabs(tabz)
-	t:SetCallback("OnGroupSelected", onGearTabSelected)
-	container:AddChild(t)	
-	t:SetPoint("TOPLEFT", container.content, "TOPLEFT", 144, -30)
-	t:SetPoint("BOTTOMRIGHT", container.content, "BOTTOMRIGHT")
-	_gearTabs = t;
+	_panelGear = AceGUI:Create("AmrUiPanel")
+	_panelGear:SetLayout("None")
+	_panelGear:SetBackgroundColor(Amr.Colors.Bg)
+	container:AddChild(_panelGear)
+	_panelGear:SetPoint("TOPLEFT", container.content, "TOPLEFT", 144, -58)
+	_panelGear:SetPoint("BOTTOMRIGHT", container.content, "BOTTOMRIGHT")
 	
 	local btnShop = AceGUI:Create("AmrUiButton")
 	btnShop:SetText(L.GearButtonShop)
@@ -473,31 +526,45 @@
 	btnShop:SetCallback("OnClick", function(widget) Amr:ShowShopWindow() end)
 	container:AddChild(btnShop)
 	btnShop:SetPoint("TOPRIGHT", container.content, "TOPRIGHT", -20, -25)
-	
-	if not _activeTab then
-		_activeTab = tostring(GetSpecialization())
+
+	-- pick a default tab based on player's current spec if none is already specified
+	if not _activeSetupId then
+		Amr:PickFirstSetupForSpec()
 	end
-	
-	t:SelectTab(_activeTab)
+
+	Amr:RefreshGearDisplay()
+
+	-- set event on dropdown after UI has been initially rendered
+	_cboSetups:SetCallback("OnChange", onSetupChange)
 end
 
--- do cleanup when the gear tab is released
 function Amr:ReleaseTabGear()
-	_gearTabs = nil
-end
-
--- show and update the gear tab for the specified spec
-function Amr:ShowGearTab(spec)
-	if not _gearTabs then return end
-	
-	_activeTab = tostring(spec)
-	_gearTabs:SelectTab(_activeTab)
+	_cboSetups = nil
+	_panelGear = nil
 end
 
 -- refresh display of the current gear tab
-function Amr:RefreshGearTab()
-	if not _gearTabs then return end
-	_gearTabs:SelectTab(_activeTab)
+function Amr:RefreshGearDisplay()
+
+	if not _panelGear then
+		return
+	end
+
+	-- fill the gear setup picker
+	local setupList = {}
+	for i, setup in ipairs(Amr.db.char.GearSetups) do
+		table.insert(setupList, { text = setup.Label, value = setup.Id })
+	end
+	_cboSetups:SetItems(setupList)
+
+	-- set selected value
+	local prev = _activeSetupId
+	_cboSetups:SelectItem(_activeSetupId)
+
+	if prev == _activeSetupId then
+		-- selecting will trigger the change event if it changed; if it didn't change, do a render now
+		renderGear(_activeSetupId, _panelGear)
+	end
 end
 
 
@@ -639,7 +706,8 @@
 	end
 	]]
 
-	local setname = "AMR " .. specName
+	local setup = getSetupById(_activeSetupId)
+	local setname = setup.Label -- "AMR " .. specName
 	local setid = C_EquipmentSet.GetEquipmentSetID(setname)
 	if setid then
 		C_EquipmentSet.SaveEquipmentSet(setid, setIcon)
@@ -657,13 +725,13 @@
 	_gearOpWaiting = nil
 
 	-- make sure the gear tab is still in sync
-	Amr:RefreshGearTab()
+	Amr:RefreshGearDisplay()
 end
 
 -- initialize a gear op to start running it
-local function initializeGearOp(op, spec, pos)
+local function initializeGearOp(op, setupId, pos)
 	op.pos = pos
-	op.spec = spec
+	op.setupId = setupId
 
 	-- fill the remaining slot list and set the starting slot
 	op.nextSlot = nil
@@ -790,7 +858,7 @@
 function nextGearOp()
 	if not _currentGearOp then return end
 
-	local spec = _currentGearOp.spec
+	local setupId = _currentGearOp.setupId
 	local pos = _currentGearOp.pos
 	local passes = _gearOpPasses	
 
@@ -817,14 +885,14 @@
 			_currentGearOp = _pendingGearOps[pos + 1]
 			if _currentGearOp then
 				-- we have another op, do it
-				initializeGearOp(_currentGearOp, spec, pos + 1)
+				initializeGearOp(_currentGearOp, setupId, pos + 1)
 				processCurrentGearOp()
 			else
 				-- we are done
 				disposeGearOp()
 
 				-- this will check if not all items were swapped, and either finish up, try again, or abort if have tried too many times
-				beginEquipGearSet(spec, passes + 1)
+				beginEquipGearSet(setupId, passes + 1)
 			end
 		end
 	else
@@ -907,15 +975,19 @@
     [36] = true -- WarriorProtection
 }
 
-function beginEquipGearSet(spec, passes)
+function beginEquipGearSet(setupId, passes)
 
-	local gear = Amr.db.char.GearSets[spec]
-	if not gear then 
+	local setup = getSetupById(setupId)
+	
+	if not setup or not setup.Gear then 
 		Amr:Print(L.GearEquipErrorEmpty)
 		return
 	end
 
-	-- ensure all our stored data is up to date
+	local gear = setup.Gear
+	local spec = setup.SpecSlot
+
+	-- ensure all our stored data is up to date	
 	local player = Amr:ExportCharacter()
 	local doOhFirst = _ohFirst[player.Specs[spec]]
 
@@ -1060,7 +1132,7 @@
 
 			_gearOpPasses = passes
 			_currentGearOp = _pendingGearOps[1]
-			initializeGearOp(_currentGearOp, spec, 1)
+			initializeGearOp(_currentGearOp, setupId, 1)
 
 			processCurrentGearOp()
 		else
@@ -1079,40 +1151,65 @@
 	local waitingSpec = _waitingForSpec
 	_waitingForSpec = 0
 	
+	-- when spec changes, change active setup to first one for this spec (does nothing if they have no setups for this spec)
+	if _activeSetupId then
+		local currentSetup = getSetupById(_activeSetupId)
+		if currentSetup.SpecSlot ~= currentSpec then
+			Amr:PickFirstSetupForSpec()
+		end
+	end
+
 	if currentSpec == waitingSpec or auto then
 		-- spec is what we want, now equip the gear but after a short delay because the game auto-swaps artifact weapons
 		Amr.Wait(2, function()
-			beginEquipGearSet(GetSpecialization(), 0)
+			beginEquipGearSet(_activeSetupId, 0)
 		end)
 	end
 end
 
 -- activate the specified spec and then equip the saved gear set
-function Amr:EquipGearSet(spec)
+function Amr:EquipGearSet(setupIndex)
 	
-	-- if no argument, then cycle spec
-	if not spec then
-		spec = GetSpecialization() + 1
+	-- if no argument, then cycle
+	if not setupIndex then
+		if not _activeSetupId then
+			Amr:PickFirstSetupForSpec()
+		end
+		for i,setup in ipairs(Amr.db.char.GearSetups) do
+			if setup.Id == _activeSetupId then
+				setupIndex = i
+				break
+			end
+		end
+		if not setupIndex then
+			setupIndex = 1
+		else
+			setupIndex = setupIndex + 1
+		end
 	end
 
-	-- allow some flexibility in the arguments
-	if spec == "1" or spec == "2" or spec == "3" or spec == "4" then spec = tonumber(spec) end
+	setupIndex = tonumber(setupIndex)
 
-	local specId = GetSpecializationInfo(spec)
-	if not specId then spec = 1 end
-	
+	if setupIndex > #Amr.db.char.GearSetups then
+		setupIndex = 1
+	end
+
 	if UnitAffectingCombat("player") then
 		Amr:Print(L.GearEquipErrorCombat)
 		return
 	end
 	
+	_activeSetupId = Amr.db.char.GearSetups[setupIndex].Id
+	Amr:RefreshGearDisplay()
+
+	local setup = Amr.db.char.GearSetups[setupIndex]
 	local currentSpec = GetSpecialization()
-	if currentSpec ~= spec then
-		_waitingForSpec = spec
-		SetSpecialization(spec)
+	if currentSpec ~= setup.SpecSlot then
+		_waitingForSpec = setup.SpecSlot
+		SetSpecialization(setup.SpecSlot)
 	else
 		-- spec is what we want, now equip the gear
-		beginEquipGearSet(currentSpec, 0)
+		beginEquipGearSet(_activeSetupId, 0)
 	end
 end
 
@@ -1138,7 +1235,7 @@
 		-- don't update during a gear operation, wait until it is totally finished
 		if _pendingGearOps then return end
 
-		Amr:RefreshGearTab()
+		Amr:RefreshGearDisplay()
 	end)
 
 	Amr:AddEventHandler("ITEM_UNLOCKED", handleItemUnlocked)
--- a/Import.lua	Mon Sep 03 19:20:00 2018 -0700
+++ b/Import.lua	Mon Nov 05 16:06:00 2018 -0800
@@ -14,7 +14,7 @@
 		_txtImport:SetFocus(true)
 	else
 		Amr:HideCover()
-		Amr:RefreshGearTab()
+		Amr:RefreshGearDisplay()
 	end
 end
 
@@ -124,7 +124,7 @@
 --
 -- Import a character, returning nil on success, otherwise an error message, import result stored in the db.
 --
-function Amr:ImportCharacter(data, isTest)
+function Amr:ImportCharacter(data, isTest, isChild)
 
     -- make sure all data is up to date before importing and get a local copy of player's current state
     local currentPlayerData = self:ExportCharacter()
@@ -133,16 +133,36 @@
         return L.ImportErrorEmpty
     end
 	
-	-- if multiple specs are included in the data, parse each individually, then quit
+	-- if multiple setups are included in the data, parse each individually, then quit
 	local specParts = { strsplit("\n", data) }
-	if #specParts > 1 then
+    if #specParts > 1 then
+        -- clear out any previously-imported BiB setups when importing new ones (non-BiB will always be imported one at a time)
+        for i = #Amr.db.char.GearSetups, 1, -1 do
+            if Amr.db.char.GearSetups[i].IsBib then
+                table.remove(Amr.db.char.GearSetups, i)
+            end
+        end
+
 		for i = 1, #specParts do
-			local err = self:ImportCharacter(specParts[i], isTest)
+			local err = self:ImportCharacter(specParts[i], isTest, true)
 			if err ~= nil then
 				return err
 			end
-		end
-		return	
+        end
+        
+        -- ensure that all BiB setups are sorted to the top
+        local nonBib = {}
+        for i = #Amr.db.char.GearSetups, 1, -1 do
+            if not Amr.db.char.GearSetups[i].IsBib then
+                table.insert(nonBib, Amr.db.char.GearSetups[i])
+                table.remove(Amr.db.char.GearSetups, i)
+            end
+        end
+        for i, setup in ipairs(nonBib) do
+            table.insert(Amr.db.char.GearSetups, setup)
+        end
+
+        return
 	end
     
     local data1 = { strsplit("$", data) }
@@ -302,9 +322,13 @@
         end
     end
     
-    -- now read any extra display information
+    -- extra information contains setup id, display label, then extra enchant info        
     parts = { strsplit("@", data1[3]) }
-    for i = 1, #parts do
+
+    local setupId = parts[2]
+    local setupName = parts[3]
+
+    for i = 4, #parts do
         local infoParts = { strsplit("\\", parts[i]) }
         
         if infoParts[1] == "e" then
@@ -344,7 +368,37 @@
         end              
     else
         -- we have succeeded, record the result
-		Amr.db.char.GearSets[specSlot] = importData
+        local result = {
+            IsBib = string.sub(setupId, 1, 3) ~= "AMR",
+            SpecSlot = tonumber(specSlot),
+            Id = setupId,
+            Label = setupName,
+            Gear = importData
+        }
+
+        if not result.IsBib then
+            -- replace if this setup already exists
+            local key = -1
+            for i,setup in ipairs(Amr.db.char.GearSetups) do
+                if setup.Id == result.Id then
+                    key = i
+                    break
+                end
+            end
+
+            if key ~= -1 then
+                Amr.db.char.GearSetups[key] = result
+            else
+                table.insert(Amr.db.char.GearSetups, result)
+            end
+            
+            if not isChild then
+                -- if doing a single import of a setup, make it active
+                Amr:SetActiveSetupId(setupId)
+            end
+        else
+            table.insert(Amr.db.char.GearSetups, result)
+        end
 
         for k,v in pairs(enchantInfo) do
             Amr.db.char.ExtraEnchantData[k] = v    
--- a/Shopping.lua	Mon Sep 03 19:20:00 2018 -0700
+++ b/Shopping.lua	Mon Nov 05 16:06:00 2018 -0800
@@ -5,7 +5,7 @@
 local _frameShop
 local _panelContent
 local _cboPlayers
-local _selectedPlayer
+local _selectedSetup
 
 local _specs = {
 	[1] = true,
@@ -14,10 +14,6 @@
 	[4] = true,
 }
 
-local _chk1
-local _chk2
-local _chk3
-local _chk4
 local _isAhOpen = false
 
 local function incrementTableItem(tbl, key, inc)
@@ -28,10 +24,6 @@
 	AceGUI:Release(widget)
 	_frameShop = nil
 	_cboPlayers = nil
-	_chk1 = nil
-	_chk2 = nil
-	_chk3 = nil
-	_chk4 = nil
 	_panelContent = nil
 end
 
@@ -41,7 +33,7 @@
 end
 
 local function onPlayerChange(widget, eventName, value)
-	_selectedPlayer = value
+	_selectedSetup = value
 	Amr:RefreshShoppingUi()
 end
 
@@ -95,36 +87,11 @@
 		_frameShop:AddChild(_cboPlayers)
 		_cboPlayers:SetPoint("TOPLEFT", _frameShop.content, "TOPLEFT", 0, -30)
 		
-		-- spec pickers
-		_chk1 = AceGUI:Create("AmrUiCheckBox")
-		_chk1:SetUserData("spec", 1)
-		_chk1:SetCallback("OnClick", onSpecClick)
-		_frameShop:AddChild(_chk1)
-		_chk1:SetPoint("TOPLEFT", _cboPlayers.frame, "BOTTOMLEFT", 0, -20)
-		
-		_chk2 = AceGUI:Create("AmrUiCheckBox")
-		_chk2:SetUserData("spec", 2)
-		_chk2:SetCallback("OnClick", onSpecClick)
-		_frameShop:AddChild(_chk2)
-		_chk2:SetPoint("LEFT", _chk1.frame, "RIGHT", 30, 0)
-		
-		_chk3 = AceGUI:Create("AmrUiCheckBox")
-		_chk3:SetUserData("spec", 3)
-		_chk3:SetCallback("OnClick", onSpecClick)
-		_frameShop:AddChild(_chk3)
-		_chk3:SetPoint("LEFT", _chk2.frame, "RIGHT", 30, 0)
-		
-		_chk4 = AceGUI:Create("AmrUiCheckBox")
-		_chk4:SetUserData("spec", 4)
-		_chk4:SetCallback("OnClick", onSpecClick)
-		_frameShop:AddChild(_chk4)
-		_chk4:SetPoint("LEFT", _chk3.frame, "RIGHT", 30, 0)
-		
 		_panelContent = AceGUI:Create("AmrUiPanel")
 		_panelContent:SetLayout("None")
 		_panelContent:SetTransparent()
 		_frameShop:AddChild(_panelContent)
-		_panelContent:SetPoint("TOPLEFT", _chk1.frame, "BOTTOMLEFT", 0, -10)
+		_panelContent:SetPoint("TOPLEFT", _cboPlayers.frame, "BOTTOMLEFT", 0, -10)
 		_panelContent:SetPoint("BOTTOMRIGHT", _frameShop.content, "BOTTOMRIGHT")
 		
 		-- update shopping list data
@@ -133,21 +100,55 @@
 		
 		-- fill player list	
 		local playerList = {}
-		for name, data in pairs(Amr.db.global.Shopping) do
-			table.insert(playerList, { text = name, value = name })
+		local firstData = nil	
+		for name, v in pairs(Amr.db.global.Shopping2) do
+			for setupName, data in pairs(v.setups) do
+				table.insert(playerList, { text = name .. " " .. setupName, value = name .. "@" .. setupName })
+				if not firstData then
+					firstData = name .. "@" .. setupName
+				end
+			end
 		end	
 		_cboPlayers:SetItems(playerList)
 		
 		-- set default selected player
-		if not _selectedPlayer then
-			_selectedPlayer = player.Name .. "-" .. player.Realm
-		end	
-		_cboPlayers:SelectItem(_selectedPlayer)
+		local playerData = Amr.db.global.Shopping2[player.Name .. "-" .. player.Realm]
+		if playerData and playerData.setups then
+			_selectedSetup = Amr:GetActiveSetupLabel()
+			if not _selectedSetup then
+				Amr:PickFirstSetupForSpec()
+				_selectedSetup = Amr:GetActiveSetupLabel()
+			end
+			if _selectedSetup and not playerData.setups[_selectedSetup] then					
+				_selectedSetup = nil
+			else
+				_selectedSetup = player.Name .. "-" .. player.Realm .. "@" .. _selectedSetup
+			end
+		end
+		
+		if not _selectedSetup then
+			if playerData and playerData.setups then
+				for k,v in pairs(playerData.setups) do
+					_selectedSetup = player.Name .. "-" .. player.Realm .. "@" .. k
+					break
+				end
+			else
+				_selectedSetup = firstData
+			end
+		end
+		_cboPlayers:SelectItem(_selectedSetup)
 		
 		Amr:RefreshShoppingUi()
 		
 		-- set event on dropdown after UI has been initially rendered
 		_cboPlayers:SetCallback("OnChange", onPlayerChange)
+
+		-- set a timer to refresh a bit after opening b/c sometimes some item info isn't available
+		Amr.Wait(2, function()
+			if _frameShop then
+				Amr:RefreshShoppingUi()
+			end
+		end)
 	else
 		_frameShop:Show()
 		Amr:RefreshShoppingUi()
@@ -270,20 +271,12 @@
 
 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]
+	local parts = { strsplit("@", _selectedSetup) }
+
+	local data = Amr.db.global.Shopping2[parts[1]].setups[parts[2]]
 	if not data then		
 		_panelContent:SetLayout("None")
 		
@@ -298,48 +291,32 @@
 		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
+		for inventoryId, stuff in pairs(data) do
+			hasStuff = true
+			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
 		
 		if hasStuff then		
@@ -419,24 +396,21 @@
 	return ret
 end
 
--- look at both gear sets and find stuff that a player needs to acquire to gem/enchant their gear
+-- look at all gear sets and find stuff that a player needs to acquire to gem/enchant their gear for each setup
 function Amr:UpdateShoppingData(player)
 
 	local required = {
-		stuff = {},
-		specs = player.Specs
+		setups = {}
 	}
-	
-	for i, spec in ipairs(required.specs) do
-		local gear = Amr.db.char.GearSets[i]
+
+	for i, setup in ipairs(Amr.db.char.GearSetups) do
+		local gear = setup.Gear
 		if gear then
-			required.stuff[i] = getShoppingData(player, gear)
-		else
-			required.stuff[i] = {}
+			required.setups[setup.Label] = getShoppingData(player, gear)
 		end
 	end
-	
-	Amr.db.global.Shopping[player.Name .. "-" .. player.Realm] = required
+
+	Amr.db.global.Shopping2[player.Name .. "-" .. player.Realm] = required
 end
 
 Amr:AddEventHandler("AUCTION_HOUSE_SHOW", function()