changeset 81:0515882856f1 v38

updated for 7.0
author yellowfive
date Tue, 19 Jul 2016 10:05:32 -0700
parents 8f235b016212
children ae52b5b44273
files AskMrRobot-Serializer/AskMrRobot-Serializer.lua AskMrRobot.toc Constants.lua Core.lua Export.lua Gear.lua Import.lua Shopping.lua localization/enUS.lua localization/frFR.lua localization/itIT.lua ui/AmrUiButton.lua ui/AmrUiDropDown.lua ui/AmrUiFrame.lua ui/AmrUiIcon.lua ui/AmrUiPanel.lua ui/AmrUiScrollFrame.lua ui/AmrUiTabGroup.lua ui/AmrUiTextButton.lua ui/AmrUiTextarea.lua ui/Ui.lua
diffstat 21 files changed, 744 insertions(+), 406 deletions(-) [+]
line wrap: on
line diff
--- a/AskMrRobot-Serializer/AskMrRobot-Serializer.lua	Tue Apr 05 16:19:31 2016 -0700
+++ b/AskMrRobot-Serializer/AskMrRobot-Serializer.lua	Tue Jul 19 10:05:32 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", 32
+local MAJOR, MINOR = "AskMrRobot-Serializer", 38
 local Amr, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
 
 if not Amr then return end -- already loaded by something else
@@ -52,52 +52,55 @@
     [250] = 1, -- DeathKnightBlood
     [251] = 2, -- DeathKnightFrost
     [252] = 3, -- DeathKnightUnholy
-    [102] = 4, -- DruidBalance
-    [103] = 5, -- DruidFeral
-    [104] = 6, -- DruidGuardian
-    [105] = 7, -- DruidRestoration
-    [253] = 8, -- HunterBeastMastery
-    [254] = 9, -- HunterMarksmanship
-    [255] = 10, -- HunterSurvival
-    [62] = 11, -- MageArcane
-    [63] = 12, -- MageFire
-    [64] = 13, -- MageFrost
-    [268] = 14, -- MonkBrewmaster
-    [270] = 15, -- MonkMistweaver
-    [269] = 16, -- MonkWindwalker
-    [65] = 17, -- PaladinHoly
-    [66] = 18, -- PaladinProtection
-    [70] = 19, -- PaladinRetribution
-    [256] = 20, -- PriestDiscipline
-    [257] = 21, -- PriestHoly
-    [258] = 22, -- PriestShadow
-    [259] = 23, -- RogueAssassination
-    [260] = 24, -- RogueCombat
-    [261] = 25, -- RogueSubtlety
-    [262] = 26, -- ShamanElemental
-    [263] = 27, -- ShamanEnhancement
-    [264] = 28, -- ShamanRestoration
-    [265] = 29, -- WarlockAffliction
-    [266] = 30, -- WarlockDemonology
-    [267] = 31, -- WarlockDestruction
-    [71] = 32, -- WarriorArms
-    [72] = 33, -- WarriorFury
-    [73] = 34 -- WarriorProtection
+	[577] = 4, -- DemonHunterHavoc
+	[581] = 5, -- DemonHunterVengeance
+    [102] = 6, -- DruidBalance
+    [103] = 7, -- DruidFeral
+    [104] = 8, -- DruidGuardian
+    [105] = 9, -- DruidRestoration
+    [253] = 10, -- HunterBeastMastery
+    [254] = 11, -- HunterMarksmanship
+    [255] = 12, -- HunterSurvival
+    [62] = 13, -- MageArcane
+    [63] = 14, -- MageFire
+    [64] = 15, -- MageFrost
+    [268] = 16, -- MonkBrewmaster
+    [270] = 17, -- MonkMistweaver
+    [269] = 18, -- MonkWindwalker
+    [65] = 19, -- PaladinHoly
+    [66] = 20, -- PaladinProtection
+    [70] = 21, -- PaladinRetribution
+    [256] = 22, -- PriestDiscipline
+    [257] = 23, -- PriestHoly
+    [258] = 24, -- PriestShadow
+    [259] = 25, -- RogueAssassination
+    [260] = 26, -- RogueOutlaw
+    [261] = 27, -- RogueSubtlety
+    [262] = 28, -- ShamanElemental
+    [263] = 29, -- ShamanEnhancement
+    [264] = 30, -- ShamanRestoration
+    [265] = 31, -- WarlockAffliction
+    [266] = 32, -- WarlockDemonology
+    [267] = 33, -- WarlockDestruction
+    [71] = 34, -- WarriorArms
+    [72] = 35, -- WarriorFury
+    [73] = 36 -- WarriorProtection
 }
 
 Amr.ClassIds = {
     ["NONE"] = 0,
     ["DEATHKNIGHT"] = 1,
-    ["DRUID"] = 2,
-    ["HUNTER"] = 3,
-    ["MAGE"] = 4,
-    ["MONK"] = 5,
-    ["PALADIN"] = 6,
-    ["PRIEST"] = 7,
-    ["ROGUE"] = 8,
-    ["SHAMAN"] = 9,
-    ["WARLOCK"] = 10,
-    ["WARRIOR"] = 11,
+	["DEMONHUNTER"] = 2,
+    ["DRUID"] = 3,
+    ["HUNTER"] = 4,
+    ["MAGE"] = 5,
+    ["MONK"] = 6,
+    ["PALADIN"] = 7,
+    ["PRIEST"] = 8,
+    ["ROGUE"] = 9,
+    ["SHAMAN"] = 10,
+    ["WARLOCK"] = 11,
+    ["WARRIOR"] = 12,
 }
 
 Amr.ProfessionIds = {
@@ -172,12 +175,6 @@
 	[1448] = true
 }
 
