diff --git a/aard_inventory.xml b/aard_inventory.xml index 4d0e45f..8c74e57 100644 --- a/aard_inventory.xml +++ b/aard_inventory.xml @@ -123,7 +123,7 @@ Usage Inventory table access dinv build confirm - dinv refresh [on | off | all] + dinv refresh [on | off | eager | all] dinv search [id | full] Item management @@ -260,7 +260,7 @@ Feature Wishlist 0) then - dbot.info("Running a full scan to check if anything was moved outside of this client") + if (inv.items.refreshGetPeriods() > 0) then + dbot.info("Running initial full scan to check if your inventory was modified outside of this plugin") retval = inv.items.refresh(0, invItemsRefreshLocAll, nil, nil) if (retval ~= DRL_RET_SUCCESS) and (retval ~= DRL_RET_UNINITIALIZED) then dbot.info("Initial full inventory rescan could not complete: " .. dbot.retval.getString(retval)) @@ -1348,8 +1348,8 @@ function inv.config.load() local retval = dbot.storage.loadTable(dbot.backup.getCurrentDir() .. inv.config.stateName, inv.config.reset) if (retval ~= DRL_RET_SUCCESS) then - dbot.warn("inv.config.load: Failed to load table from file \"@R" .. - dbot.backup.getCurrentDir() .. inv.config.stateName .. "@W\": " .. dbot.retval.getString(retval)) + dbot.warn("inv.config.load: Failed to load table from file \"@R" .. dbot.backup.getCurrentDir() .. + inv.config.stateName .. "@W\": " .. dbot.retval.getString(retval)) end -- if if (inv.config.table == nil) or (inv.config.table.tableFormat == nil) then @@ -1390,7 +1390,8 @@ function inv.config.new() isPromptEnabled = true, isBackupEnabled = true, isBuildExecuted = false, - refreshPeriod = 0 + refreshPeriod = 0, + refreshEagerSec = 0 } end -- inv.config.new @@ -1671,10 +1672,15 @@ function inv.cli.refresh.fn(name, line, wildcards) inv.tags.stop(invTagsRefresh, line, retval) elseif (command == "on") then - retval = inv.items.refreshOn(refreshPeriod) + retval = inv.items.refreshOn(refreshPeriod, 0) dbot.note("Inventory refresh is enabled") inv.tags.stop(invTagsRefresh, line, retval) + elseif (command == "eager") then + retval = inv.items.refreshOn(refreshPeriod, inv.items.timer.refreshEagerSec or 0) + dbot.note("Inventory refresh is enabled and uses eager refreshes after acquiring items") + inv.tags.stop(invTagsRefresh, line, retval) + elseif (command == "") or (command == "all") then if (inv.state == invStatePaused) then inv.state = invStateIdle @@ -1693,7 +1699,7 @@ end -- inv.cli.refresh.fn function inv.cli.refresh.usage() - dbot.print("@W " .. pluginNameCmd .. " refresh @Y[on | off | all] @w") + dbot.print("@W " .. pluginNameCmd .. " refresh @Y[on | off | eager | all] @w") end -- inv.cli.refresh.usage @@ -1719,9 +1725,13 @@ There are two types of refreshes: manual and automatic. A manual refresh simply refresh when the user requests one. An automatic refresh occurs when a timer expires after a specified period of time. Automatic refreshes are disabled by default on a new installation. If automatic refreshes are turned on ("@Gdinv refresh on @W") then an automatic refresh -refresh will trigger 5 seconds after an item is added to your inventory and every N minutes since -the previous automatic refresh (if N is not supplied, it will default to 5 minutes.) If nothing -has changed since the last refresh, the refresh simply returns. +runs every N minutes since the previous automatic refresh (if N is not supplied, it will default +to 5 minutes.) If nothing has changed since the last refresh, the refresh simply returns. + +If you really *really* like your inventory to always be up-to-date, you should use the "eager" +refresh mode ("@Gdinv refresh eager @W"). This is identical to the "refresh on" mode +described above but it will also schedule a refresh to run 5 seconds after an item is added to +your inventory. The plugin will skip a refresh or halt it early if you go to sleep, go AFK, enter combat, or hit a paging prompt. In this case, any changes that were missed will be picked up the next time a @@ -1738,12 +1748,14 @@ Examples: "@Gdinv refresh@W" 2) Disable automatic refreshes "@Gdinv refresh off@W" - 3) Enable automatic refreshes with default times (5 seconds since adding an item or 5 minutes - since the last refresh) + 3) Enable automatic refreshes with the default period (5 minutes since the last refresh) "@Gdinv refresh on@W" - 4) Enable automatic refreshes with a 10 minute delay between refreshes + 4) Enable automatic refreshes with a 10-minute delay between refreshes "@Gdinv refresh on 10@W" - 5) Perform a manual full refresh scan + 5) Enable automatic refreshes with a 7-minute delay between refreshes and an "eager" refresh + a few seconds after a new item is added to your inventory + "@Gdinv refresh eager 7@W" + 6) Perform a manual full refresh scan "@Gdinv refresh all@W" ]]) end -- inv.cli.refresh.examples @@ -4523,10 +4535,11 @@ end -- inv.cli.debug.fn -- inv.items.refreshCR -- inv.items.refreshAtTime -- inv.items.refreshDefault() --- inv.items.refreshGetPeriod() --- inv.items.refreshSetPeriod(numMinutes) --- inv.items.refreshOn(minutes) +-- inv.items.refreshGetPeriods() +-- inv.items.refreshSetPeriods(autoMin, eagerSec) +-- inv.items.refreshOn(autoMin, eagerSec) -- inv.items.refreshOff() +-- inv.items.isDirty() -- -- inv.items.build -- @@ -4607,7 +4620,7 @@ inv.items.stateName = "inv-items.state" inv.items.mainState = invItemsRefreshDirty -- state for the main inventory (as detected by invdata) inv.items.wornState = invItemsRefreshDirty -- state for items you are wearing (as detected by eqdata) -inv.items.keyringState = invItemsRefreshDirty -- state for items on your keyring (as detected by keyring data) +inv.items.keyringState = invItemsRefreshDirty -- state for keyring items (as detected by keyring data) inv.items.burstSize = 20 -- max # of items that can be moved in one atomic operation @@ -4828,7 +4841,7 @@ function inv.items.init.atActive() -- If automatic refreshes are enabled (i.e., the period is > 0 minutes), kick off the -- refresh timer to periodically scan our inventory and update the inventory table - local refreshPeriod = inv.items.refreshGetPeriod() or inv.items.timer.refreshMin + local refreshPeriod = inv.items.refreshGetPeriods() or inv.items.timer.refreshMin if (refreshPeriod > 0) then inv.items.refreshAtTime(refreshPeriod, 0) else @@ -5241,7 +5254,7 @@ function inv.items.discoverCR(maxNumItems, refreshLocations) if (refreshLocation == invItemsRefreshLocAll) or ((refreshLocation == invItemsRefreshLocDirty) and (not dbot.isWordInString(invItemsRefreshClean, keywordField))) then - dbot.note("Discovering contents of container " .. objId .. ": " .. v[invFieldColorName]) + dbot.debug("Discovering contents of container " .. objId .. ": " .. v[invFieldColorName]) -- Discover items in the container retval = inv.items.discoverLocation(objId) @@ -5799,7 +5812,7 @@ function inv.items.refresh(maxNumItems, refreshLocations, endTag, tagProxy) -- If we aren't in the "active" character state (sleeping, running, AFK, writing a note, etc.) -- then we wait a bit and try again elseif (charState ~= dbot.stateActive) then - dbot.note("Skipping refresh request: char is in state \"" .. dbot.gmcp.getStateString(charState) .. "\"") + dbot.debug("Skipping refresh request: char is in state \"" .. dbot.gmcp.getStateString(charState) .. "\"") retval = DRL_RET_NOT_ACTIVE -- If the char is in the active state (e.g., not AFK, in a note, in combat, etc.) refresh now @@ -5825,9 +5838,9 @@ function inv.items.refresh(maxNumItems, refreshLocations, endTag, tagProxy) end -- if -- Schedule the next refresh if automatic refreshes are enabled (i.e., the period is > 0 minutes) - local refreshMin = inv.items.refreshGetPeriod() or 0 + local refreshMin = inv.items.refreshGetPeriods() or 0 if (refreshMin > 0) and (inv.state ~= nil) then - dbot.note("Scheduling automatic inventory refresh in " .. refreshMin .. " minutes") + dbot.debug("Scheduling automatic inventory refresh in " .. refreshMin .. " minutes") inv.items.refreshAtTime(refreshMin, 0) end -- if @@ -5843,9 +5856,19 @@ end -- inv.items.refresh function inv.items.refreshCR() - local retval + local retval = DRL_RET_SUCCESS - dbot.info("Refreshing inventory: START") + -- We can skip the refresh if we've already done a full scan, there are no known "dirty" + -- locations or containers, and the user didn't explicitly request a full scan + if inv.items.fullScanCompleted and + (inv.items.refreshPkg.refreshLocations ~= invItemsRefreshLocAll) and + (not inv.items.isDirty()) then + dbot.debug("Skipping refresh because there are no known unidentified items") + inv.state = invStateIdle + return inv.tags.stop(inv.items.refreshPkg.tagModule, inv.items.refreshPkg.endTag, retval) + end -- if + + dbot.note("Refreshing inventory: START") -- Disable the prompt to avoid confusing output during the refresh dbot.prompt.hide() @@ -5909,7 +5932,7 @@ function inv.items.refreshCR() resultString = "ERROR! (" .. dbot.retval.getString(retval) .. ")" end -- if - dbot.info("Refreshing inventory: " .. resultString) + dbot.note("Refreshing inventory: " .. resultString) inv.state = invStateIdle @@ -5970,7 +5993,7 @@ function inv.items.refreshDefault() -- By default, refresh only dirty locations and skip item locations that don't contain any -- unidentified items. - if (inv.items.refreshGetPeriod() > 0) then + if (inv.items.refreshGetPeriods() > 0) then retval = inv.items.refresh(0, invItemsRefreshLocDirty, nil, nil) end -- if @@ -5978,38 +6001,74 @@ function inv.items.refreshDefault() end -- inv.items.refreshDefault -function inv.items.refreshGetPeriod() - return inv.config.table.refreshPeriod -end -- inv.items.refreshGetPeriod +function inv.items.refreshGetPeriods() + return inv.config.table.refreshPeriod, inv.config.table.refreshEagerSec +end -- inv.items.refreshGetPeriods + +function inv.items.refreshSetPeriods(autoMin, eagerSec) + inv.config.table.refreshPeriod = tonumber(autoMin) or inv.items.timer.refreshMin + inv.config.table.refreshEagerSec = tonumber(eagerSec) or inv.items.timer.refreshEagerSec -function inv.items.refreshSetPeriod(numMinutes) - inv.config.table.refreshPeriod = tonumber(numMinutes) or inv.items.timer.refreshMin return inv.config.save() -end -- inv.items.refreshSetPeriod +end -- inv.items.refreshSetPeriods -function inv.items.refreshOn(numMinutes) - numMinutes = tonumber(numMinutes or "") or inv.items.timer.refreshMin - if (numMinutes < 1) then +function inv.items.refreshOn(autoMin, eagerSec) + autoMin = tonumber(autoMin or "") or inv.items.timer.refreshMin + if (autoMin < 1) then dbot.warn("inv.items.refreshOn: Automatic refreshes must have a period of at least one minute") return DRL_RET_INVALID_PARAM end -- if - inv.items.refreshSetPeriod(numMinutes) + inv.items.refreshSetPeriods(autoMin, eagerSec or 0) + + inv.state = invStateIdle -- Schedule the next refresh - return inv.items.refreshAtTime(numMinutes, 0) + return inv.items.refreshAtTime(autoMin, 0) end -- inv.items.refreshOn function inv.items.refreshOff() inv.state = invStatePaused dbot.deleteTimer(inv.items.timer.refreshName) - return inv.items.refreshSetPeriod(0) + return inv.items.refreshSetPeriods(0, 0) end -- inv.items.refreshOff +-- This checks all locations and determines if there are any known unidentified items. If there +-- is at least one unidentified item, isDirty() returns true. Otherwise, it returns false. +function inv.items.isDirty() + local isDirty = false + + -- Check the easy locations first. If something unidentified is worn, on your keyring, or in + -- your main inventory, return true. We don't even need to look at containers. + if (inv.items.wornState == invItemsRefreshDirty) or + (inv.items.mainState == invItemsRefreshDirty) or + (inv.items.keyringState == invItemsRefreshDirty) then + isDirty = true + + -- Check containers to see if any are "dirty" and hold at least one unidentified item + else + -- For every item in your inventory, check if it's a container. If it is a container we must + -- next check if it has the "clean" keyword indicating that it hasn't had any unidentified + -- items added to it since its last scan. If it's not "clean", then it's "dirty". + for objId,_ in pairs(inv.items.table) do + if (inv.items.getStatField(objId, invStatFieldType) == invmon.typeStr[invmonTypeContainer]) then + local keywordField = inv.items.getStatField(objId, invStatFieldKeywords) or "" + if (not dbot.isWordInString(invItemsRefreshClean, keywordField)) then + isDirty = true + break + end -- if + end -- if + end -- for + end -- if + + return isDirty +end -- inv.items.isDirty + + function inv.items.build(endTag) local retval @@ -7826,8 +7885,10 @@ function inv.items.displayItem(objId, verbosity, wearableLoc) end if (#strip_colours(formattedName) < maxNameLen - #formattedId) then - formattedName = formattedName .. string.rep(" ", maxNameLen - #strip_colours(formattedName) - #formattedId) + formattedName = formattedName .. + string.rep(" ", maxNameLen - #strip_colours(formattedName) - #formattedId) end -- if + -- The trimmed name could end on an "@" which messes up color codes and spacing formattedName = string.gsub(formattedName, "@$", " ") .. " " .. DRL_XTERM_GREY formattedName = formattedName .. colorizedId @@ -9370,8 +9431,9 @@ function inv.items.trigger.invmon(action, objId, containerId, wearLoc) -- inventory refresh a few seconds from now. That will give some time to buffer up a few items -- if we picked up several things. local idLevel = inv.items.getField(objId, invFieldIdentifyLevel) - if (idLevel == invIdLevelNone) and (inv.state == invStateIdle) then - inv.items.refreshAtTime(0, 5) + local eagerRefreshSec = tonumber(inv.config.table.refreshEagerSec or 0) + if (idLevel == invIdLevelNone) and (inv.state == invStateIdle) and (eagerRefreshSec > 0) then + inv.items.refreshAtTime(0, eagerRefreshSec) end -- if if (action == invmonActionRemoved) then @@ -9511,6 +9573,7 @@ inv.items.timer = {} inv.items.timer.refreshName = "drlInvItemsTimerRefresh" inv.items.timer.refreshMin = 5 -- by default, run the item refresh timer every 5 minutes, 0 seconds inv.items.timer.refreshSec = 0 +inv.items.timer.refreshEagerSec = 5 -- If enabled, run a refresh 5 seconds after acquiring a new item inv.items.timer.idTimeoutName = "drlInvItemsTimerIdTimeout" inv.items.timer.idTimeoutThresholdSec = 15 -- timeout the id request if it doesn't complete in this # sec