Skip to main content

Introduction

Modules are the core feature type in LiquidBounce. Scripts can create custom modules that appear in the ClickGUI and can be toggled on/off just like built-in modules.

Basic Module

Here’s a simple module that sends a message when enabled:
SimpleModule.js
registerScript({
    name: "SimpleModule",
    version: "1.0.0",
    authors: ["YourName"]
});

registerModule({
    name: "HelloModule",
    category: "Misc",
    description: "Says hello when enabled"
}, (module) => {
    module.on("enable", () => {
        Client.displayChatMessage("Hello! Module enabled.");
    });
    
    module.on("disable", () => {
        Client.displayChatMessage("Goodbye! Module disabled.");
    });
});

Module Properties

When registering a module, provide these properties:
registerModule({
    name: "ModuleName",           // Required: Display name
    category: "Combat",           // Required: Module category
    description: "Description",   // Optional: Module description
    tag: "TagText",              // Optional: Tag shown in ArrayList
    settings: { }                // Optional: Module settings
}, (module) => {
    // Module initialization
});

Categories

Available module categories:
  • "Combat" - Combat-related modules
  • "Movement" - Movement modules
  • "Player" - Player-related modules
  • "World" - World interaction modules
  • "Render" - Visual/rendering modules
  • "Client" - Client-side modules
  • "Misc" - Miscellaneous modules
  • "Fun" - Fun/troll modules

Module Settings

Add configurable settings to your module:
SettingsExample.js
registerScript({
    name: "SettingsExample",
    version: "1.0.0",
    authors: ["YourName"]
});

registerModule({
    name: "CustomAura",
    category: "Combat",
    description: "Custom combat module",
    settings: {
        range: Setting.float({
            name: "Range",
            default: 4.2,
            range: [1.0, 6.0],
            suffix: " blocks"
        }),
        delay: Setting.int({
            name: "Delay",
            default: 100,
            range: [0, 1000],
            suffix: "ms"
        }),
        targetPlayers: Setting.boolean({
            name: "TargetPlayers",
            default: true
        }),
        mode: Setting.choose({
            name: "Mode",
            choices: ["Single", "Multi", "Switch"],
            default: "Single"
        })
    }
}, (module) => {
    // Access settings
    module.on("enable", () => {
        const range = module.settings.range.value;
        const delay = module.settings.delay.value;
        const targetPlayers = module.settings.targetPlayers.value;
        const mode = module.settings.mode.value;
        
        Client.displayChatMessage(
            "Range: " + range + ", Delay: " + delay
        );
    });
});

Setting Types

See the API Reference for all available setting types:
  • Setting.boolean() - True/false toggle
  • Setting.int() - Integer with range
  • Setting.float() - Decimal number with range
  • Setting.intRange() - Range of integers (min-max)
  • Setting.floatRange() - Range of floats (min-max)
  • Setting.text() - Text input
  • Setting.textArray() - Array of text values
  • Setting.choose() - Single choice from options
  • Setting.multiChoose() - Multiple choices
  • Setting.key() - Keybinding

Event Handlers

Modules can listen to various game events:
EventModule.js
registerScript({
    name: "EventModule",
    version: "1.0.0",
    authors: ["YourName"]
});

registerModule({
    name: "EventDemo",
    category: "Misc"
}, (module) => {
    // Called when module is enabled
    module.on("enable", () => {
        Client.displayChatMessage("Module enabled!");
    });
    
    // Called when module is disabled
    module.on("disable", () => {
        Client.displayChatMessage("Module disabled!");
    });
    
    // Called every game tick
    module.on("playerTick", () => {
        if (mc.player === null) return;
        
        // Your per-tick logic here
    });
    
    // Called when player moves
    module.on("playerMove", (event) => {
        // event.movement contains the movement vector
        const movement = event.movement;
        
        // You can modify the movement
        // event.movement = new Vec3d(0, movement.y, 0);
    });
    
    // Called when receiving a packet
    module.on("packetReceive", (event) => {
        // Access packet data
        // You can cancel the packet
        // event.cancelEvent();
    });
});

Common Events

Player Events:
  • playerTick - Every player tick (20 times per second)
  • playerMove - When player moves
  • playerJump - When player jumps
  • healthUpdate - When health changes
  • death - When player dies
Network Events:
  • packetSend - Before sending a packet
  • packetReceive - After receiving a packet