-Amr.SPEC_WARRIORPROTECTION = 34
-Amr.SUBSPEC_WARRIORPROTECTION = 38
-Amr.SUBSPEC_WARRIORPROTECTIONGLAD = 39
-Amr.SPELL_ID_GLADIATOR_STANCE = 156291
-Amr.SPELL_ID_DEFENSIVE_STANCE = 71
-
 -- IDs of set tokens that we would care about in a player's inventory
 Amr.SetTokenIds = {
 	[127970] = true,
@@ -579,6 +576,30 @@
 -- Public Utility Methods
 ----------------------------------------------------------------------------------------
 
+local function readBonusIdList(parts, first, last)
+	local ret = {}
+	for i = first, last do
+		table.insert(ret, tonumber(parts[i]))
+	end
+	table.sort(ret)
+	return ret
+end
+
+local function setRelicId(item, index, relicBonuses)
+	local relicId = item.gemIds[index] .. ""
+	for i = 1, #relicBonuses do
+		relicId = relicId .. "." .. relicBonuses[i]
+	end
+	local list = item.gemItemIds or {}
+	list[i] = relicId
+end
+
+--|color|Hitem:135820:enchant:gem1:gem2:gem3:gem4:suffixID:uniqueID:playerlevel:spec?:flags:11:numBonusIDs:bonusID1:bonusID2...:playerlevelwhengotitem, 296 for warrior artifact:upgrade ID?:num artifact bonuses?:artifact bonus 1:artifact bonus 2:artifact bonus 3:[item name]
+-- 133004 for relic on my warrior, gem2
+-- 296::3:767:1507:1809:[item name] this is for warrior artifact with the above relic in storm slot, for parts after the bonus IDs
+
+--|cffa335ee|Hitem:itemID:enchant:gem1:gem2:gem3:gem4:suffixID:uniqueID:level:unknown:unknown:instanceDifficultyID:numBonusIDs:bonusID1:bonusID2...|h[item name]|h|r
+
 -- item link format:  |cffa335ee|Hitem:itemID:enchant:gem1:gem2:gem3:gem4:suffixID:uniqueID:level:unknown:unknown:instanceDifficultyID:numBonusIDs:bonusID1:bonusID2...|h[item name]|h|r
 -- get an object with all of the parts of the item link format that we care about
 function Amr.ParseItemLink(itemLink)
@@ -590,36 +611,81 @@
     local parts = { strsplit(":", str) }
     
     local item = {}
-    item.id = tonumber(parts[1])
-    item.enchantId = tonumber(parts[2])
-    item.gemIds = { tonumber(parts[3]), tonumber(parts[4]), tonumber(parts[5]), tonumber(parts[6]) }
-    item.suffixId = math.abs(tonumber(parts[7])) -- convert suffix to positive number, that's what we use in our code
-    --item.uniqueId = tonumber(parts[8])
-    --item.level = tonumber(parts[9])
-	-- part 10 is unknown atm
-	-- part 11 is unknown atm
-    --item.difficultyId = tonumber(parts[12])
+    item.id = tonumber(parts[1]) or 0
+    item.enchantId = tonumber(parts[2]) or 0
+    item.gemIds = { tonumber(parts[3]) or 0, tonumber(parts[4]) or 0, tonumber(parts[5]) or 0, tonumber(parts[6]) or 0 }
+    item.suffixId = math.abs(tonumber(parts[7]) or 0) -- convert suffix to positive number, that's what we use in our code
+    -- part 8 is some unique ID... we never really used it
+    -- part 9 is current player level
+	-- part 10 is player spec
+	local upgradeIdType = tonumber(parts[11]) or 0 -- part 11 indicates what kind of upgrade ID is just after the bonus IDs
+    -- part 12 is instance difficulty id
     
-    local numBonuses = tonumber(parts[13])
-    if numBonuses and numBonuses > 0 then
-        item.bonusIds = {}
-        for i = 14, 13 + numBonuses do
-            table.insert(item.bonusIds, tonumber(parts[i]))
-        end
-		table.sort(item.bonusIds)
+    local numBonuses = tonumber(parts[13]) or 0
+	local offset = numBonuses
+    if numBonuses > 0 then
+        item.bonusIds = readBonusIdList(parts, 14, 13 + numBonuses)
     end
 	
-	-- if there is another part after bonus ids, that is the upgrade id	
-	if numBonuses and #parts >= 14 + numBonuses then
-		local upgradeId = tonumber(parts[14 + numBonuses])
-		item.upgradeId = upgradeId and upgradeId or 0
-	else
-		item.upgradeId = 0
+	item.upgradeId = 0
+	item.level = 0
+	
+	-- the next part after bonus IDs depends on the upgrade id type; is either the "drop level" or upgrade ID, or not sure for artifacts
+	if upgradeIdType == 4 then
+		item.upgradeId = tonumber(parts[14 + offset]) or 0
+	elseif upgradeIdType == 512 then
+		item.level = tonumber(parts[14 + offset]) or 0
 	end
-    
+	
+	-- ignore relic stuff in the item link for now, we read the relic information directly and save it with artifact power info
+	--[[
+	-- the next part is the number of bonus IDs on the first relic slot of the artifact
+	numBonuses = tonumber(parts[15 + offset]) or 0
+	if numBonuses > 0 then
+		local relicBonuses = readBonusIdList(16 + offset, 15 + offset + numBonuses, parts)
+		setRelicId(item, 1, relicBonuses)
+	end
+	
+	-- second relic slot bonus IDs
+	offset = offset + numBonuses
+	numBonuses = tonumber(parts[16 + offset]) or 0
+	if numBonuses > 0 then
+		local relicBonuses = readBonusIdList(17 + offset, 16 + offset + numBonuses, parts)
+		setRelicId(item, 2, relicBonuses)
+	end
+	
+	-- third relic slot bonus IDs
+	offset = offset + numBonuses
+	numBonuses = tonumber(parts[17 + offset]) or 0
+	if numBonuses > 0 then
+		local relicBonuses = readBonusIdList(18 + offset, 17 + offset + numBonuses, parts)
+		setRelicId(item, 3, relicBonuses)
+	end
+    ]]
+	
     return item
 end
 
+function Amr.GetItemUniqueId(item, noUpgrade)
+    if not item then return "" end
+    local ret = item.id .. ""
+    if item.bonusIds then
+        for i = 1, #item.bonusIds do
+            ret = ret .. "b" .. item.bonusIds[i]
+        end
+    end
+    if item.suffixId ~= 0 then
+        ret = ret .. "s" .. item.suffixId
+    end
+    if not noUpgrade and item.upgradeId ~= 0 then
+        ret = ret .. "u" .. item.upgradeId
+    end
+	if item.level ~= 0 then
+		ret = ret .. "v" .. item.level
+	end
+    return ret
+end
+
 -- returns true if this is an instance that AskMrRobot supports for logging
 function Amr.IsSupportedInstanceId(instanceMapID)
 	if Amr.SupportedInstanceIds[tonumber(instanceMapID)] then
@@ -635,6 +701,76 @@
 	return Amr.IsSupportedInstanceId(instanceMapID)
 end
 
+-- helper to iterate over a table in order by its keys
+local function spairs(t, order)
+    -- collect the keys
+    local keys = {}
+    for k in pairs(t) do keys[#keys+1] = k end
+
+    -- if order function given, sort by it by passing the table and keys a, b,
+    -- otherwise just sort the keys 
+    if order then
+        table.sort(keys, function(a,b) return order(t, a, b) end)
+    else
+        table.sort(keys)
+    end
+
+    -- return the iterator function
+    local i = 0
+    return function()
+        i = i + 1
+        if keys[i] then
+            return keys[i], t[keys[i]]
+        end
+    end
+end
+
+-- scanning tooltip b/c for some odd reason the api has no way to get basic item properties...
+-- so you have to generate a fake item tooltip and search for pre-defined strings in the display text
+local _scanTt
+function Amr.GetScanningTooltip()
+	if not _scanTt then
+		_scanTt = CreateFrame("GameTooltip", "AmrUiScanTooltip", nil, "GameTooltipTemplate")
+		_scanTt:SetOwner(UIParent, "ANCHOR_NONE")
+	end
+	return _scanTt
+end
+
+-- get the item tooltip for the specified item in one of your bags, or if bagId is nil, an equipped item, or if slotId is also nil, the specified item link
+function Amr.GetItemTooltip(bagId, slotId, link)
+	local tt = Amr.GetScanningTooltip()
+	tt:ClearLines()
+	if bagId then
+		tt:SetBagItem(bagId, slotId)
+	elseif slotId then
+		tt:SetInventoryItem("player", slotId)
+	else
+		tt:SetHyperlink(link)
+	end
+	return tt
+end
+
+function Amr.GetItemLevel(bagId, slotId, link)
+	local itemLevelPattern = _G["ITEM_LEVEL"]:gsub("%%d", "(%%d+)")
+	local tt = Amr.GetItemTooltip(bagId, slotId, link)
+	
+	local regions = { tt:GetRegions() }
+	for i, region in ipairs(regions) do
+		if region and region:GetObjectType() == "FontString" then
+			local text = region:GetText()
+			if text then
+				ilvl = tonumber(text:match(itemLevelPattern))
+				if ilvl then
+					return ilvl
+				end
+			end
+        end	
+	end
+	
+	-- 0 means we couldn't find it for whatever reason
+	return 0
+end
+
 
 ----------------------------------------------------------------------------------------
 -- Character Reading
@@ -649,17 +785,13 @@
 	end
 end
 
-local function getSpecId(specGroup)
-	local spec = GetSpecialization(false, false, specGroup);
-	return spec and GetSpecializationInfo(spec);
-end
-
-local function getTalents(specGroup)	
+--[[
+local function getTalents(specPos)	
     local talentInfo = {}
     local maxTiers = 7
     for tier = 1, maxTiers do
         for col = 1, 3 do
-            local id, name, texture, selected, available = GetTalentInfo(tier, col, specGroup)
+            local id, name, _, _, _, spellId, _, t, c, selected = GetTalentInfoBySpecialization(specPos, tier, col)
             if selected then
                 talentInfo[tier] = col
             end
@@ -677,7 +809,9 @@
 
 	return str
 end
+]]
 
+--[[
 local function getGlyphs(specGroup)
 	local glyphs = {}
 	for i = 1, NUM_GLYPH_SLOTS do
@@ -688,55 +822,28 @@
 	end
 	return glyphs;
 end
+]]
 
--- get specs, talents, and glyphs
-local function readSpecs(ret, subspecs)
+-- get specs and talents
+local function readSpecs(ret)
 
-    for group = 1, GetNumSpecGroups() do
+    for pos = 1, 4 do
         -- spec, convert game spec id to one of our spec ids
-        local specId = getSpecId(group)
+        local specId = GetSpecializationInfo(pos)
         if specId then
-            ret.Specs[group] = Amr.SpecIds[specId]
-			
-			-- if this is a protection warrior, use buffs to determine subspec
-			if ret.Specs[group] == Amr.SPEC_WARRIORPROTECTION then
-				local subspec = 0
-				
-				if ret.ActiveSpec ~= group then
-					-- this spec isn't active, so we can't use current buffs to determine spec, see if any old data is compatible
-					if subspecs and (subspecs[group] == Amr.SUBSPEC_WARRIORPROTECTION or subspecs[group] == Amr.SUBSPEC_WARRIORPROTECTIONGLAD) then
-						subspec = subspecs[group]
-					end
-				else
-					for i=1,40 do
-						local name,_,_,_,_,_,_,_,_,_,spellId = UnitAura("player", i, "HELPFUL")
-						if not name then break end
-						
-						if spellId == Amr.SPELL_ID_DEFENSIVE_STANCE then
-							subspec = Amr.SUBSPEC_WARRIORPROTECTION
-							break
-						elseif spellId == Amr.SPELL_ID_GLADIATOR_STANCE then
-							subspec = Amr.SUBSPEC_WARRIORPROTECTIONGLAD
-							break
-						end
-					end
-				end
-				
-				if subspec == 0 then
-					ret.SubSpecs[group] = nil
-				else
-					ret.SubSpecs[group] = subspec
-				end
-			end
-        else
-            ret.Specs[group] = 0
+            ret.Specs[pos] = Amr.SpecIds[specId]
+			-- TODO: figure out how to read inactive spec talents if possible... used to be able to but they changed it
+			--ret.Talents[pos] = getTalents(pos)
         end
-        
-        ret.Talents[group] = getTalents(group)
-        ret.Glyphs[group] = getGlyphs(group)
 	end
 end
 
+-- TODO: hopefully we can read artifact here when there is an API to get info when the artifact UI is not open
+-- get artifact info
+local function readArtifact()
+
+end
+
 -- get currently equipped items, store with currently active spec
 local function readEquippedItems(ret)
     local equippedItems = {};
@@ -749,7 +856,7 @@
 	end
     
     -- store last-seen equipped gear for each spec
-	ret.Equipped[GetActiveSpecGroup()] = equippedItems
+	ret.Equipped[GetSpecialization()] = equippedItems
 end
 
 -- Get all data about the player as an object, includes:
@@ -760,10 +867,11 @@
 -- faction
 -- level
 -- professions
--- spec/talent/glyphs for both specs
+-- spec/talent for all specs
+-- artifact for current spec
 -- equipped gear for the current spec
 --
-function Amr:GetPlayerData(subspecs)
+function Amr:GetPlayerData()
 
 	local ret = {}
 	
@@ -771,7 +879,7 @@
     ret.Realm = GetRealmName()
     ret.Name = UnitName("player")
 	ret.Guild = GetGuildInfo("player")
-    ret.ActiveSpec = GetActiveSpecGroup()
+    ret.ActiveSpec = GetSpecialization()
     ret.Level = UnitLevel("player");
     
     local cls, clsEn = UnitClass("player")
@@ -791,10 +899,11 @@
 	readProfessionInfo(firstAid, ret)
 	
 	ret.Specs = {}
-	ret.SubSpecs = {} -- only filled in for ambiguous cases, right now just prot/glad warrior
     ret.Talents = {}
-    ret.Glyphs = {}
-	readSpecs(ret, subspecs)
+	readSpecs(ret)
+	
+	ret.Artifacts = {}
+	readArtifact()
 	
 	ret.Equipped = {}
 	readEquippedItems(ret)
@@ -843,9 +952,16 @@
     local prevEnchantId = 0
     local prevUpgradeId = 0
     local prevBonusId = 0
+	local prevLevel = 0
     for i, itemData in ipairs(itemObjects) do
         local itemParts = {}
         
+		-- for now export the item level of artifacts as the "drop level" because it is a pain in the ass to figure it out from the bonus IDs
+		--local _, _, quality = GetItemInfo(itemData.link)
+		--if quality == 6 then
+		--	itemData.level = Amr.GetItemLevel(nil, nil, itemData.link)
+		--end
+		
         table.insert(itemParts, itemData.id - prevItemId)
         prevItemId = itemData.id
         
@@ -855,24 +971,30 @@
             table.insert(itemParts, "u" .. (itemData.upgradeId - prevUpgradeId))
             prevUpgradeId = itemData.upgradeId
         end
+		if itemData.level ~= 0 then
+			table.insert(itemParts, "v" .. (itemData.level - prevLevel))
+			prevLevel = itemData.level
+		end
         if itemData.bonusIds then
             for bIndex, bValue in ipairs(itemData.bonusIds) do
                 table.insert(itemParts, "b" .. (bValue - prevBonusId))
                 prevBonusId = bValue
             end
-        end        
-        if itemData.gemIds[1] ~= 0 then 
-            table.insert(itemParts, "x" .. (itemData.gemIds[1] - prevGemId))
-            prevGemId = itemData.gemIds[1]
         end
-        if itemData.gemIds[2] ~= 0 then 
-            table.insert(itemParts, "y" .. (itemData.gemIds[2] - prevGemId))
-            prevGemId = itemData.gemIds[2]
-        end
-        if itemData.gemIds[3] ~= 0 then 
-            table.insert(itemParts, "z" .. (itemData.gemIds[3] - prevGemId))
-            prevGemId = itemData.gemIds[3]
-        end
+		
+		if itemData.gemIds[1] ~= 0 then 
+			table.insert(itemParts, "x" .. (itemData.gemIds[1] - prevGemId))
+			prevGemId = itemData.gemIds[1]
+		end
+		if itemData.gemIds[2] ~= 0 then 
+			table.insert(itemParts, "y" .. (itemData.gemIds[2] - prevGemId))
+			prevGemId = itemData.gemIds[2]
+		end
+		if itemData.gemIds[3] ~= 0 then 
+			table.insert(itemParts, "z" .. (itemData.gemIds[3] - prevGemId))
+			prevGemId = itemData.gemIds[3]
+		end
+        
         if itemData.enchantId ~= 0 then 
             table.insert(itemParts, "e" .. (itemData.enchantId - prevEnchantId))
             prevEnchantId = itemData.enchantId
@@ -948,25 +1070,41 @@
     
     -- export specs
     table.insert(fields, data.ActiveSpec)
-    for spec = 1, 2 do
+    for spec = 1, 4 do
         if data.Specs[spec] and (complete or spec == data.ActiveSpec) then
             table.insert(fields, ".s" .. spec) -- indicates the start of a spec block
+			table.insert(fields, data.Specs[spec])
+            table.insert(fields, data.Talents[spec])
 			
-			-- we use subspec for some ambiguous specs like prot/glad warrior
-			if data.SubSpecs[spec] then
-				table.insert(fields, string.format("s%s", data.SubSpecs[spec]))
-			else
-				table.insert(fields, data.Specs[spec])
+			local powerids = {}
+			local powerranks = {}
+			local reliclinks = {}
+			
+			local artifactInfo = data.Artifacts and data.Artifacts[spec]
+			if artifactInfo and artifactInfo.Powers then
+				for k, v in spairs(artifactInfo.Powers) do
+					table.insert(powerids, k)
+					table.insert(powerranks, v)
+				end	
+			end
+			if artifactInfo and artifactInfo.Relics then
+				for i, link in ipairs(artifactInfo.Relics) do
+					local relic = Amr.ParseItemLink(link)
+					table.insert(reliclinks, Amr.GetItemUniqueId(relic) or "")
+				end
 			end
 			
-            table.insert(fields, data.Talents[spec])
-            table.insert(fields, toCompressedNumberList(data.Glyphs[spec]))
+			table.insert(fields, toCompressedNumberList(powerids))
+			table.insert(fields, table.concat(powerranks, ","))
+			table.insert(fields, table.concat(reliclinks, ","))
+			
+            --table.insert(fields, toCompressedNumberList(data.Glyphs[spec]))
         end
     end
     
     -- export equipped gear
     if data.Equipped then
-        for spec = 1, 2 do
+        for spec = 1, 4 do
             if data.Equipped[spec] and (complete or spec == data.ActiveSpec) then
                 table.insert(fields, ".q" .. spec) -- indicates the start of an equipped gear block
                 
@@ -974,6 +1112,7 @@
                 for k, v in pairs(data.Equipped[spec]) do
                     local itemData = Amr.ParseItemLink(v)
                     itemData.slot = k
+					itemData.link = v
                     table.insert(itemObjects, itemData)
                 end
                 
@@ -1007,6 +1146,7 @@
 	        for i, v in ipairs(data.BagItems) do
 				local itemData = Amr.ParseItemLink(v)
 				if itemData ~= nil and (IsEquippableItem(v) or Amr.SetTokenIds[itemData.id]) then
+					itemData.link = v
 					table.insert(itemObjects, itemData)
 				end
 	        end
@@ -1015,6 +1155,7 @@
 	        for i, v in ipairs(data.BankItems) do
 	        	local itemData = Amr.ParseItemLink(v)
 				if itemData ~= nil and (IsEquippableItem(v) or Amr.SetTokenIds[itemData.id]) then
+					itemData.link = v
 					table.insert(itemObjects, itemData)
 				end
 	        end
@@ -1023,6 +1164,7 @@
 	        for i, v in ipairs(data.VoidItems) do
 	        	local itemData = Amr.ParseItemLink(v)
 				if itemData ~= nil and (IsEquippableItem(v) or Amr.SetTokenIds[itemData.id]) then
+					itemData.link = v
 					table.insert(itemObjects, itemData)
 				end
 		    end
@@ -1042,10 +1184,10 @@
 	return self:SerializePlayerData(data)
 end
 
-
+--[[
 ----------------------------------------------------------------------------------------------------------------------
 -- Character Snapshots
--- This feature snapshots a player's gear/talents/glyphs when entering combat.  It is enabled by default.  Consumers
+-- This feature snapshots a player's gear/talents/artifact when entering combat.  It is enabled by default.  Consumers
 -- of this library can create a setting to enable/disable it as desired per a user setting.
 --
 -- You should register for the AMR_SNAPSHOT_STATE_CHANGED message (sent via AceEvent-3.0 messaging) to ensure that
@@ -1088,4 +1230,5 @@
 end
 
 Amr:RegisterEvent("PLAYER_REGEN_DISABLED")
---Amr:RegisterEvent("GARRISON_MISSION_NPC_OPENED") -- for debugging, fire this event when open mission table
\ No newline at end of file
+--Amr:RegisterEvent("GARRISON_MISSION_NPC_OPENED") -- for debugging, fire this event when open mission table
+]]
\ No newline at end of file
--- a/AskMrRobot.toc	Tue Apr 05 16:19:31 2016 -0700
+++ b/AskMrRobot.toc	Tue Jul 19 10:05:32 2016 -0700
@@ -1,10 +1,10 @@
-## Interface: 60200
+## Interface: 70000
 ## Title: Ask Mr. Robot
 ## Author: Team Robot, Inc.
-## Version: 32
+## Version: 38
 ## Notes: Gear import/export, combat logging, and more.
 ## URL: www.askmrrobot.com
-## SavedVariables: AskMrRobotDb2
+## SavedVariables: AskMrRobotDb3
 
 Libs\LibStub\LibStub.lua
 Libs\CallbackHandler-1.0\CallbackHandler-1.0.xml
@@ -20,8 +20,8 @@
 Libs\AceGUI-3.0\AceGUI-3.0.xml
 
 localization\enUS.lua
-localization\frFR.lua
-localization\itIT.lua
+##localization\frFR.lua
+##localization\itIT.lua
 
 AskMrRobot-Serializer\AskMrRobot-Serializer.xml
 
--- a/Constants.lua	Tue Apr 05 16:19:31 2016 -0700
+++ b/Constants.lua	Tue Jul 19 10:05:32 2016 -0700
@@ -2,11 +2,11 @@
 local L = LibStub("AceLocale-3.0"):GetLocale("AskMrRobot", true)
 
 -- min import version that we will read from the website
-Amr.MIN_IMPORT_VERSION = 21
+Amr.MIN_IMPORT_VERSION = 36
 
 -- min addon version that we will support for inter-addon communication for e.g. the team optimizer
---  last update to version 24 when item link format changed
-Amr.MIN_ADDON_VERSION = 24
+--  last update to version 36 for Legion pre-patch
+Amr.MIN_ADDON_VERSION = 36
 
 -- import some constants from the serializer for convenience
 Amr.ChatPrefix = Amr.Serializer.ChatPrefix
@@ -23,6 +23,9 @@
 Amr.IsSupportedInstanceId = Amr.Serializer.IsSupportedInstanceId
 Amr.IsSupportedInstance = Amr.Serializer.IsSupportedInstance
 Amr.SetTokenIds = Amr.Serializer.SetTokenIds
+Amr.GetItemTooltip = Amr.Serializer.GetItemTooltip
+Amr.GetItemLevel = Amr.Serializer.GetItemLevel
+Amr.GetItemUniqueId = Amr.Serializer.GetItemUniqueId
 
 -- map of slot ID to display text
 Amr.SlotDisplayText = {
@@ -67,39 +70,39 @@
     [1] = "spell_deathknight_bloodpresence", -- DeathKnightBlood
     [2] = "spell_deathknight_frostpresence", -- DeathKnightFrost
     [3] = "spell_deathknight_unholypresence", -- DeathKnightUnholy
-    [4] = "spell_nature_starfall", -- DruidBalance
-    [5] = "ability_druid_catform", -- DruidFeral
-    [6] = "ability_racial_bearform", -- DruidGuardian
-    [7] = "spell_nature_healingtouch", -- DruidRestoration
-    [8] = "ability_hunter_bestialdiscipline", -- HunterBeastMastery
-    [9] = "ability_hunter_focusedaim", -- HunterMarksmanship
-    [10] = "ability_hunter_camouflage", -- HunterSurvival
-    [11] = "spell_holy_magicalsentry", -- MageArcane
-    [12] = "spell_fire_firebolt02", -- MageFire
-    [13] = "spell_frost_frostbolt02", -- MageFrost
-    [14] = "spell_monk_brewmaster_spec", -- MonkBrewmaster
-    [15] = "spell_monk_mistweaver_spec", -- MonkMistweaver
-    [16] = "spell_monk_windwalker_spec", -- MonkWindwalker
-    [17] = "spell_holy_holybolt", -- PaladinHoly
-    [18] = "ability_paladin_shieldofthetemplar", -- PaladinProtection
-    [19] = "spell_holy_auraoflight", -- PaladinRetribution
-    [20] = "spell_holy_powerwordshield", -- PriestDiscipline
-    [21] = "spell_holy_guardianspirit", -- PriestHoly
-    [22] = "spell_shadow_shadowwordpain", -- PriestShadow
-    [23] = "ability_rogue_eviscerate", -- RogueAssassination
-    [24] = "ability_backstab", -- RogueCombat
-    [25] = "ability_stealth", -- RogueSubtlety
-    [26] = "spell_nature_lightning", -- ShamanElemental
-    [27] = "spell_nature_lightningshield", -- ShamanEnhancement
-    [28] = "spell_nature_magicimmunity", -- ShamanRestoration
-    [29] = "spell_shadow_deathcoil", -- WarlockAffliction
-    [30] = "spell_shadow_metamorphosis", -- WarlockDemonology
-    [31] = "spell_shadow_rainoffire", -- WarlockDestruction
-    [32] = "ability_warrior_savageblow", -- WarriorArms
-    [33] = "ability_warrior_innerrage", -- WarriorFury
-    [34] = "ability_warrior_defensivestance", -- WarriorProtection
-	[38] = "ability_warrior_defensivestance", -- WarriorProtection, used for special subspec handling
-	[39] = "spell_warrior_gladiatorstance" -- WarriorProtectionGlad, used for special subspec handling
+	[4] = "ability_demonhunter_specdps", -- DemonHunterHavoc
+	[5] = "ability_demonhunter_spectank", -- DemonHunterVengeance
+    [6] = "spell_nature_starfall", -- DruidBalance
+    [7] = "ability_druid_catform", -- DruidFeral
+    [8] = "ability_racial_bearform", -- DruidGuardian
+    [9] = "spell_nature_healingtouch", -- DruidRestoration
+    [10] = "ability_hunter_bestialdiscipline", -- HunterBeastMastery
+    [11] = "ability_hunter_focusedaim", -- HunterMarksmanship
+    [12] = "ability_hunter_camouflage", -- HunterSurvival
+    [13] = "spell_holy_magicalsentry", -- MageArcane
+    [14] = "spell_fire_firebolt02", -- MageFire
+    [15] = "spell_frost_frostbolt02", -- MageFrost
+    [16] = "spell_monk_brewmaster_spec", -- MonkBrewmaster
+    [17] = "spell_monk_mistweaver_spec", -- MonkMistweaver
+    [18] = "spell_monk_windwalker_spec", -- MonkWindwalker
+    [19] = "spell_holy_holybolt", -- PaladinHoly
+    [20] = "ability_paladin_shieldofthetemplar", -- PaladinProtection
+    [21] = "spell_holy_auraoflight", -- PaladinRetribution
+    [22] = "spell_holy_powerwordshield", -- PriestDiscipline
+    [23] = "spell_holy_guardianspirit", -- PriestHoly
+    [24] = "spell_shadow_shadowwordpain", -- PriestShadow
+    [25] = "ability_rogue_eviscerate", -- RogueAssassination
+    [26] = "inv_sword_30", -- RogueOutlaw
+    [27] = "ability_stealth", -- RogueSubtlety
+    [28] = "spell_nature_lightning", -- ShamanElemental
+    [29] = "spell_nature_lightningshield", -- ShamanEnhancement
+    [30] = "spell_nature_magicimmunity", -- ShamanRestoration
+    [31] = "spell_shadow_deathcoil", -- WarlockAffliction
+    [32] = "spell_shadow_metamorphosis", -- WarlockDemonology
+    [33] = "spell_shadow_rainoffire", -- WarlockDestruction
+    [34] = "ability_warrior_savageblow", -- WarriorArms
+    [35] = "ability_warrior_innerrage", -- WarriorFury
+    [36] = "ability_warrior_defensivestance", -- WarriorProtection
 }
 
 -- instance IDs ordered in preferred display order
@@ -148,8 +151,19 @@
     
     table.insert(parts, 0) -- some unique id, doesn't seem to matter
     table.insert(parts, UnitLevel("player"))
-	table.insert(parts, 0) -- unknown
-    table.insert(parts, 0) -- unknown
+	
+	local specId = GetSpecializationInfo(GetSpecialization())
+	table.insert(parts,  specId)
+
+	-- this indicates what kind of modifier appears after the bonus IDs
+	if itemObj.upgradeId and itemObj.upgradeId ~= 0 then
+		table.insert(parts, 4)
+	elseif itemObj.level and itemObj.level ~= 0 then
+		table.insert(parts, 512)
+	else
+		table.insert(parts, 0)
+	end
+
     table.insert(parts, 0) -- difficulty id, doesn't matter
     
     if itemObj.bonusIds then
@@ -161,31 +175,23 @@
 		table.insert(parts, 0) -- no bonus ids
     end
 	
-	-- upgrade id is tacked onto the end now it seems
-	if (not itemObj.bonusIds or #itemObj.bonusIds == 0) and itemObj.upgradeId and itemObj.upgradeId ~= 0 then
+	-- upgrade id or level comes after bonuses
+	if itemObj.upgradeId and itemObj.upgradeId ~= 0 then
 		table.insert(parts, itemObj.upgradeId)
+	elseif itemObj.level and itemObj.level ~= 0 then
+		table.insert(parts, itemObj.level)
+	else
+		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)
     
     return table.concat(parts, ":")
 end
 
--- a unique ID useful for determining if a player has an item equipped or not
-function Amr.GetItemUniqueId(item, noUpgrade)
-    if item == nil then return "" end
-    local ret = item.id .. ""
-    if item.bonusIds then
-        for i = 1, #item.bonusIds do
-            ret = ret .. "b" .. item.bonusIds[i]
-        end
-    end
-    if item.suffixId ~= 0 then
-        ret = ret .. "s" .. item.suffixId
-    end
-    if not noUpgrade and item.upgradeId ~= 0 then
-        ret = ret .. "u" .. item.upgradeId
-    end
-    return ret
-end
 
 -- the server event for getting item info does not specify which item it just fetched... have to track manually
 local _pendingItemIds = {}
--- a/Core.lua	Tue Apr 05 16:19:31 2016 -0700
+++ b/Core.lua	Tue Jul 19 10:05:32 2016 -0700
@@ -51,8 +51,9 @@
 	local defaults = {
 		char = {
 			FirstUse = true,           -- true if this is first time use, gets cleared after seeing the export help splash window
-			SubSpecs = {},             -- last seen subspecs for this character, used to deal with some ambiguous specs
-			Equipped = {},             -- for each spec group (1 or 2), slot id to item link
+			Talents = {},              -- for each spec, selected talents
+			Artifacts = {},            -- for each spec, artifact info
+			Equipped = {},             -- for each spec, slot id to item link
 			BagItems = {},             -- list of item links for bag
 			BankItems = {},            -- list of item links for bank
 			VoidItems = {},            -- list of item links for void storage
@@ -125,7 +126,7 @@
 		end
 	end
 	
-	Amr.db = LibStub("AceDB-3.0"):New("AskMrRobotDb2", defaults)
+	Amr.db = LibStub("AceDB-3.0"):New("AskMrRobotDb3", defaults)
 	
 	Amr.db.RegisterCallback(Amr, "OnProfileChanged", "RefreshConfig")
 	Amr.db.RegisterCallback(Amr, "OnProfileCopied", "RefreshConfig")
@@ -175,6 +176,7 @@
 
 function Amr:OnEnable()
     
+	--[[
 	-- listen for changes to the snapshot enable state, and always make sure it is enabled if using the core AskMrRobot addon
 	self:RegisterMessage("AMR_SNAPSHOT_STATE_CHANGED", function(eventName, isEnabled)
 		if not isEnabled then
@@ -183,6 +185,7 @@
 		end	
 	end)
 	self.Serializer:EnableSnapshots()
+	]]
 	
 	-- update based on current configuration whenever enabled
 	self:RefreshConfig()
@@ -496,27 +499,6 @@
 end
 
 
--- scanning tooltip b/c for some odd reason the api has no way to get basic item properties...
--- so you have to generate a fake item tooltip and search for pre-defined strings in the display text
-local _scanTt
-function Amr:GetScanningTooltip()
-	if not _scanTt then
-		_scanTt = CreateFrame("GameTooltip", "AmrUiScanTooltip", nil, "GameTooltipTemplate")
-		_scanTt:SetOwner(UIParent, "ANCHOR_NONE")
-	end
-	return _scanTt
-end
-
-local function scanTooltipHelper(txt, ...)
-	for i = 1, select("#", ...) do
-        local region = select(i, ...)
-        if region and region:GetObjectType() == "FontString" then
-            local text = region:GetText() -- string or nil
-			print(text)
-        end
-    end
-end
-
 -- search the tooltip for txt, returns true if it is encountered on any line
 function Amr:IsTextInTooltip(tt, txt)
 	local regions = { tt:GetRegions() }
@@ -532,13 +514,7 @@
 
 -- helper to determine if we can equip an item (it is already soulbound or account bound)
 function Amr:CanEquip(bagId, slotId)
-	local tt = self:GetScanningTooltip()
-	tt:ClearLines()
-	if bagId then
-		tt:SetBagItem(bagId, slotId)
-	else
-		tt:SetInventoryItem("player", slotId)
-	end
+	local tt = Amr.GetItemTooltip(bagId, slotId)
 	if self:IsTextInTooltip(tt, ITEM_SOULBOUND) then return true end
 	if self:IsTextInTooltip(tt, ITEM_BNETACCOUNTBOUND) then return true end
 	if self:IsTextInTooltip(tt, ITEM_ACCOUNTBOUND) then return true end
@@ -546,13 +522,7 @@
 
 -- helper to determine if an item has a unique constraint
 function Amr:IsUnique(bagId, slotId)
-	local tt = self:GetScanningTooltip()
-	tt:ClearLines()
-	if bagId then
-		tt:SetBagItem(bagId, slotId)
-	else
-		tt:SetInventoryItem("player", slotId)
-	end
+	local tt = Amr.GetItemTooltip(bagId, slotId)
 	if self:IsTextInTooltip(tt, ITEM_UNIQUE_EQUIPPABLE)	then return true end
 	if self:IsTextInTooltip(tt, ITEM_UNIQUE) then return true end
 	return false
--- a/Export.lua	Tue Apr 05 16:19:31 2016 -0700
+++ b/Export.lua	Tue Jul 19 10:05:32 2016 -0700
@@ -53,6 +53,10 @@
 	lbl = createLabel(panel, L.ExportSplash3, 650)
 	lbl:SetFont(Amr.CreateFont("Regular", 14, Amr.Colors.Text))
 	lbl:SetPoint("TOPLEFT", lbl2.frame, "BOTTOMLEFT", 0, -15)
+
+	lbl2 = createLabel(panel, L.ExportSplash4, 650)
+	lbl2:SetFont(Amr.CreateFont("Regular", 14, Amr.Colors.Text))
+	lbl2:SetPoint("TOPLEFT", lbl.frame, "BOTTOMLEFT", 0, -15)
 	
 	local btn = AceGUI:Create("AmrUiButton")
 	btn:SetText(L.ExportSplashClose)
@@ -81,13 +85,10 @@
 	lbl2 = createLabel(container, L.ExportHelp3)
 	lbl2:SetPoint("TOPLEFT", lbl.frame, "BOTTOMLEFT", 0, -10)
 	
-	lbl = createLabel(container, L.ExportHelp4)
-	lbl:SetPoint("TOPLEFT", lbl2.frame, "BOTTOMLEFT", 0, -10)
-	
 	_txt = AceGUI:Create("AmrUiTextarea")
 	_txt:SetWidth(800)
 	_txt:SetHeight(300)
-	_txt:SetPoint("TOP", lbl.frame, "BOTTOM", 0, -20)
+	_txt:SetPoint("TOP", lbl2.frame, "BOTTOM", 0, -20)
 	_txt:SetFont(Amr.CreateFont("Regular", 12, Amr.Colors.Text))
 	_txt:SetCallback("OnTextChanged", onTextChanged)
 	container:AddChild(_txt)
@@ -151,11 +152,10 @@
 
 -- get the player's current gear and save it, also returns the data from GetPlayerData for efficiency
 local function getEquipped()
-	local data = Amr.Serializer:GetPlayerData(Amr.db.char.SubSpecs)
-	local spec = GetActiveSpecGroup()
+	local data = Amr.Serializer:GetPlayerData()
+	local spec = GetSpecialization()
 	
 	Amr.db.char.Equipped[spec] = data.Equipped[spec]
-	Amr.db.char.SubSpecs[spec] = data.SubSpecs[spec]
 	
 	return data
 end
@@ -241,6 +241,63 @@
     return reps
 end
 
+local function scanTalents()	
+	local specPos = GetSpecialization()	
+	if not specPos or specPos < 1 or specPos > 4 then return end
+	
+	local talentInfo = {}
+    local maxTiers = 7
+    for tier = 1, maxTiers do
+        for col = 1, 3 do
+            local id, name, _, _, _, spellId, _, t, c, selected = GetTalentInfoBySpecialization(specPos, tier, col)
+            if selected then
+                talentInfo[tier] = col
+            end
+        end
+    end
+    
+    local str = ""
+    for i = 1, maxTiers do
+    	if talentInfo[i] then
+    		str = str .. talentInfo[i]
+    	else
+    		str = str .. '0'
+    	end
+    end
+	
+	Amr.db.char.Talents[specPos] = str
+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
+	
+	local powerRanks = {}
+	for k,v in pairs(powers) do
+		local spellId, cost, rank, maxRank, relicRank = C_ArtifactUI.GetPowerInfo(v)
+		if rank - relicRank > 0 then
+			powerRanks[v] = rank - relicRank
+		end
+	end
+	
+	local relicInfo = {}
+	for i = 1,3 do
+		local _, _, _, link = C_ArtifactUI.GetRelicInfo(i);
+		table.insert(relicInfo, link or "")
+	end
+	
+	-- make sure that the artifact UI didn't get closed while we were reading it, GetPowers seems to return nil unless it is open
+	powers = C_ArtifactUI.GetPowers()
+	if not powers then return end
+	
+	local spec = GetSpecialization()
+	Amr.db.char.Artifacts[spec] = {
+		Powers = powerRanks,
+		Relics = relicInfo
+	}
+end
+
 -- Returns a data object containing all information about the current player needed for an export:
 -- gear, spec, reputations, bag, bank, and void storage items.
 function Amr:ExportCharacter()
@@ -248,7 +305,11 @@
 	local data = getEquipped()
 	scanBags()
 	
-	-- get extra data that is not necessary for the base serializer, but that we add here for completeness
+	-- scan current spec's talents just before exporting
+	scanTalents()
+	
+	data.Talents = Amr.db.char.Talents
+	data.Artifacts = Amr.db.char.Artifacts
 	data.Equipped = Amr.db.char.Equipped
 	data.Reputations = getReputations()
 	data.BagItems = Amr.db.char.BagItems
@@ -272,3 +333,6 @@
 Amr:AddEventHandler("VOID_STORAGE_CONTENTS_UPDATE", scanVoid)
 Amr:AddEventHandler("VOID_STORAGE_DEPOSIT_UPDATE", scanVoid)
 Amr:AddEventHandler("VOID_STORAGE_UPDATE", scanVoid)
+
+Amr:AddEventHandler("PLAYER_TALENT_UPDATE", scanTalents)
+Amr:AddEventHandler("ARTIFACT_UPDATE", scanArtifact)
--- a/Gear.lua	Tue Apr 05 16:19:31 2016 -0700
+++ b/Gear.lua	Tue Jul 19 10:05:32 2016 -0700
@@ -14,7 +14,7 @@
 		return 1000
     end
     
-    -- different upgrade levels of the same item (only for older gear, player has control over upgrade level)
+    -- different upgrade levels of the same item
     if item1.upgradeId ~= item2.upgradeId then
         return 100
     end
@@ -138,7 +138,7 @@
 		icon:SetHeight(48)
 		
 		local iconSpec
-		if player.SubSpecs[spec] then
+		if player.SubSpecs and player.SubSpecs[spec] then
 			iconSpec = player.SubSpecs[spec]
 		else
 			iconSpec = player.Specs[spec]
@@ -149,7 +149,7 @@
 		panelGear:AddChild(icon)
 		
 		local btnEquip = AceGUI:Create("AmrUiButton")
-		btnEquip:SetText(L.GearButtonEquip(spec))
+		btnEquip:SetText(L.GearButtonEquip(L.SpecsShort[player.Specs[spec]]))
 		btnEquip:SetBackgroundColor(Amr.Colors.Green)
 		btnEquip:SetFont(Amr.CreateFont("Regular", 14, Amr.Colors.White))
 		btnEquip:SetWidth(300)
@@ -161,17 +161,6 @@
 		end)
 		panelGear:AddChild(btnEquip)
 		
-		--[[local btnShop = AceGUI:Create("AmrUiButton")
-		btnShop:SetText(L.GearButtonShop)
-		btnShop:SetBackgroundColor(Amr.Colors.Blue)
-		btnShop:SetFont(Amr.CreateFont("Regular", 14, Amr.Colors.White))
-		btnShop:SetWidth(300)
-		btnShop:SetHeight(26)
-		btnShop:SetPoint("LEFT", btnEquip.frame, "RIGHT", 75, 0)
-		btnShop:SetPoint("RIGHT", panelMods.content, "RIGHT", -20, 0)
-		btnShop:SetCallback("OnClick", function(widget) Amr:ShowShopWindow() end)
-		panelMods:AddChild(btnShop)]]
-		
 		-- each physical item can only be used once, this tracks ones we have already used
 		local usedItems = {}
 		
@@ -371,30 +360,25 @@
 	lbl2:SetPoint("TOP", lbl.frame, "BOTTOM", 10, -5)
 	container:AddChild(lbl2)
 	
-	--[[
-	local btnClean = AceGUI:Create("AmrUiButton")
-	btnClean:SetText(L.GearButtonCleanText)
-	btnClean:SetBackgroundColor(Amr.Colors.Orange)
-	btnClean:SetFont(Amr.CreateFont("Bold", 16, Amr.Colors.White))
-	btnClean:SetWidth(120)
-	btnClean:SetHeight(26)
-	btnClean:SetPoint("BOTTOMLEFT", container.content, "BOTTOMLEFT", 0, 5)
-	btnClean:SetCallback("OnClick", function(widget) Amr:CleanBags() end)
-	container:AddChild(btnClean)	
-	]]
-	
 	local t =  AceGUI:Create("AmrUiTabGroup")
 	t:SetLayout("None")
-	t:SetTabs({
-		{text=L.GearTabPrimary, value="1", style="bold"}, 
-		{text=L.GearTabSecondary, value="2", style="bold"}
-	})
+	
+	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)
 	t:SetPoint("TOPLEFT", container.content, "TOPLEFT", 144, -30)
 	t:SetPoint("BOTTOMRIGHT", container.content, "BOTTOMRIGHT")
 	container:AddChild(t)	
 	_gearTabs = t;
 	
+	--[[
 	local btnShop = AceGUI:Create("AmrUiButton")
 	btnShop:SetText(L.GearButtonShop)
 	btnShop:SetBackgroundColor(Amr.Colors.Blue)
@@ -404,10 +388,10 @@
 	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(GetActiveSpecGroup())
+		_activeTab = tostring(GetSpecialization())
 	end
 	
 	t:SelectTab(_activeTab)
@@ -707,8 +691,9 @@
 end
 
 local function onActiveTalentGroupChanged()
+
 	local auto = Amr.db.profile.options.autoGear
-	local currentSpec = GetActiveSpecGroup()
+	local currentSpec = GetSpecialization()
 	
 	if currentSpec == _waitingForSpec or auto then
 		-- spec is what we want, now equip the gear
@@ -718,21 +703,19 @@
 	_waitingForSpec = 0
 end
 
--- activate the specified spec and then equip the saved gear set for either primary (1) or secondary (2) spec
+-- activate the specified spec and then equip the saved gear set
 function Amr:EquipGearSet(spec)
 	
-	-- if no argument, then toggle spec
+	-- if no argument, then cycle spec
 	if not spec then
-		spec = GetActiveSpecGroup() == 1 and 2 or 1
+		spec = GetSpecialization() + 1
 	end
-	
+
 	-- allow some flexibility in the arguments
-	if spec == "primary" or spec == "Primary" then spec = 1 end
-	if spec == "secondary" or spec == "Secondary" then spec = 2 end
-	if spec == "1" or spec == "2" then spec = tonumber(spec) end
-	
-	-- only spec 1 or 2 are valid
-	if spec ~= 1 and spec ~= 2 then return end
+	if spec == "1" or spec == "2" or spec == "3" or spec == "4" then spec = tonumber(spec) end
+
+	local specId = GetSpecializationInfo(spec)
+	if not specId then spec = 1 end
 	
 	if UnitAffectingCombat("player") then
 		Amr:Print(L.GearEquipErrorCombat)
@@ -741,9 +724,9 @@
 	
 	_waitingForSpec = spec
 	
-	local currentSpec = GetActiveSpecGroup()
+	local currentSpec = GetSpecialization()
 	if currentSpec ~= spec then
-		SetActiveSpecGroup(spec)
+		SetSpecialization(spec)
 	else
 		onActiveTalentGroupChanged()
 	end
@@ -754,9 +737,17 @@
 	-- TODO: implement
 end
 
+--[[
+local function testfunc(message)
+	print(strsub(message, 13))
+end
+]]
+
 function Amr:InitializeGear()
-	Amr:AddEventHandler("ACTIVE_TALENT_GROUP_CHANGED", onActiveTalentGroupChanged)
+	Amr:AddEventHandler("PLAYER_SPECIALIZATION_CHANGED", onActiveTalentGroupChanged)
 
+	--Amr:AddEventHandler("CHAT_MSG_CHANNEL", testfunc)
+	
 	Amr:AddEventHandler("UNIT_INVENTORY_CHANGED", function(unitID)
 		if unitID and unitID ~= "player" then return end
 		Amr:RefreshGearTab()
--- a/Import.lua	Tue Apr 05 16:19:31 2016 -0700
+++ b/Import.lua	Tue Jul 19 10:05:32 2016 -0700
@@ -202,6 +202,7 @@
     local prevEnchantId = 0
     local prevUpgradeId = 0
     local prevBonusId = 0
+	local prevLevel = 0
     local digits = {
         ["-"] = true,
         ["0"] = true,
@@ -215,7 +216,7 @@
         ["8"] = true,
         ["9"] = true,
     }
-    for i = 16, #parts do
+    for i = 18, #parts do
         local itemString = parts[i]
         if itemString ~= "" and itemString ~= "_" then
             local tokens = {}
@@ -240,6 +241,9 @@
                     elseif prop == "u" then
                         val = val + prevUpgradeId
                         prevUpgradeId = val
+					elseif prop == "v" then
+						val = val + prevLevel
+						prevLevel = val
                     elseif prop == "b" then
                         val = val + prevBonusId
                         prevBonusId = val
@@ -272,6 +276,7 @@
             obj.id = tokens["i"]
             obj.suffixId = tokens["f"] or 0
             obj.upgradeId = tokens["u"] or 0
+			obj.level = tokens["v"] or 0
             obj.enchantId = tokens["e"] or 0
             
             obj.gemIds = {}
@@ -336,7 +341,7 @@
             	end            	
             end            
 
-            gemInfo[gemObj.enchantId] = gemObj
+            gemInfo[gemObj.id] = gemObj
             
         elseif infoParts[1] == "e" then
         
--- a/Shopping.lua	Tue Apr 05 16:19:31 2016 -0700
+++ b/Shopping.lua	Tue Jul 19 10:05:32 2016 -0700
@@ -6,12 +6,18 @@
 local _panelContent
 local _cboPlayers
 local _selectedPlayer
+
 local _specs = {
 	[1] = true,
-	[2] = true
+	[2] = true,
+	[3] = true,
+	[4] = true,
 }
+
 local _chk1
 local _chk2
+local _chk3
+local _chk4
 local _isAhOpen = false
 
 local function onShopFrameClose(widget)
@@ -20,6 +26,8 @@
 	_cboPlayers = nil
 	_chk1 = nil
 	_chk2 = nil
+	_chk3 = nil
+	_chk4 = nil
 	_panelContent = nil
 end
 
@@ -94,6 +102,18 @@
 		_chk2:SetCallback("OnClick", onSpecClick)
 		_frameShop:AddChild(_chk2)
 		
+		_chk3 = AceGUI:Create("AmrUiCheckBox")
+		_chk3:SetPoint("LEFT", _chk2.frame, "RIGHT", 30, 0)
+		_chk3:SetUserData("spec", 3)
+		_chk3:SetCallback("OnClick", onSpecClick)
+		_frameShop:AddChild(_chk3)
+		
+		_chk4 = AceGUI:Create("AmrUiCheckBox")
+		_chk4:SetPoint("LEFT", _chk3.frame, "RIGHT", 30, 0)
+		_chk4:SetUserData("spec", 4)
+		_chk4:SetCallback("OnClick", onSpecClick)
+		_frameShop:AddChild(_chk4)
+		
 		_panelContent = AceGUI:Create("AmrUiPanel")
 		_panelContent:SetLayout("None")
 		_panelContent:SetTransparent()
@@ -199,14 +219,17 @@
 
 	_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()
 	
-	-- render required gems for the selected player
 	local data = Amr.db.global.Shopping[_selectedPlayer]
 	if not data then		
 		_panelContent:SetLayout("None")
@@ -220,22 +243,32 @@
 	else
 		-- set labels on checkboxes
 		if data.specs[1] and data.specs[1] ~= 0 then
-			local id, name = GetSpecializationInfoByID(Amr.GetGameSpecId(data.specs[1]))
-			_chk1:SetText(name .. " " .. L.ShopSpecLabel)
+			_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
-			local id, name = GetSpecializationInfoByID(Amr.GetGameSpecId(data.specs[2]))
-			_chk2:SetText(name .. " " .. L.ShopSpecLabel)
+			_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] then
-			-- both unchecked, show nothing
+		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
@@ -320,22 +353,31 @@
 -- look at both gear sets and find stuff that a player needs to acquire to gem/enchant their gear
 function Amr:UpdateShoppingData(player)
 
-	-- 0 is combination of both specs, 1 is primary, 2 is secondary
+	-- TODO: re-enable shopping list when Legion comes out
+	do return end
+	
+	-- 0 is combination of all specs
 	local required = {
 		gems = {
 			[0] = {},
 			[1] = {},
-			[2] = {}
+			[2] = {},
+			[3] = {},
+			[4] = {}
 		},
 		enchants = {
 			[0] = {},
 			[1] = {},
-			[2] = {}
+			[2] = {},
+			[3] = {},
+			[4] = {}
 		},
 		materials = {
 			[0] = {},
 			[1] = {},
-			[2] = {}
+			[2] = {},
+			[3] = {},
+			[4] = {}
 		},
 		specs = player.Specs
 	}
@@ -369,53 +411,56 @@
 	end
 	
 	-- now subtract stuff the player already has, and generate a list of materials as well
-	for spec = 0, 2 do
-		-- 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]
+	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
 			
-			if enchInfo then
-				required.enchants[spec][itemId] = math.max(count - getOwnedCount(itemId), 0)
+			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)
+					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	
-		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)
+					end			
+				end	
+			end
 			
-			if required.materials[spec][itemId] == 0 then
-				required.materials[spec][itemId] = nil
+			-- 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
 	end
@@ -423,6 +468,8 @@
 	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
@@ -435,4 +482,5 @@
 	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/localization/enUS.lua	Tue Apr 05 16:19:31 2016 -0700
+++ b/localization/enUS.lua	Tue Jul 19 10:05:32 2016 -0700
@@ -21,6 +21,45 @@
 General
 ------------------------------------------------------------------------]]
 
