Scripting Basics Guide

This guide covers the fundamentals of writing Lich scripts for GemStone IV and DragonRealms.

What is a Lich Script?

Lich scripts are Ruby programs that interact with the game through Lichโ€™s API. They can automate tasks, enhance gameplay, and provide information about your character and the game world.

Script Structure

A basic Lich script looks like this:

# my_script.rb
# A simple example script

echo "Hello from my script!"

# Do something useful
fput "look"

Essential Functions

Output Functions

# Display message to the user (script output)
echo "This appears in your game window"

# Send a command to the game
fput "say Hello world"

# Put command without waiting for response
put "look"

# Send and wait for a specific response
result = dothistimeout "look", 5, /Obvious exits/

Waiting and Timing

# Wait for specific text from the game
waitfor "You are no longer stunned"

# Wait with timeout (returns nil if timeout)
line = waitfor? "Ready", 10

# Wait for roundtime to finish
waitrt?

# Wait for cast roundtime
waitcastrt?

# Pause script execution
sleep 2          # Wait 2 seconds
pause 0.5        # Same as sleep

Game State Checks

# Check if you're stunned, dead, etc.
if stunned?
  waitrt?
  wait_until { !stunned? }
end

# Check for conditions
dead?           # Are you dead?
stunned?        # Are you stunned?
webbed?         # Are you webbed?
bound?          # Are you bound?
silenced?       # Are you silenced?

Working with Game Lines

# Get lines from the game
line = get              # Get next line (blocks)
line = get?             # Get next line (non-blocking, returns nil if none)

# Match patterns in game output
if line =~ /attacks you/
  echo "Under attack!"
end

# Process multiple lines
loop do
  line = get
  if line =~ /You are stunned/
    echo "Got stunned!"
    break
  end
end

Script Management

Starting and Stopping Scripts

# Start another script (returns immediately)
Script.start("scriptname")
Script.start("scriptname", "arg1 arg2")

# Start script and wait for it to finish
Script.run("scriptname")
Script.run("scriptname", "arg1 arg2")

# Key difference:
# - Script.start: Launches script, continues immediately
# - Script.run: Launches script, waits until it completes

# Stop a running script
Script.kill("scriptname")

# Check if a script is running
if Script.running?("bigshot")
  echo "Bigshot is running"
end

# Get list of running scripts
Script.running.each do |s|
  echo "Running: #{s.name}"
end

Script Lifecycle

# Run code when script exits
before_dying {
  echo "Script is ending!"
  fput "stand" if !standing?
}

# Clear exit handlers
undo_before_dying

# Hide script from ;running list
hide_me

# Prevent script from being killed by ;kill all
no_kill_all

# Prevent script from being paused by ;pause all
no_pause_all

Script Variables

# Access script arguments (note: capital S!)
Script.current.vars[0]    # All arguments as single string
Script.current.vars[1]    # First argument
Script.current.vars[2]    # Second argument

# Example: script called with ";myscript foo bar"
# Script.current.vars[0] => "foo bar"
# Script.current.vars[1] => "foo"
# Script.current.vars[2] => "bar"

# Check for specific arguments
if Script.current.vars[1] == "--debug"
  echo "Debug mode enabled"
end

# Common pattern using variable
args = Script.current.vars
if args[1] == "help"
  echo "Usage: ;myscript <target>"
  exit
end

Pattern Matching

Using Regex

# Simple match
if line =~ /gold coins/
  echo "Found gold!"
end

# Capture groups
if line =~ /You have (\d+) gold coins/
  gold = $1.to_i
  echo "You have #{gold} gold"
end

# Match multiple patterns
attack_patterns = /attacks you|swings at you|hurls .* at you/
if line =~ attack_patterns
  echo "Incoming attack!"
end

dothistimeout

# Send command and wait for specific response
result = dothistimeout "open backpack", 5, /You open|already open|can't open/

case result
when /You open/
  echo "Opened successfully"
when /already open/
  echo "Was already open"
when /can't open/
  echo "Failed to open"
when nil
  echo "Timed out"
end

Common Patterns

Safe Command Execution

def safe_move(direction)
  result = dothistimeout "go #{direction}", 3, /^Obvious|^You can't go/
  if result =~ /can't go/
    echo "Cannot go #{direction}"
    return false
  end
  true
end

Retry Logic

def try_action(max_attempts = 3)
  attempts = 0
  loop do
    attempts += 1
    result = dothistimeout "myaction", 5, /success|fail/

    return true if result =~ /success/

    if attempts >= max_attempts
      echo "Failed after #{max_attempts} attempts"
      return false
    end

    sleep 1
  end
end

Waiting for Conditions

# Wait until a condition is true
wait_until { Char.mana >= 50 }

# Wait until with timeout
wait_until(timeout: 30) { Char.health >= Char.max_health }

Best Practices

  1. Use waitrt? - Always wait for roundtime before actions

  2. Handle failures - Check return values and handle errors

  3. Use timeouts - Never wait forever; use dothistimeout

  4. Clean up - Use before_dying to clean up on exit

  5. Be resource-friendly - Add small sleeps in tight loops

  6. Log important events - Use echo to help with debugging

  7. Case matters - Ruby is case-sensitive: Script not script

Debugging

# Toggle echo to see script output
toggle_echo   # Toggles between on/off
echo_on       # Turn echo on
echo_off      # Turn echo off

# Print debug information
echo "DEBUG: Current room = #{Room.current.id}"
echo "DEBUG: Health = #{Char.health}/#{Char.max_health}"

# Silence script output
silence_me

See Also