Version 2.0.0

willneedit [06-28-17 - 16:59]
Version 2.0.0
 * Added name matching filter
 * Reworked rules representation: Show them in sections
 * Added cross referencing of rule sections
 * Major code cleanup
Filename
CraftStoreLink.lua
FCOISLink.lua
InventoryManager.lua
InventoryManager.txt
ItemSaverLink.lua
Modules/Banking.lua
Modules/Data.lua
Modules/DelayedProcessor.lua
Modules/Extractor.lua
Modules/GuildBanking.lua
Modules/Junker.lua
Modules/Seller.lua
Rulesets.lua
RulesetsV2.lua
UI/ProfileEdit.lua
UI/RuleEdit.lua
UI/Settings.lua
lang/de.lua
lang/en.lua
diff --git a/CraftStoreLink.lua b/CraftStoreLink.lua
index fb98927..43a3cca 100644
--- a/CraftStoreLink.lua
+++ b/CraftStoreLink.lua
@@ -6,6 +6,9 @@ local function _tr(str)
 	return str
 end

+if not InventoryManager then InventoryManager = {} end
+local IM = InventoryManager
+
 local CSL = {}

 local Used_CS
@@ -13,7 +16,7 @@ local Used_CSA

 local hasCS = nil

-InventoryManager.CSL = CSL
+IM.CSL = CSL

 local function SplitLink(link,nr)
 	local split = {SplitString(':', link)}
diff --git a/FCOISLink.lua b/FCOISLink.lua
index 446811a..ed76912 100644
--- a/FCOISLink.lua
+++ b/FCOISLink.lua
@@ -5,7 +5,10 @@ d
 local function _tr(str)
     return str
 end
-
+
+if not InventoryManager then InventoryManager = {} end
+local IM = InventoryManager
+
 local TXT_NO_CARE
 local TXT_NO_MARK
 local TXT_ANY_MARK
@@ -21,7 +24,7 @@ local hasFCOIS = nil
 local staticIconList = nil
 local protection_fns = nil

-InventoryManager.FCOISL = FCOISL
+IM.FCOISL = FCOISL

 local DIList = nil
 local DIChoices = nil