+L.SpecsShort = {
+	[1] = "Blood", -- DeathKnightBlood
+    [2] = "Frost", -- DeathKnightFrost
+    [3] = "Unholy", -- DeathKnightUnholy
+	[4] = "Havoc", -- DemonHunterHavoc
+	[5] = "Vengeance", -- DemonHunterVengeance
+    [6] = "Moon", -- DruidBalance
+    [7] = "Feral", -- DruidFeral
+    [8] = "Bear", -- DruidGuardian
+    [9] = "Resto", -- DruidRestoration
+    [10] = "BM", -- HunterBeastMastery
+    [11] = "Marks", -- HunterMarksmanship
+    [12] = "Survival", -- HunterSurvival
+    [13] = "Arcane", -- MageArcane
+    [14] = "Fire", -- MageFire
+    [15] = "Frost", -- MageFrost
+    [16] = "Brew", -- MonkBrewmaster
+    [17] = "Mist", -- MonkMistweaver
+    [18] = "Wind", -- MonkWindwalker
+    [19] = "Holy", -- PaladinHoly
+    [20] = "Prot", -- PaladinProtection
+    [21] = "Ret", -- PaladinRetribution
+    [22] = "Disc", -- PriestDiscipline
+    [23] = "Holy", -- PriestHoly
+    [24] = "Shadow", -- PriestShadow
+    [25] = "Assn", -- RogueAssassination
+    [26] = "Outlaw", -- RogueOutlaw
+    [27] = "Sub", -- RogueSubtlety
+    [28] = "Elem", -- ShamanElemental
+    [29] = "Enh", -- ShamanEnhancement
+    [30] = "Resto", -- ShamanRestoration
+    [31] = "Aff", -- WarlockAffliction
+    [32] = "Demo", -- WarlockDemonology
+    [33] = "Destro", -- WarlockDestruction
+    [34] = "Arms", -- WarriorArms
+    [35] = "Fury", -- WarriorFury
+    [36] = "Prot", -- WarriorProtection
+}
+
 -- stat strings for e.g. displaying gem/enchant abbreviations, make as short as possible without being confusing/ambiguous
 L.StatsShort = {
     ["Strength"] = "Str",
@@ -80,7 +119,8 @@
 	Wand     = "Wand",
 	Bow      = "Bow",
 	Gun      = "Gun",
-	Crossbow = "Crossbow"
+	Crossbow = "Crossbow",
+	Warglaive= "Warglaive"
 }
 
 L.ArmorTypes = {
@@ -129,15 +169,15 @@
 ------------------------------------------------------------------------]]
 L.ExportTitle = "Export Instructions"
 L.ExportHelp1 = "1. Copy the text below by pressing Ctrl+C (or Cmd+C on a Mac)"
