Lich:Script allmine: Difference between revisions
m (Correcting compatibility) |
(→Notes) |
||
| (2 intermediate revisions by 2 users not shown) | |||
| Line 1: | Line 1: | ||
{{3rd-party}} |
{{3rd-party}} |
||
[[File:Allmine.jpg|thumb|right|alt=Screenshot of script working in GSIV|Screenshot of script working in GSIV]] |
|||
{{script |
{{script |
||
|title = AllMine |
|title = AllMine |
||
| Line 86: | Line 86: | ||
* The watchdog does not automatically return the character to the item's location or attempt to retrieve it. This is intentional — the script only alerts. |
* The watchdog does not automatically return the character to the item's location or attempt to retrieve it. This is intentional — the script only alerts. |
||
* |
* Known Issue: Script-generated movement from scripts like go2 (as opposed to typed commands) are not intercepted by the movement block! The watchdog's left-behind alert serves as a backstop regardless. |
||
== Script Source == |
== Script Source == |
||
<syntaxhighlight lang="ruby"> |
|||
<pre> |
|||
=begin |
=begin |
||
| Line 537: | Line 536: | ||
AllMine.run |
AllMine.run |
||
end |
end |
||
</syntaxhighlight> |
|||
</pre> |
|||
Latest revision as of 14:32, 13 July 2026
Lich:Script allmine is a third party script and is not maintained by Simutronics. Simutronics is not responsible for the accuracy of the information presented on this page, nor is it liable for issues stemming from the use of the application on players' personal devices.
| Title: | AllMine |
| Author: | Triex |
| Compatibility: | Wrayth and Wizard FE |
AllMine is a Lich script that guards a placed All Mine item (a basket, bowl, or similar placeable object like Dhu Kittens) against being accidentally left behind. All Mine items can only be picked up by their owner for a set window after being placed, but players who wander off or log out without retrieving one risk losing it once that window lapses. AllMine hard-blocks movement and logoff commands while the item is on the ground, and raises repeating alerts if it is left behind anyway.
Overview
Once armed, AllMine intercepts outgoing movement and quit/exit commands and refuses to send them to the game while the configured item remains on the ground in the current room. Sending the identical command a second time within 10 seconds overrides the block, so a deliberate decision to leave the item behind is still possible — the script only guards against accidental departures.
If the item is left behind regardless (for example, if the block is paused, or the player disconnects), a background watchdog raises a repeating alert describing where the item was placed and when. The watchdog never attempts to retrieve the item automatically or move the character — it is alert-only by design.
Features
- Detects when the configured item is placed or dropped, and arms automatically
- Also arms if the item is already on the ground when the script starts, or if a held item transitions to the ground without a recognized message
- Hard-blocks compass movement, all documented GemStone IV movement styles (crawl, limp, march, sashay, skip, sneak, stride, stumble, trudge, prance, and normal walking), and QUIT/EXIT, while the item is on the ground
- 10-second identical re-send bypass, so a deliberate departure is one extra keystroke rather than a separate confirmation command
- Repeating alert (default every 15 seconds) if the item is left behind, silenced with a single command once acknowledged
- Recognizes when the item is recovered and lifts the block automatically
- Persists its state across a Lich restart, so a crash or disconnect while the item is deployed is not silent — the script announces the item's last known location on the next launch
- Works with any All Mine item by noun (bowl, basket, etc.) — not hard-coded to one item
Requirements
- Lich 5, with Ruby-based script support
- GemStone IV, any supported front end (tested on Wrayth)
Installation
Save the script as allmine.lic in the Lich scripts directory. No dependencies beyond core Lich5.
Getting Started
Configure the noun of the item once, then start the monitor:
;allmine set bowl ;allmine
The monitor must be running for the movement block and watchdog to be active. If the item's noun is changed while the monitor is already running, restart it so the detection patterns rebuild.
Commands
| Command | Description |
|---|---|
;allmine set <noun> |
Configure the item's noun (for example, bowl)
|
;allmine (no arguments) |
Start the monitor |
;allmine arm |
Manually arm on a matching item already on the ground in the current room |
;allmine status |
Show current configuration and state |
;allmine ack |
Silence a left-behind alert without clearing the deployed state |
;allmine clear |
Manually clear the deployed state (for example, if the item was recovered by other means) |
;allmine pause |
Disable the movement block; the watchdog continues to run |
;allmine resume |
Re-enable the movement block |
;allmine debug |
Toggle echoing of raw upstream command lines, for troubleshooting |
;allmine verb add <verb> |
Add a custom movement verb to the block list |
;allmine verb remove <verb> |
Remove a previously added custom movement verb |
Default Blocked Commands
- Compass directions: n, north, s, south, e, east, w, west, ne, northeast, nw, northwest, se, southeast, sw, southwest, u, up, d, down, out
- Movement verbs: go, climb, swim, jump, leap, dive, vault, enter, board, ride, walk, crawl, limp, march, sashay, skip, sneak, stride, stumble, trudge, prance
- Logoff: quit, exit
Additional verbs can be added per character with ;allmine verb add <verb>.
Notes
- The watchdog does not automatically return the character to the item's location or attempt to retrieve it. This is intentional — the script only alerts.
- Known Issue: Script-generated movement from scripts like go2 (as opposed to typed commands) are not intercepted by the movement block! The watchdog's left-behind alert serves as a backstop regardless.
Script Source
=begin
allmine.lic - All Mine item guard
Prevents accidentally leaving a placed All Mine item (basket, bowl, etc.)
behind when moving rooms or logging off. Hard-blocks movement/quit while
the item is on the ground, with a 10-second re-send bypass. A watchdog
thread fires repeating alerts (no auto-return) if the item is left behind
anyway.
;allmine set <noun> configure the item noun (e.g. bowl)
;allmine arm manually arm on an item already on the ground
;allmine status show current state
;allmine ack acknowledge/silence a left-behind alert
;allmine clear manually clear deployed state
;allmine pause disable the movement block (monitoring continues)
;allmine resume re-enable the movement block
;allmine verb add <v> add a custom movement verb to block
;allmine verb remove <v>
;allmine help show help
Run with no arguments to start the monitor. Subcommands may be issued
while the monitor runs; they modify shared settings and exit.
NOTE: It is unverified whether script-generated movement (e.g. go2's
fput) passes through UpstreamHook. If it bypasses the block, the
watchdog alert still catches the left-behind item. Test live.
author: Triex
game: GSIV
tags: utility, allmine
version: 0.3.1
changelog:
0.3.1 (2026-07-13):
Fix mojibake in bold alert text (replaced em dash with plain hyphen;
file is now pure ASCII). Added full GSIV movement style verb list
(crawl, limp, march, sashay, skip, sneak, stride, stumble, trudge,
prance) plus walk -- previously only compass verbs like sashay
passed through unblocked.
0.3.0 (2026-07-13):
Fix movement block never firing: hooks no longer touch CharSettings
(unavailable in hook context); state mirrored into module ivars.
Tolerant upstream command parsing (optional <c> prefix). Added
;allmine debug to echo raw upstream lines.
0.2.0 (2026-07-13):
Arm when the item is already on the ground at monitor start, when a
held item transitions to ground loot (missed place message), or via
manual ;allmine arm.
0.1.0 (2026-07-13):
Initial release. Deploy/pickup detection, hard-block with 10s
re-send bypass, alert-only watchdog, crash persistence.
=end
module AllMine
UP_HOOK = 'AllMine::upstream'
DOWN_HOOK = 'AllMine::downstream'
BYPASS_WINDOW = 10 # seconds: identical re-sent command passes through
ALERT_INTERVAL = 15 # seconds between repeating left-behind alerts
POLL_INTERVAL = 1 # watchdog poll rate
# Single-word directional commands (whole command must match).
DIRS = %w[n north s south e east w west
ne northeast nw northwest se southeast sw southwest
u up d down out].freeze
# Verbs that take an argument and cause movement (first word must match).
# Includes GSIV movement styles (confirmed via in-game MOVEMENT command:
# Normal, Crawl, Limp, March, Sashay, Skip, Sneak, Stride, Stumble,
# Trudge, Prance) plus other common movement verbs. Extend with
# ;allmine verb add <verb>.
MOVE_VERBS = %w[go climb swim jump leap dive vault enter board ride walk
crawl limp march sashay skip sneak stride stumble trudge prance].freeze
# Logoff commands (whole command must match).
QUIT_CMDS = %w[quit exit].freeze
@last_blocked = nil
@pending_deploy_at = nil
@last_held_id = nil
@loot_miss_count = 0
@last_alert_at = Time.at(0)
@pending_clear_at = nil
@hs_noun = @hs_item_id = @hs_extra = nil
@hs_deployed = @hs_paused = @hs_debug = false
def self.settings
CharSettings['allmine'] ||= {}
CharSettings['allmine']
end
def self.bold(msg)
_respond Lich::Messaging.monsterbold(msg)
end
def self.info(msg)
_respond Lich::Messaging.msg_format('teal', "[AllMine] #{msg}")
end
def self.caution(msg)
_respond Lich::Messaging.msg_format('orange', "[AllMine] #{msg}")
end
# ---------------------------------------------------------------- state
def self.deployed?
settings['deployed'] == true
end
def self.set_deployed(item_id)
settings['deployed'] = true
settings['item_id'] = item_id
settings['room_id'] = (Room.current.id rescue nil)
settings['placed_at'] = Time.now.to_i
settings['acked'] = false
@loot_miss_count = 0
sync_hook_state
info "Your #{settings['noun']} (ID #{item_id || 'unknown'}) is now on the ground in room #{settings['room_id'] || 'unmapped'}. Movement is guarded."
end
def self.clear_deployed(reason)
settings['deployed'] = false
settings['item_id'] = nil
settings['acked'] = false
@loot_miss_count = 0
@pending_clear_at = nil
sync_hook_state
info "Your #{settings['noun']} has been recovered (#{reason}). Movement is no longer guarded."
end
def self.item_on_ground?
id = settings['item_id']
if id
GameObj.loot.to_a.any? { |o| o.id == id }
else
noun = settings['noun']
return false unless noun
GameObj.loot.to_a.any? { |o| o.noun == noun }
end
end
def self.item_in_hands?
id = settings['item_id']
return false unless id
[GameObj.right_hand, GameObj.left_hand].any? { |h| h && h.id == id }
end
# ------------------------------------------------------------ movement
def self.blocked_command?(cmd)
words = cmd.downcase.split
return false if words.empty?
first = words.first
if words.length == 1
return :quit if QUIT_CMDS.include?(first)
return :move if DIRS.include?(first)
end
return :move if MOVE_VERBS.include?(first) || (@hs_extra || []).include?(first)
false
end
# ---------------------------------------------------------------- hooks
# Hooks execute in Lich's stream context, not this script's thread, so
# they must never touch CharSettings (script-scoped). All state they
# need is mirrored into module ivars by sync_hook_state, which runs in
# the script thread. set_deployed/clear_deployed also update the mirror
# immediately.
def self.sync_hook_state
@hs_noun = settings['noun']
@hs_deployed = settings['deployed'] == true
@hs_paused = settings['paused'] == true
@hs_item_id = settings['item_id']
@hs_extra = (settings['extra_verbs'] || []).dup
@hs_debug = settings['debug'] == true
end
def self.install_hooks
noun = Regexp.escape(settings['noun'])
place_re = /You carefully place your [^.]*\b#{noun} on the ground\./
pickup_re = /You pick up [^.]*\b#{noun}\./
DownstreamHook.add(DOWN_HOOK, proc { |xml|
begin
if xml =~ place_re
@pending_deploy_at = Time.now
elsif xml =~ pickup_re && @hs_deployed
@pending_clear_at = Time.now # watchdog confirms and clears
end
rescue => e
respond "[AllMine] Downstream hook error: #{e}" rescue nil
end
xml
})
UpstreamHook.add(UP_HOOK, proc { |line|
begin
respond "[AllMine DEBUG] upstream: #{line.inspect}" if @hs_debug
next line unless @hs_deployed
next line if @hs_paused
# Client command framing is not documented; tolerate an optional
# <c> prefix anywhere-leading, else take the raw line.
cmd = line.sub(/\A\s*(?:<c>)?/, '').strip
next line if cmd.empty?
kind = AllMine.blocked_command?(cmd)
next line unless kind
on_ground = begin
if @hs_item_id
GameObj.loot.to_a.any? { |o| o.id == @hs_item_id }
else
GameObj.loot.to_a.any? { |o| o.noun == @hs_noun }
end
rescue
true # if the check itself fails, block rather than risk the item
end
next line unless on_ground
key = cmd.downcase
if @last_blocked && @last_blocked[:cmd] == key &&
(Time.now - @last_blocked[:time]) <= BYPASS_WINDOW
@last_blocked = nil
next line # intentional: identical command re-sent within window
end
@last_blocked = { cmd: key, time: Time.now }
if kind == :quit
AllMine.bold "*** YOUR #{@hs_noun.upcase} IS STILL ON THE GROUND - repeat #{cmd.upcase} within #{BYPASS_WINDOW} seconds to log off anyway. ***"
else
AllMine.bold "*** YOUR #{@hs_noun.upcase} IS STILL ON THE GROUND - repeat the command within #{BYPASS_WINDOW} seconds to leave anyway. ***"
end
next nil
rescue => e
respond "[AllMine] Upstream hook error: #{e}" rescue nil
next line
end
})
end
def self.remove_hooks
DownstreamHook.remove(DOWN_HOOK)
UpstreamHook.remove(UP_HOOK)
end
# ------------------------------------------------------------- watchdog
def self.watchdog_tick
sync_hook_state
noun = settings['noun']
# Track the id of a held item matching the noun, so a deploy can be
# attributed to the exact item that just left our hands.
[GameObj.right_hand, GameObj.left_hand].each do |h|
@last_held_id = h.id if h && h.id && h.noun == noun
end
# Pickup message seen by the downstream hook: confirm and clear.
if @pending_clear_at && deployed?
if settings['item_id'].nil? || item_in_hands?
clear_deployed('picked up')
return
elsif Time.now - @pending_clear_at > 3
@pending_clear_at = nil # pickup message was for a different item
end
end
# Complete a pending deploy: item should now appear in room loot.
if @pending_deploy_at
match = nil
if @last_held_id
match = GameObj.loot.to_a.find { |o| o.id == @last_held_id }
end
match ||= GameObj.loot.to_a.find { |o| o.noun == noun }
if match
@pending_deploy_at = nil
set_deployed(match.id)
elsif Time.now - @pending_deploy_at > 5
@pending_deploy_at = nil
set_deployed(nil) # deploy seen but id unresolved; guard by noun
end
return
end
unless deployed?
# Backup arm: an item we were holding (matching noun) is now on the
# ground - covers missed/unrecognized place messaging.
if @last_held_id && GameObj.loot.to_a.any? { |o| o.id == @last_held_id }
set_deployed(@last_held_id)
end
return
end
# Backup clear: item is back in our hands (missed pickup message).
if item_in_hands?
clear_deployed('back in your hands')
return
end
# Left-behind detection.
left_behind = false
here = (Room.current.id rescue nil)
if here && settings['room_id'] && here != settings['room_id']
left_behind = true
@loot_miss_count = 0
elsif !item_on_ground?
# Could be lag or an unmapped-room move; require persistence.
@loot_miss_count += 1
left_behind = true if @loot_miss_count >= 3
else
@loot_miss_count = 0
end
if left_behind && !settings['acked']
if Time.now - @last_alert_at >= ALERT_INTERVAL
@last_alert_at = Time.now
where = settings['room_id'] ? "room #{settings['room_id']} (;go2 #{settings['room_id']})" : 'an unknown room'
placed = settings['placed_at'] ? Time.at(settings['placed_at']).strftime('%H:%M') : '?'
bold "*** ALL MINE ALERT: Your #{settings['noun']} was left behind in #{where}, placed at #{placed}. Use ;allmine ack to silence this alert. ***"
end
end
end
# ------------------------------------------------------------- commands
def self.command(args)
case args
when /^set\s+(\w+)$/i
settings['noun'] = $1.downcase
info "Item noun set to '#{settings['noun']}'. If the monitor is already running, restart it so the message patterns rebuild."
when /^status$/i
info "Item noun: #{settings['noun'] || 'not set'}"
if deployed?
placed = settings['placed_at'] ? Time.at(settings['placed_at']).strftime('%Y-%m-%d %H:%M') : 'unknown'
info "Deployed: yes - item ID #{settings['item_id'] || 'unknown'}, room #{settings['room_id'] || 'unknown'}, placed at #{placed}"
else
info 'Deployed: no'
end
info "Movement block paused: #{settings['paused'] ? 'yes' : 'no'}"
info "Custom movement verbs: #{(settings['extra_verbs'] || []).empty? ? 'none' : settings['extra_verbs'].join(', ')}"
info "Monitor running: #{Script.list.count { |s| s.name == Script.current.name } > 1 ? 'yes' : 'no'}"
when /^arm$/i
noun = settings['noun']
if noun.nil?
caution 'No item noun configured. Use ;allmine set <noun> first.'
elsif (match = GameObj.loot.to_a.find { |o| o.noun == noun })
set_deployed(match.id)
else
caution "No #{noun} was found on the ground in this room."
end
when /^ack$/i
settings['acked'] = true
info 'Alert acknowledged. The deployed state remains until the item is recovered or cleared with ;allmine clear.'
when /^clear$/i
settings['deployed'] = false
settings['item_id'] = nil
settings['acked'] = false
info 'Deployed state cleared.'
when /^debug$/i
settings['debug'] = !settings['debug']
if settings['debug']
caution 'Debug mode ON - every upstream line will be echoed in raw form. Toggle off with ;allmine debug.'
else
info 'Debug mode off.'
end
when /^pause$/i
settings['paused'] = true
caution 'Movement block PAUSED. Watchdog alerts remain active.'
when /^resume$/i
settings['paused'] = false
info 'Movement block resumed.'
when /^verb\s+add\s+(\w+)$/i
settings['extra_verbs'] ||= []
v = $1.downcase
if settings['extra_verbs'].include?(v) || MOVE_VERBS.include?(v)
info "The verb '#{v}' is already blocked."
else
settings['extra_verbs'].push(v)
info "Added movement verb '#{v}'."
end
when /^verb\s+remove\s+(\w+)$/i
settings['extra_verbs'] ||= []
v = $1.downcase
if settings['extra_verbs'].delete(v)
info "Removed movement verb '#{v}'."
else
caution "The verb '#{v}' is not in the custom verb list."
end
else
info 'Usage: set <noun> | arm | status | ack | clear | pause | resume | debug | verb add <verb> | verb remove <verb>'
info "Blocked by default: #{DIRS.join(' ')} | #{MOVE_VERBS.join(' ')} | #{QUIT_CMDS.join(' ')}"
end
end
# ---------------------------------------------------------------- main
def self.run
unless settings['noun']
caution "No item noun configured. Use ;#{Script.current.name} set <noun> (for example, ;#{Script.current.name} set bowl)."
exit
end
if Script.list.count { |s| s.name == Script.current.name } > 1 && Script.current.vars[0].to_s.strip.empty?
caution 'The monitor is already running.'
exit
end
# Crash/disconnect recovery: stale deployed flag from a prior session.
if deployed?
where = settings['room_id'] ? "room #{settings['room_id']} (;go2 #{settings['room_id']})" : 'an unknown room'
placed = settings['placed_at'] ? Time.at(settings['placed_at']).strftime('%Y-%m-%d %H:%M') : 'an unknown time'
bold "*** ALL MINE: A prior session shows your #{settings['noun']} may still be on the ground in #{where}, placed at #{placed}. Use ;allmine clear if it has been recovered. ***"
end
before_dying {
AllMine.remove_hooks
if AllMine.deployed?
AllMine.bold "*** AllMine is exiting while your #{AllMine.settings['noun']} is still on the ground! ***"
end
}
sync_hook_state
install_hooks
info "Monitoring your #{settings['noun']}. The movement block is active with a #{BYPASS_WINDOW}-second re-send bypass, and the watchdog will alert (but never auto-return) if the item is left behind."
# Start-time arm: item matching the noun is already on the ground in
# this room (placed before the monitor started).
if !deployed? && (match = GameObj.loot.to_a.find { |o| o.noun == settings['noun'] })
set_deployed(match.id)
caution "Note: armed from a #{settings['noun']} already on the ground here. If it is not yours, use ;#{Script.current.name} clear."
end
loop {
watchdog_tick
sleep POLL_INTERVAL
}
end
end
if (args = Script.current.vars[0].to_s.strip) && !args.empty?
AllMine.command(args)
else
AllMine.run
end