Skip to main content
The ScriptManager is a singleton object that manages LiquidBounce’s script system. It handles loading, unloading, and managing scripts written in various languages supported by GraalVM.

Object Declaration

object ScriptManager

Overview

ScriptManager allows extending the client by loading scripts at runtime. Scripts can:
  • Add custom modules
  • Register commands
  • Create custom modes
  • Hook into game events
  • Interact with the client through the Script API

Properties

scripts

Mutable set of all currently loaded scripts.
val scripts: MutableSet<PolyglotScript>
scripts
MutableSet<PolyglotScript>
Collection of all loaded script instances

root

The directory where scripts are stored.
val root: File // ConfigSystem.rootFolder/scripts
root
File
Root directory for script storage. Created automatically if it doesn’t exist.
Default location: <config_folder>/scripts/

Methods

initializeEngine()

Initializes the GraalVM polyglot engine.
fun initializeEngine()
This method must be called before loading any scripts. It:
  • Creates the GraalVM engine
  • Logs supported languages
  • Initializes the tick scheduler
Logged output example:
[ScriptAPI] Engine Version: 23.1.0, Supported languages: [ js, python, ruby, R ]

loadAll()

Loads all scripts from the scripts directory.
fun loadAll()
Behavior:
  1. Scans the root directory for script files
  2. Looks for main.* files in subdirectories
  3. Loads scripts from marketplace installations
  4. Enables all loaded scripts
Throws: IllegalStateException if engine is not initialized Directory structure example:
scripts/
├── simple-script.js          # Loaded directly
├── complex-script/
│   ├── main.js              # Entry point (loaded)
│   ├── utils.js             # Support file
│   └── config.json
└── another-script.py         # Loaded directly

loadScript()

Loads a specific script file.
fun loadScript(
    file: File,
    language: String = Source.findLanguage(file),
    debugOptions: ScriptDebugOptions = ScriptDebugOptions()
): PolyglotScript
file
File
required
The script file to load
language
String
Script language (auto-detected from file extension if not specified)
debugOptions
ScriptDebugOptions
Debug configuration for the script
return
PolyglotScript
The loaded and initialized script instance
Throws: IllegalStateException if engine is not initialized Example:
val script = ScriptManager.loadScript(
    file = File(ScriptManager.root, "my-script.js"),
    debugOptions = ScriptDebugOptions(enabled = true, port = 9229)
)

unloadScript()

Unloads a specific script.
fun unloadScript(script: PolyglotScript)
script
PolyglotScript
required
The script instance to unload
Behavior:
  1. Disables the script
  2. Closes the script context
  3. Removes from the scripts collection

unloadAll()

Unloads all currently loaded scripts.
fun unloadAll()
Behavior:
  1. Disables all scripts
  2. Closes all script contexts
  3. Clears the scripts collection
  4. Clears tick scheduler
  5. Cleans up script context provider

enableAll()

Enables all loaded scripts.
fun enableAll()
Behavior:
  1. Enables each script in the collection
  2. Reloads ClickGUI if any scripts were enabled
  3. Calls each script’s enable event handler

disableAll()

Disables all loaded scripts.
fun disableAll()
Calls each script’s disable event handler and deactivates all script features.

reload()

Reloads all scripts from disk.
fun reload()
Behavior:
  1. Disables all scripts
  2. Unloads all scripts
  3. Loads all scripts from directory
  4. Enables all scripts
  5. Logs success message
Error handling: Catches and logs errors during unload phase, continues with loading.

Script Loading Process

When a script is loaded:
fun loadScript(file: File, language: String, debugOptions: ScriptDebugOptions): PolyglotScript {
    require(isInitialized) { "Cannot load scripts before the script engine is initialized." }

    val script = PolyglotScript(language, file, debugOptions)
    script.initScript()

    scripts += script
    return script
}

Initialization Steps

  1. Context Creation - GraalVM context is created with:
    • Host access enabled
    • IO access enabled
    • Working directory set to script’s parent folder
    • Language-specific options (e.g., CommonJS for JavaScript)
  2. Binding Setup - Global bindings are registered:
    • registerScript - Script registration function
    • mc - Minecraft client instance
    • Client - ScriptClient object
    • Utility classes (RotationUtil, ItemUtil, etc.)
  3. Script Evaluation - Source code is executed
  4. Load Event - Script’s load event handler is called
  5. Validation - Ensures script has required metadata (name, version, authors)

Language Support

Supported languages depend on GraalVM installation:

JavaScript

Built-in support with ECMAScript 2023 and CommonJS

Python

Requires GraalPython installation

Ruby

Requires TruffleRuby installation

R

Requires FastR installation

Usage Examples

Basic Script Management

// Initialize the engine (once at startup)
ScriptManager.initializeEngine()

// Load all scripts
ScriptManager.loadAll()

// Reload scripts (useful after editing)
ScriptManager.reload()

// Unload all scripts
ScriptManager.unloadAll()

Loading Individual Scripts

val scriptFile = File(ScriptManager.root, "custom-module.js")
val script = ScriptManager.loadScript(scriptFile)

// Later, unload it
ScriptManager.unloadScript(script)

Checking Loaded Scripts

ScriptManager.scripts.forEach { script ->
    println("${script.scriptName} v${script.scriptVersion} by ${script.scriptAuthors.joinToString(", ")}")
}

Script Access from Scripts

// Inside a JavaScript script
const scriptManager = Client.scriptManager;

// Get all loaded scripts
const allScripts = scriptManager.scripts;

// Reload all scripts
scriptManager.reload();

Debug Support

Scripts can be loaded with debugging enabled:
val debugOptions = ScriptDebugOptions(
    enabled = true,
    protocol = DebugProtocol.INSPECT,
    port = 9229,
    suspendOnStart = false,
    inspectInternals = true
)

val script = ScriptManager.loadScript(
    file = File("my-script.js"),
    debugOptions = debugOptions
)
Debug protocols:
  • INSPECT - Chrome DevTools Protocol
  • DAP - Debug Adapter Protocol (VS Code)

Integration with Marketplace

ScriptManager automatically loads scripts from marketplace installations:
val files = root.listFiles { file ->
    Source.findLanguage(file) != null || file.isDirectory
} + MarketplaceManager.getSubscribedItemsOfType(MarketplaceItemType.SCRIPT).map { item ->
    item.getInstallationFolder()
}
Marketplace scripts are treated identically to local scripts.

Error Handling

All script loading operations use error catching:
private fun loadCatched(file: File) = runCatching {
    loadScript(file)
}.onFailure {
    logger.error("Unable to load script ${file.name}.", it)
}.getOrNull()
This ensures a single faulty script doesn’t prevent others from loading.

See Also

Build docs developers (and LLMs) love