diff --git a/FCOISLink.lua b/FCOISLink.lua index e1abb2b..5748d0c 100644 --- a/FCOISLink.lua +++ b/FCOISLink.lua @@ -146,13 +146,13 @@ function FCOISL:IsAnyMark(mark) return mark == I_ANY_MARK end function FCOISL:GetDynamicIconChoices() if DIChoices then return DIChoices end + if not self:hasAddon() then return { TXT_NO_CARE } 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 = FCOISL:GetIconText(dynamicIconNr) diff --git a/InventoryManager.lua b/InventoryManager.lua index 957dfb8..e7695d3 100644 --- a/InventoryManager.lua +++ b/InventoryManager.lua @@ -150,6 +150,10 @@ function InventoryManager:run() self:WorkBackpack(false) end +function InventoryManager:OpenSettings() + self.LAM:OpenToPanel(self.UI.panel) +end + function InventoryManager:help() -- self:SetCurrentInventory(BAG_BACKPACK) -- for i, entry in pairs(self.currentInventory) do @@ -166,6 +170,7 @@ function InventoryManager:help() 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") + CHAT_SYSTEM:AddMessage("/im settings - Open up the settings menu") end function InventoryManager:SlashCommand(argv) @@ -185,6 +190,8 @@ function InventoryManager:SlashCommand(argv) self:dryrun() elseif options[1] == "run" then self:run() + elseif options[1] == "settings" then + self:OpenSettings() else CHAT_SYSTEM:AddMessage("Unknown parameter '" .. argv .. "'") end diff --git a/InventoryManager.txt b/InventoryManager.txt index f9a828d..479fcf4 100644 --- a/InventoryManager.txt +++ b/InventoryManager.txt @@ -2,7 +2,7 @@ ## APIVersion: 100018 ## OptionalDependsOn: LibAddonMenu-2.0 ## SavedVariables: IMSavedVars -## Version: 1.2.2 +## Version: 1.3.0 ## Author: iwontsay ## Description: iwontsay's Inventory Manager diff --git a/Modules/Banking.lua b/Modules/Banking.lua index d69d940..b5fb1f3 100644 --- a/Modules/Banking.lua +++ b/Modules/Banking.lua @@ -6,73 +6,110 @@ local function _tr(str) return str end -local ST_OK = 0 -local ST_TGTFULL = 1 -local ST_SPAM = 2 +local IM = InventoryManager -local InvCache = nil +local RevCaches = nil +local Empties = nil -local function ScanInventory(bagType) +local function CreateReverseCache(bagType) + local _revCache = { } local _empties = { } - local _stackCount = { } - local inv = SHARED_INVENTORY:GetOrCreateBagCache(bagType) + for i = 0, GetBagSize(bagType)-1, 1 do - if not inv[i] then - _empties[#_empties + 1] = i + local curStack, maxStack = GetSlotStackSize(bagType, i) + if curStack > 0 then + local id = GetItemInstanceId(bagType, i) + if curStack < maxStack then + if not _revCache[id] then _revCache[id] = { } end + local entry = _revCache[id] + entry[i] = { curStack, maxStack } + end else - local curStack, maxStack = GetSlotStackSize(bagType, i) - _stackCount[i] = { - ["id"] = inv[i].itemInstanceId, - ["current"] = curStack, - ["max"] = maxStack - } + _empties[#_empties + 1] = i end end - -- Empties is a list of empty slots, items an overview over stack counts in specific slots - return { ["empties"] = _empties, ["items"] = _stackCount } + return _revCache, _empties end -local function RebuildStackCache(tgtBagType) - InvCache["tgtStackCache"][tgtBagType] = { } - - local tgtInv = InvCache[tgtBagType] - local tgtStackCache = InvCache["tgtStackCache"][tgtBagType] +local function CreateReverseCaches() + RevCaches = { } + Empties = { } + RevCaches[BAG_BACKPACK], Empties[BAG_BACKPACK] = CreateReverseCache(BAG_BACKPACK) + RevCaches[BAG_BANK], Empties[BAG_BANK] = CreateReverseCache(BAG_BANK) +end - for k,v in pairs(tgtInv["items"]) do - local missing = v["max"] - v["current"] - if missing > 0 then - tgtStackCache[v["id"]] = { missing, k } +local function UpdateCaches(srcBagType, srcSlotId, tgtBagType, tgtSlotId, count) + local id = GetItemInstanceId(srcBagType, srcSlotId) + local stack + + stack = RevCaches[srcBagType][id] and RevCaches[srcBagType][id][srcSlotId] + if stack then + stack[1] = stack[1] - count + if stack[1] == 0 then + local newempties = Empties[srcBagType] + newempties[#newempties + 1] = srcSlotId + RevCaches[srcBagType][id][srcSlotId] = nil end + else + local newempties = Empties[srcBagType] + newempties[#newempties + 1] = srcSlotId + end + + stack = RevCaches[tgtBagType][id] and RevCaches[tgtBagType][id][tgtSlotId] + if stack then + stack[1] = stack[1] + count + if stack[1] == stack[2] then + RevCaches[tgtBagType][id][tgtSlotId] = nil + end + else + local _, maxStack = GetSlotStackSize(srcBagType, srcSlotId) + if count < maxStack then + -- We ended up with a new incomplete stack. + if not RevCaches[tgtBagType][id] then RevCaches[tgtBagType][id] = { } end + local newstack = RevCaches[tgtBagType][id] + newstack[tgtSlotId] = { count, maxStack } + end end end +-- Returns (empties source?), tgtSlotId, transferCount local function FindTargetSlot(srcBagType, srcSlotId, tgtBagType) - local srcInv = InvCache[srcBagType] - local tgtInv = InvCache[tgtBagType] - local tgtStackCache = InvCache["tgtStackCache"][tgtBagType] - - local iId = srcInv["items"][srcSlotId]["id"] - local count = srcInv["items"][srcSlotId]["current"] - - if tgtStackCache[iId] then - local missing = tgtStackCache[iId][1] + local curStack, maxStack = GetSlotStackSize(srcBagType, srcSlotId) + local id = GetItemInstanceId(srcBagType, srcSlotId) - if missing > 0 then - local k = tgtStackCache[iId][2] - local empties = missing >= count - return empties, false, k, (empties and count) or missing + local stacks = RevCaches[tgtBagType][id] + if stacks then + -- First, try to find a stack small enough to hold the entirety of the source + for tgtSlotId, v in pairs(stacks) do + if v[2]-v[1] >= curStack then + UpdateCaches(srcBagType, srcSlotId, tgtBagType, tgtSlotId, curStack) + return true, tgtSlotId, curStack + end end - end - - -- No incomplete stack found, return an empty slot or a failure - local emptyslots = tgtInv["empties"] - if #emptyslots == 0 then - return false, false, -1, 0 + -- Now, fill any incomplete stack we might have, splitting the source stack + for tgtSlotId, v in pairs(stacks) do + local missing = v[2] - v[1] + if missing > 0 then + UpdateCaches(srcBagType, srcSlotId, tgtBagType, tgtSlotId, missing) + return false, tgtSlotId, missing + end + end end + + -- All the stacks we might have found are already full, we need to find a free slot + local empties = Empties[tgtBagType] + + -- No such luck? + if #empties == 0 then return false, -1, 0 end + + -- It's a complete move, remove the empty slot from the target list, and create a new one on the source list + local tgtSlotId = empties[#empties] + empties[#empties] = nil - -- We'd start another stack, but we're sure it'll be a complete transfer - return true, true, emptyslots[#emptyslots], count + UpdateCaches(srcBagType, srcSlotId, tgtBagType, tgtSlotId, curStack) + return true, tgtSlotId, curStack + end @@ -91,86 +128,19 @@ local function CollectSingleDirection(action) return _moveSlots end -local function PrepareMoveCaches() - InvCache = { - [BAG_BACKPACK] = ScanInventory(BAG_BACKPACK), - [BAG_BANK] = ScanInventory(BAG_BANK), - ["tgtStackCache"] = { [BAG_BACKPACK] = { }, [BAG_BANK] = { } } - } - - RebuildStackCache(BAG_BACKPACK) - RebuildStackCache(BAG_BANK) - - Moves = { - ["stash"] = CollectSingleDirection(InventoryManager.ACTION_STASH), - ["retrieve"] = CollectSingleDirection(InventoryManager.ACTION_RETRIEVE) - } - -end - local function CalculateSingleMove(direction) - local IMR = InventoryManager.IM_Ruleset local srcBagType = (direction == 1 and BAG_BACKPACK) or BAG_BANK local tgtBagType = (direction == 1 and BAG_BANK) or BAG_BACKPACK - local tgtStackCache = InvCache["tgtStackCache"][tgtBagType] local srcSlotRepo = Moves[(direction == 1 and "stash") or "retrieve"] - if #srcSlotRepo == 0 then - return "src_empty" - end + if #srcSlotRepo == 0 then return "src_empty" end local srcSlotId = srcSlotRepo[#srcSlotRepo] - local empties, newSlot, tgtSlotId, count = FindTargetSlot(srcBagType, srcSlotId, tgtBagType) - if count == 0 then - return "tgt_full" - end + local empties, tgtSlotId, count = FindTargetSlot(srcBagType, srcSlotId, tgtBagType) + if count == 0 then return "tgt_full" end - InventoryManager:SetCurrentInventory(srcBagType) - local data = InventoryManager:GetItemData(srcSlotId) - - if count > 0 then - end - - if empties then - -- Empties source slot, remove from pending moves - InvCache[srcBagType]["items"][srcSlotId] = nil - - local emptyslots = InvCache[srcBagType]["empties"] - emptyslots[#emptyslots + 1] = srcSlotId - - srcSlotRepo[#srcSlotRepo] = nil - else - -- Incomplete move, deduce count in source cache - local srcslot = InvCache[srcBagType]["items"][srcSlotId] - srcslot["current"] = srcslot["current"] - count - end - - if newSlot then - -- Filled up a new slot in the target - InvCache[tgtBagType]["items"][tgtSlotId] = { - ["id"] = data.itemInstanceId, - ["current"] = count, - ["max"] = data.maxCount - } - - local emptyslots = InvCache[tgtBagType]["empties"] - emptyslots[#emptyslots] = nil - - local tgtslot = InvCache[tgtBagType]["items"][tgtSlotId] - tgtStackCache[data.itemInstanceId] = { tgtslot["max"] - tgtslot["current"], tgtSlotId } - else - -- Stashed onto existing stack, increase count - local tgtslot = InvCache[tgtBagType]["items"][tgtSlotId] - tgtslot["current"] = tgtslot["current"] + count - tgtStackCache[data.itemInstanceId] = { tgtslot["max"] - tgtslot["current"], tgtSlotId } - - -- If we filled a stack, rescan, maybe there's another incomplete stack. - if tgtStackCache[data.itemInstanceId][1] == 0 then - RebuildStackCache(tgtBagType) - end - end - + if empties then srcSlotRepo[#srcSlotRepo] = nil end return "ok", { ["srcbag"] = srcBagType, @@ -183,9 +153,16 @@ end local function CalculateMoves() -- Prepare an overview of the inventories and the pending transfers in both directions - PrepareMoveCaches() + CreateReverseCaches() local continue = true local _moveStack = { } + + InventoryManager.currentRuleset:ResetCounters() + + Moves = { + ["stash"] = CollectSingleDirection(IM.ACTION_STASH), + ["retrieve"] = CollectSingleDirection(IM.ACTION_RETRIEVE) + } -- We alternate between stashing and retrieving to minimize the chance of one -- of the inventories running full. @@ -226,9 +203,7 @@ end InventoryManager.moveStatus = nil -function ProcessMove(move) - local IMR = InventoryManager.IM_Ruleset - +function ProcessMove(move) local bagIdFrom = move["srcbag"] local slotIdFrom = move["srcslot"] local bagIdTo = move["tgtbag"] @@ -290,22 +265,39 @@ function InventoryManager:BalanceCurrency(currencyType, minCur, maxCur, curName) end end +local function event_filter_fn(eventCode, bagId, slotId, isNewItem, itemSoundCategory, inventoryUpdateReason, stackCountChange) + -- Bank moves fire two events, react only if we got the receiver side. + if not stackCountChange or stackCountChange > 0 then + return true + end + return false +end + function InventoryManager:OnBankOpened() local moves self.moveStatus, moves = CalculateMoves() + -- Flip the list. The processors start from the end, and the sequence is important here. + for i = 1, #moves / 2, 1 do + local tmp = moves[i] + moves[i] = moves[(#moves+1) - i] + moves[(#moves+1) - i] = tmp + end + 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:DoDelayedProcessing(moves, - ProcessMove, - function() InventoryManager:FinishMoves() end, - InventoryManager.settings.bankMoveDelay, + zo_callLater( + function() + IM:DoEventProcessing(moves, + ProcessMove, + function() IM:FinishMoves() end, + EVENT_INVENTORY_SINGLE_SLOT_UPDATE, + EVENT_CLOSE_BANK, + InventoryManager.settings.bankMoveDelay, + event_filter_fn) + end, InventoryManager.settings.bankInitDelay) end -local function OnBankOpened(eventCode) - InventoryManager:OnBankOpened() -end - -EVENT_MANAGER:RegisterForEvent(InventoryManager.name, EVENT_OPEN_BANK, OnBankOpened) +EVENT_MANAGER:RegisterForEvent(InventoryManager.name, EVENT_OPEN_BANK, function() InventoryManager:OnBankOpened() end) diff --git a/Modules/DelayedProcessor.lua b/Modules/DelayedProcessor.lua index b53c54b..b26d27b 100644 --- a/Modules/DelayedProcessor.lua +++ b/Modules/DelayedProcessor.lua @@ -15,6 +15,7 @@ local _Finish_fn = nil local _Event_Next = nil local _Event_Abort = nil +local _Event_Filter_fn = nil -- Simple processing loop, call next element after delay local function ProcessLoop() @@ -33,6 +34,11 @@ 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) + -- Bail out if this event is not meant for us. + if _Event_Filter_fn then + if not _Event_Filter_fn(eventCode, a1, a2, a3, a4, a5, a6, a7, a8) then return end + end + if not _Pending or #_Pending == 0 then if _Event_Next then EVENT_MANAGER:UnregisterForEvent("IMEventProcessLoop", _Event_Next) @@ -62,13 +68,14 @@ local function EventProcessLoopAbort() if _Finish_fn then _Finish_fn(false) end end -function IM:DoEventProcessing(list, loop_fn, finish_fn, loop_event, abort_event, run_delay) +function IM:DoEventProcessing(list, loop_fn, finish_fn, loop_event, abort_event, run_delay, event_filter_fn) _Pending = list _Loop_fn = loop_fn _Finish_fn = finish_fn _Delay = run_delay or 1 _Event_Next = loop_event _Event_Abort = abort_event + _Event_Filter_fn = event_filter_fn if _Event_Next then EVENT_MANAGER:RegisterForEvent("IMEventProcessLoop", _Event_Next, EventProcessLoop) @@ -127,10 +134,10 @@ function IM:ProcessBag(bagId, filter_fn, loop_fn, finish_fn, run_delay, init_del 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) +function IM:EventProcessBag(bagId, filter_fn, loop_fn, finish_fn, loop_event, abort_event, run_delay, event_filter_fn) local list = IM:CreateInventoryList(bagId, filter_fn) - self:DoEventProcessing(list, loop_fn, finish_fn, loop_event, abort_event, run_delay) + self:DoEventProcessing(list, loop_fn, finish_fn, loop_event, abort_event, run_delay, event_filter_fn) end function IM:AbortProcessing() diff --git a/Modules/Extractor.lua b/Modules/Extractor.lua index dee47a7..94b96b5 100644 --- a/Modules/Extractor.lua +++ b/Modules/Extractor.lua @@ -92,6 +92,8 @@ local function extract_single_item(tradeskill, data) end local function InitDeconstruction(tradeskill) + InventoryManager.currentRuleset:ResetCounters() + local list = IM:CreateInventoryList(BAG_BACKPACK, function(data) return filter_for_deconstruction(tradeskill, data) end) diff --git a/Modules/Junker.lua b/Modules/Junker.lua index c3e886c..2f6574e 100644 --- a/Modules/Junker.lua +++ b/Modules/Junker.lua @@ -10,6 +10,8 @@ function IM:CheckAndDestroy() return end + InventoryManager.currentRuleset:ResetCounters() + self:SetCurrentInventory(BAG_BACKPACK) for i,_ in pairs(self.currentInventory) do local data = self:GetItemData(i) @@ -39,6 +41,7 @@ local function filter_for_backpack_action(dryrun, data) end function IM:WorkBackpack(dryrun) + InventoryManager.currentRuleset:ResetCounters() self:ProcessBag(BAG_BACKPACK, function(data) return filter_for_backpack_action(dryrun, data) end, function(data) IM:ProcessSingleItem(dryrun, data) end, diff --git a/Modules/Seller.lua b/Modules/Seller.lua index f0bc21e..8ebacf2 100644 --- a/Modules/Seller.lua +++ b/Modules/Seller.lua @@ -53,6 +53,7 @@ function InventoryManager:SellItems(stolen) if eventCode ~= nil then _Gain = _Gain + money end + InventoryManager.currentRuleset:ResetCounters() InventoryManager:EventProcessBag(BAG_BACKPACK, filter_for_launder, function(data) InventoryManager:ProcessSingleItem(false, data) end, @@ -63,6 +64,7 @@ function InventoryManager:SellItems(stolen) end _Gain = 0 + InventoryManager.currentRuleset:ResetCounters() self:EventProcessBag(BAG_BACKPACK, function(data) return filter_for_sale(stolen, data) end, do_sell, diff --git a/Rulesets.lua b/Rulesets.lua index 42e4bc9..93140f4 100644 --- a/Rulesets.lua +++ b/Rulesets.lua @@ -40,6 +40,10 @@ function IM_Rule:ToString() " " .. GetString(self.filterType)) + if self.maxCount then + actionText = actionText .. " " .. zo_strformat(GetString(IM_RULETXT_EXECOUNT), self.maxCount) + end + if self.traitType then local which = (self.filterType == "IM_FILTER_CONSUMABLE" and 1) or 0 if self.traitType < 0 then @@ -188,7 +192,15 @@ function IM_Ruleset:New() return _new end +local ExecCounters = nil + +function IM_Ruleset:ResetCounters() + ExecCounters = nil +end + function IM_Ruleset:Match(data) + if not ExecCounters then ExecCounters = { } end + for k, v in pairs(self.rules) do local res = v:Filter(data) @@ -201,7 +213,13 @@ function IM_Ruleset:Match(data) end 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 + res = false + end + if res then + ExecCounters[k] = (ExecCounters[k] or 0) + 1 return v.action, k, v:ToString() end end diff --git a/UI/RuleEdit.lua b/UI/RuleEdit.lua index 074dc9e..6524281 100644 --- a/UI/RuleEdit.lua +++ b/UI/RuleEdit.lua @@ -167,9 +167,16 @@ function RE:GetControls() setFunc = function(value) RE.editingRule.action = RE.actionList["reverse"][value] end, }, { - type = "description", - text = "", - width = "half", + type = "slider", + name = GetString(IM_SET_EXECOUNT), + tooltip = GetString(IM_SET_EXECOUNT_TT), + min = 0, + max = 500, + getFunc = function() return RE.editingRule.maxCount or 0 end, + setFunc = function(value) + RE.editingRule.maxCount = (value ~= 0 and value) or nil + end, + width = "half", --or "half" (optional) }, { type = "dropdown", diff --git a/lang/de.lua b/lang/de.lua index b6e2b33..695a10d 100644 --- a/lang/de.lua +++ b/lang/de.lua @@ -11,6 +11,7 @@ local lang = { IM_RULETXT_JUNKED = "weggeworfene(s)", IM_RULETXT_QUALITY1 = "mit Qualität <<1>>", IM_RULETXT_QUALITY2 = "mit Qualität von <<1>> bis <<2>>", + IM_RULETXT_EXECOUNT = "(max. <<1>>-mal)", IM_ACTIONTXT0 = "Behalten", IM_ACTIONTXT1 = "Zum Müll stecken", @@ -143,6 +144,8 @@ local lang = { 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_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_PM_PROFILENAME_TOOLTIP = "Namen vom Profil hier eingeben", IM_RM_PRESETRULES = "--- Voreingestellte Profile ---", diff --git a/lang/en.lua b/lang/en.lua index 51b1388..23137dc 100644 --- a/lang/en.lua +++ b/lang/en.lua @@ -11,6 +11,7 @@ local lang = { IM_RULETXT_JUNKED = "junked", IM_RULETXT_QUALITY1 = "with quality <<1>>", IM_RULETXT_QUALITY2 = "with quality from <<1>> to <<2>>", + IM_RULETXT_EXECOUNT = "(max. <<1>> times)", IM_ACTIONTXT0 = "Keep", IM_ACTIONTXT1 = "Put to junk", @@ -143,6 +144,8 @@ local lang = { 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_SET_EXECOUNT = "Maximum execution count", + IM_SET_EXECOUNT_TT = "How often this rule may be executed in a single run. 0 means 'unlimited'", IM_PM_PROFILENAME_TOOLTIP = "Enter the name of the new profile here", IM_RM_PRESETRULES = "--- Preset profiles ---",