-L.ExportHelp2 = "2. Go to http://www.askmrrobot.com/wow/player and load your character"
-L.ExportHelp3 = "3. Press the green IMPORT (from addon) link just above your character name"
-L.ExportHelp4 = "4. Paste into the textbox on the website and press Import!"
+L.ExportHelp2 = "2. Go to http://beta.askmrrobot.com/wow/simulator/run and open the character picker"
+L.ExportHelp3 = "3. Paste into the textbox under the ADDON section"
 
 L.ExportSplashTitle = "Getting Started"
 L.ExportSplashSubtitle = "This is your first time using the new version of the addon. Do the following things to initialize your item database:"
 L.ExportSplash1 = "1. Activate each of your specs once and equip your latest gear for each spec"
-L.ExportSplash2 = "2. Open your bank and leave it open for at least two seconds"
-L.ExportSplash3 = "3. If you have gear in void storage, open it and leave it open for at least two seconds"
+L.ExportSplash2 = "2. Equip and open your artifact weapon for each spec"
+L.ExportSplash3 = "3. Open your bank and leave it open for at least two seconds"
+L.ExportSplash4 = "4. If you have gear in void storage, open it and leave it open for at least two seconds"
 L.ExportSplashClose = "Continue"
 
 
@@ -145,13 +185,10 @@
 Gear Tab
 ------------------------------------------------------------------------]]
 L.GearImportNote = "Click Import to paste data from the website."
