Version 1.2.0

willneedit [03-31-17 - 18:11]
Version 1.2.0

 * Translation update
 * Made Seller more robust: Revenue is tallied up correctly
 * Added negative message for dryrun if FCOIS denies a specific action
 * Added Deconstruct Inventory action
 * Chained in Laundering when selling to Fences
 * Generalized selling into ruleset
 * Added 'junked' as filter criterion
 * Added more delay options
 * Added /im run command
 * Extended FCO marking detection with static markers
Filename
CraftStoreLink.lua
FCOISLink.lua
InventoryManager.lua
InventoryManager.txt
Modules/Banking.lua
Modules/Data.lua
Modules/DelayedProcessor.lua
Modules/Extractor.lua
Modules/Junker.lua
Modules/Seller.lua
Rulesets.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 bfb809f..5209a9a 100644
--- a/CraftStoreLink.lua
+++ b/CraftStoreLink.lua
@@ -36,12 +36,12 @@ function CSL:hasCSAddon()
 	if CS then
 		Used_CS  = CS
 		Used_CSA = Used_CS.account
-		CHAT_SYSTEM:AddMessage(GetString("IM_INIT_DETECTED_CS_OLD"))
+		CHAT_SYSTEM:AddMessage(GetString(IM_INIT_DETECTED_CS_OLD))
 		hasCS = "old"
 	elseif CraftStoreFixedAndImprovedLongClassName then
 		Used_CS  = CraftStoreFixedAndImprovedLongClassName
 		Used_CSA = Used_CS.Account
-		CHAT_SYSTEM:AddMessage(GetString("IM_INIT_DETECTED_CS_NEW"))
+		CHAT_SYSTEM:AddMessage(GetString(IM_INIT_DETECTED_CS_NEW))
 		hasCS = "new"
 	else
 		hasCS = false
diff --git a/FCOISLink.lua b/FCOISLink.lua
index e1ad0f3..e1abb2b 100644
--- a/FCOISLink.lua
+++ b/FCOISLink.lua
@@ -18,6 +18,9 @@ local FCOISL = {}

 local hasFCOIS = nil

+local staticIconList = nil
+local protection_fns = nil
+
 InventoryManager.FCOISL = FCOISL

 local DIList = nil
@@ -26,15 +29,72 @@ local DIChoices = nil
 function FCOISL:hasAddon()
 	if hasFCOIS ~= nil then return hasFCOIS end

-	TXT_NO_CARE = GetString("IM_FCOIS_NOCAREMARK")
-	TXT_NO_MARK = GetString("IM_FCOIS_NOMARK")
-	TXT_ANY_MARK = GetString("IM_FCOIS_ANYMARK")
+	TXT_NO_CARE = GetString(IM_FCOIS_NOCAREMARK)
+	TXT_NO_MARK = GetString(IM_FCOIS_NOMARK)
+	TXT_ANY_MARK = GetString(IM_FCOIS_ANYMARK)

 	hasFCOIS = ( FCOIS ~= nil and FCOIsMarked ~= nil and FCOGetDynamicInfo ~= nil and FCOGetIconText ~= nil)

+	if(hasFCOIS) then
+		staticIconList = {
+			FCOIS_CON_ICON_GEAR_1,
+			FCOIS_CON_ICON_GEAR_2,
+			FCOIS_CON_ICON_GEAR_3,
+			FCOIS_CON_ICON_GEAR_4,
+			FCOIS_CON_ICON_GEAR_5,
+			FCOIS_CON_ICON_LOCK,
+			FCOIS_CON_ICON_SELL,
+			FCOIS_CON_ICON_RESEARCH,
+			FCOIS_CON_ICON_DECONSTRUCTION,
+			FCOIS_CON_ICON_IMPROVEMENT,
+			FCOIS_CON_ICON_SELL_AT_GUILDSTORE,
+			FCOIS_CON_ICON_INTRICATE,
+		}
+
+		protected_actions = {
+			[InventoryManager.ACTION_DESTROY] 		=  FCOIS.IsDestroyLocked,
+			[InventoryManager.ACTION_SELL]			= {
+				[false]	= FCOIS.IsVendorSellLocked,
+				[true]	= FCOIS.IsFenceSellLocked
+			},
+			[InventoryManager.ACTION_LAUNDER]		=  FCOIS.IsLaunderLocked,
+			[InventoryManager.ACTION_DECONSTRUCT]	= {
+				[false]	= FCOIS.IsDeconstructionLocked,
+				[true] 	= FCOIS.IsEnchantingExtractionLocked
+			},
+		}
+	end
+
 	return hasFCOIS
 end

+function FCOISL:IsProtectedAction(action, bagId, slotId, extraParm)
+	if not protected_actions or not protected_actions[action] then return false end
+
+	local pa = protected_actions[action]
+	if extraParm ~= nil then
+		return pa[extraParm](bagId, slotId)
+	end
+
+	if type(pa) == "table" then
+		for _, v in pairs(pa) do
+			if not v(bagId, slotId) then return false end
+		end
+		return true
+	end
+	return pa(bagId, slotId)
+end
+
+function FCOISL:GetIconText(iconNr)
+	local str = FCOGetIconText(iconNr)
+	if str then return str end
+
+	str = GetString("IM_FCO_STATIC_TXT", iconNr)
+	if str == "" then return nil end
+
+	return str
+end
+
 function FCOISL:GetDynamicIconList()
 	if DIList then return DIList end

@@ -42,8 +102,15 @@ function FCOISL:GetDynamicIconList()
 	if not self:hasAddon() then return DIList end

 	local totalNumberOfDynamicIcons, numberToDynamicIconNr = FCOGetDynamicInfo()
