Skip to main content

Overview

The Vars module provides persistent key-value storage that’s automatically saved and loaded per character. It’s simpler than Settings and ideal for storing script state, counters, and configuration values. Vars are scoped to "#{XMLData.game}:#{XMLData.name}" (current game + character), stored in SQLite, and automatically saved every 5 minutes plus on program exit.
Vars is designed for simplicity - use Settings for more complex structured data or custom scoping.

Basic Usage

Bracket Notation

# Set a variable
Vars['hunting_area'] = 'kobolds'

# Get a variable
area = Vars['hunting_area']  # => 'kobolds'

# Delete a variable
Vars['hunting_area'] = nil

Method Syntax

Variables can also be accessed as methods:
# Set using method syntax
Vars.hunting_area = 'kobolds'
Vars.kill_count = 0

# Get using method syntax
echo Vars.hunting_area  # => 'kobolds'
echo Vars.kill_count    # => 0

# Delete
Vars.hunting_area = nil

Symbol Keys

Symbols are automatically converted to strings:
Vars[:hunting_area] = 'kobolds'
Vars['hunting_area']  # => 'kobolds' (same variable)

Vars.hunting_area     # => 'kobolds' (same variable)

Data Types

Vars supports any Marshal-serializable Ruby object:
# Strings
Vars['name'] = 'Bob'

# Numbers  
Vars['count'] = 42
Vars['ratio'] = 3.14

# Booleans
Vars['enabled'] = true

# Arrays
Vars['items'] = ['sword', 'shield', 'potion']
Vars['items'] << 'helmet'  # Modifies and auto-saves

# Hashes
Vars['config'] = { mode: 'fast', loot: true }
Vars['config'][:mode] = 'slow'  # Modifies and auto-saves

# Time objects
Vars['last_run'] = Time.now

Common Patterns

Default Values

# Set default if not already set
Vars['count'] ||= 0
Vars['config'] ||= { mode: 'normal' }

# Or use || for inline defaults
count = Vars['count'] || 0
mode = Vars.dig('config', 'mode') || 'normal'

Counters

# Initialize counter
Vars['kill_count'] ||= 0

# Increment
Vars['kill_count'] += 1

# Reset
Vars['kill_count'] = 0

echo "Total kills: #{Vars['kill_count']}"

Lists

# Initialize list
Vars['visited_rooms'] ||= []

# Add item
Vars['visited_rooms'] << Room.current.id

# Check membership
if Vars['visited_rooms'].include?(Room.current.id)
  echo 'Already visited this room'
end

# Remove item
Vars['visited_rooms'].delete(Room.current.id)

Configuration

# Store script configuration
Vars['hunt_config'] ||= {
  area: 'kobolds',
  loot: true,
  skin: true,
  min_health: 50
}

# Access config
config = Vars['hunt_config']
if XMLData.health < config[:min_health]
  echo 'Health too low!'
end

# Update config
Vars['hunt_config'][:area] = 'trolls'

Timestamps

# Track last execution
Vars['last_hunt'] = Time.now

# Check elapsed time
if Vars['last_hunt']
  elapsed = Time.now - Vars['last_hunt']
  echo "Last hunted #{(elapsed / 60).round} minutes ago"
end

# Cooldown check
if Vars['last_hunt'] && Time.now - Vars['last_hunt'] < 3600
  echo 'On cooldown, please wait'
  exit
end

Vars['last_hunt'] = Time.now

State Flags

# Track script state
Vars['hunting_active'] = true
Vars['auto_loot'] = true
Vars['auto_skin'] = false

# Check state
if Vars['hunting_active']
  # Continue hunting
end

# Toggle state
Vars['auto_loot'] = !Vars['auto_loot']
echo "Auto-loot: #{Vars['auto_loot'] ? 'ON' : 'OFF'}"

Methods

Vars.list

Returns a duplicate Hash of all variables.
return
hash
Hash with string keys containing all stored variables
all_vars = Vars.list

all_vars.each do |key, value|
  echo "#{key}: #{value}"
end

# Check if a variable exists
if Vars.list.key?('hunting_area')
  echo 'Hunting area is configured'
end

Vars.save

Force immediate save to database.
Vars are automatically saved every 5 minutes and on exit. Manual save is rarely needed.
# Make critical change and save immediately
Vars['important_data'] = critical_value
Vars.save  # Force immediate save

Vars.[]

