Version 2.5.0

willneedit [03-28-20 - 08:54]
Version 2.5.0

 *  Straightening out some texts
 *  Code cleanup in CraftStoreLink
 *  Made deconstruction of jewelry working
 *  Revamped CraftStoreLink to make to more robust to changes to CraftStore
Filename
CraftStoreLink.lua
InventoryManager.lua
InventoryManager.txt
Modules/Extractor.lua
lang/de.lua
lang/en.lua
diff --git a/CraftStoreLink.lua b/CraftStoreLink.lua
index eda8eeb..ff48880 100644
--- a/CraftStoreLink.lua
+++ b/CraftStoreLink.lua
@@ -11,129 +11,81 @@ local IM = InventoryManager

 local CSL = {}

-local Used_CS
-local Used_CSA
-
 local hasCS = nil

 IM.CSL = CSL

-local function SplitLink(link,nr)
-	local split = {SplitString(':', link)}
-	if split[nr] then return tonumber(split[nr]) else return false end
-end
-
 function CSL:hasCSAddon()
 	if hasCS ~= nil then

-		-- This variable is late initialized, update if needed
-		if hasCS == "new" then
-			Used_CSA = Used_CS.Account
-		end
-
-		return hasCS ~= false and true
+		return hasCS
 	end

 	if CS then
-		Used_CS  = CS
-		Used_CSA = Used_CS.Account
-		CHAT_SYSTEM:AddMessage(GetString(IM_INIT_DETECTED_CS_NEW))
-		hasCS = "new"
-	elseif CraftStoreFixedAndImprovedLongClassName then
-		-- In case someone is lazy updating past 1.74
-		Used_CS  = CraftStoreFixedAndImprovedLongClassName
-		Used_CSA = Used_CS.Account
 		CHAT_SYSTEM:AddMessage(GetString(IM_INIT_DETECTED_CS_NEW))
-		hasCS = "new"
+		hasCS = true
 	else
 		hasCS = false
 	end
 	return hasCS
 end