@@ -52,13 +55,13 @@ function FCOISL:hasAddon()
         }

         protected_actions = {
-            [InventoryManager.ACTION_DESTROY]       =  FCOIS.IsDestroyLocked,
-            [InventoryManager.ACTION_SELL]          = {
+            [IM.ACTION_DESTROY]       =  FCOIS.IsDestroyLocked,
+            [IM.ACTION_SELL]          = {
                 [false] = FCOIS.IsVendorSellLocked,
                 [true]  = FCOIS.IsFenceSellLocked
             },
-            [InventoryManager.ACTION_LAUNDER]       =  FCOIS.IsLaunderLocked,
-            [InventoryManager.ACTION_DECONSTRUCT]   = {
+            [IM.ACTION_LAUNDER]       =  FCOIS.IsLaunderLocked,
+            [IM.ACTION_DECONSTRUCT]   = {
                 [false] = FCOIS.IsDeconstructionLocked,
                 [true]  = FCOIS.IsEnchantingExtractionLocked
             },
diff --git a/InventoryManager.lua b/InventoryManager.lua
index 97274f9..fe32149 100644
--- a/InventoryManager.lua
+++ b/InventoryManager.lua
@@ -6,47 +6,29 @@ local function _tr(str)
 	return str
 end

-InventoryManager = {}
-
+if not InventoryManager then InventoryManager = {} end
 local IM = InventoryManager

-InventoryManager.LAM = LibStub:GetLibrary("LibAddonMenu-2.0")
+IM.LAM = LibStub:GetLibrary("LibAddonMenu-2.0")

-InventoryManager.name = "InventoryManager"
-InventoryManager.loadedAddons = {}
+IM.name = "InventoryManager"
+IM.loadedAddons = {}

 -- The current inventory we're working on
-InventoryManager.currentInventory = nil
-InventoryManager.currentBagType = nil
+IM.currentInventory = nil
+IM.currentBagType = nil

 -- The current ruleset we're working with
-InventoryManager.currentRuleset = { }
-
-function InventoryManager:ProcessSingleItem(dryrun, data)
-	local action = data.action
-
-	if not dryrun then
-		-- Destroying is only done as an afterthought, junk at most for now.
-		if action == self.ACTION_DESTROY then
-			action = self.ACTION_JUNK
-		end
+IM.currentRuleset = { }

-		if action == self.ACTION_JUNK then
-			SetItemIsJunk(data.bagId, data.slotId, true)
-		elseif action == self.ACTION_SELL then
-			SellInventoryItem(data.bagId, data.slotId, data.count )
-		elseif action == self.ACTION_LAUNDER then
-			LaunderItem(data.bagId, data.slotId, data.count )
-		elseif action == self.ACTION_GB_STASH then
-			TransferToGuildBank(data.bagId, data.slotId)
-		elseif action == self.ACTION_GB_RETRIEVE then
-			TransferFromGuildBank(data.slotId)
-		end
-	end
-	IM:ReportAction(data, dryrun, action, data.index, data.text)
+function IM:ProcessSingleItem(dryrun, data)
+	if not dryrun then IM.actionfunctions[data.action](data) end
+	IM:ReportAction(data, dryrun, data.action, data.index, data.text)
 end

-function InventoryManager:ReportAction(data, dryrun, action, rIndex, rString)
+function IM:ReportAction(data, dryrun, action, rIndex, rString)
+  if not rIndex then return end
+
 	local index = (dryrun and 0) or 1
 	if self.FCOISL:IsProtectedAction(data.action, data.bagId, data.slotId) then
 		index = 2
@@ -59,14 +41,14 @@ function InventoryManager:ReportAction(data, dryrun, action, rIndex, rString)
 			rString or ""))
 end

-function InventoryManager:SetCurrentInventory(bagType)
+function IM:SetCurrentInventory(bagType)
 	self.currentInventory = SHARED_INVENTORY:GetOrCreateBagCache(bagType)

 	self.currentBagType = bagType

 end

-function InventoryManager:SetShownInventory()
+function IM:SetShownInventory()
 	local bagType = nil

 	if SCENE_MANAGER.currentScene == SCENE_MANAGER.scenes.inventory then
@@ -86,7 +68,7 @@ function InventoryManager:SetShownInventory()
 	return bagType
 end

-function InventoryManager:GetItemData(slotId, _inv)
+function IM:GetItemData(slotId, _inv)
 	local data = {}
 	local inv = nil

@@ -137,30 +119,23 @@ function InventoryManager:GetItemData(slotId, _inv)
 	return data
 end

-function InventoryManager:listrules()
-	CHAT_SYSTEM:AddMessage(GetString(IM_LIST_NUM_RULES) .. #InventoryManager.currentRuleset.rules)
-
-	for i = 1, #InventoryManager.currentRuleset.rules, 1 do
-		if not InventoryManager.currentRuleset.rules[i] then
-			break
-		end
-		CHAT_SYSTEM:AddMessage(GetString(IM_LIST_RULE) .. i .. ":" .. InventoryManager.currentRuleset.rules[i]:ToString())
-	end
+function IM:listrules()
+  IM.currentRuleset:List()
 end

-function InventoryManager:dryrun()
+function IM:dryrun()
 	self:WorkBackpack(true)
 end

-function InventoryManager:run()
+function IM:run()
 	self:WorkBackpack(false)
 end

-function InventoryManager:OpenSettings()
+function IM:OpenSettings()
 	self.LAM:OpenToPanel(self.UI.panel)
 end

-function InventoryManager:help()
+function IM:help()
 	-- self:SetCurrentInventory(BAG_BACKPACK)
 	-- for i, entry in pairs(self.currentInventory) do
 		-- local knownString = ""
@@ -179,7 +154,7 @@ function InventoryManager:help()
 	CHAT_SYSTEM:AddMessage("/im settings  - Open up the settings menu")
 end

-function InventoryManager:SlashCommand(argv)
+function IM:SlashCommand(argv)
     local options = {}
     local searchResult = { string.match(argv,"^(%S*)%s*(.-)$") }
     for i,v in pairs(searchResult) do
@@ -203,16 +178,16 @@ function InventoryManager:SlashCommand(argv)
 	end
 end

-InventoryManager.UI = { }
-InventoryManager.UI.RuleEdit = { }
-InventoryManager.UI.ProfileEdit = { }
-InventoryManager.UI.Settings = { }
+IM.UI = { }
+IM.UI.RuleEdit = { }
+IM.UI.ProfileEdit = { }
+IM.UI.Settings = { }

-local RuleEdit = InventoryManager.UI.RuleEdit
-local ProfileEdit = InventoryManager.UI.ProfileEdit
-local Settings = InventoryManager.UI.Settings
+local RuleEdit = IM.UI.RuleEdit
+local ProfileEdit = IM.UI.ProfileEdit
+local Settings = IM.UI.Settings

-function InventoryManager:InitializeUI()
+function IM:InitializeUI()
 	local panelData = {
 		type = "panel",
 		name = "InventoryManager",
@@ -245,40 +220,28 @@ function InventoryManager:InitializeUI()
 	self.LAM:RegisterOptionControls("iwontsayInventoryManager", mainPanel)
 end

-local function ctorandload(ctor, ctorob, data)
-	local _new = ctor(ctorob)
-	for k,v in pairs(data) do
-		if v then _new[k] = v end
-	end
-	return _new
-end
-
-local function loadRule(ruleData)
-	return ctorandload(InventoryManager.IM_Ruleset.NewRule, InventoryManager.IM_Ruleset, ruleData)
-end
-
-local function loadRulelist(rulelistData)
-	local _new = { }
-	for k,v in pairs(rulelistData) do
-		_new[k] = loadRule(v)
-	end
-	return _new
-end
-
 local function loadProfile(profileData)
 	local _new = { }
 	for k,v in pairs(profileData) do
-		_new[k] = { }
-		_new[k]["name"] = v["name"]
-		_new[k]["rules"] = loadRulelist(v["rules"])
+		_new[k] = IM.IM_RulesetV2.Clone(v)
+    _new[k].name = v.name
+    _new[k].settings = profileData.settings or { }
+    _new[k].settings.Version = 3
 	end
 	return _new
 end

-function InventoryManager:Update()
+function IM:Update()
 	local version = self.settings.Version or 1
+
+  if version < 3 then
+    self.currentRuleset         = IM.IM_Ruleset:New()
+    self.currentRuleset.rules	  = self.charVariables.currentRules or { }
+    self.currentRuleset         = self.currentRuleset:Clone()
+  end
+
 	if version < 2 then
-		local _rule = self.IM_Ruleset:NewRule()
+		local _rule = self.IM_Rule:New()
 		_rule.action = self.ACTION_SELL
 		_rule.junk = true
 		local rs = self.currentRuleset.rules
@@ -286,25 +249,29 @@ function InventoryManager:Update()
 		CHAT_SYSTEM:AddMessage(GetString(IM_INIT_UPDATE_V2_NOTE))
 	end

-	self.settings.Version = 2
+  if version < 3 then
+    self.currentRuleset         = IM.IM_RulesetV2.Clone(self.currentRuleset)
+		CHAT_SYSTEM:AddMessage(GetString(IM_INIT_UPDATE_V3_NOTE))
+  end
+
+	self.settings.Version = 3
 	self:Save()
 end

-function InventoryManager:Init()
-	self.currentRuleset			= self.IM_Ruleset:New()
-
+function IM:Init()
+
 	self.charDefaults = {
-		["currentRules"]	= self.currentRuleset["rules"],
-		["settings"]		= {
+		["settings"]		    = {
 			["destroyThreshold"]	= 5,
-			["bankMoveDelay"]		= 20,
-			["bankInitDelay"]		= 1000,
+			["bankMoveDelay"]		  = 20,
+			["bankInitDelay"]	  	= 1000,
 			["statusChangeDelay"]	= 20,
-			["maxGold"]				= 5000,
-			["minGold"]				= 1000,
-			["maxTV"]				= 10,
-			["minTV"]				= 0,
-			["autosell"]			= true,
+			["maxGold"]		    		= 5000,
+			["minGold"]		    		= 1000,
+			["maxTV"]			      	= 10,
+			["minTV"]			      	= 0,
+			["autosell"]	    		= true,
+      ["Version"]           = 3,
 		}
 	}

@@ -324,12 +291,17 @@ function InventoryManager:Init()
 		nil,
 		self.charDefaults)

-	self.Profiles 				= loadProfile(self.accVariables.Profiles)
-	self.currentRuleset.rules	= loadRulelist(self.charVariables.currentRules)
-	self.settings				= self.charVariables.settings
+	self.Profiles 		      		= loadProfile(self.accVariables.Profiles)
+	self.presetProfiles			    = loadProfile(self.presetProfiles)
+
+  if self.charVariables.currentRuleset then
+    self.currentRuleset = IM.IM_RulesetV2.Clone(self.charVariables.currentRuleset)
+  else
+    self.currentRuleset = IM.IM_RulesetV2:New()
+  end
+
+	self.settings				        = self.charVariables.settings

-	self.presetProfiles			= loadProfile(self.presetProfiles)
-
 	self.CSL.hasCSAddon()
 	self.FCOISL:hasAddon()

@@ -341,20 +313,20 @@ function InventoryManager:Init()

 end

-function InventoryManager:Save()
-	self.charVariables.settings		= self.settings
-	self.charVariables.currentRules	= self.currentRuleset.rules
-	self.charVariables.Profiles		= nil
+function IM:Save()
+	self.charVariables.settings		      = self.settings
+	self.charVariables.currentRuleset	  = self.currentRuleset
+	self.charVariables.Profiles		      = nil

-	self.accVariables.Profiles 		= self.Profiles
-	self.accVariables.currentRules	= nil
+	self.accVariables.Profiles 		      = self.Profiles
+	self.accVariables.currentRules	    = nil
 end

 local function OnAddOnLoaded(eventCode, addonName)
-	if addonName == InventoryManager.name then
-		InventoryManager:Init()
+	if addonName == IM.name then
+		IM:Init()
 	else
-		InventoryManager.loadedAddons[addonName] = true
+		IM.loadedAddons[addonName] = true
 	end
 end

@@ -364,8 +336,8 @@ local function OnPCCreated()
 	Settings:PopulateUI()
 end

-EVENT_MANAGER:RegisterForEvent(InventoryManager.name, EVENT_ADD_ON_LOADED, OnAddOnLoaded)
+EVENT_MANAGER:RegisterForEvent(IM.name, EVENT_ADD_ON_LOADED, OnAddOnLoaded)

-SLASH_COMMANDS["/im"] = function(argv) InventoryManager:SlashCommand(argv) end
+SLASH_COMMANDS["/im"] = function(argv) IM:SlashCommand(argv) end

 CALLBACK_MANAGER:RegisterCallback("LAM-PanelControlsCreated", OnPCCreated)
diff --git a/InventoryManager.txt b/InventoryManager.txt
index 24e01fb..4663acc 100644
--- a/InventoryManager.txt
+++ b/InventoryManager.txt
@@ -2,7 +2,7 @@
 ## APIVersion: 100019
 ## OptionalDependsOn: LibAddonMenu-2.0
 ## SavedVariables: IMSavedVars
-## Version: 1.5.1
+## Version: 2.0.0
 ## Author: iwontsay
 ## Description: iwontsay's Inventory Manager

@@ -25,8 +25,8 @@ libs/LibAddonMenu-2.0/controls/slider.lua
 libs/LibAddonMenu-2.0/controls/texture.lua

 InventoryManager.lua
-Modules/DelayedProcessor.lua
 Modules/Data.lua
+Modules/DelayedProcessor.lua
 Modules/Banking.lua
 Modules/GuildBanking.lua
 Modules/Junker.lua
@@ -38,6 +38,7 @@ FCOISLink.lua
 ItemSaverLink.lua

 Rulesets.lua
+RulesetsV2.lua
 UI/RuleEdit.lua
 UI/ProfileEdit.lua
 UI/Settings.lua
diff --git a/ItemSaverLink.lua b/ItemSaverLink.lua
index 1c1541c..d0defc8 100644
--- a/ItemSaverLink.lua
+++ b/ItemSaverLink.lua
@@ -6,6 +6,9 @@ local function _tr(str)
     return str
 end

+if not InventoryManager then InventoryManager = {} end
+local IM = InventoryManager
+
 local TXT_NO_CARE
 local TXT_NO_MARK
 local TXT_ANY_MARK
@@ -15,7 +18,6 @@ local I_NO_MARK = -2
 local I_ANY_MARK = -1

 local ISL = { }
-local IM = InventoryManager

 IM.ISL = ISL;

diff --git a/Modules/Banking.lua b/Modules/Banking.lua
index f0c5910..8b00dbd 100644
--- a/Modules/Banking.lua
+++ b/Modules/Banking.lua
@@ -6,10 +6,12 @@ local function _tr(str)
 	return str
 end

+if not InventoryManager then InventoryManager = {} end
 local IM = InventoryManager

 local RevCaches = nil
 local Empties = nil
+local Moves = nil

 local function CreateReverseCache(bagType)
 	local _revCache = { }
@@ -97,7 +99,7 @@ end
 -- We try the subscriber bank before the regular one, to keep the slots of the regular one free in case
 -- someone unsubs.
 local function FindTargetSlot(srcBagType, srcSlotId, tgtBagType)
-	local curStack, maxStack = GetSlotStackSize(srcBagType, srcSlotId)
+	local curStack, _ = GetSlotStackSize(srcBagType, srcSlotId)
 	local id = GetItemInstanceId(srcBagType, srcSlotId)

 	local tgtSlotId = nil
@@ -136,7 +138,7 @@ local function FindTargetSlot(srcBagType, srcSlotId, tgtBagType)
 	if #empties == 0 then return false, -1, 0, tgtBagType end

 	-- It's a complete move, remove the empty slot from the target list
-	local tgtSlotId = empties[#empties]
+	tgtSlotId = empties[#empties]
 	empties[#empties] = nil

 	UpdateCaches(srcBagType, srcSlotId, tgtBagType, tgtSlotId, curStack)
@@ -199,11 +201,11 @@ local function CalculateMoves()

 	InventoryManager.currentRuleset:ResetCounters()

-    Moves = {
-		[BAG_BACKPACK] = CollectSingleDirection(IM.ACTION_STASH, BAG_BACKPACK),
-		[BAG_BANK] = CollectSingleDirection(IM.ACTION_RETRIEVE, BAG_BANK),
+  Moves = {
+		[BAG_BACKPACK]        = CollectSingleDirection(IM.ACTION_STASH, BAG_BACKPACK),
+		[BAG_BANK]            = CollectSingleDirection(IM.ACTION_RETRIEVE, BAG_BANK),
 		[BAG_SUBSCRIBER_BANK] = CollectSingleDirection(IM.ACTION_RETRIEVE, BAG_SUBSCRIBER_BANK),
-    }
+  }

 	-- We alternate between stashing and retrieving to minimize the chance of one
 	-- of the inventories running full.
@@ -244,7 +246,7 @@ end

 InventoryManager.moveStatus = nil

-function ProcessMove(move)
+local function ProcessMove(move)
 	local bagIdFrom = move["srcbag"]
 	local slotIdFrom = move["srcslot"]
 	local bagIdTo = move["tgtbag"]
@@ -254,7 +256,7 @@ function ProcessMove(move)

 	InventoryManager.currentBagType = bagIdFrom
 	local data = InventoryManager:GetItemData(slotIdFrom, SHARED_INVENTORY:GetOrCreateBagCache(bagIdFrom))
-	InventoryManager:ReportAction(data, false, action)
+	InventoryManager:ReportAction(data, false, action, "", "")

 	if IsProtectedFunction("RequestMoveItem") then
 		CallSecureProtected("RequestMoveItem", bagIdFrom, slotIdFrom, bagIdTo, slotIdTo, qtyToMove)
diff --git a/Modules/Data.lua b/Modules/Data.lua
index 9504710..687b171 100644
--- a/Modules/Data.lua
+++ b/Modules/Data.lua
@@ -1,7 +1,10 @@

+if not InventoryManager then InventoryManager = {} end
+local IM = InventoryManager
+
 local function generateFiltertypes()
 	local _new = { }
-	for _, f in pairs(InventoryManager.filterorder) do
+	for _, f in pairs(IM.filterorder) do
 		local _innernew = { }
 		for _, ff in pairs(f[2]) do
 			_innernew[ff[1]] = ff[2]
@@ -11,26 +14,26 @@ local function generateFiltertypes()
 	return _new
 end

-InventoryManager.IM_Ruleset = { }
+if not IM.IM_Ruleset then IM.IM_Ruleset = ZO_Object:Subclass() end

-local IM_Ruleset = InventoryManager.IM_Ruleset
+local IM_Ruleset = IM.IM_Ruleset

-function InventoryManager:getIQString(itemQuality)
+function IM:getIQString(itemQuality)
 	return GetItemQualityColor(itemQuality):Colorize(GetString("SI_ITEMQUALITY", itemQuality))
 end

-InventoryManager.ACTION_KEEP		=  0
-InventoryManager.ACTION_JUNK		=  1
-InventoryManager.ACTION_DESTROY 	=  2
-InventoryManager.ACTION_SELL		=  3
-InventoryManager.ACTION_LAUNDER		=  4
-InventoryManager.ACTION_DECONSTRUCT	=  5
-InventoryManager.ACTION_LOCK		=  6
-InventoryManager.ACTION_UNLOCK		=  7
-InventoryManager.ACTION_STASH		=  10
-InventoryManager.ACTION_GB_STASH	=  11
-InventoryManager.ACTION_RETRIEVE	=  20
-InventoryManager.ACTION_GB_RETRIEVE	=  21
+IM.ACTION_KEEP		=  0
+IM.ACTION_JUNK		=  1
+IM.ACTION_DESTROY 	=  2
+IM.ACTION_SELL		=  3
+IM.ACTION_LAUNDER		=  4
+IM.ACTION_DECONSTRUCT	=  5
+IM.ACTION_LOCK		=  6
+IM.ACTION_UNLOCK		=  7
+IM.ACTION_STASH		=  10
+IM.ACTION_GB_STASH	=  11
+IM.ACTION_RETRIEVE	=  20
+IM.ACTION_GB_RETRIEVE	=  21

 IM_Ruleset.ITEM_TRAIT_TYPE_ANY				= -1
 IM_Ruleset.ITEM_TRAIT_TYPE_ANYUNKOTHERS		= -2
@@ -38,7 +41,7 @@ IM_Ruleset.ITEM_TRAIT_TYPE_ANYUNKNOWN		= -3
 IM_Ruleset.ITEM_TRAIT_TYPE_NOTRAIT			= -4


-InventoryManager.filterorder = {
+IM.filterorder = {
 		{ "IM_FILTER_ANY" 			, {
 			{ "IM_FILTERSPEC_ANY"			, { } },
 		} },
@@ -102,31 +105,58 @@ InventoryManager.filterorder = {
 		} },
 }

-InventoryManager.filtertypes = generateFiltertypes()
+IM.filtertypes = generateFiltertypes()
+
+IM.qualityorder = {
+	{ IM:getIQString(ITEM_QUALITY_TRASH), 		ITEM_QUALITY_TRASH },
+	{ IM:getIQString(ITEM_QUALITY_NORMAL), 	ITEM_QUALITY_NORMAL },
+	{ IM:getIQString(ITEM_QUALITY_MAGIC), 		ITEM_QUALITY_MAGIC },
+	{ IM:getIQString(ITEM_QUALITY_ARCANE), 	ITEM_QUALITY_ARCANE },
+	{ IM:getIQString(ITEM_QUALITY_ARTIFACT), 	ITEM_QUALITY_ARTIFACT },
+	{ IM:getIQString(ITEM_QUALITY_LEGENDARY), 	ITEM_QUALITY_LEGENDARY }
+}

-InventoryManager.qualityorder = {
-	{ InventoryManager:getIQString(ITEM_QUALITY_TRASH), 		ITEM_QUALITY_TRASH },
-	{ InventoryManager:getIQString(ITEM_QUALITY_NORMAL), 	ITEM_QUALITY_NORMAL },
-	{ InventoryManager:getIQString(ITEM_QUALITY_MAGIC), 		ITEM_QUALITY_MAGIC },
-	{ InventoryManager:getIQString(ITEM_QUALITY_ARCANE), 	ITEM_QUALITY_ARCANE },
-	{ InventoryManager:getIQString(ITEM_QUALITY_ARTIFACT), 	ITEM_QUALITY_ARTIFACT },
-	{ InventoryManager:getIQString(ITEM_QUALITY_LEGENDARY), 	ITEM_QUALITY_LEGENDARY }
+IM.actionorder = {
+	{ IM.ACTION_JUNK },
+	{ IM.ACTION_DESTROY },
+	{ IM.ACTION_STASH },
+	{ IM.ACTION_RETRIEVE },
+	{ IM.ACTION_GB_STASH },
+	{ IM.ACTION_GB_RETRIEVE },
+	{ IM.ACTION_SELL },
+	{ IM.ACTION_LAUNDER },
+	{ IM.ACTION_DECONSTRUCT },
 }

-InventoryManager.actionorder = {
-	{ InventoryManager.ACTION_KEEP },
-	{ InventoryManager.ACTION_JUNK },
-	{ InventoryManager.ACTION_DESTROY },
-	{ InventoryManager.ACTION_STASH },
-	{ InventoryManager.ACTION_RETRIEVE },
-	{ InventoryManager.ACTION_GB_STASH },
-	{ InventoryManager.ACTION_GB_RETRIEVE },
-	{ InventoryManager.ACTION_SELL },
-	{ InventoryManager.ACTION_LAUNDER },
-	{ InventoryManager.ACTION_DECONSTRUCT },
+IM.xreforder = {
+	{ IM.ACTION_KEEP },
+	{ IM.ACTION_JUNK },
+	{ IM.ACTION_DESTROY },
+	{ IM.ACTION_STASH },
+	{ IM.ACTION_RETRIEVE },
+	{ IM.ACTION_GB_STASH },
+	{ IM.ACTION_GB_RETRIEVE },
+	{ IM.ACTION_SELL },
+	{ IM.ACTION_LAUNDER },
+	{ IM.ACTION_DECONSTRUCT },
+}
+
+IM.actionfunctions = {
+  [IM.ACTION_KEEP]		    =  function(data) end,  -- Do nothing
+  [IM.ACTION_JUNK]		    =  function(data) SetItemIsJunk(data.bagId, data.slotId, true) end,
+  [IM.ACTION_DESTROY] 	  =  function(data) DestroyItem(data.bagId, data.slotId) end,
+  [IM.ACTION_SELL]		    =  function(data) SellInventoryItem(data.bagId, data.slotId, data.count) end,
+  [IM.ACTION_LAUNDER]		  =  function(data) LaunderItem(data.bagId, data.slotId, data.count) end,
+  [IM.ACTION_GB_STASH]	  =  function(data) TransferToGuildBank(data.bagId, data.slotId) end,
+  [IM.ACTION_GB_RETRIEVE]	=  function(data) TransferFromGuildBank(data.slotId) end,
+  [IM.ACTION_DECONSTRUCT]	=  function(data) end,  -- Added in Extractor.lua
+  [IM.ACTION_STASH]		    =  function(data) end,  -- Bank moves are handled differently
+  [IM.ACTION_RETRIEVE]	  =  function(data) end,
+  [IM.ACTION_LOCK]		    =  nil,                 -- RFU
+  [IM.ACTION_UNLOCK]		  =  nil,
 }

-InventoryManager.traitsorder = {
+IM.traitsorder = {
 	["IM_FILTER_ANY"] = {
 		0, -- Redefined to "don't care about traits"
 		IM_Ruleset.ITEM_TRAIT_TYPE_NOTRAIT,
@@ -195,7 +225,7 @@ InventoryManager.traitsorder = {
 	},
 }

-InventoryManager.presetProfiles = {
+IM.presetProfiles = {
 	[1] =
 	{
 		["rules"] =
@@ -206,11 +236,8 @@ InventoryManager.presetProfiles = {
 				["filterType"] = "IM_FILTER_ANY",
 				["filterSubType"] = "IM_FILTERSPEC_ANY",
 				["action"] = 10,
-				["New"] = nil, -- invalid value type [function] used
 				["traitType"] = -2,
 				["maxQuality"] = 5,
-				["Filter"] = nil, -- invalid value type [function] used
-				["ToString"] = nil, -- invalid value type [function] used
 			},
 			[2] =
 			{
@@ -218,11 +245,8 @@ InventoryManager.presetProfiles = {
 				["filterType"] = "IM_FILTER_ANY",
 				["filterSubType"] = "IM_FILTERSPEC_ANY",
 				["action"] = 10,
-				["New"] = nil, -- invalid value type [function] used
 				["traitType"] = 9,
 				["maxQuality"] = 5,
-				["Filter"] = nil, -- invalid value type [function] used
-				["ToString"] = nil, -- invalid value type [function] used
 			},
 			[3] =
 			{
@@ -230,11 +254,8 @@ InventoryManager.presetProfiles = {
 				["filterType"] = "IM_FILTER_ANY",
 				["filterSubType"] = "IM_FILTERSPEC_ANY",
 				["action"] = 1,
-				["New"] = nil, -- invalid value type [function] used
 				["traitType"] = 10,
 				["maxQuality"] = 5,
-				["Filter"] = nil, -- invalid value type [function] used
-				["ToString"] = nil, -- invalid value type [function] used
 			},
 			[4] =
 			{
@@ -242,19 +263,13 @@ InventoryManager.presetProfiles = {
 				["filterType"] = "IM_FILTER_ANY",
 				["filterSubType"] = "IM_FILTERSPEC_ANY",
 				["action"] = 20,
-				["New"] = nil, -- invalid value type [function] used
 				["traitType"] = -3,
 				["maxQuality"] = 5,
-				["Filter"] = nil, -- invalid value type [function] used
-				["ToString"] = nil, -- invalid value type [function] used
 			},
 			[5] =
 			{
 				["minQuality"] = 0,
-				["Filter"] = nil, -- invalid value type [function] used
 				["action"] = 1,
-				["New"] = nil, -- invalid value type [function] used
-				["ToString"] = nil, -- invalid value type [function] used
 				["maxQuality"] = 5,
 				["filterSubType"] = "IM_FILTERSPEC_TRASH",
 				["filterType"] = "IM_FILTER_MISC",
@@ -272,11 +287,8 @@ InventoryManager.presetProfiles = {
 				["filterType"] = "IM_FILTER_ANY",
 				["filterSubType"] = "IM_FILTERSPEC_ANY",
 				["action"] = 20,
-				["New"] = nil, -- invalid value type [function] used
 				["traitType"] = -3,
 				["maxQuality"] = 5,
-				["Filter"] = nil, -- invalid value type [function] used
-				["ToString"] = nil, -- invalid value type [function] used
 			},
 			[2] =
 			{
@@ -284,11 +296,8 @@ InventoryManager.presetProfiles = {
 				["filterType"] = "IM_FILTER_ANY",
 				["filterSubType"] = "IM_FILTERSPEC_ANY",
 				["action"] = 10,
-				["New"] = nil, -- invalid value type [function] used
 				["traitType"] = -2,
 				["maxQuality"] = 5,
-				["Filter"] = nil, -- invalid value type [function] used
-				["ToString"] = nil, -- invalid value type [function] used
 			},
 			[3] =
 			{
@@ -296,11 +305,8 @@ InventoryManager.presetProfiles = {
 				["filterType"] = "IM_FILTER_ANY",
 				["filterSubType"] = "IM_FILTERSPEC_ANY",
 				["action"] = 20,
-				["New"] = nil, -- invalid value type [function] used
 				["traitType"] = 9,
 				["maxQuality"] = 5,
-				["Filter"] = nil, -- invalid value type [function] used
-				["ToString"] = nil, -- invalid value type [function] used
 			},
 			[4] =
 			{
@@ -308,19 +314,13 @@ InventoryManager.presetProfiles = {
 				["filterType"] = "IM_FILTER_ANY",
 				["filterSubType"] = "IM_FILTERSPEC_ANY",
 				["action"] = 1,
-				["New"] = nil, -- invalid value type [function] used
 				["traitType"] = 10,
 				["maxQuality"] = 5,
-				["Filter"] = nil, -- invalid value type [function] used
-				["ToString"] = nil, -- invalid value type [function] used
 			},
 			[5] =
 			{
 				["minQuality"] = 0,
-				["Filter"] = nil, -- invalid value type [function] used
 				["action"] = 1,
-				["New"] = nil, -- invalid value type [function] used
-				["ToString"] = nil, -- invalid value type [function] used
 				["maxQuality"] = 5,
 				["filterSubType"] = "IM_FILTERSPEC_TRASH",
 				["filterType"] = "IM_FILTER_MISC",
diff --git a/Modules/DelayedProcessor.lua b/Modules/DelayedProcessor.lua
index 964b007..7dfae20 100644
--- a/Modules/DelayedProcessor.lua
+++ b/Modules/DelayedProcessor.lua
@@ -2,6 +2,7 @@ local DEBUG =
 -- function() end
 d

+if not InventoryManager then InventoryManager = {} end
 local IM = InventoryManager

 local _Pending = nil
diff --git a/Modules/Extractor.lua b/Modules/Extractor.lua
index 03f2554..1dd5752 100644
--- a/Modules/Extractor.lua
+++ b/Modules/Extractor.lua
@@ -6,6 +6,7 @@ local function _tr(str)
 	return str
 end

+if not InventoryManager then InventoryManager = {} end
 local IM = InventoryManager

 local _waitforpanel = nil
@@ -80,17 +81,18 @@ local function filter_for_deconstruction(tradeskill, data)
 		return true
 end

-local function extract_single_item(tradeskill, data)
-	if tradeskill == CRAFTING_TYPE_ENCHANTING then
+local used_tradeskill = nil
+IM.actionfunctions[IM.ACTION_DECONSTRUCT] = function(data)
+	if used_tradeskill == CRAFTING_TYPE_ENCHANTING then
 		ExtractEnchantingItem(data.bagId, data.slotId)
 	else
 		ExtractOrRefineSmithingItem(data.bagId, data.slotId)
 	end
-	IM:ReportAction(data, false, data.action, data.index, data.text)
 end

 local function InitDeconstruction(tradeskill)
-	InventoryManager.currentRuleset:ResetCounters()
+  used_tradeskill = tradeskill
+	IM.currentRuleset:ResetCounters()

 	local list = IM:CreateInventoryList(BAG_BACKPACK, IM.ACTION_DECONSTRUCT,
 		function(data) return filter_for_deconstruction(tradeskill, data) end)
@@ -104,11 +106,11 @@ local function InitDeconstruction(tradeskill)
 		list)

 	IM:DoEventProcessing(list,
-		function(data) return extract_single_item(tradeskill, data) end,
+		function(data) IM:ProcessSingleItem(false, data) end,
 		function() end,
 		EVENT_CRAFT_COMPLETED,
 		EVENT_END_CRAFTING_STATION_INTERACT,
-		InventoryManager.settings.bankMoveDelay)
+		IM.settings.bankMoveDelay)
 end

 local function WaitPanel()
diff --git a/Modules/GuildBanking.lua b/Modules/GuildBanking.lua
index 6556b84..b6c910b 100644
--- a/Modules/GuildBanking.lua
+++ b/Modules/GuildBanking.lua
@@ -6,6 +6,7 @@ local function _tr(str)
 	return str
 end

+if not InventoryManager then InventoryManager = {} end
 local IM = InventoryManager

 local current_gn = nil
diff --git a/Modules/Junker.lua b/Modules/Junker.lua
index 3ed5f32..c9f7818 100644
--- a/Modules/Junker.lua
+++ b/Modules/Junker.lua
@@ -2,50 +2,31 @@ local DEBUG =
 -- function() end
 d

-
+if not InventoryManager then InventoryManager = {} end
 local IM = InventoryManager

-function IM:CheckAndDestroy()
+function IM:CheckAndDestroy(dryrun)
 	if GetNumBagFreeSlots(BAG_BACKPACK) >= IM.settings.destroyThreshold then
 		return
 	end

-	InventoryManager.currentRuleset:ResetCounters()
-
-	self:SetCurrentInventory(BAG_BACKPACK)
-	for i,_ in pairs(self.currentInventory) do
-		local data = self:GetItemData(i)
-		local action, index, text = IM.currentRuleset:Match(data)
-		if action == self.ACTION_DESTROY and not
-			self.FCOISL:IsProtectedAction(action, BAG_BACKPACK, i) then
-			self:ReportAction(data, false, action, index, text)
-			DestroyItem(BAG_BACKPACK, i)
-		end
-	end
-end
-
-local function filter_for_backpack_action(dryrun, data)
-	if data.action == IM.ACTION_JUNK then
-		return not data.junk
-	end
-	if data.action == IM.ACTION_DESTROY then
-		return true
-	end
-
-	-- List other inventory actions only if it's a dryrun.
-	-- Else we need to get to the specific stations to actually perform them
-	if data.action ~= IM.ACTION_KEEP and data.action ~= IM.ACTION_RETRIEVE and data.action ~= IM.ACTION_GB_RETRIEVE and dryrun then
-		return true
-	end
-	return false
+	IM.currentRuleset:ResetCounters()
+  IM:ProcessBag(BAG_BACKPACK, IM.ACTION_DESTROY,
+    function(data) return data.junk and not IM.FCOISL:IsProtectedAction(data.action, data.bagId, data.slotId) end,
+    function(data) IM:ProcessSingleItem(dryrun, data) end,
+    function() end,
+    IM.settings.statusChangeDelay)
 end

 function IM:WorkBackpack(dryrun)
-	InventoryManager.currentRuleset:ResetCounters()
-	self:ProcessBag(BAG_BACKPACK, nil,
-		function(data) return filter_for_backpack_action(dryrun, data) end,
+  local action = IM.ACTION_JUNK
+  if dryrun then action = nil end
+
+	IM.currentRuleset:ResetCounters()
+	IM:ProcessBag(BAG_BACKPACK, action,
+		function(data) return dryrun or not data.junk end,
 		function(data) IM:ProcessSingleItem(dryrun, data) end,
-		function() IM:CheckAndDestroy() end,
+		function() IM:CheckAndDestroy(dryrun) end,
 		IM.settings.statusChangeDelay)
 end

@@ -56,19 +37,17 @@ function IM:UnJunk()
 end

 function IM:OnInvSlotUpdate(bagId, slotId)
-	self:SetCurrentInventory(bagId)
-	local data = self:GetItemData(slotId)
+	IM:SetCurrentInventory(bagId)
+	local data = IM:GetItemData(slotId)

 	if not data then return end

 	if not self.currentRuleset or not self.currentRuleset.Match then return end

-	data.action, data.index, data.text = self.currentRuleset:Match(data)
-	if filter_for_backpack_action(false, data) then
-		IM:ProcessSingleItem(false, data)
-	end
+	data.action, data.index, data.text = self.currentRuleset:Match(data, IM.ACTION_JUNK)
+  IM:ProcessSingleItem(false, data)

-	self:CheckAndDestroy()
+	IM:CheckAndDestroy()
 end

 local function OnInvSlotUpdate(eventCode, bagId, slotId, isNewItem, itemSoundCategory, inventoryUpdateReason, stackCountChange)
diff --git a/Modules/Seller.lua b/Modules/Seller.lua
index 6f50554..2d6eeea 100644
--- a/Modules/Seller.lua
+++ b/Modules/Seller.lua
@@ -6,20 +6,23 @@ local function _tr(str)
 	return str
 end

+if not InventoryManager then InventoryManager = {} end
+local IM = InventoryManager
+
 local _Gain = nil

 local function do_sell(data, eventCode, itemName, itemQuantity, money)
 	if eventCode ~= nil then
 		_Gain = _Gain + money
 	end
-	InventoryManager:ProcessSingleItem(false, data)
+	IM:ProcessSingleItem(false, data)
 end

 local function filter_for_sale(fence, data)
 	if data.stolen ~= fence then return false end

-	if InventoryManager.FCOISL:IsProtectedAction(
-		InventoryManager.ACTION_SELL,
+	if IM.FCOISL:IsProtectedAction(
+		IM.ACTION_SELL,
 		data.bagId,
 		data.slotId,
 		fence) then return false end
@@ -27,14 +30,14 @@ local function filter_for_sale(fence, data)
 end

 local function filter_for_launder(data)
-	if InventoryManager.FCOISL:IsProtectedAction(
-		InventoryManager.ACTION_LAUNDER,
+	if IM.FCOISL:IsProtectedAction(
+		IM.ACTION_LAUNDER,
 		data.bagId,
 		data.slotId) then return false end
 	return true
 end

-function InventoryManager:SellItems(stolen)
+function IM:SellItems(stolen)
 	local list = { }
 	local end_fn = function(abort, eventCode, itemName, itemQuantity, money)
 			if eventCode ~= nil then
@@ -47,39 +50,39 @@ function InventoryManager:SellItems(stolen)
 		if eventCode ~= nil then
 			_Gain = _Gain + money
 		end
-		InventoryManager.currentRuleset:ResetCounters()
-		InventoryManager:EventProcessBag(BAG_BACKPACK, InventoryManager.ACTION_LAUNDER,
+		IM.currentRuleset:ResetCounters()
+		IM:EventProcessBag(BAG_BACKPACK, IM.ACTION_LAUNDER,
 			filter_for_launder,
-			function(data) InventoryManager:ProcessSingleItem(false, data) end,
+			function(data) IM:ProcessSingleItem(false, data) end,
 			function(abort) end_fn(abort) end,
 			EVENT_ITEM_LAUNDER_RESULT,
 			EVENT_CLOSE_STORE,
-			InventoryManager.settings.bankMoveDelay)
+			IM.settings.bankMoveDelay)
 		end

 	_Gain = 0
-	InventoryManager.currentRuleset:ResetCounters()
-	self:EventProcessBag(BAG_BACKPACK, InventoryManager.ACTION_SELL,
+	IM.currentRuleset:ResetCounters()
+	self:EventProcessBag(BAG_BACKPACK, IM.ACTION_SELL,
 		function(data) return filter_for_sale(stolen, data) end,
 		do_sell,
 		((stolen and launder_run) or end_fn),
 		EVENT_SELL_RECEIPT,
 		EVENT_CLOSE_STORE,
-		InventoryManager.settings.bankMoveDelay)
+		IM.settings.bankMoveDelay)

 end

 local function OnOpenStore(eventCode)
-	if InventoryManager.settings.autosell then
-		InventoryManager:SellItems(false)
+	if IM.settings.autosell then
+		IM:SellItems(false)
 	end
 end

 local function OnOpenFence(eventCode)
-	if InventoryManager.settings.autosell then
-		InventoryManager:SellItems(true)
+	if IM.settings.autosell then
+		IM:SellItems(true)
 	end
 end

-EVENT_MANAGER:RegisterForEvent(InventoryManager.name, EVENT_OPEN_STORE, OnOpenStore)
-EVENT_MANAGER:RegisterForEvent(InventoryManager.name, EVENT_OPEN_FENCE, OnOpenFence)
+EVENT_MANAGER:RegisterForEvent(IM.name, EVENT_OPEN_STORE, OnOpenStore)
+EVENT_MANAGER:RegisterForEvent(IM.name, EVENT_OPEN_FENCE, OnOpenFence)
diff --git a/Rulesets.lua b/Rulesets.lua
index e8b4e41..431a822 100644
--- a/Rulesets.lua
+++ b/Rulesets.lua
@@ -7,30 +7,38 @@ local function _tr(str)
 end


-local IM_Rule = {}
-local IM_Ruleset = InventoryManager.IM_Ruleset
+if not InventoryManager then InventoryManager = {} end
+local IM = InventoryManager

-IM_Rule.action		= InventoryManager.ACTION_KEEP
-IM_Rule.minQuality 	= ITEM_QUALITY_TRASH
-IM_Rule.maxQuality 	= ITEM_QUALITY_LEGENDARY
+if not IM.IM_Rule then IM.IM_Rule = ZO_Object:Subclass() end
+local IMR = IM.IM_Rule

-IM_Rule.filterType 		= "IM_FILTER_ANY"
-IM_Rule.filterSubType 	= "IM_FILTERSPEC_ANY"
+if not IM.IM_Ruleset then IM.IM_Ruleset = ZO_Object:Subclass() end
+local IMRS = IM.IM_Ruleset

-function IM_Rule:New()
-	local _new = { }
-
-	for k,v in pairs(self) do
-		_new[k] = v
-	end
+IMR.text = ""
+
+function IMR:New()
+	local rule = ZO_Object.New(self)
+  rule.action		= IM.ACTION_KEEP
+  rule.minQuality 	= ITEM_QUALITY_TRASH
+  rule.maxQuality 	= ITEM_QUALITY_LEGENDARY
+
+  rule.filterType 		= "IM_FILTER_ANY"
+  rule.filterSubType 	= "IM_FILTERSPEC_ANY"
+
+  return rule
+end

-	return _new
+function IMR:Clone()
+  local rule = IMR:New()
+  for k,v in pairs(self) do
+    rule[k] = v
+  end
+  return rule
 end

-function IM_Rule:ToString()
-	local stolenText = ""
-	local traitText = ""
-	local worthlessText = ""
+function IMR:ToString()
 	local qualityRangeText = ""
 	local isSetText = ""
 	local actionText = GetString("IM_ACTIONTXT", self.action)
@@ -47,8 +55,8 @@ function IM_Rule:ToString()
 	if self.traitType then
 		local which = (self.filterType == "IM_FILTER_CONSUMABLE" and 1) or 0
 		if self.traitType < 0 then
-			if self.traitType == IM_Ruleset.ITEM_TRAIT_TYPE_NOTRAIT then which = 2 end
-			local str = (self.traitType == IM_Ruleset.ITEM_TRAIT_TYPE_ANY and "") or GetString("IM_META_TRAIT_TYPE", -self.traitType)
+			if self.traitType == IMRS.ITEM_TRAIT_TYPE_NOTRAIT then which = 2 end
+			local str = (self.traitType == IMRS.ITEM_TRAIT_TYPE_ANY and "") or GetString("IM_META_TRAIT_TYPE", -self.traitType)
 			itemDescription = zo_strformat(
 				GetString("IM_META_TRAIT_TYPE_FORMAT", which),
 				itemDescription,
@@ -70,15 +78,15 @@ function IM_Rule:ToString()
 		itemDescription = GetString(IM_RULETXT_JUNKED) .. " " .. itemDescription
 	end

-	if (InventoryManager.FCOISL:hasAddon() or InventoryManager.ISL:hasAddon()) and self.FCOISMark then
-		if InventoryManager.FCOISL:IsNoMark(self.FCOISMark) then
+	if (IM.FCOISL:hasAddon() or IM.ISL:hasAddon()) and self.FCOISMark then
+		if IM.FCOISL:IsNoMark(self.FCOISMark) then
 			itemDescription = GetString(IM_FCOIS_UNMARKED) .. " " .. itemDescription
-		elseif InventoryManager.FCOISL:IsAnyMark(self.FCOISMark) then
+		elseif IM.FCOISL:IsAnyMark(self.FCOISMark) then
 			itemDescription = itemDescription .. " " .. GetString(IM_FCOIS_WITHANYMARK)
 		else
 			itemDescription = itemDescription .. " " .. zo_strformat(
 				GetString(IM_FCOIS_MARKEDASX),
-				InventoryManager.FCOISL:GetIndexedMark(self.FCOISMark))
+				IM.FCOISL:GetIndexedMark(self.FCOISMark))
 		end
 	end

@@ -90,16 +98,18 @@ function IM_Rule:ToString()
 		isSetText = " " .. GetString(IM_RULETXT_ISSET)
 	end

-	colorMin = GetItemQualityColor(self.minQuality)
-	colorMax = GetItemQualityColor(self.maxQuality)
-
+  itemDescription = zo_strlower(itemDescription)
+  if self.text ~= "" then
+    itemDescription = itemDescription .. " " .. zo_strformat(GetString(IM_RULETXT_TXT), self.text)
+  end
+
 	if self.minQuality == self.maxQuality then
 		qualityRangeText = " " .. zo_strformat(GetString("IM_RULETXT_QUALITY", 1),
-			InventoryManager:getIQString(self.minQuality))
+			IM:getIQString(self.minQuality))
 	elseif self.minQuality ~= ITEM_QUALITY_TRASH or self.maxQuality ~= ITEM_QUALITY_LEGENDARY then
 		qualityRangeText = " " .. zo_strformat(GetString("IM_RULETXT_QUALITY", 2),
-			InventoryManager:getIQString(self.minQuality),
-			InventoryManager:getIQString(self.maxQuality))
+			IM:getIQString(self.minQuality),
+			IM:getIQString(self.maxQuality))
 	end

 	return zo_strformat(GetString(IM_RULETXTFORMAT),
@@ -109,9 +119,9 @@ function IM_Rule:ToString()
 		actionText)
 end

-function IM_Rule:Filter(data)
+function IMR:Filter(data)

-	local filterList = InventoryManager.filtertypes[self.filterType][self.filterSubType]
+	local filterList = IM.filtertypes[self.filterType][self.filterSubType]

 	if #filterList > 0 then
 		local attrName = filterList[1]
@@ -148,13 +158,13 @@ function IM_Rule:Filter(data)

 	-- Ornate, Intricate, ect.
 	if self.traitType then
-		if self.traitType == IM_Ruleset.ITEM_TRAIT_TYPE_ANY then
+		if self.traitType == IMRS.ITEM_TRAIT_TYPE_ANY then
 			if traitType == ITEM_TRAIT_TYPE_NONE then return false end
-		elseif self.traitType == IM_Ruleset.ITEM_TRAIT_TYPE_NOTRAIT then
+		elseif self.traitType == IMRS.ITEM_TRAIT_TYPE_NOTRAIT then
 			if traitType ~= ITEM_TRAIT_TYPE_NONE then return false end
-		elseif self.traitType == IM_Ruleset.ITEM_TRAIT_TYPE_ANYUNKOTHERS then
+		elseif self.traitType == IMRS.ITEM_TRAIT_TYPE_ANYUNKOTHERS then
 			if not data.unknownothers then return false end
-		elseif self.traitType == IM_Ruleset.ITEM_TRAIT_TYPE_ANYUNKNOWN then
+		elseif self.traitType == IMRS.ITEM_TRAIT_TYPE_ANYUNKNOWN then
 			if not data.unknownself then return false end
 		elseif self.traitType ~= traitType then
 			return false
@@ -163,8 +173,8 @@ function IM_Rule:Filter(data)

 	-- FCO ItemSaver marker?
     -- Call with parameters suitable for both API's and let FCOISL sort it out.
-    if (not InventoryManager.FCOISL:FitMark(data.itemInstanceId, self.FCOISMark, data.bagId, data.slotId)) or
-	   (not InventoryManager.ISL:FitMark(data.itemInstanceId, self.FCOISMark, data.bagId, data.slotId)) then return false end
+    if (not IM.FCOISL:FitMark(data.itemInstanceId, self.FCOISMark, data.bagId, data.slotId)) or
+	   (not IM.ISL:FitMark(data.itemInstanceId, self.FCOISMark, data.bagId, data.slotId)) then return false end

 	-- Junked?
 	if self.junk and not data.junk then return false end
@@ -184,33 +194,39 @@ function IM_Rule:Filter(data)
 	-- outside wanted quality range?
 	if data.quality < self.minQuality or data.quality > self.maxQuality  then return false end

+  -- text matching
+  if self.text ~= "" and not string.match(data.name, self.text) then return false end
+
 	return true
 end

-function IM_Ruleset:New()
-	local _new = { }
-
-	for k,v in pairs(self) do
-		_new[k] = v
-	end
+IMRS.version = 1

-	_new["rules"] = { }
-	if self.rules then
-		for k,v in pairs(self.rules) do
-			_new["rules"][k] = v:New()
-		end
-	end
-
-	return _new
+function IMRS:New()
+  local ruleset = ZO_Object.New(self)
+  ruleset.rules = { }
+  return ruleset
+end
+
+function IMRS:Clone()
+  local ruleset = IMRS:New()
+  for k,v in pairs(self) do
+    ruleset[k] = v
+  end
+  ruleset["rules"] = { }
+  for k,v in pairs(self["rules"]) do
+    ruleset["rules"][k] = IMR.Clone(v)
+  end
+  return ruleset
 end

 local ExecCounters = nil

-function IM_Ruleset:ResetCounters()
+function IMRS:ResetCounters()
 	ExecCounters = nil
 end

-function IM_Ruleset:Match(data, action)
+function IMRS:Match(data, action)
 	if not ExecCounters then ExecCounters = { } end

 	for k, v in pairs(self.rules) do
@@ -221,12 +237,12 @@ function IM_Ruleset:Match(data, action)
 		-- If it's stolen, we can't put it in the bank.
 		if res then
 			if data.locked then res = false
-			elseif data.stolen and v.action == InventoryManager.ACTION_STASH then res = false
+			elseif data.stolen and v.action == IM.ACTION_STASH then res = false
 			end
 		end

 		-- If we want a specific action, skip if it's not the one.
-		if action and (action ~= v.action and v.action ~= InventoryManager.ACTION_KEEP) then res = false end
+		if action and (action ~= v.action and v.action ~= IM.ACTION_KEEP) then res = false end

 		-- If we reached the max execution count for that particular rule, skip it.
 		if res and v.maxCount and ExecCounters[k] and ExecCounters[k] >= v.maxCount then
@@ -241,36 +257,20 @@ function IM_Ruleset:Match(data, action)
 		end
 	end

-	return InventoryManager.ACTION_KEEP, nil, nil
+	return IM.ACTION_KEEP, nil, nil
 end

-function IM_Ruleset:NewRule()
-	return IM_Rule:New()
+function IMRS:List()
+	CHAT_SYSTEM:AddMessage(GetString(IM_LIST_NUM_RULES) .. #self.rules)
+
+	for i = 1, #self.rules, 1 do
+		if not self.rules[i] then
+			break
+		end
+		CHAT_SYSTEM:AddMessage(GetString(IM_LIST_RULE) .. i .. ":" .. self.rules[i]:ToString())
+	end
 end

-InventoryManager.IM_Ruleset = IM_Ruleset
-
--- DEBUG CODE
--- Ruleset = IM_Ruleset:New()
-
--- local Rule1 = IM_Ruleset:NewRule()
--- Rule1.filterType = "IM_FILTER_MISC"
--- Rule1.filterSubType = "IM_FILTERSPEC_TRASH"
--- Rule1.action = InventoryManager.ACTION_JUNK
-
--- local Rule2 = IM_Ruleset:NewRule()
--- Rule2.filterType = "IM_FILTER_WEAPON"
--- Rule2.filterSubType = "IM_FILTERSPEC_2H"
--- Rule2.minQuality = ITEM_QUALITY_MAGIC
--- Rule2.action = InventoryManager.ACTION_RETRIEVE
-
--- local Rule3 = IM_Ruleset:NewRule()
--- Rule3.filterType = "IM_FILTER_APPAREL"
--- Rule3.filterSubType = "IM_FILTERSPEC_MEDIUM"
--- Rule3.stolen = true
--- Rule3.action = InventoryManager.ACTION_DESTROY
-
--- Ruleset.rules = { Rule1, Rule2, Rule3 }
--- -- Ruleset.rules = { }
-
--- InventoryManager.currentRuleset = Ruleset
+function IMRS:GetRuleList(action)
+  return self.rules
+end
diff --git a/RulesetsV2.lua b/RulesetsV2.lua
new file mode 100644
index 0000000..33abe0e
--- /dev/null
+++ b/RulesetsV2.lua
@@ -0,0 +1,275 @@
+local DEBUG =
+function() end
+-- d
+
+local function _tr(str)
+	return str
+end
+
+
+if not InventoryManager then InventoryManager = {} end
+local IM = InventoryManager
+
+local VisitedLists = { }
+
+IM.IM_RuleV2 = IM.IM_Rule:Subclass()
+local IMR2 = IM.IM_RuleV2
+
+IM.IM_RulesetV2 = IM.IM_Ruleset:Subclass()
+local IMRS2 = IM.IM_RulesetV2
+
+IMR2.version = 2
+
+function IMR2:New()
+  local rule = IM.IM_Rule.New(self)
+  rule.action = nil
+  return rule
+end
+
+function IMR2:Clone()
+  local rule = IMR2:New()
+    for k,v in pairs(self) do
+    rule[k] = v
+  end
+
+  local action = rule.action
+  rule.action = nil
+
+  return rule, action
+end
+
+function IMR2:ToString()
+	local qualityRangeText = ""
+	local isSetText = ""
+  local formatindex = 1
+
+	local exeCountText = ""
+	if self.maxCount then
+		exeCountText = " " .. zo_strformat(GetString(IM_RULETXT_EXECOUNT), self.maxCount)
+	end
+
+  if self.negate then formatindex = 0 end
+
+  if self.xref and self.xref ~= IM.ACTION_KEEP then
+    formatindex = formatindex + 2
+    local tgtlist = GetString("IM_R2_HEADING", self.xref)
+    return zo_strformat(GetString("IM_R2_FORMAT", formatindex), tgtlist, exeCountText)
+  end
+
+	local itemDescription = zo_strformat(
+		GetString(self.filterSubType),
+			" " .. GetString(self.filterType))
+
+
+
+	if self.traitType then
+		local which = (self.filterType == "IM_FILTER_CONSUMABLE" and 1) or 0
+		if self.traitType < 0 then
+			if self.traitType == IMRS2.ITEM_TRAIT_TYPE_NOTRAIT then which = 2 end
+			local str = (self.traitType == IMRS2.ITEM_TRAIT_TYPE_ANY and "") or GetString("IM_META_TRAIT_TYPE", -self.traitType)
+			itemDescription = zo_strformat(
+				GetString("IM_META_TRAIT_TYPE_FORMAT", which),
+				itemDescription,
+				str)
+		else
+			itemDescription = GetString("SI_ITEMTRAITTYPE", self.traitType) .. " " .. itemDescription
+		end
+	end
+
+	if self.crafted then
+		itemDescription = GetString(IM_RULETXT_CRAFTED) .. " " .. itemDescription
+	end
+
+	if self.worthless then
+		itemDescription = GetString(IM_RULETXT_WORTHLESS) .. " " .. itemDescription
+	end
+
+	if self.junk then
+		itemDescription = GetString(IM_RULETXT_JUNKED) .. " " .. itemDescription
+	end
+
+	if (IM.FCOISL:hasAddon() or IM.ISL:hasAddon()) and self.FCOISMark then
+		if IM.FCOISL:IsNoMark(self.FCOISMark) then
+			itemDescription = GetString(IM_FCOIS_UNMARKED) .. " " .. itemDescription
+		elseif IM.FCOISL:IsAnyMark(self.FCOISMark) then
+			itemDescription = itemDescription .. " " .. GetString(IM_FCOIS_WITHANYMARK)
+		else
+			itemDescription = itemDescription .. " " .. zo_strformat(
+				GetString(IM_FCOIS_MARKEDASX),
+				IM.FCOISL:GetIndexedMark(self.FCOISMark))
+		end
+	end
+
+	if self.stolen then
+		itemDescription = GetString(IM_RULETXT_STOLEN) .. " " .. itemDescription
+	end
+
+	if self.isSet then
+		isSetText = " " .. GetString(IM_RULETXT_ISSET)
+	end
+
+  itemDescription = zo_strlower(itemDescription)
+  if self.text ~= "" then
+    itemDescription = itemDescription .. " " .. zo_strformat(GetString(IM_RULETXT_TXT), self.text)
+  end
+
+	if self.minQuality == self.maxQuality then
+		qualityRangeText = " " .. zo_strformat(GetString("IM_RULETXT_QUALITY", 1),
+			IM:getIQString(self.minQuality))
+	elseif self.minQuality ~= ITEM_QUALITY_TRASH or self.maxQuality ~= ITEM_QUALITY_LEGENDARY then
+		qualityRangeText = " " .. zo_strformat(GetString("IM_RULETXT_QUALITY", 2),
+			IM:getIQString(self.minQuality),
+			IM:getIQString(self.maxQuality))
+	end
+
+	return zo_strformat(GetString("IM_R2_FORMAT", formatindex),
+		itemDescription,
+		qualityRangeText,
+		isSetText,
+		exeCountText)
+end
+
+function IMR2:Filter(data)
+  if self.xref and self.xref ~= IM.ACTION_KEEP then
+
+    -- Avoid cycles - break if we were already here.
+    if VisitedLists[self.xref] then return false end
+
+    VisitedLists[self.xref] = true
+    local action, index, text = IM.currentRuleset:Match(data, self.xref)
+    VisitedLists[self.xref] = nil
+
+    -- If we found a positive match on the referenced list, this cross reference matches
+    if action ~= IM.ACTION_KEEP then return true end
+  end
+
+  return IM.IM_Rule.Filter(self, data)
+end
+
+IMRS2.version = 2
+
+function IMRS2:New()
+  local ruleset = IM.IM_Ruleset.New(self)
+  ruleset.rules = nil
+  ruleset.rulelists = { }
+
+  for k,_ in pairs(IM.actionfunctions) do
+    ruleset.rulelists[k] = { }
+  end
+  ruleset.rulelists[IM.ACTION_KEEP] = nil
+
+  return ruleset
+end
+
+function IMRS2:Clone()
+  if not self.rulelists then return IMRS2.Upgrade(self) end
+
+  local ruleset = IMRS2:New()
+  for k,v in pairs(self) do
+    ruleset[k] = v
+  end
+
+  ruleset.rulelists = { }
+  for k,v in pairs(self.rulelists) do
+    ruleset.rulelists[k] = { }
+    for k2,v2 in pairs(v) do
+      ruleset.rulelists[k][k2] = IMR2.Clone(v2)
+    end
+  end
+
+  return ruleset
+end
+
+function IMRS2:Upgrade()
+  local ruleset = IMRS2:New()
+  for _,v in pairs(self.rules) do
+    local rule, action = IMR2.Clone(v)
+    if action == IM.ACTION_KEEP then
+      rule.negate = true
+      for _, list in pairs(ruleset.rulelists) do
+        list[#list + 1] = rule:Clone()
+      end
+    else
+      local list = ruleset.rulelists[action]
+      list[#list + 1] = rule
+    end
+  end
+  return ruleset
+end
+
+local ExecCounters = { }
+
+function IMRS2:ResetCounters()
+	ExecCounters = { }
+  VisitedLists = { }
+end
+
+function IMRS2:Match(data, action)
+
+  -- Locked: Do nothing in any case
+  if data.locked then
+    return IM.ACTION_KEEP, nil, nil
+  end
+
+  -- If action is not given, try any action that matches
+  if not action then
+    local raction = IM.ACTION_KEEP
+    local rindex, rtext
+    for k, _ in pairs(self.rulelists) do
+      local action, index, text = self:Match(data, k)
+
+      -- If we find an explicit 'do nothing' here, try to find a better match.
+      -- Maybe it's because of a negative cross reference.
+      if index and (not raction or action ~= IM.ACTION_KEEP) then
+        raction = action
+        rindex = index
+        rtext = text
+      end
+    end
+    return raction, rindex, rtext
+  end
+
+	if not ExecCounters[action] then ExecCounters[action] = { } end
+
+  -- Stolen: No storing in bank or guild bank
+  if data.stolen and (action == IM.ACTION_STASH or action == IM.ACTION_GB_STASH) then
+    return IM.ACTION_KEEP, nil, nil
+  end
+  local rulelist = self.rulelists[action]
+	for k = 1, #rulelist, 1 do
+    local v = rulelist[k]
+		local res = v:Filter(data)
+
+		-- If we reached the max execution count for that particular rule, skip it.
+		if res and v.maxCount and ExecCounters[action][k] and ExecCounters[action][k] >= v.maxCount then
+			res = false
+		end
+
+		if res then
+			ExecCounters[action][k] = (ExecCounters[action][k] or 0) + 1
+      data.action = (v.negate and IM.ACTION_KEEP) or action
+			data.guildbank = v.guildbank
+			return data.action, action .. ":" .. k, v:ToString()
+		end
+	end
+
+	return IM.ACTION_KEEP, nil, nil
+end
+
+function IMRS2:List()
+  for action, rulelist in pairs(self.rulelists) do
+    if #rulelist > 0 then
+      local str = GetString("IM_R2_HEADING", action) .. GetString(IM_R2_COUNT_TAG)
+      CHAT_SYSTEM:AddMessage(zo_strformat(str, #rulelist))
+
+      for i = 1, #rulelist, 1 do
+        CHAT_SYSTEM:AddMessage("  " .. GetString(IM_LIST_RULE) .. i .. ":" .. rulelist[i]:ToString())
+      end
+      CHAT_SYSTEM:AddMessage("----")
+    end
+  end
+end
+
+function IMRS2:GetRuleList(action)
+  return self.rulelists[action]
+end
diff --git a/UI/ProfileEdit.lua b/UI/ProfileEdit.lua
index 4a806f7..6c3684b 100644
--- a/UI/ProfileEdit.lua
+++ b/UI/ProfileEdit.lua
@@ -6,7 +6,9 @@ local function _tr(str)
 	return str
 end

+if not InventoryManager then InventoryManager = {} end
 local IM = InventoryManager
+
 local PE = IM.UI.ProfileEdit

 PE.profileList = { }
@@ -102,8 +104,9 @@ function PE:BtnLoadClicked()
 		selProfile = IM.presetProfiles[-PE.selectedProfile]
 	end

-	IM.currentRuleset.rules = selProfile["rules"]
-	IM.currentRuleset = IM.currentRuleset:New()
+	IM.currentRuleset = selProfile:Clone()
+  IM.currentRuleset.name = nil
+  IM.currentRuleset.settings = nil

 	IM.settings = { }
 	for k,v in pairs(IM.charDefaults["settings"]) do
@@ -127,11 +130,10 @@ function PE:BtnSaveClicked()
 		PE.selectedProfile = #profiles + 1
 	end

-	profiles[PE.selectedProfile] = {
-		["name"] = PE.selectedName,
-		["rules"] = IM.currentRuleset:New()["rules"],
-		["settings"] = IM.settings,
-	}
+  local selProfile = IM.currentRuleset:Clone();
+  selProfile.name = PE.selectedName
+  selProfile.settings = IM.settings
+	profiles[PE.selectedProfile] = selProfile

 	PE:UpdateProfileList(PE.selectedProfile)
 	IM:Save()
diff --git a/UI/RuleEdit.lua b/UI/RuleEdit.lua
index dd55992..2de6925 100644
--- a/UI/RuleEdit.lua
+++ b/UI/RuleEdit.lua
@@ -6,7 +6,9 @@ local function _tr(str)
 	return str
 end

+if not InventoryManager then InventoryManager = {} end
 local IM = InventoryManager
+
 local RE = IM.UI.RuleEdit


@@ -15,7 +17,7 @@ local function getChoiceboxLists(structure, fun)
 	local _reverse = { }
 	local _order = { }
 	for i = 1, #structure, 1 do
-		n = structure[i][1]
+		local n = structure[i][1]
 		_new[n] = fun(n, i)
 		_order[i] = _new[n]
 		_reverse[_new[n]] = n
@@ -61,7 +63,6 @@ local function getSpecificFilterTypes(whichFilter)
 end

 local function getSpecificTraitTypes(whichFilter, whichSubFilter)
-	local rs = IM.IM_Ruleset
 	local ttlist = IM.traitsorder[whichSubFilter] or IM.traitsorder[whichFilter] or { }

 	local _new = { }
@@ -90,7 +91,8 @@ end
 function RE:GetControls()

 	RE.filterTypesList = getChoiceboxLists(IM.filterorder, function(n) return GetString(n) end)
-	RE.actionList = getChoiceboxLists(IM.actionorder, function(n) return GetString("IM_ACTIONTXT", n) end)
+	RE.actionList = getChoiceboxLists(IM.actionorder, function(n) return GetString("IM_R2_HEADING", n) end)
+  RE.xrefList  = getChoiceboxLists(IM.xreforder, function(n) return GetString("IM_R2_HEADING", n) end)
 	RE.qualityList = getChoiceboxListsAssoc(IM.qualityorder)

 	local guilds = { }
@@ -102,12 +104,31 @@ function RE:GetControls()
 	end
 	RE.guildList = getChoiceboxListsAssoc(guilds)

-	RE.editingRule = IM.IM_Ruleset:NewRule()
+	RE.editingRule = IM.IM_RuleV2:New()
+  RE.selectedAction = IM.ACTION_JUNK
+
 	local rule = RE.editingRule
 	RE:UpdateFilterSpecList(rule.filterType, rule.filterSubType)
 	RE:UpdateTraitList(rule.filterType, rule.filterSubType)

 	return {
+    {
+      type = "dropdown",
+			width = "half",
+      name = GetString(IM_SET_RULELIST),
+      tooltip = GetString(IM_SET_RULELIST_TT),
+			choices = RE.actionList["order"],
+			getFunc = function() return RE.actionList["forward"][RE.selectedAction] end,
+			setFunc = function(value)
+        RE.selectedAction = RE.actionList["reverse"][value]
+        RE:UpdateRuleList()
+      end,
+    },
+		{
+			type = "description",
+			text = "",
+			width = "half",
+		},
 		{
 			type = "dropdown",
 			name = GetString(IM_RE_CURRENTRULES),
@@ -168,22 +189,21 @@ function RE:GetControls()
 			text = GetString(IM_RE_DESC),
 		},
 		{
-			type = "dropdown",
-			name = GetString(IM_RE_ACTION),
+			type = "checkbox",
+			name = GetString(IM_SET_NEGATE),
+      tooltip = GetString(IM_SET_NEGATE_TT),
 			width = "half",
-			choices = RE.actionList["order"],
-			getFunc = function() return RE.actionList["forward"][RE.editingRule.action] end,
-			setFunc = function(value) RE.editingRule.action = RE.actionList["reverse"][value] end,
+			getFunc = function() return RE.editingRule.negate end,
+			setFunc = function(value) RE.editingRule.negate = value end,
 		},
 		{
 			type = "dropdown",
-			name = GetString(IM_RE_GUILDBANK),
-			tooltip = GetString(IM_RE_GUILDBANK_TT),
+			name = GetString(IM_SET_XREF),
+			tooltip = GetString(IM_SET_XREF_TT),
 			width = "half",
-			choices = RE.guildList["order"],
-			getFunc = function() return RE.editingRule.guildbank or RE.guildList["seltext"] end,
-			setFunc = function(value) RE.editingRule.guildbank = value end,
-			disabled = function() return RE:GetChoGBDisabled() end,
+			choices = RE.xrefList["order"],
+			getFunc = function() return RE.xrefList["forward"][RE.editingRule.xref or IM.ACTION_KEEP] end,
+			setFunc = function(value) RE.editingRule.xref = RE.xrefList["reverse"][value] end,
 		},
 		{
 			type = "slider",
@@ -198,9 +218,14 @@ function RE:GetControls()
 			width = "half",	--or "half" (optional)
 		},
 		{
-			type = "description",
-			text = "",
+			type = "dropdown",
+			name = GetString(IM_RE_GUILDBANK),
+			tooltip = GetString(IM_RE_GUILDBANK_TT),
 			width = "half",
+			choices = RE.guildList["order"],
+			getFunc = function() return RE.editingRule.guildbank or RE.guildList["seltext"] end,
+			setFunc = function(value) RE.editingRule.guildbank = value end,
+			disabled = function() return RE:GetChoGBDisabled() end,
 		},
 		{
 			type = "dropdown",
@@ -229,12 +254,13 @@ function RE:GetControls()
 			reference = "IWONTSAY_IM_CHO_TRAIT",
 		},
 		{
-			type = "checkbox",
-			name = GetString(IM_RE_PARTOFSET),
+			type = "dropdown",
+			name = GetString(IM_FCOIS_CHOICE),
 			width = "half",
-			disabled = function() return RE:GetIsSetCheckDisabled() end,
-			getFunc = function() return RE.editingRule.isSet end,
-			setFunc = function(value) RE.editingRule.isSet = value end,
+			choices = IM.FCOISL:GetIconChoices(),
+			getFunc = function() return IM.FCOISL:GetIndexedMark(RE.editingRule.FCOISMark) end,
+			setFunc = function(value) RE.editingRule.FCOISMark = IM.FCOISL:GetMarkIndex(value) end,
+			disabled = function() return not IM.FCOISL:hasAddon() and not IM.ISL:hasAddon() end
 		},
 		{
 			type = "dropdown",
@@ -253,13 +279,12 @@ function RE:GetControls()
 			setFunc = function(value) RE.editingRule.maxQuality = RE.qualityList["reverse"][value] end,
 		},
 		{
-			type = "dropdown",
-			name = GetString(IM_FCOIS_CHOICE),
+			type = "checkbox",
+			name = GetString(IM_RE_PARTOFSET),
 			width = "half",
-			choices = IM.FCOISL:GetIconChoices(),
-			getFunc = function() return IM.FCOISL:GetIndexedMark(RE.editingRule.FCOISMark) end,
-			setFunc = function(value) RE.editingRule.FCOISMark = IM.FCOISL:GetMarkIndex(value) end,
-			disabled = function() return not IM.FCOISL:hasAddon() and not IM.ISL:hasAddon() end
+			disabled = function() return RE:GetIsSetCheckDisabled() end,
+			getFunc = function() return RE.editingRule.isSet end,
+			setFunc = function(value) RE.editingRule.isSet = value end,
 		},
 		{
 			type = "checkbox",
@@ -290,8 +315,12 @@ function RE:GetControls()
 			setFunc = function(value) RE.editingRule.crafted = value end,
 		},
 		{
-			type = "description",
-			text = "",
+			type = "editbox",
+			name = GetString(IM_SET_TXTMATCH),
+			tooltip = GetString(IM_SET_TXTMATCH_TT),
+			getFunc = function() return RE.editingRule.text end,
+			setFunc = function(text) RE.editingRule.text = text end,
+			isMultiline = false,
 			width = "half",
 		},
 	}
@@ -303,7 +332,7 @@ end

 function RE:BtnAddBeforeClicked()
 	DEBUG("--- OnBtnAddBefore")
-	local rs = IM.currentRuleset.rules
+	local rs = IM.currentRuleset:GetRuleList(RE.selectedAction)
 	RE.selectedRule = RE.selectedRule or 1
 	table.insert(rs, RE.selectedRule, RE.editingRule)
 	RE:UpdateRuleList(RE.selectedRule)
@@ -312,7 +341,7 @@ end

 function RE:BtnAddAfterClicked()
 	DEBUG("--- OnBtnAddAfter")
-	local rs = IM.currentRuleset.rules
+	local rs = IM.currentRuleset:GetRuleList(RE.selectedAction)
 	RE.selectedRule = RE.selectedRule or 0
 	RE.selectedRule = RE.selectedRule + 1
 	table.insert(rs, RE.selectedRule, RE.editingRule)
@@ -322,7 +351,7 @@ end

 function RE:BtnDeleteClicked()
 	DEBUG("--- OnBtnDelete")
-	local rs = IM.currentRuleset.rules
+	local rs = IM.currentRuleset:GetRuleList(RE.selectedAction)
 	table.remove(rs, RE.selectedRule)
 	if RE.selectedRule > #rs then
 		RE.selectedRule = #rs
@@ -333,7 +362,7 @@ end

 function RE:BtnReplaceClicked()
 	DEBUG("--- OnBtnAddAfter")
-	local rs = IM.currentRuleset.rules
+	local rs = IM.currentRuleset:GetRuleList(RE.selectedAction)
 	RE.selectedRule = RE.selectedRule or 1
 	rs[RE.selectedRule] = RE.editingRule
 	RE:UpdateRuleList(RE.selectedRule)
@@ -341,7 +370,7 @@ function RE:BtnReplaceClicked()
 end

 local function moveRule(direction)
-	local rs = IM.currentRuleset.rules
+	local rs = IM.currentRuleset:GetRuleList(RE.selectedAction)
 	local tmp = rs[RE.selectedRule]
 	rs[RE.selectedRule] = rs[RE.selectedRule+direction]
 	rs[RE.selectedRule+direction] = tmp
@@ -376,7 +405,7 @@ function RE:GetBtnMoveDownDisabled()
 end

 function RE:GetChoGBDisabled()
-	return RE.editingRule.action ~= IM.ACTION_GB_STASH and RE.editingRule.action ~= IM.ACTION_GB_RETRIEVE
+	return RE.selectedAction ~= IM.ACTION_GB_STASH and RE.selectedAction ~= IM.ACTION_GB_RETRIEVE
 end

 function RE:UpdateTraitList(filterType, filterSubType)
@@ -385,9 +414,6 @@ function RE:UpdateTraitList(filterType, filterSubType)

 	local traitTxt = (RE.editingRule.traitType and RE.traitList["forward"][RE.editingRule.traitType]) or RE.traitList["seltext"]

-	-- Explicitely set here. If traitTxt is nil, because no traits are listed, it wouldn't be set elsewhere
-	RE.editingRule.traitType = RE.traitList["reverse"][value]
-
 	if not IWONTSAY_IM_CHO_TRAIT then return end

 	IWONTSAY_IM_CHO_TRAIT:UpdateChoices(RE.traitList["order"]);
@@ -412,7 +438,7 @@ end
 function RE:UpdateRuleList(preselection)
 	DEBUG("--- UpdateRuleList()", preselection)

-	local rules = IM.currentRuleset.rules
+	local rules = IM.currentRuleset:GetRuleList(RE.selectedAction)
 	RE.ruleList = { }
 	RE.reverseRuleList = { }
 	if #rules then
@@ -452,8 +478,8 @@ end
 function RE:SetSelectedRule(whichRuleText)
 	DEBUG("--- SetSelectedRule", whichRuleText)
 	RE.selectedRule = RE.reverseRuleList[whichRuleText]
-	local rule = IM.currentRuleset.rules[RE.selectedRule]
-	RE.editingRule = (rule and rule:New()) or RE.editingRule
+	local rule = IM.currentRuleset:GetRuleList(RE.selectedAction)[RE.selectedRule]
+	RE.editingRule = (rule and rule:Clone()) or RE.editingRule
 	rule = RE.editingRule

 	RE:UpdateFilterSpecList(rule.filterType, rule.filterSubType)
diff --git a/UI/Settings.lua b/UI/Settings.lua
index 473ceab..d6e5bd3 100644
--- a/UI/Settings.lua
+++ b/UI/Settings.lua
@@ -5,7 +5,10 @@ d
 local function _tr(str)
 	return str
 end
+
+if not InventoryManager then InventoryManager = {} end
 local IM = InventoryManager
+
 local SE = IM.UI.Settings

 function SE:GetControls()
diff --git a/lang/de.lua b/lang/de.lua
index 1537f82..7eee38d 100644
--- a/lang/de.lua
+++ b/lang/de.lua
@@ -4,7 +4,7 @@ local lang = {

 	-- parameters are itemDescription, qualityRangeText, isSetText, actionText
 	-- e.g. "put in trash any stolen worthless light armor with quality Trash to Normal"
-	IM_RULETXTFORMAT			= "<<4>>: jeder <<z:1>><<z:2>><<z:3>>",
+	IM_RULETXTFORMAT			= "<<4>>: jeder <<1>><<z:2>><<z:3>>",
 	IM_RULETXT_ISSET			= "(Teil eines Sets)",
 	IM_RULETXT_STOLEN			= "gestohlene(s)",
 	IM_RULETXT_WORTHLESS		= "wertlos(es)",
@@ -13,6 +13,7 @@ local lang = {
 	IM_RULETXT_QUALITY1			= "mit Qualität <<1>>",
 	IM_RULETXT_QUALITY2			= "mit Qualität von <<1>> bis <<2>>",
 	IM_RULETXT_EXECOUNT 		= "(max. <<1>>-mal)",
+  IM_RULETXT_TXT          = "passend auf '<<1>>'",

 	IM_ACTIONTXT0				= "Behalten",
 	IM_ACTIONTXT1				= "Zum Müll stecken",
@@ -154,6 +155,14 @@ local lang = {
 	IM_SET_INV_TT 				= "Setzt die Verzögerung zwischen Änderungen im Inventar wie Sperren/Entsperren usw.",
 	IM_SET_EXECOUNT 			= "Maximale Anzahl Ausführungen",
 	IM_SET_EXECOUNT_TT 			= "Wie oft diese Regel maximal in einem Lauf ausgeführt werden darf. 0 bedeutet 'unbegrenzt'",
+	IM_SET_RULELIST       = "Regelliste",
+  IM_SET_RULELIST_TT    = "Gibt die Gelegenheit an, bei der die nachfolgende Liste angewendet wird",
+  IM_SET_NEGATE         = "Negieren",
+  IM_SET_NEGATE_TT      = "Wenn gesetzt, wird diese Aktion NICHT auf die Gegenstände angewendet, die passen",
+  IM_SET_XREF           = "Kreuzreferenz",
+  IM_SET_XREF_TT        = "Wenn gesetzt, verweist es auf eine andere Regelliste, die zu Rate gezogen wird",
+  IM_SET_TXTMATCH       = "Filtertext",
+  IM_SET_TXTMATCH_TT    = "Wenn nicht leer, bezeichnet es den Namen oder einen Namensteil von dem passenden Objekt. Reguläre Ausdrücke sind zulässig.",

 	IM_PM_PROFILENAME_TOOLTIP	= "Namen vom Profil hier eingeben",
 	IM_RM_PRESETRULES			= "--- Voreingestellte Profile ---",
@@ -182,7 +191,8 @@ local lang = {

 	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_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_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",

 	IM_FCO_STATIC_TXT1			= "zur Sperrung",
 	IM_FCO_STATIC_TXT2			= "Ausrüstungssatz 1",
@@ -195,7 +205,24 @@ local lang = {
 	IM_FCO_STATIC_TXT9			= "zur Zerlegung",
 	IM_FCO_STATIC_TXT10			= "zur Verbesserung",
 	IM_FCO_STATIC_TXT11			= "zum Verkauf im Gildenladen",
-	IM_FCO_STATIC_TXT12			= "intrikat",
+	IM_FCO_STATIC_TXT12			= "aufwändig",
+
+  IM_R2_HEADING0          = "(Keine Kreuzreferenz)",
+  IM_R2_HEADING1          = "Beim Aufheben eines Gegenstands, in den Müll, wenn ...",
+  IM_R2_HEADING2          = "Beim vollen Inventar, vernichte ...",
+  IM_R2_HEADING3          = "Verkaufe beim Geschäft oder Hehler ...",
+  IM_R2_HEADING4          = "Wasche beim Hehler ...",
+  IM_R2_HEADING5          = "Am Arbeitstisch, zerlege ...",
+  IM_R2_HEADING10         = "Lege in die eigene Bank ...",
+  IM_R2_HEADING20         = "Hole aus der eigenen Bank ...",
+  IM_R2_HEADING11         = "Lege die in der Regel angegebenen Gildenbank ...",
+  IM_R2_HEADING21         = "Hole aus die in der Regel angegebenen Gildenbank ...",
+  IM_R2_COUNT_TAG         = " (Regeln: <<1>>)",
+
+	IM_R2_FORMAT0     			= "Kein <<1>><<z:2>><<z:3>> <<4>>",         -- Rule V2 text, negative
+	IM_R2_FORMAT1     			= "Jeder <<1>><<z:2>><<z:3>> <<4>>",        -- Rule V2 text, positive
+  IM_R2_FORMAT2           = "Nichts von der Liste '<<z:1>>' <<2>>",   -- Rule V2 text, cross reference, negative
+  IM_R2_FORMAT3           = "Alles von der Liste '<<z:1>>' <<2>>"     -- Rule V2 text, cross reference, positive

 }

diff --git a/lang/en.lua b/lang/en.lua
index 6a93ad8..d685e78 100644
--- a/lang/en.lua
+++ b/lang/en.lua
@@ -4,7 +4,7 @@ local lang = {

 	-- parameters are itemDescription, qualityRangeText, isSetText, actionText
 	-- e.g. "put in trash any stolen worthless light armor with quality Trash to Normal"
-	IM_RULETXTFORMAT			= "<<z:4>> any <<z:1>><<z:2>><<z:3>>",
+	IM_RULETXTFORMAT			= "<<z:4>> any <<1>><<z:2>><<z:3>>",
 	IM_RULETXT_ISSET			= "which is part of a set",
 	IM_RULETXT_STOLEN			= "stolen",
 	IM_RULETXT_WORTHLESS		= "worthless",
@@ -13,6 +13,7 @@ local lang = {
 	IM_RULETXT_QUALITY1			= "with quality <<1>>",
 	IM_RULETXT_QUALITY2			= "with quality from <<1>> to <<2>>",
 	IM_RULETXT_EXECOUNT 		= "(max. <<1>> times)",
+  IM_RULETXT_TXT          = "matching '<<1>>'",

 	IM_ACTIONTXT0				= "Keep",
 	IM_ACTIONTXT1				= "Put to junk",
@@ -155,7 +156,15 @@ local lang = {
 	IM_SET_INV_TT 				= "Sets the delay between inventory status changes like junk/unjunk lock/unlock and so on.",
 	IM_SET_EXECOUNT 			= "Maximum execution count",
 	IM_SET_EXECOUNT_TT 			= "How often this rule may be executed in a single run. 0 means 'unlimited'",
-
+	IM_SET_RULELIST       = "Rule list",
+  IM_SET_RULELIST_TT    = "Select the occasion the list of rules apply to",
+  IM_SET_NEGATE         = "Negate",
+  IM_SET_NEGATE_TT      = "When set, matching items will NOT have this action performed on.",
+  IM_SET_XREF           = "Cross Reference",
+  IM_SET_XREF_TT        = "When set, refer to another rule list to determine the course of action",
+  IM_SET_TXTMATCH       = "Filter text",
+  IM_SET_TXTMATCH_TT    = "When not empty, it states a name or the part of the name of the item to match. Note: Regular expressions do work here.",
+
 	IM_PM_PROFILENAME_TOOLTIP	= "Enter the name of the new profile here",
 	IM_RM_PRESETRULES			= "--- Preset profiles ---",
 	IM_RM_CUSTOMRULES			= "--- Custom profiles ---",
@@ -184,6 +193,7 @@ local lang = {
 	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_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",

 	IM_FCO_STATIC_TXT1			= "for locking",
 	IM_FCO_STATIC_TXT2			= "gear set 1",
@@ -197,6 +207,24 @@ local lang = {
 	IM_FCO_STATIC_TXT10			= "for improvement",
 	IM_FCO_STATIC_TXT11			= "for sale at guildstore",
 	IM_FCO_STATIC_TXT12			= "intricate",
+
+  IM_R2_HEADING0          = "(No cross reference)",
+  IM_R2_HEADING1          = "On picking up an item, junk it if ...",
+  IM_R2_HEADING2          = "When inventory filled up, destroy ...",
+  IM_R2_HEADING3          = "Sell at shop or fence ...",
+  IM_R2_HEADING4          = "Launder at fence ...",
+  IM_R2_HEADING5          = "Deconstruct at crafting station ...",
+  IM_R2_HEADING10         = "Store in own bank ...",
+  IM_R2_HEADING20         = "Retrieve from own bank ...",
+  IM_R2_HEADING11         = "Store in named guild bank ...",
+  IM_R2_HEADING21         = "Retrieve from named guild bank ...",
+  IM_R2_COUNT_TAG         = " (rules: <<1>>)",
+
+	IM_R2_FORMAT0     			= "No <<1>><<z:2>><<z:3>> <<4>>",               -- Rule V2 text, negative
+	IM_R2_FORMAT1     			= "Any <<1>><<z:2>><<z:3>> <<4>>",              -- Rule V2 text, positive
+  IM_R2_FORMAT2           = "Nothing from the list '<<z:1>>' <<2>>",      -- Rule V2 text, cross reference, negative
+  IM_R2_FORMAT3           = "Everything from the list '<<z:1>>' <<2>>"   	-- Rule V2 text, cross reference, positive
+
 }

 for stringId, stringValue in pairs(lang) do