-L.GearTabPrimary = "Primary Spec"
-L.GearTabSecondary = "Secondary Spec"
 L.GearBlank = "You have not loaded any gear for this spec yet."
 L.GearBlank2 = "Go to askmrrobot.com to optimize your gear, then use the Import button to the left."
 L.GearButtonEquip = function(spec)
-	-- spec 1 is primary, 2 is secondary
-	return string.format("Activate %s Spec and Equip Gear", spec == 1 and "Primary" or "Secondary")
+	return string.format("Activate %s Spec and Equip Gear", spec)
 end
 L.GearButtonShop = "Show Shopping List"
 
@@ -176,10 +213,8 @@
 OR! You can use slash commands:]]
 
 L.GearTipCommands = 
-[[/amr equip [1 or 2]
-1 = primary
-2 = secondary
-no arg = toggle]]
+[[/amr equip [1-4]
+no arg = cycle]]
 -- note to translators: the slash commands are literal and should stay as english
 
 
--- a/localization/frFR.lua	Tue Apr 05 16:19:31 2016 -0700
+++ b/localization/frFR.lua	Tue Jul 19 10:05:32 2016 -0700
@@ -21,6 +21,45 @@
 General
 ------------------------------------------------------------------------]]
 
+L.SpecsShort = {
+	[1] = "Blood", -- DeathKnightBlood
+    [2] = "Frost", -- DeathKnightFrost
+    [3] = "Unholy", -- DeathKnightUnholy
+	[4] = "Havoc", -- DemonHunterHavoc
+	[5] = "Vengeance", -- DemonHunterVengeance
+    [6] = "Moon", -- DruidBalance
+    [7] = "Feral", -- DruidFeral
+    [8] = "Bear", -- DruidGuardian
+    [9] = "Resto", -- DruidRestoration
+    [10] = "BM", -- HunterBeastMastery
+    [11] = "Marks", -- HunterMarksmanship
+    [12] = "Survival", -- HunterSurvival
+    [13] = "Arcane", -- MageArcane
+    [14] = "Fire", -- MageFire
+    [15] = "Frost", -- MageFrost
+    [16] = "Brew", -- MonkBrewmaster
+    [17] = "Mist", -- MonkMistweaver
+    [18] = "Wind", -- MonkWindwalker
+    [19] = "Holy", -- PaladinHoly
+    [20] = "Prot", -- PaladinProtection
+    [21] = "Ret", -- PaladinRetribution
+    [22] = "Disc", -- PriestDiscipline
+    [23] = "Holy", -- PriestHoly
+    [24] = "Shadow", -- PriestShadow
+    [25] = "Assn", -- RogueAssassination
+    [26] = "Outlaw", -- RogueOutlaw
+    [27] = "Sub", -- RogueSubtlety
+    [28] = "Elem", -- ShamanElemental
+    [29] = "Enh", -- ShamanEnhancement
+    [30] = "Resto", -- ShamanRestoration
+    [31] = "Aff", -- WarlockAffliction
+    [32] = "Demo", -- WarlockDemonology
+    [33] = "Destro", -- WarlockDestruction
+    [34] = "Arms", -- WarriorArms
+    [35] = "Fury", -- WarriorFury
+    [36] = "Prot", -- WarriorProtection
+}
+
 -- stat strings for e.g. displaying gem/enchant abbreviations, make as short as possible without being confusing/ambiguous
 L.StatsShort = {
     ["Strength"] = "Str",
@@ -145,13 +184,10 @@
 Gear Tab
 ------------------------------------------------------------------------]]
 L.GearImportNote = "Clique Importer pour coller des données du site."