Get a variable value.
name
string | symbol
required
Variable name
return
object | nil
Variable value, or nil if not set
value = Vars['my_var']
value = Vars[:my_var]  # Same as above

Vars.[]=

Set a variable value.
name
string | symbol
required
Variable name
value
object | nil
required
Value to store (or nil to delete)
Vars['my_var'] = 'value'
Vars[:my_var] = 'value'  # Same as above
Vars['my_var'] = nil     # Delete variable

Checking for Variables

# Simple existence check
if Vars['my_var']
  echo "my_var is set to #{Vars['my_var']}"
end

# Explicit nil check
if Vars['my_var'].nil?
  echo 'my_var is not set'
end

# Check with list
if Vars.list.key?('my_var')
  echo 'my_var exists'
end

# Safe navigation
value = Vars['config']&.[]('setting') || 'default'

Automatic Persistence

Vars are automatically:
  1. Loaded on first access after login
  2. Saved every 5 minutes by background thread
  3. Saved when Lich exits normally
# These all automatically save eventually
Vars['count'] = 5
Vars['list'] << 'item'
Vars['hash'][:key] = 'value'

# Force immediate save if needed
Vars.save

Scoping

Vars are scoped to the current character:
  • Scope: "#{XMLData.game}:#{XMLData.name}"
  • Each character has independent variables
  • Variables persist across script runs
  • Variables persist across Lich restarts
# Character "Bob" in GemStone IV
Vars['hunting_area'] = 'kobolds'  # Saved for GSIV:Bob

# Switch to character "Alice"  
Vars['hunting_area']              # => nil (different character)

Comparison with Settings

FeatureVarsSettings
SyntaxSimple bracket/methodCharSettings, GameSettings
ScopingCharacter onlyCharacter, Game, Custom
ComplexitySimple key-valueNested structures
Use CaseScript state, countersConfiguration, complex data
Auto-saveEvery 5 minutesOn every change
# Vars - Simple script state
Vars['kill_count'] = 0
Vars['last_hunt'] = Time.now

# Settings - Complex configuration
CharSettings['hunt_config'] = {
  areas: { kobolds: true, trolls: false },
  loot_settings: { min_value: 1000 },
  thresholds: { health: 50, mana: 30 }
}

Best Practices

  1. Use descriptive names: Vars['hunting_area'] not Vars['ha']
  2. Initialize with defaults: Use ||= pattern
  3. Use for simple data: Complex nested data → use Settings
  4. Don’t store huge objects: Keep it lightweight
  5. Prefer Vars for counts/flags: Simple and efficient

Migration

If migrating from older code:
# Old pattern (still works)
if Vars['count'].nil?
  Vars['count'] = 0
end

# Better pattern
Vars['count'] ||= 0

# Old explicit save
Vars['value'] = 123
Vars.save

# New (save is automatic)
Vars['value'] = 123
# Already saved (within 5 minutes)

Examples

Hunt Counter Script

# Initialize counters
Vars['kills'] ||= 0
Vars['deaths'] ||= 0
Vars['session_start'] ||= Time.now

# Track kills
Vars['kills'] += 1
echo "Total kills: #{Vars['kills']}"

# Track deaths
if XMLData.spirit == 0
  Vars['deaths'] += 1
  echo "Total deaths: #{Vars['deaths']}"
end

# Session stats
session_time = Time.now - Vars['session_start']
kph = (Vars['kills'].to_f / (session_time / 3600)).round(1)
echo "Kills per hour: #{kph}"

Room Tracking

# Track visited rooms
Vars['visited_rooms'] ||= []

current_room = Room.current.id

if Vars['visited_rooms'].include?(current_room)
  echo 'Been here before'
else
  Vars['visited_rooms'] << current_room
  echo 'New room discovered!'
end

echo "Explored #{Vars['visited_rooms'].length} rooms"

Cooldown Management

# Track last cast time
Vars['last_spell_cast'] ||= {}

spell = '920'
cooldown = 60  # seconds

if Vars['last_spell_cast'][spell]
  elapsed = Time.now - Vars['last_spell_cast'][spell]
  if elapsed < cooldown
    remaining = (cooldown - elapsed).ceil
    echo "Spell on cooldown: #{remaining}s remaining"
    exit
  end
end

# Cast spell
fput "prep #{spell}"
Vars['last_spell_cast'][spell] = Time.now

See Also

  • Settings - More complex configuration storage
  • Script - Script management
  • Script.db - SQLite database access for scripts

Build docs developers (and LLMs) love