-function CSL:IsTraitNeeded(itemLink)
-	local need = { }
-	local craft, row, trait = Used_CS.GetTrait(itemLink)
-	-- Loop all chars known by CS
-	for char, data in pairs(Used_CSA.crafting.studies) do
-		--if a char study this item
-		if data[craft] and data[craft][row] and (data[craft][row]) then
-			-- If this char didn't yet researched this item
-			local csr = Used_CSA.crafting.research
-			if csr[char][craft] and csr[char][craft][row] and csr[char][craft][row][trait] == false then
-				need[char] = true
-				need[#need + 1] = char
-			end
-		end
+local CURRENT_PLAYER = GetUnitName("player")
+
+-- It would help me LOTS if Is*Needed would return the raw names list rather than formatted strings.
+-- Now I have to take them apart again and make graceful assumptions on their format. :(
+function CSL:parseNeeded(needStr)
+
+	-- Can happen with items which cannot be deconstructed (or researched)
+	if not needStr then
+		return false, false
 	end
-	return need
-end

-local CURRENT_PLAYER = GetUnitName("player")
+	local nameHeader = "|cFF1010" -- output modifier: red coloring
+	local formattedName = nameHeader .. CURRENT_PLAYER .. "|r" -- including tail to reset the output modifier
+	local headerpos = needStr:find(nameHeader)

-function CSL:IsStyleNeeded(link)
-	local id, need = SplitLink(link,3), { }
-	if id then
-		for _, char in pairs(Used_CS.GetCharacters()) do
-			if Used_CSA.style.tracking[char] and not Used_CS.Data.style.knowledge[char][id] then
-				need[char] = true
-				need[#need + 1] = char
-			end
-		end
+	-- If we don't find a red coloured name at all, no one seems to need it.
+	if not headerpos then
+		return false, false
 	end
-	return need
-end

-function CSL:IsCookRecipeNeeded(link)
-	local id, need = SplitLink(link,3), { }
-	if id then
-		for char,data in pairs(Used_CS.Data.cook.knowledge) do
-			if data[id] ~= nil and not data[id] and Used_CSA.cook.tracking[char] then
-				need[char] = true
-				need[#need + 1] = char
-			end
-		end
+	-- true of we found our own name on the list
+	local needSelf = (needStr:find(formattedName) or -1) > -1
+
+	-- string length, minus header (everything in front of the first name), and
+	-- eventually the own name. If there's something left, other characters need it, too.
+	local lenOthers = needStr:len() - headerpos + 1
+	if needSelf then
+		lenOthers = lenOthers - formattedName:len()
 	end
-	return need
+
+	local needOthers = lenOthers > 0
+
+	return needSelf, needOthers
 end

-function CSL:IsBlueprintNeeded(link)
-	local id, need = SplitLink(link,3), { }
+function CSL:IsItemNeeded(itemLink, uID)
+	local craft, row, trait = CS.GetTrait(itemLink)
+	return self:parseNeeded(CS.IsItemNeeded(craft, row, trait, uID, itemLink))
+end

-	if not Used_CSA.furnisher then
-		return need
-	end

-	if id then
-		for char,data in pairs(Used_CS.Data.furnisher.knowledge) do
-			if data[id] ~= nil and not data[id] and Used_CSA.furnisher.tracking[char] then
-				need[char] = true
-				need[#need + 1] = char
-			end
-		end
-	end
-	return need
+function CSL:IsStyleNeeded(link)
+	return self:parseNeeded(CS.IsStyleNeeded(itemLink))
 end

 function CSL:IsRecipeNeeded(link)
-	local collate1 = self:IsCookRecipeNeeded(link)
-	local collate2 = self:IsBlueprintNeeded(link)
-	local all = { }
-	for k, v in pairs(collate1) do
-		if type(k) == "string" then all[k] = true end
-	end
-	for k, v in pairs(collate2) do
-		if type(k) == "string" then all[k] = true end
-	end
-	for k, v in pairs(all) do
-		all[#all+1] = k
-	end
-	return all
+	return self:parseNeeded(CS.IsRecipeNeeded(itemLink))
+end
+
+function CSL:IsBlueprintNeeded(link)
+	return self:parseNeeded(CS.IsBlueprintNeeded(itemLink))
 end

-function CSL:isUnknown(itemLink)
-	local chars
-	local itemType
+function CSL:isUnknown(itemLink, uID)
+	local oneself, others
+	local itemType, specItemType

 	if not CSL:hasCSAddon() then
 		return false, false
@@ -148,25 +100,16 @@ function CSL:isUnknown(itemLink)
 		specItemType == SPECIALIZED_ITEMTYPE_RECIPE_PROVISIONING_DESIGN_FURNISHING or
 		specItemType == SPECIALIZED_ITEMTYPE_RECIPE_WOODWORKING_BLUEPRINT_FURNISHING or
 		specItemType == SPECIALIZED_ITEMTYPE_RECIPE_JEWELRYCRAFTING_SKETCH_FURNISHING then
-			DEBUG("Blueprint:", CS.IsBlueprintNeeded(itemLink))
-			chars = CSL:IsBlueprintNeeded(itemLink)
+			oneself, others = CSL:IsBlueprintNeeded(itemLink)
 		else
-			DEBUG("Cooking Recipe:", CS.IsRecipeNeeded(itemLink))
-			chars = CSL:IsCookRecipeNeeded(itemLink)
+			oneself, others = CSL:IsRecipeNeeded(itemLink)
 		end
 	elseif itemType == ITEMTYPE_RACIAL_STYLE_MOTIF then
-		chars = CSL:IsStyleNeeded(itemLink)
+		oneself, others = CSL:IsStyleNeeded(itemLink)
 	elseif itemType == ITEMTYPE_WEAPON or itemType == ITEMTYPE_ARMOR then
-		chars = CSL:IsTraitNeeded(itemLink)
+		oneself, others = CSL:IsItemNeeded(itemLink, uID)
 	end

-	if not chars then
-		return false, false
-	end
-
-	local oneself = (chars[CURRENT_PLAYER] or false)
-	local numothers = #chars - ((oneself and 1) or 0)
-	local others = numothers > 0
-
+	-- allow (nil, nil) for items which don't fit in any category
 	return oneself, others
 end
diff --git a/InventoryManager.lua b/InventoryManager.lua
index 75a1510..4945bf6 100644
--- a/InventoryManager.lua
+++ b/InventoryManager.lua
@@ -23,7 +23,7 @@ IM.opssuspended = false
 -- The current ruleset we're working with
 IM.currentRuleset = { }

-local ADDON_VERSION = "2.4.4"
+local ADDON_VERSION = "2.5.0"
 local ADDON_WEBSITE = "https://www.esoui.com/downloads/info1642-InventoryManager.html"

 function IM:ProcessSingleItem(dryrun, data)
@@ -90,6 +90,7 @@ function IM:GetItemData(slotId, _inv)
 	end

 	local itemLink = GetItemLink(self.currentBagType, slotId)
+	local uID = Id64ToString(GetItemUniqueId(self.currentBagType, slotId))

 	data.bagId = self.currentBagType
 	data.slotId = slotId
@@ -122,7 +123,7 @@ function IM:GetItemData(slotId, _inv)
 	data.crafted = IsItemLinkCrafted(itemLink)
 	data.unique = IsItemLinkUnique(itemLink)

-	data.unknownself, data.unknownothers = self.CSL:isUnknown(itemLink)
+	data.unknownself, data.unknownothers = self.CSL:isUnknown(itemLink, uID)
 	return data
 end

diff --git a/InventoryManager.txt b/InventoryManager.txt
index ecb3d10..fd4fac3 100644
--- a/InventoryManager.txt
+++ b/InventoryManager.txt
@@ -2,7 +2,7 @@
 ## APIVersion: 100030
 ## DependsOn: LibAddonMenu-2.0
 ## SavedVariables: IMSavedVars
-## Version: 2.4.4
+## Version: 2.5.0
 ## Author: iwontsay & iFedix
 ## Description: Automatically stash, retrieve and dispose your items with custom rules!

diff --git a/Modules/Extractor.lua b/Modules/Extractor.lua
index 1dd5752..512c660 100644
--- a/Modules/Extractor.lua
+++ b/Modules/Extractor.lua
@@ -1,6 +1,6 @@
 local DEBUG =
--- function() end
-d
+function() end
+-- d

 local function _tr(str)
 	return str
@@ -33,37 +33,46 @@ local wt2tradeskill = {
     [WEAPONTYPE_TWO_HANDED_SWORD]	= CRAFTING_TYPE_BLACKSMITHING,
 }

+local jt2tradeskill = {
+	[EQUIP_TYPE_RING] 				= CRAFTING_TYPE_JEWELRYCRAFTING,
+	[EQUIP_TYPE_NECK] 				= CRAFTING_TYPE_JEWELRYCRAFTING,
+}
+
 local it2tradeskill = {
 	[ITEMTYPE_GLYPH_ARMOR] 			= CRAFTING_TYPE_ENCHANTING,
 	[ITEMTYPE_GLYPH_WEAPON]			= CRAFTING_TYPE_ENCHANTING,
 	[ITEMTYPE_GLYPH_JEWELRY]		= CRAFTING_TYPE_ENCHANTING,
-	[ITEMTYPE_ARMOR]				= { "armorType", at2tradeskill },
-	[ITEMTYPE_WEAPON]				= { "weaponType", wt2tradeskill },
+	[ITEMTYPE_ARMOR]				= { ["equipType"] = jt2tradeskill, ["armorType"] = at2tradeskill },
+	[ITEMTYPE_WEAPON]				= { ["weaponType"] = wt2tradeskill },
 }

-local dt2tradeskill = { "itemType", it2tradeskill }
+local dt2tradeskill = { ["itemType"] = it2tradeskill }

 -- Recurse through the decision tree to get the correct tradeskill for the item
 local function GetItemTradeSkill(data, _used_table)
 	if not _used_table then _used_table = dt2tradeskill end

-	local _key = _used_table[1]
-	local _tab = _used_table[2]
-
-	local entry = _tab[data[_key]]
-
-	if not entry then return nil end
+	for _key, _tab in pairs(_used_table) do
+		local entry = _tab[data[_key]]

-	if type(entry) ~= "table" then return entry end
+		-- Recurse if we see a table with more decision criteria
+		if entry and type(entry) == "table" then return GetItemTradeSkill(data, entry) end

-	return GetItemTradeSkill(data, entry)
+		-- Return if we find a plain value at a leaf
+		if entry then
+			DEBUG("Found!", entry)
+			return entry
+		end
+	end
+
+	return nil
 end

 local function GetTradeskillUsed()
 	if not ZO_EnchantingTopLevelExtractionSlotContainer:IsHidden() then
 		return CRAFTING_TYPE_ENCHANTING
 	elseif not ZO_SmithingTopLevelDeconstructionPanelSlotContainer:IsHidden() then
-		return CRAFTING_TYPE_BLACKSMITHING
+		return CRAFTING_TYPE_BLACKSMITHING -- Includes everything else that is not Enchanting
 	end

 	return nil
@@ -74,7 +83,7 @@ local function filter_for_deconstruction(tradeskill, data)

 		if ts ~= tradeskill then return false end

-		if not CanItemBeSmithingExtractedOrRefined(data.bagId, data.slotId, ts) then return false end
+		if not CanItemBeDeconstructed(data.bagId, data.slotId, ts) then return false end

 		if IM.FCOISL:IsProtectedAction(data.action, data.bagId, data.slotId, ts == CRAFTING_TYPE_ENCHANTING) then return false end

diff --git a/lang/de.lua b/lang/de.lua
index e69b54e..94b6434 100644
--- a/lang/de.lua
+++ b/lang/de.lua
@@ -201,12 +201,12 @@ local lang = {
 	IM_FCOIS_UNMARKED			= "unmarkiert(es)",
 	IM_FCOIS_WITHANYMARK		= "mit einer Markierung",
 	IM_FCOIS_MARKEDASX			= "markiert als <<z:1>>",
-	IM_FCOIS_NOCAREMARK		= "Nicht relevant",
+	IM_FCOIS_NOCAREMARK		= "(irrelevant)",
 	IM_FCOIS_NOMARK			= "Keine Markierung",
 	IM_FCOIS_ANYMARK			= "Irgendeine Markierung",

 	IM_INIT_DETECTED_CS_OLD	= "IM: Altes CraftStore 3.00+ AddOn erkannt. Es ist überholt, bitte aktualisieren Sie auf 'CraftStore Fixed And Improved'",
-	IM_INIT_DETECTED_CS_NEW	= "IM: CraftStore Fixed And Improved AddOn erkannt",
+	IM_INIT_DETECTED_CS_NEW	= "IM: CraftStore AddOn erkannt",
 	IM_INIT_UPDATE_V2_NOTE 	= "Aktualisiere Charakterdaten nach Version 2: Regel 'Verkaufe alle Gegenstände im Müll' hinzugefügt, um altes Verhalten beizubehalten.",
   IM_INIT_UPDATE_V3_NOTE  = "Aktualisiere Charakterdaten nach Version 3: Regeln neu organisiert",

diff --git a/lang/en.lua b/lang/en.lua
index c56cfde..a2ce1be 100644
--- a/lang/en.lua
+++ b/lang/en.lua
@@ -201,12 +201,12 @@ local lang = {
 	IM_FCOIS_UNMARKED			= "unmarked",
 	IM_FCOIS_WITHANYMARK		= "with any mark",
 	IM_FCOIS_MARKEDASX			= "marked as <<z:1>>",
-	IM_FCOIS_NOCAREMARK			= "Don't care",
+	IM_FCOIS_NOCAREMARK			= "(irrelevant)",
 	IM_FCOIS_NOMARK				= "No mark",
 	IM_FCOIS_ANYMARK			= "Any mark",

 	IM_INIT_DETECTED_CS_OLD		= "IM: Old CraftStore 3.00+ detected. It's outdated, please update to 'CraftStore Fixed And Improved'",
-	IM_INIT_DETECTED_CS_NEW		= "IM: CraftStore Fixed And Improved detected",
+	IM_INIT_DETECTED_CS_NEW		= "IM: CraftStore detected",
 	IM_INIT_UPDATE_V2_NOTE 		= "Upgraded character data to version 2: Added sell rule up front to maintain backwards compatibility.",
   IM_INIT_UPDATE_V3_NOTE    = "Upgraded character data to version 3: Reorganized rules",

@@ -218,7 +218,7 @@ local lang = {
 	IM_FCO_STATIC_TXT6			= "gear set 3",
 	IM_FCO_STATIC_TXT7			= "gear set 4",
 	IM_FCO_STATIC_TXT8			= "gear set 5",
-	IM_FCO_STATIC_TXT9			= "deconstruction",
+	IM_FCO_STATIC_TXT9			= "for deconstruction",
 	IM_FCO_STATIC_TXT10			= "for improvement",
 	IM_FCO_STATIC_TXT11			= "for sale at guildstore",
 	IM_FCO_STATIC_TXT12			= "intricate",