-L.GearTabPrimary = "Spé primaire"
-L.GearTabSecondary = "Spé secondaire"
 L.GearBlank = "Tu n'as pas encore chargé de stuff pour cette spé."
 L.GearBlank2 = "Va sur askmrrobot.com pour optimiser ton stuff. Ensuite, utilise le bouton Importer sur la gauche."
 L.GearButtonEquip = function(spec)
-	-- spec 1 is primary, 2 is secondary
-	return string.format("Activer la spé %s et équipper le stuff", spec == 1 and "Primaire" or "Secondaire")
+	return string.format("Activer la spé %s et équipper le stuff", spec)
 end
 L.GearButtonShop = "Voir Shopping List"
 
@@ -176,10 +212,8 @@
 OU! Tu peux utiliser des commandes /:]]
 
 L.GearTipCommands = 
-[[/amr equip [1 ou 2]
-1 = spé primaire
-2 = spé secondaire
-pas d'argument = change de spé]]
+[[/amr equip [1-4]
+pas d'argument = cycle]]
 -- note to translators: the slash commands are literal and should stay as english
 
 
--- a/localization/itIT.lua	Tue Apr 05 16:19:31 2016 -0700
+++ b/localization/itIT.lua	Tue Jul 19 10:05:32 2016 -0700
@@ -21,6 +21,45 @@
 General
 ------------------------------------------------------------------------]]
 
+L.SpecsShort = {
+	[1] = "Blood", -- DeathKnightBlood
+    [2] = "Frost", -- DeathKnightFrost
+    [3] = "Unholy", -- DeathKnightUnholy
+	[4] = "Havoc", -- DemonHunterHavoc
+	[5] = "Vengeance", -- DemonHunterVengeance
+    [6] = "Moon", -- DruidBalance
+    [7] = "Feral", -- DruidFeral
+    [8] = "Bear", -- DruidGuardian
+    [9] = "Resto", -- DruidRestoration
+    [10] = "BM", -- HunterBeastMastery
+    [11] = "Marks", -- HunterMarksmanship
+    [12] = "Survival", -- HunterSurvival
+    [13] = "Arcane", -- MageArcane
+    [14] = "Fire", -- MageFire
+    [15] = "Frost", -- MageFrost
+    [16] = "Brew", -- MonkBrewmaster
+    [17] = "Mist", -- MonkMistweaver
+    [18] = "Wind", -- MonkWindwalker
+    [19] = "Holy", -- PaladinHoly
+    [20] = "Prot", -- PaladinProtection
+    [21] = "Ret", -- PaladinRetribution
+    [22] = "Disc", -- PriestDiscipline
+    [23] = "Holy", -- PriestHoly
+    [24] = "Shadow", -- PriestShadow
+    [25] = "Assn", -- RogueAssassination
+    [26] = "Outlaw", -- RogueOutlaw
+    [27] = "Sub", -- RogueSubtlety
+    [28] = "Elem", -- ShamanElemental
+    [29] = "Enh", -- ShamanEnhancement
+    [30] = "Resto", -- ShamanRestoration
+    [31] = "Aff", -- WarlockAffliction
+    [32] = "Demo", -- WarlockDemonology
+    [33] = "Destro", -- WarlockDestruction
+    [34] = "Arms", -- WarriorArms
+    [35] = "Fury", -- WarriorFury
+    [36] = "Prot", -- WarriorProtection
+}
+
 -- stat strings for e.g. displaying gem/enchant abbreviations, make as short as possible without being confusing/ambiguous
 L.StatsShort = {
     ["Strength"] = "For",
@@ -145,13 +184,10 @@
 Gear Tab
 ------------------------------------------------------------------------]]
 L.GearImportNote = "Clicca Importa per importare i dati dal sito."
-L.GearTabPrimary = "Spec Primaria"
-L.GearTabSecondary = "Spec Secondaria"
 L.GearBlank = "Non hai caricato nessun equipaggiamento per questa specializzazione."
 L.GearBlank2 = "Vai su askmrrobot.com per ottimizzare l'equipaggiamento, quindi usa il pulsante Importa sulla sinistra."
 L.GearButtonEquip = function(spec)
-	-- spec 1 is primary, 2 is secondary
-	return string.format("Attiva %s Spec ed Equip", spec == 1 and "Primaria" or "Secondaria")
+	return string.format("Attiva %s Spec ed Equip", spec)
 end
 L.GearButtonShop = "Show Shopping List"
 
@@ -176,10 +212,8 @@
 Oppure! Puoi usare i seguenti comandi:]]
 
 L.GearTipCommands = 