+
+	for _, dynamicIconNr in pairs(staticIconList) do
+        local dynIconName = FCOISL:GetIconText(dynamicIconNr)
+		DIList[#DIList + 1] = { dynamicIconNr, dynIconName }
+		DIList[dynIconName] = dynamicIconNr
+    end
+
 	for index, dynamicIconNr in pairs(numberToDynamicIconNr) do
-        local dynIconName = FCOGetIconText(dynamicIconNr)
+        local dynIconName = FCOISL:GetIconText(dynamicIconNr)
 		DIList[#DIList + 1] = { dynamicIconNr, dynIconName }
 		DIList[dynIconName] = dynamicIconNr
     end
@@ -57,7 +124,7 @@ function FCOISL:GetIndexedMark(mark)
 	elseif mark == I_ANY_MARK then return TXT_ANY_MARK
 	end

-	return (FCOISL:hasAddon() and FCOGetIconText(mark)) or TXT_NO_CARE
+	return (FCOISL:hasAddon() and FCOISL:GetIconText(mark)) or TXT_NO_CARE
 end

 function FCOISL:GetMarkIndex(markText)
@@ -80,11 +147,15 @@ function FCOISL:GetDynamicIconChoices()
 	if DIChoices then return DIChoices end

 	DIChoices = { TXT_NO_CARE, TXT_NO_MARK, TXT_ANY_MARK }
+	for _, v in pairs(staticIconList) do
+		DIChoices[#DIChoices + 1] = FCOISL:GetIconText(v)
+	end
+
 	if not self:hasAddon() then return DIChoices end

 	local totalNumberOfDynamicIcons, numberToDynamicIconNr = FCOGetDynamicInfo()
 	for index, dynamicIconNr in pairs(numberToDynamicIconNr) do
-        local dynIconName = FCOGetIconText(dynamicIconNr)
+        local dynIconName = FCOISL:GetIconText(dynamicIconNr)
 		DIChoices[#DIChoices + 1] = dynIconName
     end

diff --git a/InventoryManager.lua b/InventoryManager.lua
index 3551f15..957dfb8 100644
--- a/InventoryManager.lua
+++ b/InventoryManager.lua
@@ -8,6 +8,8 @@ end

 InventoryManager = {}

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

 InventoryManager.name = "InventoryManager"
@@ -20,8 +22,31 @@ InventoryManager.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
+
+		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 )
+		end
+	end
+	IM:ReportAction(data, dryrun, action, data.index, data.text)
+end
+
 function InventoryManager:ReportAction(data, dryrun, action, rIndex, rString)
 	local index = (dryrun and 0) or 1
+	if self.FCOISL:IsProtectedAction(data.action, data.bagId, data.slotId) then
+		index = 2
+	end
 	CHAT_SYSTEM:AddMessage(zo_strformat(GetString("IM_TAKENACTION", index),
 			GetString("IM_ACTIONTXT",action),
 			data.icon,
@@ -73,6 +98,9 @@ function InventoryManager:GetItemData(slotId, _inv)

 	local itemLink = inv[slotId].lnk

+	data.bagId = self.currentBagType
+	data.slotId = slotId
+
 	data.name = inv[slotId].name
 	data.lnk = itemLink
 	data.itemInstanceId = inv[slotId].itemInstanceId
@@ -104,13 +132,13 @@ function InventoryManager:GetItemData(slotId, _inv)
 end

 function InventoryManager:listrules()
-	CHAT_SYSTEM:AddMessage(GetString("IM_LIST_NUM_RULES") .. #InventoryManager.currentRuleset.rules)
+	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())
+		CHAT_SYSTEM:AddMessage(GetString(IM_LIST_RULE) .. i .. ":" .. InventoryManager.currentRuleset.rules[i]:ToString())
 	end
 end

@@ -118,6 +146,10 @@ function InventoryManager:dryrun()
 	self:WorkBackpack(true)
 end

+function InventoryManager:run()
+	self:WorkBackpack(false)
+end
+
 function InventoryManager:help()
 	-- self:SetCurrentInventory(BAG_BACKPACK)
 	-- for i, entry in pairs(self.currentInventory) do
@@ -133,6 +165,7 @@ function InventoryManager:help()
 	-- end
 	CHAT_SYSTEM:AddMessage("/im listrules - list the rules currently defined")
 	CHAT_SYSTEM:AddMessage("/im dryrun    - show what the currently defined rules would do to your inventory")
+	CHAT_SYSTEM:AddMessage("/im run       - make a pass of the filters over your inventory")
 end

 function InventoryManager:SlashCommand(argv)
@@ -150,6 +183,8 @@ function InventoryManager:SlashCommand(argv)
 		self:listrules()
 	elseif options[1] == "dryrun" then
 		self:dryrun()
+	elseif options[1] == "run" then
+		self:run()
 	else
 		CHAT_SYSTEM:AddMessage("Unknown parameter '" .. argv .. "'")
 	end
@@ -174,20 +209,20 @@ function InventoryManager:InitializeUI()
 	local mainPanel = {
 		{
 			type = "submenu",
-			name = GetString("IM_UI_PM"),
-			tooltip = GetString("IM_UI_PM_TOOLTIP"),	--(optional)
+			name = GetString(IM_UI_PM),
+			tooltip = GetString(IM_UI_PM_TOOLTIP),	--(optional)
 			controls = ProfileEdit:GetControls(),
 		},
 		{
 			type = "submenu",
-			name = GetString("IM_UI_RM"),
-			tooltip = GetString("IM_UI_RM_TOOLTIP"),	--(optional)
+			name = GetString(IM_UI_RM),
+			tooltip = GetString(IM_UI_RM_TOOLTIP),	--(optional)
 			controls = RuleEdit:GetControls(),
 		},
 		{
 			type = "submenu",
-			name = GetString("IM_UI_SETTINGS"),
-			tooltip = GetString("IM_UI_SETTINGS_TOOLTIP"),	--(optional)
+			name = GetString(IM_UI_SETTINGS),
+			tooltip = GetString(IM_UI_SETTINGS_TOOLTIP),	--(optional)
 			controls = Settings:GetControls(),
 		},

@@ -227,6 +262,21 @@ local function loadProfile(profileData)
 	return _new
 end

+function InventoryManager:Update()
+	local version = self.settings.Version or 1
+	if version < 2 then
+		local _rule = self.IM_Ruleset:NewRule()
+		_rule.action = self.ACTION_SELL
+		_rule.junk = true
+		local rs = self.currentRuleset.rules
+		table.insert(rs, 1, _rule)
+		CHAT_SYSTEM:AddMessage(GetString(IM_INIT_UPDATE_V2_NOTE))
+	end
+
+	self.settings.Version = 2
+	self:Save()
+end
+
 function InventoryManager:Init()
 	self.currentRuleset			= self.IM_Ruleset:New()

@@ -235,6 +285,8 @@ function InventoryManager:Init()
 		["settings"]		= {
 			["destroyThreshold"]	= 5,
 			["bankMoveDelay"]		= 20,
+			["bankInitDelay"]		= 1000,
+			["statusChangeDelay"]	= 20,
 			["maxGold"]				= 5000,
 			["minGold"]				= 1000,
 			["maxTV"]				= 10,
@@ -268,6 +320,7 @@ function InventoryManager:Init()
 	self.CSL.hasCSAddon()
 	self.FCOISL:hasAddon()

+	self:Update()
 	self:InitializeUI()

 	CHAT_SYSTEM:AddMessage(self.name .. " Addon Loaded.")
diff --git a/InventoryManager.txt b/InventoryManager.txt
index 18861a1..78d9bd4 100644
--- a/InventoryManager.txt
+++ b/InventoryManager.txt
@@ -2,7 +2,7 @@
 ## APIVersion: 100018
 ## OptionalDependsOn: LibAddonMenu-2.0
 ## SavedVariables: IMSavedVars
-## Version: 1.1.1
+## Version: 1.2.0
 ## Author: iwontsay
 ## Description: iwontsay's Inventory Manager

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

 InventoryManager.lua
+Modules/DelayedProcessor.lua
 Modules/Data.lua
 Modules/Banking.lua
 Modules/Junker.lua
 Modules/Seller.lua
+Modules/Extractor.lua

 CraftStoreLink.lua
 FCOISLink.lua
diff --git a/Modules/Banking.lua b/Modules/Banking.lua
index 1492df6..d69d940 100644
--- a/Modules/Banking.lua
+++ b/Modules/Banking.lua
@@ -11,7 +11,6 @@ local ST_TGTFULL = 1
 local ST_SPAM = 2

 local InvCache = nil
-local Moves = nil

 local function ScanInventory(bagType)
 	local _empties = { }
@@ -78,7 +77,7 @@ end


 local function CollectSingleDirection(action)
-	local bagType = (action == InventoryManager.IM_Ruleset.ACTION_STASH and BAG_BACKPACK) or BAG_BANK
+	local bagType = (action == InventoryManager.ACTION_STASH and BAG_BACKPACK) or BAG_BANK
 	local _moveSlots = { }

 	InventoryManager:SetCurrentInventory(bagType)
@@ -103,8 +102,8 @@ local function PrepareMoveCaches()
 	RebuildStackCache(BAG_BANK)

 	Moves = {
-		["stash"] = CollectSingleDirection(InventoryManager.IM_Ruleset.ACTION_STASH),
-		["retrieve"] = CollectSingleDirection(InventoryManager.IM_Ruleset.ACTION_RETRIEVE)
+		["stash"] = CollectSingleDirection(InventoryManager.ACTION_STASH),
+		["retrieve"] = CollectSingleDirection(InventoryManager.ACTION_RETRIEVE)
 	}

 end
@@ -225,51 +224,38 @@ local function CalculateMoves()
 	-- NOTREACHED
 end

--- Pending slots for time delayed actions
-InventoryManager.pendingMoves = nil
 InventoryManager.moveStatus = nil

-function InventoryManager:ProcessMoves()
-	if not self.pendingMoves or self.currentMove > #self.pendingMoves then
-		return self:FinishMoves()
-	end
-
+function ProcessMove(move)
 	local IMR = InventoryManager.IM_Ruleset

-	local move = self.pendingMoves[self.currentMove]
-
 	local bagIdFrom = move["srcbag"]
 	local slotIdFrom = move["srcslot"]
 	local bagIdTo = move["tgtbag"]
 	local slotIdTo = move["tgtslot"]
 	local qtyToMove = move["count"]
-	local action = (bagIdFrom == BAG_BACKPACK and IMR.ACTION_STASH) or IMR.ACTION_RETRIEVE
+	local action = (bagIdFrom == BAG_BACKPACK and InventoryManager.ACTION_STASH) or InventoryManager.ACTION_RETRIEVE

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

 	if IsProtectedFunction("RequestMoveItem") then
 		CallSecureProtected("RequestMoveItem", bagIdFrom, slotIdFrom, bagIdTo, slotIdTo, qtyToMove)
 	else
 		RequestMoveItem(bagIdFrom, slotIdFrom, bagIdTo, slotIdTo, qtyToMove)
 	end
-
-	self.currentMove = self.currentMove + 1
-	zo_callLater(
-		function() InventoryManager:ProcessMoves() end,
-		InventoryManager.settings.bankMoveDelay)
 end

 function InventoryManager:FinishMoves()
 	local result
 	if self.moveStatus == "limited" then
-		result = GetString("IM_BANK_LIMITED")
+		result = GetString(IM_BANK_LIMITED)
 	elseif self.moveStatus == "deadlock" then
-		result = GetString("IM_BANK_DEADLOCK")
+		result = GetString(IM_BANK_DEADLOCK)
 	elseif self.moveStatus == "partial" then
-		result = GetString("IM_BANK_PARTIAL")
+		result = GetString(IM_BANK_PARTIAL)
 	elseif self.moveStatus == "ok" then
-		result = GetString("IM_BANK_OK")
+		result = GetString(IM_BANK_OK)
 	end

 	if result ~= "" then
@@ -292,26 +278,30 @@ function InventoryManager:BalanceCurrency(currencyType, minCur, maxCur, curName)
 		return
 	elseif move > 0 then
 		CHAT_SYSTEM:AddMessage(
-			zo_strformat(GetString("IM_CUR_DEPOSIT"), move, curName))
+			zo_strformat(GetString(IM_CUR_DEPOSIT), move, curName))
 		DepositCurrencyIntoBank(currencyType, move)
 	else
 		move = -move
 		if move > banked then move = banked end
 		if move == 0 then return end
 		CHAT_SYSTEM:AddMessage(
-			zo_strformat(GetString("IM_CUR_WITHDRAW"), move, curName))
+			zo_strformat(GetString(IM_CUR_WITHDRAW), move, curName))
 		WithdrawCurrencyFromBank(currencyType, move)
 	end
 end

 function InventoryManager:OnBankOpened()
-	self.moveStatus, self.pendingMoves = CalculateMoves()
-	self.currentMove = 1
+	local moves
+	self.moveStatus, moves = CalculateMoves()

-	self:BalanceCurrency(CURT_MONEY, self.settings.minGold, self.settings.maxGold, GetString("IM_CUR_GOLD"))
-	self:BalanceCurrency(CURT_TELVAR_STONES, self.settings.minTV, self.settings.maxTV, GetString("IM_CUR_TVSTONES"))
+	self:BalanceCurrency(CURT_MONEY, self.settings.minGold, self.settings.maxGold, GetString(IM_CUR_GOLD))
+	self:BalanceCurrency(CURT_TELVAR_STONES, self.settings.minTV, self.settings.maxTV, GetString(IM_CUR_TVSTONES))

-	zo_callLater(function() InventoryManager:ProcessMoves() end, 100)
+	self:DoDelayedProcessing(moves,
+		ProcessMove,
+		function() InventoryManager:FinishMoves() end,
+		InventoryManager.settings.bankMoveDelay,
+		InventoryManager.settings.bankInitDelay)
 end

 local function OnBankOpened(eventCode)
diff --git a/Modules/Data.lua b/Modules/Data.lua
index 65ac4ff..52c7ff5 100644
--- a/Modules/Data.lua
+++ b/Modules/Data.lua
@@ -19,11 +19,16 @@ function InventoryManager:getIQString(itemQuality)
 	return GetItemQualityColor(itemQuality):Colorize(GetString("SI_ITEMQUALITY", itemQuality))
 end

-IM_Ruleset.ACTION_KEEP		=  0
-IM_Ruleset.ACTION_JUNK		=  1
-IM_Ruleset.ACTION_DESTROY 	=  2
-IM_Ruleset.ACTION_STASH		= 10
-IM_Ruleset.ACTION_RETRIEVE	= 20
+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_RETRIEVE	=  20

 IM_Ruleset.ITEM_TRAIT_TYPE_ANY				= -1
 IM_Ruleset.ITEM_TRAIT_TYPE_ANYUNKOTHERS		= -2
@@ -106,11 +111,14 @@ InventoryManager.qualityorder = {
 }

 InventoryManager.actionorder = {
-	{ IM_Ruleset.ACTION_KEEP },
-	{ IM_Ruleset.ACTION_JUNK },
-	{ IM_Ruleset.ACTION_DESTROY },
-	{ IM_Ruleset.ACTION_STASH },
-	{ IM_Ruleset.ACTION_RETRIEVE },
+	{ InventoryManager.ACTION_KEEP },
+	{ InventoryManager.ACTION_JUNK },
+	{ InventoryManager.ACTION_DESTROY },
+	{ InventoryManager.ACTION_STASH },
+	{ InventoryManager.ACTION_RETRIEVE },
+	{ InventoryManager.ACTION_SELL },
+	{ InventoryManager.ACTION_LAUNDER },
+	{ InventoryManager.ACTION_DECONSTRUCT },
 }

 InventoryManager.traitsorder = {
diff --git a/Modules/DelayedProcessor.lua b/Modules/DelayedProcessor.lua
new file mode 100644
index 0000000..b53c54b
--- /dev/null
+++ b/Modules/DelayedProcessor.lua
@@ -0,0 +1,138 @@
+local DEBUG =
+-- function() end
+d
+
+local IM = InventoryManager
+
+local _Pending = nil
+local _Delay = nil
+
+-- function _Loop_fn(entry)
+local _Loop_fn = nil
+
+-- function _Finish_fn(finished_ok)
+local _Finish_fn = nil
+
+local _Event_Next = nil
+local _Event_Abort = nil
+
+-- Simple processing loop, call next element after delay
+local function ProcessLoop()
+	if not _Pending or #_Pending == 0 then
+		if _Finish_fn then _Finish_fn(true) end
+		return
+	end
+
+	local entry = _Pending[#_Pending]
+	_Pending[#_Pending] = nil
+	_Loop_fn(entry)
+
+	zo_callLater(ProcessLoop, _Delay)
+end
+
+-- Event driven processing loop, called directly for first element,
+-- then is fired by _Loop_fn's completion for the subsequent ones.
+local function EventProcessLoop(eventCode, a1, a2, a3, a4, a5, a6, a7, a8)
+	if not _Pending or #_Pending == 0 then
+		if _Event_Next then
+			EVENT_MANAGER:UnregisterForEvent("IMEventProcessLoop", _Event_Next)
+		end
+		if _Event_Abort then
+			EVENT_MANAGER:UnregisterForEvent("IMEventProcessLoop", _Event_Abort)
+		end
+
+		if _Finish_fn then _Finish_fn(true, eventCode, a1, a2, a3, a4, a5, a6, a7, a8) end
+		return
+	end
+
+	local entry = _Pending[#_Pending]
+	_Pending[#_Pending] = nil
+
+	zo_callLater(function() _Loop_fn(entry, eventCode, a1, a2, a3, a4, a5, a6, a7, a8) end, _Delay)
+end
+
+local function EventProcessLoopAbort()
+	if _Event_Next then
+		EVENT_MANAGER:UnregisterForEvent("IMEventProcessLoop", _Event_Next)
+	end
+	if _Event_Abort then
+		EVENT_MANAGER:UnregisterForEvent("IMEventProcessLoop", _Event_Abort)
+	end
+
+	if _Finish_fn then _Finish_fn(false) end
+end
+
+function IM:DoEventProcessing(list, loop_fn, finish_fn, loop_event, abort_event, run_delay)
+	_Pending = list
+	_Loop_fn = loop_fn
+	_Finish_fn = finish_fn
+	_Delay = run_delay or 1
+	_Event_Next = loop_event
+	_Event_Abort = abort_event
+
+	if _Event_Next then
+		EVENT_MANAGER:RegisterForEvent("IMEventProcessLoop", _Event_Next, EventProcessLoop)
+	end
+	if _Event_Abort then
+		EVENT_MANAGER:RegisterForEvent("IMEventProcessLoop", _Event_Abort, EventProcessLoopAbort)
+	end
+
+	zo_callLater(function() EventProcessLoop() end, _Delay)
+end
+
+function IM:DoDelayedProcessing(list, loop_fn, finish_fn, run_delay, init_delay)
+	_Pending = list
+	_Loop_fn = loop_fn
+	_Finish_fn = finish_fn
+	_Delay = run_delay or 1
+
+	local _Init_Delay = init_delay or _Delay
+
+	zo_callLater(ProcessLoop, _Init_Delay)
+end
+
+function IM:CreateInventoryList(bagId, filter_fn, list)
+	self:SetCurrentInventory(bagId)
+	if not list then list = { }
+	else
+		for i = 1, #list / 2, 1 do
+			local tmp = list[i]
+			list[i] = list[(#list+1) - i]
+			list[(#list+1) - i] = tmp
+		end
+	end
+
+	for i,_ in pairs(self.currentInventory) do
+		if #list > 90 then break end
+		local data = self:GetItemData(i)
+		data.action, data.index, data.text = self.currentRuleset:Match(data)
+
+		if filter_fn(data) then
+			list[#list + 1] = data
+		end
+	end
+
+	for i = 1, #list / 2, 1 do
+		local tmp = list[i]
+		list[i] = list[(#list+1) - i]
+		list[(#list+1) - i] = tmp
+	end
+
+	return list
+end
+
+function IM:ProcessBag(bagId, filter_fn, loop_fn, finish_fn, run_delay, init_delay)
+	local list = IM:CreateInventoryList(bagId, filter_fn)
+
+	self:DoDelayedProcessing(list, loop_fn, finish_fn, run_delay, init_delay)
+end
+
+function IM:EventProcessBag(bagId, filter_fn, loop_fn, finish_fn, loop_event, abort_event, run_delay)
+	local list = IM:CreateInventoryList(bagId, filter_fn)
+
+	self:DoEventProcessing(list, loop_fn, finish_fn, loop_event, abort_event, run_delay)
+end
+
+function IM:AbortProcessing()
+	_Pending = nil
+end
diff --git a/Modules/Extractor.lua b/Modules/Extractor.lua
new file mode 100644
index 0000000..948ea4e
--- /dev/null
+++ b/Modules/Extractor.lua
@@ -0,0 +1,131 @@
+local DEBUG =
+-- function() end
+d
+
+local function _tr(str)
+	return str
+end
+
+local IM = InventoryManager
+
+local _waitforpanel = nil
+
+local at2tradeskill = {
+	[ARMORTYPE_HEAVY]				= CRAFTING_TYPE_BLACKSMITHING,
+	[ARMORTYPE_MEDIUM]				= CRAFTING_TYPE_CLOTHIER,
+	[ARMORTYPE_LIGHT]				= CRAFTING_TYPE_CLOTHIER,
+}
+
+local wt2tradeskill = {
+    [WEAPONTYPE_AXE]				= CRAFTING_TYPE_BLACKSMITHING,
+    [WEAPONTYPE_BOW]				= CRAFTING_TYPE_WOODWORKING,
+    [WEAPONTYPE_DAGGER]				= CRAFTING_TYPE_BLACKSMITHING,
+    [WEAPONTYPE_FIRE_STAFF]			= CRAFTING_TYPE_WOODWORKING,
+    [WEAPONTYPE_FROST_STAFF]		= CRAFTING_TYPE_WOODWORKING,
+    [WEAPONTYPE_HAMMER]				= CRAFTING_TYPE_BLACKSMITHING,
+    [WEAPONTYPE_HEALING_STAFF]		= CRAFTING_TYPE_WOODWORKING,
+    [WEAPONTYPE_LIGHTNING_STAFF]	= CRAFTING_TYPE_WOODWORKING,
+    [WEAPONTYPE_SHIELD]				= CRAFTING_TYPE_WOODWORKING,
+    [WEAPONTYPE_SWORD]				= CRAFTING_TYPE_BLACKSMITHING,
+    [WEAPONTYPE_TWO_HANDED_AXE]		= CRAFTING_TYPE_BLACKSMITHING,
+    [WEAPONTYPE_TWO_HANDED_HAMMER]	= CRAFTING_TYPE_BLACKSMITHING,
+    [WEAPONTYPE_TWO_HANDED_SWORD]	= CRAFTING_TYPE_BLACKSMITHING,
+}
+
+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 },
+}
+
+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
+
+	if type(entry) ~= "table" then return entry end
+
+	return GetItemTradeSkill(data, entry)
+end
+
+local function GetTradeskillUsed()
+	if not ZO_EnchantingTopLevelExtractionSlotContainer:IsHidden() then
+		return CRAFTING_TYPE_ENCHANTING
+	elseif not ZO_SmithingTopLevelDeconstructionPanelSlotContainer:IsHidden() then
+		return CRAFTING_TYPE_BLACKSMITHING
+	end
+
+	return nil
+end
+
+local function filter_for_deconstruction(tradeskill, data)
+		if data.action ~= IM.ACTION_DECONSTRUCT then return false end
+
+		local ts = GetItemTradeSkill(data)
+
+		if ts ~= tradeskill then return false end
+
+		if not CanItemBeSmithingExtractedOrRefined(data.bagId, data.slotId, ts) then return false end
+
+		if IM.FCOISL:IsProtectedAction(action, data.bagId, data.slotId, ts == CRAFTING_TYPE_ENCHANTING) then return false end
+
+		return true
+end
+
+local function extract_single_item(tradeskill, data)
+	if 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)
+	local list = IM:CreateInventoryList(BAG_BACKPACK,
+		function(data) return filter_for_deconstruction(tradeskill, data) end)
+
+	list = IM:CreateInventoryList(BAG_BANK,
+		function(data) return filter_for_deconstruction(tradeskill, data) end,
+		list)
+
+	IM:DoEventProcessing(list,
+		function(data) return extract_single_item(tradeskill, data) end,
+		function() end,
+		EVENT_CRAFT_COMPLETED,
+		EVENT_END_CRAFTING_STATION_INTERACT,
+		InventoryManager.settings.bankMoveDelay)
+end
+
+local function WaitPanel()
+	if GetTradeskillUsed() then
+		InitDeconstruction(_waitforpanel)
+		_waitforpanel = nil
+	end
+
+	if not _waitforpanel then return end
+
+	zo_callLater(WaitPanel, 500)
+end
+
+local function OnOpenCraftingStation(eventCode, craftSkill, sameStation)
+	_waitforpanel = craftSkill
+	zo_callLater(WaitPanel, 500)
+end
+
+local function OnCloseCraftingStation(eventCode, craftSkill)
+	_waitforpanel = nil
+end
+
+EVENT_MANAGER:RegisterForEvent(IM.name, EVENT_CRAFTING_STATION_INTERACT, OnOpenCraftingStation)
+EVENT_MANAGER:RegisterForEvent(IM.name, EVENT_END_CRAFTING_STATION_INTERACT, OnCloseCraftingStation)
diff --git a/Modules/Junker.lua b/Modules/Junker.lua
index 8f8b0a2..b7b9255 100644
--- a/Modules/Junker.lua
+++ b/Modules/Junker.lua
@@ -1,60 +1,65 @@
+local DEBUG =
+-- function() end
+d

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

 	self:SetCurrentInventory(BAG_BACKPACK)
 	for i,_ in pairs(self.currentInventory) do
 		local data = self:GetItemData(i)
-		local action, index, text = InventoryManager.currentRuleset:Match(data)
-		if action == self.IM_Ruleset.ACTION_DESTROY then
+		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

-function InventoryManager:WorkBackpack(dryrun)
-	self:SetCurrentInventory(BAG_BACKPACK)
-	for i,_ in pairs(self.currentInventory) do
-		local data = self:GetItemData(i)
-		local action, index, text = self.currentRuleset:Match(data)
-
-		if action == self.IM_Ruleset.ACTION_JUNK or
-			action == self.IM_Ruleset.ACTION_DESTROY then
-			if not dryrun then
-				action = self.IM_Ruleset.ACTION_JUNK
-				SetItemIsJunk(BAG_BACKPACK, i, true)
-			end
-			self:ReportAction(data, dryrun, action, index, text)
-		end
-		if (action == self.IM_Ruleset.ACTION_STASH) and dryrun then
-			self:ReportAction(data, dryrun, action, index, text)
-		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
-	if not dryrun then
-		self:CheckAndDestroy()
+
+	-- 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 dryrun then
+		return true
 	end
+	return false
 end

-function InventoryManager:UnJunk()
+function IM:WorkBackpack(dryrun)
+	self:ProcessBag(BAG_BACKPACK,
+		function(data) return filter_for_backpack_action(dryrun, data) end,
+		function(data) IM:ProcessSingleItem(dryrun, data) end,
+		function() IM:CheckAndDestroy() end,
+		IM.settings.statusChangeDelay)
+end
+
+function IM:UnJunk()
 	for i = 1, GetBagSize(BAG_BACKPACK), 1 do
 		SetItemIsJunk(BAG_BACKPACK, i, false)
 	end
 end

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

 	if not data then return end
-
-	local action = self.currentRuleset:Match(data)
-	if action == self.IM_Ruleset.ACTION_JUNK or
-		action == self.IM_Ruleset.ACTION_DESTROY then
-		self:ReportAction(data, false, action)
-		SetItemIsJunk(bagId, slotId, true)
+
+	if filter_for_backpack_action(false, data) then
+		IM:ProcessSingleItem(false, data)
 	end

 	self:CheckAndDestroy()
@@ -63,7 +68,7 @@ end
 local function OnInvSlotUpdate(eventCode, bagId, slotId, isNewItem, itemSoundCategory, inventoryUpdateReason, stackCountChange)
 	if not isNewItem or bagId ~= BAG_BACKPACK then return end

-	InventoryManager:OnInvSlotUpdate(bagId, slotId)
+	IM:OnInvSlotUpdate(bagId, slotId)
 end

-EVENT_MANAGER:RegisterForEvent(InventoryManager.name, EVENT_INVENTORY_SINGLE_SLOT_UPDATE, OnInvSlotUpdate)
+EVENT_MANAGER:RegisterForEvent(IM.name, EVENT_INVENTORY_SINGLE_SLOT_UPDATE, OnInvSlotUpdate)
diff --git a/Modules/Seller.lua b/Modules/Seller.lua
index b92b914..f0bc21e 100644
--- a/Modules/Seller.lua
+++ b/Modules/Seller.lua
@@ -6,50 +6,82 @@ local function _tr(str)
 	return str
 end

-local Sells = nil
-local StartGold = 0
-
-local function ProcessMoves()
-	if not Sells or #Sells == 0 then
-		local gain = GetCarriedCurrencyAmount(CURT_MONEY) - StartGold
-		CHAT_SYSTEM:AddMessage(zo_strformat(GetString("IM_CUR_SOLDJUNK"), gain))
-		return
+local _Gain = nil
+
+local function do_sell(data, eventCode, itemName, itemQuantity, money)
+	if eventCode ~= nil then
+		_Gain = _Gain + money
 	end
+	InventoryManager:ProcessSingleItem(false, data)
+end
+
+local function filter_for_sale(fence, data)
+	data.action, data.index, data.text = InventoryManager.currentRuleset:Match(data)

-	local entry = Sells[#Sells]
-	local slotId = entry[1]
-	local count = entry[2]
+	if data.stolen ~= fence then return false end

-	SellInventoryItem(BAG_BACKPACK, slotId, count)
-	Sells[#Sells] = nil
-	zo_callLater(ProcessMoves, InventoryManager.settings.bankMoveDelay)
+	if data.action ~= InventoryManager.ACTION_SELL then return false end
+
+	if InventoryManager.FCOISL:IsProtectedAction(
+		InventoryManager.ACTION_SELL,
+		data.bagId,
+		data.slotId,
+		fence) then return false end
+	return true
 end

-function InventoryManager:SellJunk(stolen)
-	Sells = { }
-	StartGold = GetCarriedCurrencyAmount(CURT_MONEY)
-	self:SetCurrentInventory(BAG_BACKPACK)
-	for i,_ in pairs(self.currentInventory) do
-		if #Sells > 90 then
-			break
+local function filter_for_launder(data)
+	if data.action ~= InventoryManager.ACTION_LAUNDER then return false end
+
+	if InventoryManager.FCOISL:IsProtectedAction(
+		InventoryManager.ACTION_LAUNDER,
+		data.bagId,
+		data.slotId) then return false end
+	return true
+end
+
+function InventoryManager:SellItems(stolen)
+	local list = { }
+	local end_fn = function(abort, eventCode, itemName, itemQuantity, money)
+			if eventCode ~= nil then
+				_Gain = _Gain + money
+			end
+			CHAT_SYSTEM:AddMessage(zo_strformat(GetString(IM_CUR_SOLDJUNK), _Gain))
 		end
-		local data = self:GetItemData(i)
-		if data.junk and data.stolen == stolen then
-			Sells[#Sells + 1] = { i, data.count }
+
+	local launder_run = function(abort, eventCode, itemName, itemQuantity, money)
+		if eventCode ~= nil then
+			_Gain = _Gain + money
 		end
-	end
-	zo_callLater(ProcessMoves, InventoryManager.settings.bankMoveDelay)
+		InventoryManager:EventProcessBag(BAG_BACKPACK,
+			filter_for_launder,
+			function(data) InventoryManager:ProcessSingleItem(false, data) end,
+			function(abort) end_fn(abort) end,
+			EVENT_ITEM_LAUNDER_RESULT,
+			EVENT_CLOSE_STORE,
+			InventoryManager.settings.bankMoveDelay)
+		end
+
+	_Gain = 0
+	self:EventProcessBag(BAG_BACKPACK,
+		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)
+
 end

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

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

diff --git a/Rulesets.lua b/Rulesets.lua
index 707dea6..42e4bc9 100644
--- a/Rulesets.lua
+++ b/Rulesets.lua
@@ -10,7 +10,7 @@ end
 local IM_Rule = {}
 local IM_Ruleset = InventoryManager.IM_Ruleset

-IM_Rule.action		= IM_Ruleset.ACTION_KEEP
+IM_Rule.action		= InventoryManager.ACTION_KEEP
 IM_Rule.minQuality 	= ITEM_QUALITY_TRASH
 IM_Rule.maxQuality 	= ITEM_QUALITY_LEGENDARY

@@ -54,27 +54,31 @@ function IM_Rule:ToString()
 	end

 	if self.worthless then
-		itemDescription = GetString("IM_RULETXT_WORTHLESS") .. " " .. itemDescription
+		itemDescription = GetString(IM_RULETXT_WORTHLESS) .. " " .. itemDescription
 	end

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

 	if self.stolen then
-		itemDescription = GetString("IM_RULETXT_STOLEN") .. " " .. itemDescription
+		itemDescription = GetString(IM_RULETXT_STOLEN) .. " " .. itemDescription
 	end

 	if self.isSet then
-		isSetText = " " .. GetString("IM_RULETXT_ISSET")
+		isSetText = " " .. GetString(IM_RULETXT_ISSET)
 	end

 	colorMin = GetItemQualityColor(self.minQuality)
@@ -89,7 +93,7 @@ function IM_Rule:ToString()
 			InventoryManager:getIQString(self.maxQuality))
 	end

-	return zo_strformat(GetString("IM_RULETXTFORMAT"),
+	return zo_strformat(GetString(IM_RULETXTFORMAT),
 		itemDescription,
 		qualityRangeText,
 		isSetText,
@@ -148,6 +152,9 @@ function IM_Rule:Filter(data)

 	-- FCO ItemSaver marker?
 	if not InventoryManager.FCOISL:FitMark(data.itemInstanceId, self.FCOISMark) then return false end
+
+	-- Junked?
+	if self.junk and not data.junk then return false end

 	-- Part of a set?
 	if self.isSet and not data.isSet then return false end
@@ -190,8 +197,7 @@ function IM_Ruleset:Match(data)
 		-- If it's stolen, we can't put it in the bank.
 		if res then
 			if data.locked then res = false
-			elseif data.junk and v.action ~= IM_Ruleset.ACTION_DESTROY then res = false
-			elseif data.stolen and v.action == IM_Ruleset.ACTION_STASH then res = false
+			elseif data.stolen and v.action == InventoryManager.ACTION_STASH then res = false
 			end
 		end

@@ -200,7 +206,7 @@ function IM_Ruleset:Match(data)
 		end
 	end

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

 function IM_Ruleset:NewRule()
@@ -215,19 +221,19 @@ InventoryManager.IM_Ruleset = IM_Ruleset
 -- local Rule1 = IM_Ruleset:NewRule()
 -- Rule1.filterType = "IM_FILTER_MISC"
 -- Rule1.filterSubType = "IM_FILTERSPEC_TRASH"
--- Rule1.action = IM_Ruleset.ACTION_JUNK
+-- 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 = IM_Ruleset.ACTION_RETRIEVE
+-- 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 = IM_Ruleset.ACTION_DESTROY
+-- Rule3.action = InventoryManager.ACTION_DESTROY

 -- Ruleset.rules = { Rule1, Rule2, Rule3 }
 -- -- Ruleset.rules = { }
diff --git a/UI/ProfileEdit.lua b/UI/ProfileEdit.lua
index 88e5c2b..4a806f7 100644
--- a/UI/ProfileEdit.lua
+++ b/UI/ProfileEdit.lua
@@ -18,7 +18,7 @@ function PE:GetControls()
 	return {
 		{
 			type = "dropdown",
-			name = GetString("IM_PE_PROFILES"),
+			name = GetString(IM_PE_PROFILES),
 			width = "half",
 			choices = {  },
 			getFunc = function() return PE:GetSelectedProfile() end,
@@ -27,14 +27,14 @@ function PE:GetControls()
 		},
 		{
 			type = "button",
-			name = GetString("IM_PE_LOADPROFILE"),
+			name = GetString(IM_PE_LOADPROFILE),
 			width = "half",
 			disabled = function() return PE:GetBtnLoadDisabled() end,
 			func = function() return PE:BtnLoadClicked() end,
 		},
 		{
 			type = "button",
-			name = GetString("IM_PE_DELETEPROFILE"),
+			name = GetString(IM_PE_DELETEPROFILE),
 			width = "half",
 			disabled = function() return PE:GetBtnDeleteDisabled() end,
 			func = function() return PE:BtnDeleteClicked() end,
@@ -46,8 +46,8 @@ function PE:GetControls()
 		},
 		{
 			type = "editbox",
-			name = GetString("IM_PE_EDITPRNAME"),
-			tooltip = GetString("IM_PM_PROFILENAME_TOOLTIP"),
+			name = GetString(IM_PE_EDITPRNAME),
+			tooltip = GetString(IM_PM_PROFILENAME_TOOLTIP),
 			getFunc = function() return PE:GetProfileName() end,
 			setFunc = function(text) PE:SetProfileName(text) end,
 			isMultiline = false,
@@ -55,7 +55,7 @@ function PE:GetControls()
 		},
 		{
 			type = "button",
-			name = GetString("IM_PE_SAVEPROFILE"),
+			name = GetString(IM_PE_SAVEPROFILE),
 			width = "half",
 			disabled = function() return PE:GetBtnSaveDisabled() end,
 			func = function() return PE:BtnSaveClicked() end,
@@ -155,7 +155,7 @@ function PE:UpdateProfileList(preselection)
 	local profiles
 	profiles = IM.presetProfiles

-	PE.profileList[1] = GetString("IM_RM_PRESETRULES")
+	PE.profileList[1] = GetString(IM_RM_PRESETRULES)

 	if #profiles then
 		for i = 1, #profiles, 1 do
@@ -165,7 +165,7 @@ function PE:UpdateProfileList(preselection)
 		end
 	end

-	PE.profileList[#PE.profileList + 1] = GetString("IM_RM_CUSTOMRULES")
+	PE.profileList[#PE.profileList + 1] = GetString(IM_RM_CUSTOMRULES)

 	profiles = IM.Profiles
 	if #profiles then
diff --git a/UI/RuleEdit.lua b/UI/RuleEdit.lua
index e0de620..074dc9e 100644
--- a/UI/RuleEdit.lua
+++ b/UI/RuleEdit.lua
@@ -57,7 +57,7 @@ local function getSpecificFilterTypes(whichFilter)
 		end
 	end

-	return getChoiceboxLists(found, function(n) return zo_strformat(GetString(n), GetString("IM_FILTER_RULE_ANY")) end)
+	return getChoiceboxLists(found, function(n) return zo_strformat(GetString(n), GetString(IM_FILTER_RULE_ANY)) end)
 end

 local function getSpecificTraitTypes(whichFilter, whichSubFilter)
@@ -101,9 +101,9 @@ function RE:GetControls()
 	return {
 		{
 			type = "dropdown",
-			name = GetString("IM_RE_CURRENTRULES"),
+			name = GetString(IM_RE_CURRENTRULES),
 			width = "half",
-			tooltip = GetString("IM_UI_LISTRULES_HEAD"),
+			tooltip = GetString(IM_UI_LISTRULES_HEAD),
 			choices = { "(invalid)" },
 			getFunc = function() return RE:GetSelectedRule() end,
 			setFunc = function(value) RE:SetSelectedRule(value) end,
@@ -111,40 +111,40 @@ function RE:GetControls()
 		},
 		{
 			type = "button",
-			name = GetString("IM_RE_DELETERULE"),
+			name = GetString(IM_RE_DELETERULE),
 			width = "half",
 			disabled = function() return RE:GetBtnDeleteDisabled() end,
 			func = function() return RE:BtnDeleteClicked() end,
 		},
 		{
 			type = "button",
-			name = GetString("IM_RE_MOVERULEUP"),
+			name = GetString(IM_RE_MOVERULEUP),
 			width = "half",
 			disabled = function() return RE:GetBtnMoveUpDisabled() end,
 			func = function() return RE:BtnMoveUpClicked() end,
 		},
 		{
 			type = "button",
-			name = GetString("IM_RE_ADDRULEBEFORE"),
+			name = GetString(IM_RE_ADDRULEBEFORE),
 			width = "half",
 			func = function() return RE:BtnAddBeforeClicked() end,
 		},
 		{
 			type = "button",
-			name = GetString("IM_RE_MOVERULEDN"),
+			name = GetString(IM_RE_MOVERULEDN),
 			width = "half",
 			disabled = function() return RE:GetBtnMoveDownDisabled() end,
 			func = function() return RE:BtnMoveDownClicked() end,
 		},
 		{
 			type = "button",
-			name = GetString("IM_RE_ADDRULEAFTER"),
+			name = GetString(IM_RE_ADDRULEAFTER),
 			width = "half",
 			func = function() return RE:BtnAddAfterClicked() end,
 		},
 		{
 			type = "button",
-			name = GetString("IM_RE_REPLACERULE"),
+			name = GetString(IM_RE_REPLACERULE),
 			width = "half",
 			disabled = function() return RE:GetBtnDeleteDisabled() end, -- Same condition as Delete
 			func = function() return RE:BtnReplaceClicked() end,
@@ -156,11 +156,11 @@ function RE:GetControls()
 		},
 		{
 			type = "description",
-			text = GetString("IM_RE_DESC"),
+			text = GetString(IM_RE_DESC),
 		},
 		{
 			type = "dropdown",
-			name = GetString("IM_RE_ACTION"),
+			name = GetString(IM_RE_ACTION),
 			width = "half",
 			choices = RE.actionList["order"],
 			getFunc = function() return RE.actionList["forward"][RE.editingRule.action] end,
@@ -173,7 +173,7 @@ function RE:GetControls()
 		},
 		{
 			type = "dropdown",
-			name = GetString("IM_RE_GENTYPE"),
+			name = GetString(IM_RE_GENTYPE),
 			width = "half",
 			choices = RE.filterTypesList["order"],
 			getFunc = function() return RE.filterTypesList["forward"][RE.editingRule.filterType] end,
@@ -181,7 +181,7 @@ function RE:GetControls()
 		},
 		{
 			type = "dropdown",
-			name = GetString("IM_RE_SPECTYPE"),
+			name = GetString(IM_RE_SPECTYPE),
 			width = "half",
 			choices = { "(invalid)" },
 			getFunc = function() return RE.filterSubTypesList["forward"][RE.editingRule.filterSubType] end,
@@ -190,7 +190,7 @@ function RE:GetControls()
 		},
 		{
 			type = "dropdown",
-			name = GetString("IM_RE_TRAIT"),
+			name = GetString(IM_RE_TRAIT),
 			width = "half",
 			choices = { "(invalid)" },
 			getFunc = function() return RE.traitList["forward"][RE.editingRule.traitType or 0] end,
@@ -199,7 +199,7 @@ function RE:GetControls()
 		},
 		{
 			type = "checkbox",
-			name = GetString("IM_RE_PARTOFSET"),
+			name = GetString(IM_RE_PARTOFSET),
 			width = "half",
 			disabled = function() return RE:GetIsSetCheckDisabled() end,
 			getFunc = function() return RE.editingRule.isSet end,
@@ -207,7 +207,7 @@ function RE:GetControls()
 		},
 		{
 			type = "dropdown",
-			name = GetString("IM_RE_MINQUAL"),
+			name = GetString(IM_RE_MINQUAL),
 			width = "half",
 			choices = RE.qualityList["order"],
 			getFunc = function() return RE.qualityList["forward"][RE.editingRule.minQuality] end,
@@ -215,7 +215,7 @@ function RE:GetControls()
 		},
 		{
 			type = "dropdown",
-			name = GetString("IM_RE_MAXQUAL"),
+			name = GetString(IM_RE_MAXQUAL),
 			width = "half",
 			choices = RE.qualityList["order"],
 			getFunc = function() return RE.qualityList["forward"][RE.editingRule.maxQuality] end,
@@ -223,7 +223,8 @@ function RE:GetControls()
 		},
 		{
 			type = "dropdown",
-			name = GetString("IM_FCOIS_CHOICE"),
+			name = GetString(IM_FCOIS_CHOICE),
+			width = "half",
 			choices = IM.FCOISL:GetDynamicIconChoices(),
 			getFunc = function() return IM.FCOISL:GetIndexedMark(RE.editingRule.FCOISMark) end,
 			setFunc = function(value) RE.editingRule.FCOISMark = IM.FCOISL:GetMarkIndex(value) end,
@@ -231,14 +232,21 @@ function RE:GetControls()
 		},
 		{
 			type = "checkbox",
-			name = GetString("IM_RE_STOLEN"),
+			name = GetString(IM_RE_INJUNK),
+			width = "half",
+			getFunc = function() return RE.editingRule.junk end,
+			setFunc = function(value) RE.editingRule.junk = value end,
+		},
+		{
+			type = "checkbox",
+			name = GetString(IM_RE_STOLEN),
 			width = "half",
 			getFunc = function() return RE.editingRule.stolen end,
 			setFunc = function(value) RE.editingRule.stolen = value end,
 		},
 		{
 			type = "checkbox",
-			name = GetString("IM_RE_WORTHLESS"),
+			name = GetString(IM_RE_WORTHLESS),
 			width = "half",
 			getFunc = function() return RE.editingRule.worthless end,
 			setFunc = function(value) RE.editingRule.worthless = value end,
@@ -372,8 +380,8 @@ function RE:UpdateRuleList(preselection)
 		IWONTSAY_IM_CHO_RULES:UpdateValue(false, RE.ruleList[preselection or 1])
 	else
 		DEBUG(" -- Setting (empty)...")
-		IWONTSAY_IM_CHO_RULES:UpdateChoices({ GetString("IM_RE_EMPTY") })
-		IWONTSAY_IM_CHO_RULES:UpdateValue(false, GetString("IM_RE_EMPTY"))
+		IWONTSAY_IM_CHO_RULES:UpdateChoices({ GetString(IM_RE_EMPTY) })
+		IWONTSAY_IM_CHO_RULES:UpdateValue(false, GetString(IM_RE_EMPTY))
 	end
 end

diff --git a/UI/Settings.lua b/UI/Settings.lua
index f653f38..1f54ea4 100644
--- a/UI/Settings.lua
+++ b/UI/Settings.lua
@@ -12,8 +12,8 @@ function SE:GetControls()
 	return {
 		{
 			type = "slider",
-			name = GetString("IM_SET_MIN_GOLD"),
-			tooltip = GetString("IM_SET_MIN_GOLD_TOOLTIP"),
+			name = GetString(IM_SET_MIN_GOLD),
+			tooltip = GetString(IM_SET_MIN_GOLD_TOOLTIP),
 			min = 0,
 			max = 100000,
 			getFunc = function() return IM.settings.minGold end,
@@ -22,8 +22,8 @@ function SE:GetControls()
 		},
 		{
 			type = "slider",
-			name = GetString("IM_SET_MAX_GOLD"),
-			tooltip = GetString("IM_SET_MAX_GOLD_TOOLTIP"),
+			name = GetString(IM_SET_MAX_GOLD),
+			tooltip = GetString(IM_SET_MAX_GOLD_TOOLTIP),
 			min = 0,
 			max = 100000,
 			getFunc = function() return IM.settings.maxGold end,
@@ -32,8 +32,8 @@ function SE:GetControls()
 		},
 		{
 			type = "slider",
-			name = GetString("IM_SET_MIN_TV"),
-			tooltip = GetString("IM_SET_MIN_TV_TOOLTIP"),
+			name = GetString(IM_SET_MIN_TV),
+			tooltip = GetString(IM_SET_MIN_TV_TOOLTIP),
 			min = 0,
 			max = 100000,
 			getFunc = function() return IM.settings.minTV end,
@@ -42,8 +42,8 @@ function SE:GetControls()
 		},
 		{
 			type = "slider",
-			name = GetString("IM_SET_MAX_TV"),
-			tooltip = GetString("IM_SET_MAX_TV_TOOLTIP"),
+			name = GetString(IM_SET_MAX_TV),
+			tooltip = GetString(IM_SET_MAX_TV_TOOLTIP),
 			min = 0,
 			max = 100000,
 			getFunc = function() return IM.settings.maxTV end,
@@ -52,8 +52,8 @@ function SE:GetControls()
 		},
 		{
 			type = "slider",
-			name = GetString("IM_SET_BANK"),
-			tooltip = GetString("IM_SET_BANK_TOOLTIP"),
+			name = GetString(IM_SET_BANK),
+			tooltip = GetString(IM_SET_BANK_TOOLTIP),
 			min = 2,
 			max = 200,
 			getFunc = function() return IM.settings.bankMoveDelay end,
@@ -62,8 +62,28 @@ function SE:GetControls()
 		},
 		{
 			type = "slider",
-			name = GetString("IM_SET_DEST"),
-			tooltip = GetString("IM_SET_DEST_TOOLTIP"),
+			name = GetString(IM_SET_START_BM),
+			tooltip = GetString(IM_SET_START_BM_TT),
+			min = 10,
+			max = 5000,
+			getFunc = function() return IM.settings.bankInitDelay end,
+			setFunc = function(value) IM.settings.bankInitDelay = value end,
+			width = "half",	--or "half" (optional)
+		},
+		{
+			type = "slider",
+			name = GetString(IM_SET_INV),
+			tooltip = GetString(IM_SET_INV_TT),
+			min = 2,
+			max = 200,
+			getFunc = function() return IM.settings.statusChangeDelay end,
+			setFunc = function(value) IM.settings.statusChangeDelay = value end,
+			width = "half",	--or "half" (optional)
+		},
+		{
+			type = "slider",
+			name = GetString(IM_SET_DEST),
+			tooltip = GetString(IM_SET_DEST_TOOLTIP),
 			min = 0,
 			max = 500,
 			getFunc = function() return IM.settings.destroyThreshold end,
@@ -72,8 +92,8 @@ function SE:GetControls()
 		},
 		{
 			type = "checkbox",
-			name = GetString("IM_SET_AUTOSELL"),
-			tooltip = GetString("IM_SET_AUTOSELL_TOOLTIP"),
+			name = GetString(IM_SET_AUTOSELL),
+			tooltip = GetString(IM_SET_AUTOSELL_TOOLTIP),
 			width = "half",
 			getFunc = function() return IM.settings.autosell end,
 			setFunc = function(value) IM.settings.autosell = value end,
@@ -85,29 +105,29 @@ function SE:GetControls()
 		},
 		{
 			type = "button",
-			name = GetString("IM_SET_LIST"),
-			tooltip = GetString("IM_SET_LIST_TOOLTIP"),
+			name = GetString(IM_SET_LIST),
+			tooltip = GetString(IM_SET_LIST_TOOLTIP),
 			func = function() IM:listrules() end,
 			width = "half",	--or "half" (optional)
 		},
 		{
 			type = "button",
-			name = GetString("IM_SET_UNJUNK"),
-			tooltip = GetString("IM_SET_UNJUNK_TOOLTIP"),
+			name = GetString(IM_SET_UNJUNK),
+			tooltip = GetString(IM_SET_UNJUNK_TOOLTIP),
 			func = function() IM:UnJunk() end,
 			width = "half",	--or "half" (optional)
 		},
 		{
 			type = "button",
-			name = GetString("IM_SET_DRYRUN"),
-			tooltip = GetString("IM_SET_DRYRUN_TOOLTIP"),
+			name = GetString(IM_SET_DRYRUN),
+			tooltip = GetString(IM_SET_DRYRUN_TOOLTIP),
 			func = function() IM:dryrun() end,
 			width = "half",	--or "half" (optional)
 		},
 		{
 			type = "button",
-			name = GetString("IM_SET_RUN"),
-			tooltip = GetString("IM_SET_RUN_TOOLTIP"),
+			name = GetString(IM_SET_RUN),
+			tooltip = GetString(IM_SET_RUN_TOOLTIP),
 			func = function() IM:WorkBackpack(false) end,
 			width = "half",	--or "half" (optional)
 		},
diff --git a/lang/de.lua b/lang/de.lua
index 1f63803..b6e2b33 100644
--- a/lang/de.lua
+++ b/lang/de.lua
@@ -4,23 +4,30 @@ local lang = {

 	-- parameters are itemDescription, qualityRangeText, isSetText, actionText
 	-- e.g. "put in trash any stolen worthless light armor with quality Trash to Normal"
-	IM_RULETXTFORMAT0			= "<<4>>: jeder <<z:1>><<z:2>><<z:3>>.",
-	IM_RULETXT_ISSET0			= "(Teil eines Sets)",
-	IM_RULETXT_STOLEN0			= "gestohlene(s)",
-	IM_RULETXT_WORTHLESS0		= "wertlos(es)",
+	IM_RULETXTFORMAT			= "<<4>>: jeder <<z:1>><<z:2>><<z:3>>",
+	IM_RULETXT_ISSET			= "(Teil eines Sets)",
+	IM_RULETXT_STOLEN			= "gestohlene(s)",
+	IM_RULETXT_WORTHLESS		= "wertlos(es)",
+	IM_RULETXT_JUNKED			= "weggeworfene(s)",
 	IM_RULETXT_QUALITY1			= "mit Qualität <<1>>",
 	IM_RULETXT_QUALITY2			= "mit Qualität von <<1>> bis <<2>>",

 	IM_ACTIONTXT0				= "Behalten",
 	IM_ACTIONTXT1				= "Zum Müll stecken",
 	IM_ACTIONTXT2				= "Vernichten",
+	IM_ACTIONTXT3				= "Verkaufen",
+	IM_ACTIONTXT4				= "Waschen",
+	IM_ACTIONTXT5				= "Zerlegen",
+	IM_ACTIONTXT6				= "Sperren",
+	IM_ACTIONTXT7				= "Entsperren",
 	IM_ACTIONTXT10				= "Einlagern",
 	IM_ACTIONTXT20				= "Auslagern",

-	IM_TAKENACTION0				= "Würde <<z:1>>: |t16:16:<<2>>|t <<3>> wegen Regel <<4>>: <<5>>",
+	IM_TAKENACTION0				= "Würde <<z:1>>: |t16:16:<<2>>|t <<3>> wegen Regel <<4>>: <<5>>.",
 	IM_TAKENACTION1				= "<<1>>: |t16:16:<<2>>|t <<3>>",
+	IM_TAKENACTION2				= "Würde |cff4444NICHT|r <<z:1>>: |t16:16:<<2>>|t <<3>> wegen Regel <<4>>: <<5>>, aber es ist durch FCOIS gesperrt",

-	IM_FILTER_RULE_ANY0			= "Alle",
+	IM_FILTER_RULE_ANY			= "Alle",
 	IM_FILTER_ANY0				= "Gegenstand",
 	IM_FILTERSPEC_ANY0			= "<<1>>",
 	IM_FILTER_WEAPON0			= "Waffe",
@@ -77,88 +84,107 @@ local lang = {
 	IM_META_TRAIT_TYPE2			= "unbekannt für andere",
 	IM_META_TRAIT_TYPE3			= "unbekannt",

-	IM_RE_CURRENTRULES0			= "Derzeitige Regeln",
-	IM_RE_DELETERULE0			= "Regel löschen",
-	IM_RE_MOVERULEUP0			= "Regel nach oben",
-	IM_RE_ADDRULEBEFORE0		= "Neue Regel vor dieser",
-	IM_RE_MOVERULEDN0			= "Regel nach unten",
-	IM_RE_ADDRULEAFTER0			= "Neue Regel nach dieser",
-	IM_RE_REPLACERULE0			= "Regel ersetzen",
-	IM_RE_DESC0					= "Verändern Sie diese Felder um die Regel zu definieren, die Sie hinzufügen wollen.",
-	IM_RE_ACTION0				= "Aktion",
-	IM_RE_GENTYPE0				= "Typ",
-	IM_RE_SPECTYPE0				= "Spezieller Typ",
-	IM_RE_TRAIT0				= "Eigenschaft",
-	IM_RE_PARTOFSET0			= "Teil eines Sets",
-	IM_RE_MINQUAL0				= "Minimale Qualität",
-	IM_RE_MAXQUAL0				= "Maximale Qualität",
-	IM_RE_STOLEN0				= "Gestohlen",
-	IM_RE_WORTHLESS0			= "Wertlos",
-	IM_RE_EMPTY0				= "(leer)",
+	IM_RE_CURRENTRULES			= "Derzeitige Regeln",
+	IM_RE_DELETERULE			= "Regel löschen",
+	IM_RE_MOVERULEUP			= "Regel nach oben",
+	IM_RE_ADDRULEBEFORE			= "Neue Regel vor dieser",
+	IM_RE_MOVERULEDN			= "Regel nach unten",
+	IM_RE_ADDRULEAFTER			= "Neue Regel nach dieser",
+	IM_RE_REPLACERULE			= "Regel ersetzen",
+	IM_RE_DESC					= "Verändern Sie diese Felder um die Regel zu definieren, die Sie hinzufügen wollen.",
+	IM_RE_ACTION				= "Aktion",
+	IM_RE_GENTYPE				= "Typ",
+	IM_RE_SPECTYPE				= "Spezieller Typ",
+	IM_RE_TRAIT					= "Eigenschaft",
+	IM_RE_PARTOFSET				= "Teil eines Sets",
+	IM_RE_MINQUAL				= "Minimale Qualität",
+	IM_RE_MAXQUAL				= "Maximale Qualität",
+	IM_RE_STOLEN				= "Gestohlen",
+	IM_RE_WORTHLESS				= "Wertlos",
+	IM_RE_EMPTY					= "(leer)",
+	IM_RE_INJUNK 				= "Im Müll",

-	IM_PE_PROFILES0				= "Profile",
-	IM_PE_LOADPROFILE0			= "Profil laden",
-	IM_PE_DELETEPROFILE0		= "Profil löschen",
-	IM_PE_EDITPRNAME0			= "Profilname",
-	IM_PE_SAVEPROFILE0			= "Profil speichern",
+	IM_PE_PROFILES				= "Profile",
+	IM_PE_LOADPROFILE			= "Profil laden",
+	IM_PE_DELETEPROFILE			= "Profil löschen",
+	IM_PE_EDITPRNAME			= "Profilname",
+	IM_PE_SAVEPROFILE			= "Profil speichern",

-	IM_BANK_LIMITED0			= "Transaktion unvollständig - ZOS Anti-Spam-Filterung",
-	IM_BANK_DEADLOCK0			= "Transaktion unvollständig - beide Lager voll",
-	IM_BANK_PARTIAL0			= "Transaktion unvollständig - eines der Lager voll",
-	IM_BANK_OK0					= "Transaktion abgeschlossen",
+	IM_BANK_LIMITED				= "Transaktion unvollständig - ZOS Anti-Spam-Filterung",
+	IM_BANK_DEADLOCK			= "Transaktion unvollständig - beide Lager voll",
+	IM_BANK_PARTIAL				= "Transaktion unvollständig - eines der Lager voll",
+	IM_BANK_OK					= "Transaktion abgeschlossen",

-	IM_UI_LISTRULES_HEAD0		= "Liste der Regeln",
-	IM_SET_MIN_GOLD0			= "Minimum Gold",
-	IM_SET_MIN_GOLD_TOOLTIP0	= "Wieviele Münzen mindestens beim Charakter behalten werden",
-	IM_SET_MAX_GOLD0			= "Maximum Gold",
-	IM_SET_MAX_GOLD_TOOLTIP0	= "Wieviele Münzen höchstens beim Charakter behalten werden",
-	IM_SET_MIN_TV0				= "Minimum Tel Var stones",
-	IM_SET_MIN_TV_TOOLTIP0		= "Wieviele Steine mindestens beim Charakter behalten werden",
-	IM_SET_MAX_TV0				= "Maximum Tel Var stones",
-	IM_SET_MAX_TV_TOOLTIP0		= "Wieviele Steine höchstens beim Charakter behalten werden",
-	IM_SET_BANK0				= "Bank-Verzögerung",
-	IM_SET_BANK_TOOLTIP0		= "Zeit in Millisekunden zwischen einzelnen Bankbewegungen",
-	IM_SET_DEST0				= "Zerstörungsschwelle",
-	IM_SET_DEST_TOOLTIP0		= "Zerstöre die durch Regeln angegebenen Gegenstände, wenn weniger Slots frei sind als hier angegeben",
-	IM_SET_LIST0				= "Regeln auflisten",
-	IM_SET_LIST_TOOLTIP0		= "Listet alle Regeln im Chatfenster auf",
-	IM_SET_UNJUNK0				= "Müll-Marker löschen",
-	IM_SET_UNJUNK_TOOLTIP0		= "Löscht alle Müll-Markierungen auf den Gegenständen im Inventar",
-	IM_SET_DRYRUN0				= "Probelauf",
-	IM_SET_DRYRUN_TOOLTIP0		= "Listet die Aktionen, die auf die Gegenstände im Inventar ausgeführt würden",
-	IM_SET_RUN0					= "Inventar bearbeiten",
-	IM_SET_RUN_TOOLTIP0			= "Führt die Wegwerf/Zerstörungsaktionen über die Gegenstände im Inventar aus",
+	IM_UI_LISTRULES_HEAD		= "Liste der Regeln",
+	IM_SET_MIN_GOLD				= "Minimum Gold",
+	IM_SET_MIN_GOLD_TOOLTIP		= "Wieviele Münzen mindestens beim Charakter behalten werden",
+	IM_SET_MAX_GOLD				= "Maximum Gold",
+	IM_SET_MAX_GOLD_TOOLTIP		= "Wieviele Münzen höchstens beim Charakter behalten werden",
+	IM_SET_MIN_TV				= "Minimum Tel Var stones",
+	IM_SET_MIN_TV_TOOLTIP		= "Wieviele Steine mindestens beim Charakter behalten werden",
+	IM_SET_MAX_TV				= "Maximum Tel Var stones",
+	IM_SET_MAX_TV_TOOLTIP		= "Wieviele Steine höchstens beim Charakter behalten werden",
+	IM_SET_BANK					= "Bank-Verzögerung",
+	IM_SET_BANK_TOOLTIP			= "Zeit in Millisekunden zwischen einzelnen Bankbewegungen",
+	IM_SET_DEST					= "Zerstörungsschwelle",
+	IM_SET_DEST_TOOLTIP			= "Zerstöre die durch Regeln angegebenen Gegenstände, wenn weniger Slots frei sind als hier angegeben",
+	IM_SET_LIST					= "Regeln auflisten",
+	IM_SET_LIST_TOOLTIP			= "Listet alle Regeln im Chatfenster auf",
+	IM_SET_UNJUNK				= "Müll-Marker löschen",
+	IM_SET_UNJUNK_TOOLTIP		= "Löscht alle Müll-Markierungen auf den Gegenständen im Inventar",
+	IM_SET_DRYRUN				= "Probelauf",
+	IM_SET_DRYRUN_TOOLTIP		= "Listet die Aktionen, die auf die Gegenstände im Inventar ausgeführt würden",
+	IM_SET_RUN					= "Inventar bearbeiten",
+	IM_SET_RUN_TOOLTIP			= "Führt die Wegwerf/Zerstörungsaktionen über die Gegenstände im Inventar aus",

-	IM_SET_AUTOSELL0			= "Gegenstände im Müll verkaufen",
-	IM_SET_AUTOSELL_TOOLTIP0	= "Wenn gesetzt, verkaufe alle Gegenstände im Müll, sobald man einen Händler oder Hehler besucht",
-
-	IM_PM_PROFILENAME_TOOLTIP0	= "Namen vom Profil hier eingeben",
-	IM_RM_PRESETRULES0			= "--- Voreingestellte Profile ---",
-	IM_RM_CUSTOMRULES0			= "--- Eigene Profile ---",
-	IM_LIST_NUM_RULES0			= "Gefundene Regeln: ",
-	IM_LIST_RULE0				= "Regel ",
-	IM_UI_PM0					= "Profilmanagement",
-	IM_UI_PM_TOOLTIP0			= "Auswählen, Hinzufügen und Löschen von Profilen",
-	IM_UI_RM0					= "Regelmanagement",
-	IM_UI_RM_TOOLTIP0			= "Auswählen, Hinzufügen und Löschen von Regeln",
-	IM_UI_SETTINGS0				= "Einstellungen",
-	IM_UI_SETTINGS_TOOLTIP0		= "Generelles Verhalten anpassen",
-	IM_CUR_SOLDJUNK0			= "Müll verkauft, Erlös <<1>> Münzen.",
-	IM_CUR_DEPOSIT0				= "Zahle <<1>> <<2>> ein.",
-	IM_CUR_WITHDRAW0			= "Hebe <<1>> <<2>> ab.",
-	IM_CUR_GOLD0				= "Goldmnünzen",
-	IM_CUR_TVSTONES0			= "Tel Var Steine",
+	IM_SET_AUTOSELL				= "Gegenstände im Müll verkaufen",
+	IM_SET_AUTOSELL_TOOLTIP		= "Wenn gesetzt, verkaufe alle Gegenstände im Müll, sobald man einen Händler oder Hehler besucht",
+	IM_SET_START_BM 			= "Verzögerung vor Bankbewegung",
+	IM_SET_START_BM_TT 			= "Setzt die Verzögerung, bevor mit Bankbewegungen angefangen wird. Es ist ratsam, bei hochvolumigen Addons wie Inventory Grid View einen höheren Wert anzusetzen.",
+	IM_SET_INV 					= "Verzögerung zw. Inv.-Änderung",
+	IM_SET_INV_TT 				= "Setzt die Verzögerung zwischen Änderungen im Inventar wie Sperren/Entsperren usw.",
+
+	IM_PM_PROFILENAME_TOOLTIP	= "Namen vom Profil hier eingeben",
+	IM_RM_PRESETRULES			= "--- Voreingestellte Profile ---",
+	IM_RM_CUSTOMRULES			= "--- Eigene Profile ---",
+	IM_LIST_NUM_RULES			= "Gefundene Regeln: ",
+	IM_LIST_RULE				= "Regel ",
+	IM_UI_PM					= "Profilmanagement",
+	IM_UI_PM_TOOLTIP			= "Auswählen, Hinzufügen und Löschen von Profilen",
+	IM_UI_RM					= "Regelmanagement",
+	IM_UI_RM_TOOLTIP			= "Auswählen, Hinzufügen und Löschen von Regeln",
+	IM_UI_SETTINGS				= "Einstellungen",
+	IM_UI_SETTINGS_TOOLTIP		= "Generelles Verhalten anpassen",
+	IM_CUR_SOLDJUNK				= "Gegenstände verkauft, Erlös <<1>> Münzen.",
+	IM_CUR_DEPOSIT				= "Zahle <<1>> <<2>> ein.",
+	IM_CUR_WITHDRAW				= "Hebe <<1>> <<2>> ab.",
+	IM_CUR_GOLD					= "Goldmnünzen",
+	IM_CUR_TVSTONES				= "Tel Var Steine",
+
+	IM_FCOIS_CHOICE			= "FCO ItemSaver Markierung",
+	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_NOMARK			= "Keine Markierung",
+	IM_FCOIS_ANYMARK			= "Irgendeine Markierung",

-	IM_FCOIS_CHOICE0			= "FCO ItemSaver Markierung",
-	IM_FCOIS_UNMARKED0			= "unmarkiert(es)",
-	IM_FCOIS_WITHANYMARK0		= "mit einer Markierung",
-	IM_FCOIS_MARKEDASX0			= "markiert als <<z:1>>",
-	IM_FCOIS_NOCAREMARK0		= "Nicht relevant",
-	IM_FCOIS_NOMARK0			= "Keine Markierung",
-	IM_FCOIS_ANYMARK0			= "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_UPDATE_V2_NOTE 		= "Aktualisiere Charakterdaten nach Version 2: Regel 'Verkaufe alle Gegenstände im Müll' hinzugefügt, um altes Verhalten beizubehalten.",

-	IM_INIT_DETECTED_CS_OLD0	= "IM: Altes CraftStore 3.00+ AddOn erkannt. Es ist überholt, bitte aktualisieren Sie auf 'CraftStore Fixed And Improved'",
-	IM_INIT_DETECTED_CS_NEW0	= "IM: CraftStore Fixed And Improved AddOn erkannt",
+	IM_FCO_STATIC_TXT1			= "zur Sperrung",
+	IM_FCO_STATIC_TXT2			= "Ausrüstungssatz 1",
+	IM_FCO_STATIC_TXT3			= "für Forschung",
+	IM_FCO_STATIC_TXT4			= "Ausrüstungssatz 2",
+	IM_FCO_STATIC_TXT5			= "zum Verkauf",
+	IM_FCO_STATIC_TXT6			= "Ausrüstungssatz 3",
+	IM_FCO_STATIC_TXT7			= "Ausrüstungssatz 4",
+	IM_FCO_STATIC_TXT8			= "Ausrüstungssatz 5",
+	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",

 }

diff --git a/lang/en.lua b/lang/en.lua
index 5b5ccae..51b1388 100644
--- a/lang/en.lua
+++ b/lang/en.lua
@@ -4,23 +4,30 @@ local lang = {

 	-- parameters are itemDescription, qualityRangeText, isSetText, actionText
 	-- e.g. "put in trash any stolen worthless light armor with quality Trash to Normal"
-	IM_RULETXTFORMAT0			= "<<z:4>> any <<z:1>><<z:2>><<z:3>>.",
-	IM_RULETXT_ISSET0			= "which is part of a set",
-	IM_RULETXT_STOLEN0			= "stolen",
-	IM_RULETXT_WORTHLESS0		= "worthless",
+	IM_RULETXTFORMAT			= "<<z:4>> any <<z:1>><<z:2>><<z:3>>",
+	IM_RULETXT_ISSET			= "which is part of a set",
+	IM_RULETXT_STOLEN			= "stolen",
+	IM_RULETXT_WORTHLESS		= "worthless",
+	IM_RULETXT_JUNKED			= "junked",
 	IM_RULETXT_QUALITY1			= "with quality <<1>>",
 	IM_RULETXT_QUALITY2			= "with quality from <<1>> to <<2>>",

 	IM_ACTIONTXT0				= "Keep",
 	IM_ACTIONTXT1				= "Put to junk",
 	IM_ACTIONTXT2				= "Destroy",
+	IM_ACTIONTXT3				= "Sell",
+	IM_ACTIONTXT4				= "Launder",
+	IM_ACTIONTXT5				= "Deconstruct",
+	IM_ACTIONTXT6				= "Lock",
+	IM_ACTIONTXT7				= "Unlock",
 	IM_ACTIONTXT10				= "Put in bank",
 	IM_ACTIONTXT20				= "Pull from bank",

-	IM_TAKENACTION0				= "Would <<z:1>>: |t16:16:<<2>>|t <<3>> because of Rule <<4>>: <<5>>",
+	IM_TAKENACTION0				= "Would <<z:1>>: |t16:16:<<2>>|t <<3>> because of Rule <<4>>: <<5>>.",
 	IM_TAKENACTION1				= "<<1>>: |t16:16:<<2>>|t <<3>>",
+	IM_TAKENACTION2				= "Would |cff4444NOT|r <<z:1>>: |t16:16:<<2>>|t <<3>> because of Rule <<4>>: <<5>>, but it's locked by FCOIS",

-	IM_FILTER_RULE_ANY0			= "Any",
+	IM_FILTER_RULE_ANY			= "Any",
 	IM_FILTER_ANY0				= "Item",
 	IM_FILTERSPEC_ANY0			= "<<1>>",
 	IM_FILTER_WEAPON0			= "Weapon",
@@ -77,89 +84,107 @@ local lang = {
 	IM_META_TRAIT_TYPE2			= "unknown to others",
 	IM_META_TRAIT_TYPE3			= "unknown",

-	IM_RE_CURRENTRULES0			= "Current Rules",
-	IM_RE_DELETERULE0			= "Delete Rule",
-	IM_RE_MOVERULEUP0			= "Move rule up",
-	IM_RE_ADDRULEBEFORE0		= "Add rule before this one",
-	IM_RE_MOVERULEDN0			= "Move rule down",
-	IM_RE_ADDRULEAFTER0			= "Add rule after this one",
-	IM_RE_REPLACERULE0			= "Replace Rule",
-	IM_RE_DESC0					= "Modify the contents of these fields to specify the rule to add.",
-	IM_RE_ACTION0				= "Action",
-	IM_RE_GENTYPE0				= "General type",
-	IM_RE_SPECTYPE0				= "Specific type",
-	IM_RE_TRAIT0				= "Trait",
-	IM_RE_PARTOFSET0			= "Part of a set",
-	IM_RE_MINQUAL0				= "Minimum Quality",
-	IM_RE_MAXQUAL0				= "Maximum Quality",
-	IM_RE_STOLEN0				= "stolen",
-	IM_RE_WORTHLESS0			= "worthless",
-	IM_RE_EMPTY0				= "(empty)",
+	IM_RE_CURRENTRULES			= "Current Rules",
+	IM_RE_DELETERULE			= "Delete Rule",
+	IM_RE_MOVERULEUP			= "Move rule up",
+	IM_RE_ADDRULEBEFORE			= "Add rule before this one",
+	IM_RE_MOVERULEDN			= "Move rule down",
+	IM_RE_ADDRULEAFTER			= "Add rule after this one",
+	IM_RE_REPLACERULE			= "Replace Rule",
+	IM_RE_DESC					= "Modify the contents of these fields to specify the rule to add.",
+	IM_RE_ACTION				= "Action",
+	IM_RE_GENTYPE				= "General type",
+	IM_RE_SPECTYPE				= "Specific type",
+	IM_RE_TRAIT					= "Trait",
+	IM_RE_PARTOFSET				= "Part of a set",
+	IM_RE_MINQUAL				= "Minimum Quality",
+	IM_RE_MAXQUAL				= "Maximum Quality",
+	IM_RE_STOLEN				= "stolen",
+	IM_RE_WORTHLESS				= "worthless",
+	IM_RE_EMPTY					= "(empty)",
+	IM_RE_INJUNK 				= "In Junk",

-	IM_PE_PROFILES0				= "Profiles",
-	IM_PE_LOADPROFILE0			= "Load Profile",
-	IM_PE_DELETEPROFILE0		= "Delete Profile",
-	IM_PE_EDITPRNAME0			= "Edit Profile Name",
-	IM_PE_SAVEPROFILE0			= "Save Profile",
+	IM_PE_PROFILES				= "Profiles",
+	IM_PE_LOADPROFILE			= "Load Profile",
+	IM_PE_DELETEPROFILE			= "Delete Profile",
+	IM_PE_EDITPRNAME			= "Edit Profile Name",
+	IM_PE_SAVEPROFILE			= "Save Profile",

-	IM_BANK_LIMITED0			= "Incomplete transaction - avoiding anti-flood filtering",
-	IM_BANK_DEADLOCK0			= "Incomplete transaction - both inventories full",
-	IM_BANK_PARTIAL0			= "Incomplete transaction - one inventory full",
-	IM_BANK_OK0					= "All transactions sent",
+	IM_BANK_LIMITED				= "Incomplete transaction - avoiding anti-flood filtering",
+	IM_BANK_DEADLOCK			= "Incomplete transaction - both inventories full",
+	IM_BANK_PARTIAL				= "Incomplete transaction - one inventory full",
+	IM_BANK_OK					= "All transactions sent",

-	IM_UI_LISTRULES_HEAD0		= "List of rules",
-	IM_SET_MIN_GOLD0			= "Minimum Gold",
-	IM_SET_MIN_GOLD_TOOLTIP0	= "Minimum amount of gold to keep on character",
-	IM_SET_MAX_GOLD0			= "Maximum Gold",
-	IM_SET_MAX_GOLD_TOOLTIP0	= "Maximum amount of gold to keep on character",
-	IM_SET_MIN_TV0				= "Minimum Tel Var stones",
-	IM_SET_MIN_TV_TOOLTIP0		= "Minimum amount of Tel Var stones to keep on character",
-	IM_SET_MAX_TV0				= "Maximum Tel Var stones",
-	IM_SET_MAX_TV_TOOLTIP0		= "Maximum amount of Tel Var stones to keep on character",
-	IM_SET_BANK0				= "Delay between bank moves",
-	IM_SET_BANK_TOOLTIP0		= "Time in milliseconds to wait between bank moves",
-	IM_SET_DEST0				= "Destroy Threshold",
-	IM_SET_DEST_TOOLTIP0		= "Destroy items when inventory space drops below this number of slots",
-	IM_SET_LIST0				= "List rules",
-	IM_SET_LIST_TOOLTIP0		= "List the current ruleset in the chat window",
-	IM_SET_UNJUNK0				= "UnJunk all",
-	IM_SET_UNJUNK_TOOLTIP0		= "Remove Junk markings on all of your items",
-	IM_SET_DRYRUN0				= "Dry run",
-	IM_SET_DRYRUN_TOOLTIP0		= "List the actions which would be performed on your inventory",
-	IM_SET_RUN0					= "Run over Inventory",
-	IM_SET_RUN_TOOLTIP0			= "Perform the junk/destroy options on your current inventory",
+	IM_UI_LISTRULES_HEAD		= "List of rules",
+	IM_SET_MIN_GOLD				= "Minimum Gold",
+	IM_SET_MIN_GOLD_TOOLTIP		= "Minimum amount of gold to keep on character",
+	IM_SET_MAX_GOLD				= "Maximum Gold",
+	IM_SET_MAX_GOLD_TOOLTIP		= "Maximum amount of gold to keep on character",
+	IM_SET_MIN_TV				= "Minimum Tel Var stones",
+	IM_SET_MIN_TV_TOOLTIP		= "Minimum amount of Tel Var stones to keep on character",
+	IM_SET_MAX_TV				= "Maximum Tel Var stones",
+	IM_SET_MAX_TV_TOOLTIP		= "Maximum amount of Tel Var stones to keep on character",
+	IM_SET_BANK					= "Delay between bank moves",
+	IM_SET_BANK_TOOLTIP			= "Time in milliseconds to wait between bank moves",
+	IM_SET_DEST					= "Destroy Threshold",
+	IM_SET_DEST_TOOLTIP			= "Destroy items when inventory space drops below this number of slots",
+	IM_SET_LIST					= "List rules",
+	IM_SET_LIST_TOOLTIP			= "List the current ruleset in the chat window",
+	IM_SET_UNJUNK				= "UnJunk all",
+	IM_SET_UNJUNK_TOOLTIP		= "Remove Junk markings on all of your items",
+	IM_SET_DRYRUN				= "Dry run",
+	IM_SET_DRYRUN_TOOLTIP		= "List the actions which would be performed on your inventory",
+	IM_SET_RUN					= "Run over Inventory",
+	IM_SET_RUN_TOOLTIP			= "Perform the junk/destroy options on your current inventory",

-	IM_SET_AUTOSELL0			= "Auto-Sell junked items",
-	IM_SET_AUTOSELL_TOOLTIP0	= "When set, junked items will be sold whenever visiting a merchant or a fence",
+	IM_SET_AUTOSELL				= "Auto-Sell junked items",
+	IM_SET_AUTOSELL_TOOLTIP		= "When set, junked items will be sold whenever visiting a merchant or a fence",
+	IM_SET_START_BM 			= "Delay before starting bank moves",
+	IM_SET_START_BM_TT 			= "Sets the delay before starting bank moves. It's advisable to set a higher delay if you use high-impact addons like Inventory Grid View.",
+	IM_SET_INV 					= "Delay between inv changes",
+	IM_SET_INV_TT 				= "Sets the delay between inventory status changes like junk/unjunk lock/unlock and so on.",

-	IM_PM_PROFILENAME_TOOLTIP0	= "Enter the name of the new profile here",
-	IM_RM_PRESETRULES0			= "--- Preset profiles ---",
-	IM_RM_CUSTOMRULES0			= "--- Custom profiles ---",
-	IM_LIST_NUM_RULES0			= "Rules found: ",
-	IM_LIST_RULE0				= "Rule ",
-	IM_UI_PM0					= "Profile Management",
-	IM_UI_PM_TOOLTIP0			= "Select, add or delete profiles",
-	IM_UI_RM0					= "Rule Management",
-	IM_UI_RM_TOOLTIP0			= "Add, modify and delete rules",
-	IM_UI_SETTINGS0				= "Settings",
-	IM_UI_SETTINGS_TOOLTIP0		= "Adapt the general behavior",
-	IM_CUR_SOLDJUNK0			= "Sold junk, Revenue is <<1>> gold coins.",
-	IM_CUR_DEPOSIT0				= "Depositing <<1>> <<2>>.",
-	IM_CUR_WITHDRAW0			= "Withdrawing <<1>> <<2>>.",
-	IM_CUR_GOLD0				= "gold coins",
-	IM_CUR_TVSTONES0			= "Tel Var stones",
+	IM_PM_PROFILENAME_TOOLTIP	= "Enter the name of the new profile here",
+	IM_RM_PRESETRULES			= "--- Preset profiles ---",
+	IM_RM_CUSTOMRULES			= "--- Custom profiles ---",
+	IM_LIST_NUM_RULES			= "Rules found: ",
+	IM_LIST_RULE				= "Rule ",
+	IM_UI_PM					= "Profile Management",
+	IM_UI_PM_TOOLTIP			= "Select, add or delete profiles",
+	IM_UI_RM					= "Rule Management",
+	IM_UI_RM_TOOLTIP			= "Add, modify and delete rules",
+	IM_UI_SETTINGS				= "Settings",
+	IM_UI_SETTINGS_TOOLTIP		= "Adapt the general behavior",
+	IM_CUR_SOLDJUNK				= "Sold items, Revenue is <<1>> gold coins.",
+	IM_CUR_DEPOSIT				= "Depositing <<1>> <<2>>.",
+	IM_CUR_WITHDRAW				= "Withdrawing <<1>> <<2>>.",
+	IM_CUR_GOLD					= "gold coins",
+	IM_CUR_TVSTONES				= "Tel Var stones",

-	IM_FCOIS_CHOICE0			= "FCO ItemSaver Marking",
-	IM_FCOIS_UNMARKED0			= "unmarked",
-	IM_FCOIS_WITHANYMARK0		= "with any mark",
-	IM_FCOIS_MARKEDASX0			= "marked as <<z:1>>",
-	IM_FCOIS_NOCAREMARK0		= "Don't care",
-	IM_FCOIS_NOMARK0			= "No mark",
-	IM_FCOIS_ANYMARK0			= "Any mark",
+	IM_FCOIS_CHOICE				= "FCO ItemSaver Marking",
+	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_NOMARK				= "No mark",
+	IM_FCOIS_ANYMARK			= "Any mark",

-	IM_INIT_DETECTED_CS_OLD0	= "IM: Old CraftStore 3.00+ detected. It's outdated, please update to 'CraftStore Fixed And Improved'",
-	IM_INIT_DETECTED_CS_NEW0	= "IM: CraftStore Fixed And Improved detected",
+	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_FCO_STATIC_TXT1			= "for locking",
+	IM_FCO_STATIC_TXT2			= "gear set 1",
+	IM_FCO_STATIC_TXT3			= "for research",
+	IM_FCO_STATIC_TXT4			= "gear set 2",
+	IM_FCO_STATIC_TXT5			= "for sale",
+	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_TXT10			= "for improvement",
+	IM_FCO_STATIC_TXT11			= "for sale at guildstore",
+	IM_FCO_STATIC_TXT12			= "intricate",
 }

 for stringId, stringValue in pairs(lang) do