What is the Scripting System?
The scripting system allows you to extend LiquidBounce’s functionality by writing custom scripts at runtime. You can create custom modules, commands, and integrate with LiquidBounce’s API without modifying the client’s source code.
LiquidBounce uses GraalVM Polyglot to support multiple programming languages including JavaScript, Python, Ruby, and more.
GraalVM Polyglot Support
GraalVM is a high-performance runtime that supports multiple languages. LiquidBounce leverages this to provide flexible scripting capabilities:
val engine = Engine. create ()
logger. info (
"[ScriptAPI] Engine Version: ${ engine.version } , " +
"Supported languages: [ ${ engine.languages.keys. joinToString ( ", " ) } ]"
)
Source: ScriptManager.kt:62-66
Supported Languages
Depending on your GraalVM installation, you can use:
JavaScript Most commonly used, excellent documentation
Python Familiar syntax for Python developers
Ruby Elegant and expressive scripting
R Statistical computing and data analysis
Additional languages may require installing GraalVM language components separately.
Script Location
Scripts are stored in the scripts directory within your LiquidBounce folder:
val root = File (ConfigSystem.rootFolder, "scripts" ). apply {
if ( ! exists ()) {
mkdir ()
}
}
Source: ScriptManager.kt:52-56
Directory Structure
LiquidBounce/
└── scripts/
├── my-script.js # Single file script
├── complex-script/ # Directory-based script
│ ├── main.js # Entry point (must be named 'main')
│ ├── utils.js # Additional files
│ └── config.json # Script configuration
└── marketplace-script/ # Installed from marketplace
└── main.js
Main File Requirement
For directory-based scripts, you must have a file named main with the appropriate extension:
val mainFile = file. listFiles { dirFile ->
dirFile.nameWithoutExtension == "main" && Source. findLanguage (dirFile) != null
}?. firstOrNull ()
Source: ScriptManager.kt:85-87
Script Lifecycle
Scripts go through several lifecycle stages:
Loading
Scripts are discovered and loaded from the scripts directory
Initialization
The script’s main code is executed and objects are created
Enabling
Script modules/features are registered with LiquidBounce
Running
Script code executes and responds to events
Disabling
Script features are unregistered when disabled
Unloading
Script is completely removed from memory
Loading Scripts
fun loadAll () {
require (isInitialized) { "Cannot load scripts before the script engine is initialized." }
val files = root. listFiles { file ->
Source. findLanguage (file) != null || file.isDirectory
} + MarketplaceManager. getSubscribedItemsOfType (MarketplaceItemType.SCRIPT). map { item ->
item. getInstallationFolder ()
}
files. forEach { file ->
// Load logic...
}
enableAll ()
}
Source: ScriptManager.kt:74-102
Unloading Scripts
fun unloadAll () {
scripts. forEach (PolyglotScript:: disable )
scripts. forEach (PolyglotScript:: close )
scripts. clear ()
ScriptAsyncUtil.TickScheduler. clear ()
ScriptContextProvider. cleanup ()
}
Source: ScriptManager.kt:107-113
Script API Overview
LiquidBounce provides a rich API for scripts to interact with the client:
Core API Categories
Create custom modules with events and settings var myModule = {
name: "CustomKillAura" ,
category: "Combat" ,
description: "My custom kill aura" ,
settings: {
range: floatValue ( "Range" , 4.0 , 3.0 , 6.0 ),
speed: intValue ( "Speed" , 10 , 1 , 20 )
},
onEnable : function () {
chat ( "Module enabled!" );
},
onDisable : function () {
chat ( "Module disabled!" );
},
onUpdate : function () {
// Your update logic here
}
};
moduleManager . registerModule ( myModule );
Create custom commands var myCommand = {
name: "test" ,
aliases: [ "t" ],
execute : function ( args ) {
chat ( "Test command executed!" );
chat ( "Arguments: " + args . join ( ", " ));
}
};
commandManager . registerCommand ( myCommand );
Listen to game events events . on ( "Update" , function ( event ) {
// Runs every tick
});
events . on ( "Attack" , function ( event ) {
// Runs when attacking
var target = event . target ;
});
events . on ( "Packet" , function ( event ) {
// Intercept packets
if ( event . packet instanceof C03PacketPlayer ) {
// Handle player packet
}
});
Access player information and controls // Get player position
var x = player . posX ;
var y = player . posY ;
var z = player . posZ ;
// Check player state
var isOnGround = player . onGround ;
var health = player . health ;
// Control player
player . jump ();
player . swingItem ();
Interact with the world // Get blocks
var block = world . getBlockAt ( x , y , z );
// Get entities
var entities = world . getLoadedEntities ();
// World information
var worldTime = world . getWorldTime ();
Script API Access
Modules expose settings through the Script API:
@ScriptApiRequired
open val settings by lazy { inner . associateBy { it.name } }
Source: ClientModule.kt:109-110
The @ScriptApiRequired annotation marks members that are exposed to scripts.
Async Operations
Scripts can perform async operations using the tick scheduler:
// Schedule task for next tick
schedule ( function () {
chat ( "This runs next tick!" );
});
// Schedule delayed task
scheduleDelayed ( function () {
chat ( "This runs after 20 ticks (1 second)" );
}, 20 );
// Schedule repeating task
scheduleRepeating ( function () {
chat ( "This runs every second" );
}, 0 , 20 );
Value Types in Scripts
Scripts can create various value types for module settings:
Boolean
Integer
Float
Text
Choice
var enabled = booleanValue ( "Enabled" , true );
// Access
if ( enabled . get ()) {
// Do something
}
// Set
enabled . set ( false );
var speed = intValue ( "Speed" , 10 , 1 , 20 );
// name, default, min, max
var currentSpeed = speed . get ();
speed . set ( 15 );
var range = floatValue ( "Range" , 4.0 , 3.0 , 6.0 );
var currentRange = range . get ();
range . set ( 4.5 );
var message = textValue ( "Message" , "Hello World" );
var text = message . get ();
message . set ( "New message" );
var mode = listValue ( "Mode" , [ "Vanilla" , "Custom" , "AAC" ], "Vanilla" );
var currentMode = mode . get ();
mode . set ( "Custom" );
Setting Value Programmatically
The Script API supports setting values from polyglot values:
@ScriptApiRequired
@JvmName ( "setValue" )
fun setValue (t: PolyglotValue ) = runCatching {
set (
when ( inner ) {
is Float -> t. asDouble (). toFloat () as T
is Int -> t. asInt () as T
is String -> t. asString () as T
is Boolean -> t. asBoolean () as T
else -> error ( "Unsupported value type $inner " )
}
)
}
Managing Scripts
Via Commands
Load Scripts
List Scripts
Enable/Disable
# Reload all scripts
.script reload
# This will:
# - Unload all current scripts
# - Scan the scripts directory
# - Load and enable all found scripts
Via Code
// Load a specific script
val script = ScriptManager. loadScript (
file = File ( "path/to/script.js" ),
language = "js"
)
// Unload a script
ScriptManager. unloadScript (script)
// Reload all scripts
ScriptManager. reload ()
Script Debug Options
Scripts support debug options for development:
fun loadScript (
file: File ,
language: String = Source. findLanguage (file),
debugOptions: ScriptDebugOptions = ScriptDebugOptions ()
): PolyglotScript
Source: ScriptManager.kt:135-139
Marketplace Integration
Scripts can be installed from the LiquidBounce marketplace:
val files = root. listFiles { file ->
Source. findLanguage (file) != null || file.isDirectory
} + MarketplaceManager. getSubscribedItemsOfType (MarketplaceItemType.SCRIPT). map { item ->
item. getInstallationFolder ()
}
Source: ScriptManager.kt:76-80
Use the .marketplace command to browse and install community scripts!
Example Scripts
Simple Kill Aura
var KillAura = {
name: "ScriptKillAura" ,
description: "A simple kill aura script" ,
category: "Combat" ,
settings: {
range: floatValue ( "Range" , 4.0 , 3.0 , 6.0 ),
autoBlock: booleanValue ( "AutoBlock" , true )
},
onEnable : function () {
chat ( "Kill Aura enabled" );
},
onUpdate : function () {
var range = this . settings . range . get ();
var entities = world . getLoadedEntities ();
for ( var i = 0 ; i < entities . length ; i ++ ) {
var entity = entities [ i ];
var distance = player . getDistanceToEntity ( entity );
if ( distance <= range && isValidTarget ( entity )) {
player . attackEntity ( entity );
break ;
}
}
}
};
moduleManager . registerModule ( KillAura );
Custom Command
var WelcomeCommand = {
name: "welcome" ,
description: "Sends a welcome message" ,
aliases: [ "w" , "hi" ],
execute : function ( args ) {
if ( args . length > 0 ) {
var name = args . join ( " " );
chat ( "Welcome, " + name + "!" );
} else {
chat ( "Welcome to LiquidBounce!" );
}
}
};
commandManager . registerCommand ( WelcomeCommand );
Event Listener
var PacketLogger = {
name: "PacketLogger" ,
description: "Logs all packets" ,
category: "Misc" ,
onEnable : function () {
this . packetListener = events . on ( "Packet" , function ( event ) {
var packet = event . packet ;
console . log ( "Packet: " + packet . getClass (). getName ());
});
},
onDisable : function () {
events . off ( this . packetListener );
}
};
moduleManager . registerModule ( PacketLogger );
Best Practices
Start Simple
Begin with simple scripts and gradually add complexity
Use Proper Names
Give your scripts descriptive names that indicate their purpose
Handle Errors
Use try-catch blocks to prevent scripts from crashing
Clean Up Resources
Unregister event listeners in onDisable to prevent memory leaks
Test Thoroughly
Test scripts in single-player before using on servers
Document Your Code
Add comments explaining what your script does
Scripts run in the game’s main thread. Heavy operations can cause lag.
Avoid complex calculations in frequently called events (like onUpdate)
Cache values when possible instead of recalculating
Use conditional checks to skip unnecessary work
Consider using tick delays for non-critical operations
Profile your scripts to identify bottlenecks
Security
Only load scripts from trusted sources. Scripts have full access to the client and can potentially be malicious.
Safe Script Practices
Review script code before loading
Use scripts from the official marketplace when possible
Be cautious with scripts that:
Access the file system
Make network requests
Execute system commands
Modify game files
Troubleshooting
Check if the file extension is supported
Verify GraalVM has the language installed
Look for syntax errors in the console
Ensure the script is in the correct directory
For directories, verify the main file exists
Read the error message in console
Check if you’re using the correct API methods
Verify all required modules are loaded
Test the script in isolation
Modules Understand modules that scripts can extend
Commands Create custom commands with scripts
Configuration Access configuration from scripts
API Reference Full Script API documentation