-[[/amr equip [1 or 2]
-1 = primary
-2 = secondary
-no arg = toggle]]
+[[/amr equip [1-4]
+no arg = cycle]]
 -- note to translators: the slash commands are literal and should stay as english
 
 
--- a/ui/AmrUiButton.lua	Tue Apr 05 16:19:31 2016 -0700
+++ b/ui/AmrUiButton.lua	Tue Jul 19 10:05:32 2016 -0700
@@ -50,8 +50,8 @@
 	
 	-- color is an object with R, G, B
 	["SetBackgroundColor"] = function(self, color)
-		self.texNormal:SetTexture(color.R, color.G, color.B, 1)
-		self.texPush:SetTexture(color.R, color.G, color.B, 1)
+		self.texNormal:SetColorTexture(color.R, color.G, color.B, 1)
+		self.texPush:SetColorTexture(color.R, color.G, color.B, 1)
 	end,
 
 	["SetDisabled"] = function(self, disabled)
@@ -100,7 +100,7 @@
 	
 	-- not perfect, but more or less achieves the effect of lightening the bg color slightly on hover
 	local texHigh = frame:CreateTexture(nil, "BORDER")
-	texHigh:SetTexture(1, 1, 1, 0.1)
+	texHigh:SetColorTexture(1, 1, 1, 0.1)
 	texHigh:SetAllPoints(true)
 	frame:SetHighlightTexture(texHigh)
 
