Skip to main content

Overview

Lich 5 operates as a transparent proxy between the game server and your front-end client. This architecture enables powerful scripting capabilities while maintaining compatibility with multiple game clients.
Game ServerLich ProxyFront-End Client

Proxy Architecture

The proxy architecture provides several key benefits:
  • Data interception: Parse XML streams from the game server before they reach your client
  • Command injection: Send automated commands to the game server
  • State management: Track game state without modifying the client
  • Script isolation: Run multiple scripts independently with separate contexts

Connection Flow

  1. Your front-end client connects to Lich instead of directly to the game server
  2. Lich establishes a connection to the game server
  3. All traffic flows through Lich, allowing for inspection and modification
  4. Scripts can read game data and inject commands into the stream

Core Components

Game Module (lib/games.rb)

The Game module provides the foundation for game-specific functionality:
module Lich
  module GameBase
    module GameInstanceFactory
      def self.create(game_type)
        case game_type
        when /^GS/
          Gemstone::GameInstance.new
        when /^DR/
          DragonRealms::GameInstance.new
        else
          GameInstance::Base.new
        end
      end
    end
  end
end
This factory pattern allows Lich to adapt to different game types (GemStone IV, DragonRealms, etc.) while maintaining a common interface.

XMLParser (lib/common/xmlparser.rb)

Parses the XML stream from the game server and populates XMLData with current game state:
class XMLParser
  include REXML::StreamListener
  
  attr_reader :mana, :max_mana, :health, :max_health,
              :room_name, :room_description, :room_exits
  
  def tag_start(name, attributes)
    # Process XML tags as they arrive
  end
  
  def text(text_string)
    # Handle text content within tags
  end
end
The XMLParser uses Ruby’s REXML stream listener for efficient processing of the continuous XML stream from the game server.

GameObj (lib/common/gameobj.rb)

Tracks all in-game objects (NPCs, loot, inventory, etc.) with efficient deduplication:
class GameObj
  # Class-level registries
  @@loot = []
  @@npcs = []
  @@pcs  = []
  @@inv  = []
  
  attr_reader :id, :noun, :name
  
  def self.new_npc(id, noun, name, status = nil)
    # Create and register a new NPC
  end
end

Script (lib/common/script.rb)

Manages script execution with proper isolation and lifecycle:
class Script
  attr_reader :name, :vars
  
  def self.start(script_name, args = '')
    # Start a new script
  end
  
  def self.current
    # Get the currently executing script
  end
end

Settings (lib/common/settings.rb)

Provides persistent storage for script configuration:
module Settings
  def self.[](name)
    # Get a setting value
  end
  
  def self.[]=(name, value)
    # Set a setting value (auto-saves)
  end
end

Data Flow

Downstream (Server → Client)

  1. Game server sends XML data
  2. XMLParser processes tags and updates XMLData
  3. GameObj registries are updated (NPCs, loot, etc.)
  4. Scripts can read from XMLData and GameObj
  5. Data is passed to the front-end client
# Scripts access parsed data
echo "Current room: #{XMLData.room_name}"
echo "Health: #{XMLData.health}/#{XMLData.max_health}"
echo "NPCs present: #{GameObj.npcs.map(&:name).join(', ')}"

Upstream (Client → Server)

  1. User types command in client OR script calls put
  2. Command is sent to game server
  3. Scripts can intercept with UpstreamHook
  4. Server processes command and sends response
# Scripts send commands
put "look"              # Send to server
fput "get sword"        # Send and wait for roundtime

Component Interaction

1

Game Server Connection

Lich connects to the game server and begins receiving the XML stream
2

XML Stream Processing

XMLParser processes the stream in real-time, updating XMLData
3

Object Registry Updates

GameObj registries are updated as NPCs/items appear and disappear
4

Script Execution

Scripts run in isolated threads, accessing shared game state
5

Client Display

Processed data is forwarded to the front-end client for display

Thread Safety

Lich uses Ruby threads extensively:
  • Each script runs in its own thread with isolated bindings
  • Shared data structures (XMLData, GameObj) are accessed from multiple threads
  • Synchronization is handled internally for critical operations
# Each script runs independently
Thread.new {
  script = Script.current
  # Script code executes in isolated context
}
When writing scripts that share data between scripts or modify global state, use proper synchronization mechanisms.

Database Layer

Lich uses SQLite3 for persistent storage:
# From lib/lich.rb:169
def Lich.db
  @@lich_db ||= SQLite3::Database.new("#{DATA_DIR}/lich.db3")
end

# Tables initialized at startup
Lich.db.execute("CREATE TABLE IF NOT EXISTS script_setting ...")
Lich.db.execute("CREATE TABLE IF NOT EXISTS script_auto_settings ...")
Settings, trusted scripts, and other persistent data are stored in lich.db3.

Next Steps

Scripting Engine

Learn how scripts are executed and isolated

XML Parsing

Understand how game data is parsed

Game Objects

Explore the GameObj tracking system

Settings

Manage persistent script configuration

Build docs developers (and LLMs) love