Render Events:
  • gameRender - During game rendering
  • worldRender - During world rendering
  • overlayRender - During HUD/overlay rendering
World Events:
  • blockBreak - When breaking a block
  • blockPlace - When placing a block
See the API Reference for a complete event list.

Dynamic Module Tag

Update the module’s tag dynamically (shown in ArrayList):
registerModule({
    name: "SpeedCounter",
    category: "Render",
    tag: "0.00"
}, (module) => {
    module.on("playerTick", () => {
        if (mc.player === null) return;
        
        const speed = MovementUtil.getSpeed();
        // Update the tag
        module.tag = speed.toFixed(2);
    });
});

Complete Module Example

Here’s a complete combat module:
CustomKillAura.js
registerScript({
    name: "CustomKillAura",
    version: "1.0.0",
    authors: ["YourName"]
});

registerModule({
    name: "CustomKillAura",
    category: "Combat",
    description: "Custom kill aura implementation",
    settings: {
        range: Setting.float({
            name: "Range",
            default: 4.2,
            range: [1.0, 6.0],
            suffix: " blocks"
        }),
        delay: Setting.intRange({
            name: "Delay",
            default: [50, 100],
            range: [0, 500],
            suffix: "ms"
        }),
        targetPlayers: Setting.boolean({
            name: "TargetPlayers",
            default: true
        }),
        targetMobs: Setting.boolean({
            name: "TargetMobs",
            default: false
        }),
        rotations: Setting.boolean({
            name: "Rotations",
            default: true
        })
    }
}, (module) => {
    let lastAttack = 0;
    let currentTarget = null;
    
    module.on("enable", () => {
        lastAttack = 0;
        currentTarget = null;
    });
    
    module.on("disable", () => {
        currentTarget = null;
    });
    
    module.on("playerTick", () => {
        const player = mc.player;
        const world = mc.level;
        
        if (player === null || world === null) return;
        
        // Get settings
        const range = module.settings.range.value;
        const delayMin = module.settings.delay.value.start;
        const delayMax = module.settings.delay.value.endInclusive;
        const targetPlayers = module.settings.targetPlayers.value;
        const targetMobs = module.settings.targetMobs.value;
        const useRotations = module.settings.rotations.value;
        
        // Find target
        currentTarget = findTarget(world, player, range, targetPlayers, targetMobs);
        
        if (currentTarget === null) {
            module.tag = "No Target";
            return;
        }
        
        // Update tag
        module.tag = currentTarget.getName().getString();
        
        // Check delay
        const now = Date.now();
        const randomDelay = delayMin + Math.random() * (delayMax - delayMin);
        
        if (now - lastAttack < randomDelay) {
            return;
        }
        
        // Aim at target
        if (useRotations) {
            const rotation = RotationUtil.newRotationEntity(currentTarget);
            RotationUtil.aimAtRotation(rotation, true);
        }
        
        // Attack
        mc.gameMode.attack(player, currentTarget);
        player.swing(Hand.MAIN_HAND);
        
        lastAttack = now;
    });
    
    function findTarget(world, player, range, targetPlayers, targetMobs) {
        let closestTarget = null;
        let closestDistance = range * range; // Squared for performance
        
        const entities = world.entitiesForRendering();
        
        for (let i = 0; i < entities.size(); i++) {
            const entity = entities.get(i);
            
            // Skip self
            if (entity === player) continue;
            
            // Check entity type
            if (entity.getType().toString().includes("Player")) {
                if (!targetPlayers) continue;
            } else if (entity instanceof Java.type("net.minecraft.world.entity.Mob")) {
                if (!targetMobs) continue;
            } else {
                continue;
            }
            
            // Check distance
            const distSq = player.distanceToSqr(entity);
            if (distSq < closestDistance) {
                closestDistance = distSq;
                closestTarget = entity;
            }
        }
        
        return closestTarget;
    }
});

Best Practices

  1. Always check for null - mc.player and mc.level can be null
  2. Clean up on disable - Reset variables in the disable event
  3. Use efficient algorithms - Avoid expensive operations in playerTick
  4. Handle errors - Wrap risky code in try-catch blocks
  5. Meaningful tags - Update module.tag to show useful info
  6. Reasonable defaults - Choose sensible default values for settings

Advanced: Module Modes

For complex modules, you can register sub-modes. See the source code reference in script/bindings/features/ScriptMode.kt for details on using registerMode().

Next Steps

Build docs developers (and LLMs) love