--- a/ui/AmrUiDropDown.lua	Tue Apr 05 16:19:31 2016 -0700
+++ b/ui/AmrUiDropDown.lua	Tue Jul 19 10:05:32 2016 -0700
@@ -137,7 +137,7 @@
 		
 		-- not perfect, but more or less achieves the effect of lightening the bg color slightly on hover
 		local texHigh = item:CreateTexture(nil, "BORDER")
-		texHigh:SetTexture(1, 1, 1, 0.1)
+		texHigh:SetColorTexture(1, 1, 1, 0.1)
 		texHigh:SetAllPoints(true)
 		item:SetHighlightTexture(texHigh)
 		
--- a/ui/AmrUiFrame.lua	Tue Apr 05 16:19:31 2016 -0700
+++ b/ui/AmrUiFrame.lua	Tue Jul 19 10:05:32 2016 -0700
@@ -95,11 +95,11 @@
 	
 	-- color is an object with R, G, B
 	["SetBackgroundColor"] = function(self, color)
-		self.bg:SetTexture(color.R, color.G, color.B, 1)
+		self.bg:SetColorTexture(color.R, color.G, color.B, 1)
 	end,
 	
 	["SetBorderColor"] = function(self, color)
-		self.border:SetTexture(color.R, color.G, color.B, 1)
+		self.border:SetColorTexture(color.R, color.G, color.B, 1)
 	end,
 	
 	["Raise"] = function(self)
--- a/ui/AmrUiIcon.lua	Tue Apr 05 16:19:31 2016 -0700
+++ b/ui/AmrUiIcon.lua	Tue Jul 19 10:05:32 2016 -0700
@@ -55,7 +55,7 @@
 
 	["SetIcon"] = function(self, icon)
 		if not icon then
-			self.icon:SetTexture(0, 0, 0, 0)
+			self.icon:SetColorTexture(0, 0, 0, 0)
 		else
 			self.icon:SetTexture(icon)
 		end
@@ -66,11 +66,11 @@
 	end,
 	
 	["SetIconBorderColor"] = function(self, color, a)
-		self.bg:SetTexture(color.R, color.G, color.B, a or 1)
+		self.bg:SetColorTexture(color.R, color.G, color.B, a or 1)
 	end,
 	
 	["HideIconBorder"] = function(self)
-		self.bg:SetTexture(0, 0, 0, 0)
+		self.bg:SetColorTexture(0, 0, 0, 0)
 	end,
 	
 	["SetStrata"] = function(self, strata)
@@ -108,25 +108,25 @@
 	icon:SetTexCoord(0.05, 0.95, 0.05, 0.95)
 	
 	local borderTop = frame:CreateTexture(nil, "ARTWORK", nil, 2)
-	borderTop:SetTexture(0, 0, 0, 1)
+	borderTop:SetColorTexture(0, 0, 0, 1)
 	borderTop:SetHeight(1)
 	borderTop:SetPoint("TOPLEFT", icon, "TOPLEFT")
 	borderTop:SetPoint("TOPRIGHT", icon, "TOPRIGHT")
 	
 	local borderRight = frame:CreateTexture(nil, "ARTWORK", nil, 2)
-	borderRight:SetTexture(0, 0, 0, 1)
+	borderRight:SetColorTexture(0, 0, 0, 1)
 	borderRight:SetWidth(1)
 	borderRight:SetPoint("TOPRIGHT", icon, "TOPRIGHT")
 	borderRight:SetPoint("BOTTOMRIGHT", icon, "BOTTOMRIGHT")
 	
 	local borderBottom = frame:CreateTexture(nil, "ARTWORK", nil, 2)
-	borderBottom:SetTexture(0, 0, 0, 1)
+	borderBottom:SetColorTexture(0, 0, 0, 1)
 	borderBottom:SetHeight(1)
 	borderBottom:SetPoint("BOTTOMLEFT", icon, "BOTTOMLEFT")
 	borderBottom:SetPoint("BOTTOMRIGHT", icon, "BOTTOMRIGHT")
 	
 	local borderLeft = frame:CreateTexture(nil, "ARTWORK", nil, 2)
-	borderLeft:SetTexture(0, 0, 0, 1)
+	borderLeft:SetColorTexture(0, 0, 0, 1)
 	borderLeft:SetWidth(1)
 	borderLeft:SetPoint("TOPLEFT", icon, "TOPLEFT")
 	borderLeft:SetPoint("BOTTOMLEFT", icon, "BOTTOMLEFT")
--- a/ui/AmrUiPanel.lua	Tue Apr 05 16:19:31 2016 -0700
+++ b/ui/AmrUiPanel.lua	Tue Jul 19 10:05:32 2016 -0700
@@ -34,7 +34,7 @@
 	end,
 
 	["SetBackgroundColor"] = function(self, color, a)
-		self.bg:SetTexture(color.R, color.G, color.B, a or 1)
+		self.bg:SetColorTexture(color.R, color.G, color.B, a or 1)
 	end,
 	
 	-- set a transparent bg to make this panel invisible
--- a/ui/AmrUiScrollFrame.lua	Tue Apr 05 16:19:31 2016 -0700
+++ b/ui/AmrUiScrollFrame.lua	Tue Jul 19 10:05:32 2016 -0700
@@ -230,7 +230,7 @@
 	local scrollbg = scrollbar:CreateTexture(nil, "BACKGROUND")
 	scrollbg:SetPoint("TOPLEFT", scrollbar, "TOPLEFT", 0, 16)
 	scrollbg:SetPoint("BOTTOMRIGHT", scrollbar, "BOTTOMRIGHT", 0, -16)
-	scrollbg:SetTexture(0, 0, 0, 0.3)
+	scrollbg:SetColorTexture(0, 0, 0, 0.3)
 	
 	--Container Support
 	local content = CreateFrame("Frame", nil, scrollframe)
--- a/ui/AmrUiTabGroup.lua	Tue Apr 05 16:19:31 2016 -0700
+++ b/ui/AmrUiTabGroup.lua	Tue Jul 19 10:05:32 2016 -0700
@@ -237,7 +237,7 @@
 	
 	local line = border:CreateTexture(nil, "ARTWORK")
 	line:Hide()
-	line:SetTexture(1, 1, 1, 1)
+	line:SetColorTexture(1, 1, 1, 1)
 	line:SetHeight(4)
 
 	local content = CreateFrame("Frame", nil, border)
--- a/ui/AmrUiTextButton.lua	Tue Apr 05 16:19:31 2016 -0700
+++ b/ui/AmrUiTextButton.lua	Tue Jul 19 10:05:32 2016 -0700
@@ -78,7 +78,7 @@
 	end,
 	
 	["SetBackgroundColor"] = function(self, color, alpha)
-		self.bg:SetTexture(color.R, color.G, color.B, alpha)
+		self.bg:SetColorTexture(color.R, color.G, color.B, alpha)
 	end,
 	
 	["SetBackgroundImage"] = function(self, image)
@@ -87,7 +87,7 @@
 	end,
 	
 	["SetHoverBackgroundColor"] = function(self, color, alpha)
-		self.hover:SetTexture(color.R, color.G, color.B, alpha)
+		self.hover:SetColorTexture(color.R, color.G, color.B, alpha)
 	end,
 	
 	["SetHoverBackgroundImage"] = function(self, image)
--- a/ui/AmrUiTextarea.lua	Tue Apr 05 16:19:31 2016 -0700
+++ b/ui/AmrUiTextarea.lua	Tue Jul 19 10:05:32 2016 -0700
@@ -186,11 +186,11 @@
 	frame:SetScrollChild(editbox)
 	
 	local border = frame:CreateTexture(nil, "BACKGROUND")
-	border:SetTexture(Amr.Colors.BorderGray.R, Amr.Colors.BorderGray.G, Amr.Colors.BorderGray.B, 1)
+	border:SetColorTexture(Amr.Colors.BorderGray.R, Amr.Colors.BorderGray.G, Amr.Colors.BorderGray.B, 1)
 	border:SetAllPoints(true)
 	
 	local bg = frame:CreateTexture(nil, "BORDER")
-	bg:SetTexture(Amr.Colors.BgInput.R, Amr.Colors.BgInput.G, Amr.Colors.BgInput.B, 1)
+	bg:SetColorTexture(Amr.Colors.BgInput.R, Amr.Colors.BgInput.G, Amr.Colors.BgInput.B, 1)
 	bg:SetPoint("TOPLEFT", frame, "TOPLEFT", 1, -1)
 	bg:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -1, 1)
 	
--- a/ui/Ui.lua	Tue Apr 05 16:19:31 2016 -0700
+++ b/ui/Ui.lua	Tue Jul 19 10:05:32 2016 -0700
@@ -87,24 +87,24 @@
 -- helper to create a solid texture from a color with R,G,B properties
 function Amr.CreateTexture(parent, color, alpha, layer)
 	local t = parent:CreateTexture(nil, layer or "ARTWORK")
-	t:SetTexture(color.R, color.G, color.B, alpha or 1)
+	t:SetColorTexture(color.R, color.G, color.B, alpha or 1)
 	return t
 end
 
 -- helper to create a cheater shadow without having to create custom images
 function Amr.DropShadow(frame)
 	local shadow = frame:CreateTexture(nil, "BACKGROUND")
-	shadow:SetTexture(0, 0, 0, 0.4)
+	shadow:SetColorTexture(0, 0, 0, 0.4)
 	shadow:SetPoint("TOPLEFT", frame, "TOPLEFT", 2, -2)
 	shadow:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", 1, -1)
 	
 	shadow = frame:CreateTexture(nil, "BACKGROUND")
-	shadow:SetTexture(0, 0, 0, 0.3)
+	shadow:SetColorTexture(0, 0, 0, 0.3)
 	shadow:SetPoint("TOPLEFT", frame, "TOPLEFT", 2, -2)
 	shadow:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", 2, -2)
 	
 	shadow = frame:CreateTexture(nil, "BACKGROUND")
-	shadow:SetTexture(0, 0, 0, 0.1)
+	shadow:SetColorTexture(0, 0, 0, 0.1)
 	shadow:SetPoint("TOPLEFT", frame, "TOPLEFT", 2, -2)
 	shadow:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", 3, -3)
 end