Overview
Lich 5 provides a comprehensive settings system for persistent storage of script configuration. Settings are automatically saved to the SQLite database and support nested data structures.
Settings Modules
Lich provides three settings interfaces:
Settings - Script-specific settings (default scope)
CharSettings - Character-specific settings (shared across scripts)
GameSettings - Game-specific settings (shared across characters)
# From lib/common/settings.rb
module Settings
def self.[] ( name ) # Get setting
def self.[]= ( name , value ) # Set setting (auto-saves)
end
All settings are automatically persisted to the database. There’s no need to call a save method.
Basic Usage
Simple Values
# Store simple values
Settings [ 'last_room' ] = 12345
Settings [ 'greeting' ] = "Hello, world!"
Settings [ 'enabled' ] = true
# Retrieve values
echo Settings [ 'last_room' ] # 12345
echo Settings [ 'greeting' ] # "Hello, world!"
echo Settings [ 'enabled' ] # true
Default Values
Use the || operator for defaults:
# If setting doesn't exist, use default
hunt_target = Settings [ 'hunt_target' ] || 'troll'
max_hunts = Settings [ 'max_hunts' ] || 10
# Store with default
Settings [ 'hunt_target' ] ||= 'troll'
Settings that don’t exist return nil, making the || pattern work naturally.
Nested Structures
Hashes
# Store a hash
Settings [ 'stats' ] = {
'kills' => 0 ,
'deaths' => 0 ,
'items_found' => 0
}
# Access nested values
kills = Settings [ 'stats' ][ 'kills' ]
# Modify nested values
Settings [ 'stats' ][ 'kills' ] += 1
# Add new keys
Settings [ 'stats' ][ 'sessions' ] = 1
Arrays
# Store an array
Settings [ 'targets' ] = [ 'troll' , 'orc' , 'kobold' ]
# Access array
targets = Settings [ 'targets' ]
echo targets[ 0 ] # 'troll'
# Modify array
Settings [ 'targets' ]. push ( 'goblin' )
Settings [ 'targets' ]. delete ( 'orc' )
Deep Nesting
# Complex nested structure
Settings [ 'config' ] = {
'hunting' => {
'enabled' => true ,
'targets' => [ 'troll' , 'orc' ],
'max_rt' => 5
},
'looting' => {
'enabled' => true ,
'types' => [ 'gem' , 'skin' ]
}
}
# Access deep values
enabled = Settings [ 'config' ][ 'hunting' ][ 'enabled' ]
targets = Settings [ 'config' ][ 'hunting' ][ 'targets' ]
# Modify deep values
Settings [ 'config' ][ 'hunting' ][ 'max_rt' ] = 3
Settings [ 'config' ][ 'looting' ][ 'types' ]. push ( 'jewelry' )
Settings Scopes
Script Settings (Default)
Script-specific settings isolated per script:
# In script 'hunter.lic'
Settings [ 'target' ] = 'troll' # Stored for 'hunter' only
# In script 'looter.lic'
Settings [ 'target' ] = 'gem' # Different setting, different script
CharSettings
Shared across all scripts for the current character:
# Any script can access
CharSettings [ 'home_room' ] = 12345
CharSettings [ 'bank_account' ] = 1000000
# In any other script
room = CharSettings [ 'home_room' ] # 12345
CharSettings scope:
" #{ XMLData . game } : #{ XMLData . name } " # e.g., "GSIV:Tamsin"
GameSettings
Shared across all characters in the same game:
# Store game-wide data
GameSettings [ 'town_square' ] = 12345
GameSettings [ 'bank_location' ] = 'Town Square, Southeast'
# Any character can access
location = GameSettings [ 'bank_location' ]
GameSettings scope:
" #{ XMLData . game } :" # e.g., "GSIV:"
SettingsProxy
Nested structures return a SettingsProxy for transparent modification:
Settings [ 'config' ] = { 'enabled' => true }
# Returns a SettingsProxy wrapping the hash
config = Settings [ 'config' ]
echo config. class # SettingsProxy
# Modifications auto-save
config[ 'enabled' ] = false # Automatically saved
config[ 'new_key' ] = 'value' # Automatically saved
Non-destructive operations (like .sort) on proxies may require explicit handling. Use .to_a or similar to materialize the data first.
Reading Settings
to_hash
Get a snapshot of all settings:
# Get all script settings as hash
all_settings = Settings . to_hash
echo all_settings. inspect
# Get character settings
char_data = CharSettings . to_hash
echo char_data. inspect
Existence Checks
# Check if setting exists
if Settings [ 'my_setting' ]. nil?
echo "Setting not configured"
else
echo "Setting value: #{ Settings [ 'my_setting' ] } "
end
# With default
value = Settings [ 'my_setting' ] || 'default'
Common Patterns
Configuration Management
# Initialize defaults on first run
unless Settings [ 'initialized' ]
Settings [ 'config' ] = {
'enabled' => true ,
'targets' => [ 'troll' ],
'max_hunts' => 10 ,
'loot_types' => [ 'gem' , 'skin' ]
}
Settings [ 'initialized' ] = true
end
# Load config
config = Settings [ 'config' ]
Session Tracking
# Track sessions
Settings [ 'sessions' ] ||= 0
Settings [ 'sessions' ] += 1
Settings [ 'last_run' ] = Time . now . to_i
Settings [ 'total_time' ] ||= 0
# Calculate duration later
start_time = Time . now . to_i
at_exit {
duration = Time . now . to_i - start_time
Settings [ 'total_time' ] += duration
echo "Session lasted #{ duration } seconds"
echo "Total time: #{ Settings [ 'total_time' ] } seconds"
}
Statistics Tracking
# Initialize stats
Settings [ 'stats' ] ||= {
'kills' => 0 ,
'deaths' => 0 ,
'exp_gained' => 0 ,
'items_found' => 0
}
# Update stats
def record_kill ( creature )
Settings [ 'stats' ][ 'kills' ] += 1
echo "Total kills: #{ Settings [ 'stats' ][ 'kills' ] } "
end
def record_death
Settings [ 'stats' ][ 'deaths' ] += 1
echo "Total deaths: #{ Settings [ 'stats' ][ 'deaths' ] } "
end
Per-Character Configuration
# Store preferences per character
CharSettings [ 'hunting_area' ] = 'River' s Rest '
CharSettings[' preferred_weapon '] = ' broadsword '
CharSettings[' home_room '] = 12345
# Access from any script
area = CharSettings[' hunting_area ']
fput "go2 #{CharSettings[' home_room ']}"
Advanced Usage
Direct Scope Access
# Access specific scope
scope = " #{ XMLData . game } : #{ XMLData . name } "
value = Settings . get_scoped_setting (scope, 'my_key' )
# Create root proxy for scope
proxy = Settings . root_proxy_for (scope)
proxy[ 'my_setting' ] = 'value'
Custom Scopes
For advanced use cases:
# Create custom scope
custom_scope = "custom:shared"
proxy = Settings . root_proxy_for (custom_scope)
# All scripts can access this scope
proxy[ 'shared_data' ] = { 'value' => 123 }
Database Backend
Settings are stored in SQLite:
# From lib/common/settings/database_adapter.rb
class DatabaseAdapter
def save_settings ( script_name , settings_data , scope )
serialized = Marshal . dump (settings_data)
db. execute (
"INSERT OR REPLACE INTO script_auto_settings(script, scope, hash)
VALUES(?, ?, ?)" ,
[script_name, scope, SQLite3 :: Blob . new (serialized)]
)
end
end
Location: ~/.lich/data/lich.db3
Table: script_auto_settings
Migration from Old Settings
If migrating from Lich 4:
# Old style (deprecated)
Settings . auto = true
Settings . save
Settings . load
# New style (automatic)
Settings [ 'key' ] = 'value' # Auto-saves
value = Settings [ 'key' ] # Auto-loads
The old Settings.save and Settings.load methods are now no-ops. Settings are automatically managed.
Error Handling
begin
Settings [ 'config' ] = complex_data
rescue => e
echo "Error saving settings: #{ e. message } "
respond e. backtrace . first
end
# Settings that fail to load return nil
config = Settings [ 'config' ]
if config. nil?
echo "Failed to load config, using defaults"
config = default_config
end
Settings are cached in memory. Reading the same setting multiple times only hits the database once per session.
Settings are immediately written to the database. For frequent updates, batch changes in a single structure.
While deep nesting is supported, very deep structures (10+ levels) may have performance implications.
Debugging Settings
Enable debug logging:
# From lib/common/settings.rb
Settings . set_log_level ( :debug )
# Log levels:
# :none - No logging (default)
# :error - Errors only
# :info - Informational messages
# :debug - Detailed debugging
Inspect settings:
# View all settings
require 'pp'
pp Settings . to_hash
# View specific scope
pp CharSettings . to_hash
pp GameSettings . to_hash
Example: Complete Configuration System
# hunter.lic - Full configuration example
# Initialize default configuration
unless Settings [ 'config' ]
Settings [ 'config' ] = {
'hunting' => {
'enabled' => true ,
'targets' => [ 'troll' , 'orc' ],
'area' => 'River \' s Rest' ,
'max_rt' => 5
},
'looting' => {
'enabled' => true ,
'types' => [ 'gem' , 'skin' , 'jewelry' ],
'min_value' => 100
},
'safety' => {
'min_health' => 50 ,
'min_mana' => 20 ,
'flee_on_death' => true
}
}
Settings [ 'stats' ] = {
'kills' => 0 ,
'deaths' => 0 ,
'sessions' => 0 ,
'total_time' => 0
}
end
# Load configuration
config = Settings [ 'config' ]
stats = Settings [ 'stats' ]
# Update session count
stats[ 'sessions' ] += 1
start_time = Time . now . to_i
# Use configuration
hunting_config = config[ 'hunting' ]
if hunting_config[ 'enabled' ]
echo "Hunting enabled in #{ hunting_config[ 'area' ] } "
hunting_config[ 'targets' ]. each { | target |
echo "Target: #{ target } "
}
end
# Safety check using config
def safe_to_hunt?
safety = Settings [ 'config' ][ 'safety' ]
XMLData . health > safety[ 'min_health' ] &&
XMLData . mana > safety[ 'min_mana' ]
end
# Record stats
def record_kill
Settings [ 'stats' ][ 'kills' ] += 1
end
# Cleanup
at_exit {
duration = Time . now . to_i - start_time
Settings [ 'stats' ][ 'total_time' ] += duration
echo "Session stats:"
echo " Duration: #{ duration } s"
echo " Kills: #{ Settings [ 'stats' ][ 'kills' ] } "
echo " Deaths: #{ Settings [ 'stats' ][ 'deaths' ] } "
}
Next Steps
Architecture Understand Lich’s overall structure
Scripting Learn about script execution