Lich’s scripting engine executes Ruby code in isolated contexts, providing both security and flexibility. Scripts can be trusted or untrusted, with different levels of access to the Ruby environment.
Script bindings are the execution contexts in which script code runs. Lich uses Ruby’s binding mechanism to create isolated environments.
The binding system is carefully designed to prevent scripts from accessing each other’s local variables while still allowing shared access to game data.
# From lib/common/script.rb:1066fixline = proc { |line| if line =~ /^([\s\t]*)counter\s+(add|sub|set)\s+([0-9]+)/i line = "#{$1}c #{counter_action[$2]}= #{$3}" elsif line =~ /^([\s\t]*)echo[\s\t]+(.+)/i line = "#{$1}echo #{fixstring.call($2.inspect)}" # ... more transformations}
# Run code inlineScript.start('exec', 'echo "Hello from exec"')# Or with a blockExecScript.start { echo "Running inline code" 5.times { |i| echo "Count: #{i}" }}
# From lib/common/script.rb:346def Script.start(*args) @@elevated_script_start.call(args)enddef Script.run(*args) if (s = @@elevated_script_start.call(args)) sleep 0.1 while @@running.include?(s) endend
Example usage:
# Start a script (non-blocking)Script.start('hunting', 'trolls')# Run a script (blocking until complete)Script.run('go2', 'town square')# Force start even if already runningScript.start('myscript', '', force: true)
@@running = Array.new # All script instancesdef Script.running list = Array.new for script in @@running list.push(script) unless script.hidden end return listenddef Script.hidden list = Array.new for script in @@running list.push(script) if script.hidden end return listend
Script.at_exit { echo "Script is ending, cleaning up..." # Close files, save state, etc.}# Clear all at-exit handlersScript.clear_exit_procs# Exit without running handlersScript.exit!
At-exit handlers run in the script’s thread context. If the thread is forcibly killed, handlers may not execute.