Skip to main content

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:
1

Loading

Scripts are discovered and loaded from the scripts directory
2

Initialization

The script’s main code is executed and objects are created
3

Enabling

Script modules/features are registered with LiquidBounce
4

Running

Script code executes and responds to events
5

Disabling

Script features are unregistered when disabled
6

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:
var enabled = booleanValue("Enabled", true);

// Access
if (enabled.get()) {
    // Do something
}

// Set
enabled.set(false);

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")
        }
    )
}
Source: Value.kt:183-218

Managing Scripts

Via Commands

# 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

1

Start Simple

Begin with simple scripts and gradually add complexity
2

Use Proper Names

Give your scripts descriptive names that indicate their purpose
3

Handle Errors

Use try-catch blocks to prevent scripts from crashing
4

Clean Up Resources

Unregister event listeners in onDisable to prevent memory leaks
5

Test Thoroughly

Test scripts in single-player before using on servers
6

Document Your Code

Add comments explaining what your script does

Performance Considerations

Scripts run in the game’s main thread. Heavy operations can cause lag.

Tips for Performance

  • 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
  • Check if the script is doing too much in update events
  • Use profiling to identify slow operations
  • Consider optimizing or splitting the script
  • Disable other scripts to isolate the issue

Modules

Understand modules that scripts can extend

Commands

Create custom commands with scripts

Configuration

Access configuration from scripts

API Reference

Full Script API documentation

Build docs developers (and LLMs) love