Dusk scripting
Write small Lua files that hook into the client — HUD overlays, combat helpers, movement tweaks, packet filters. Each script can become a real module in the ClickGUI with its own settings and keybind, or run quietly in the background.
Getting started
The engine is Lua 5.2 (via LuaJ, bundled — nothing to install). Drop a
.lua file in your Scripts folder, open the client, and it loads automatically.
The folder ships with working example scripts you can edit.
Open the Scripts module (Scripts category) in the ClickGUI for two actions:
- Reload Scripts — re-scans and recompiles the folder without restarting.
- Open Folder — opens the folder in your file browser.
Scripts folder
Scripts live in:
%LOCALAPPDATA%\FLStudio\Local\Scripts
This folder is created on first launch and seeded with example scripts
(example.lua, session_hud.lua, player_esp.lua,
aim_assist.lua). Every *.lua file in it
is loaded on startup.
Want them somewhere else? Set a path in the Scripts module's
Folder field and press Reload Scripts — scripting (and the
sandboxed files/storage APIs)
will use that directory instead. Leave it blank for the default.
Script anatomy
A script is a .lua file that returns a table. The table holds
metadata and callback functions. Every callback receives the module handle m as its
first argument.
return {
name = "My Script", -- becomes the module name in the GUI
description = "What it does.", -- tooltip
category = "SCRIPTS", -- COMBAT / MOVEMENT / MISC / RENDER / SCRIPTS / CLIENT
-- background = true, -- optional: no GUI entry, always running
on_load = function(m) -- register settings here (runs once, at load)
m:number("Range", 5.0, 1.0, 20.0, 0.5)
m:bool("Silent", true)
end,
on_enable = function(m) client.message("on") end,
on_disable = function(m) client.message("off") end,
on_tick = function(m) end, -- every client tick while enabled
on_render_2d = function(m, e) end, -- HUD drawing; e = {width, height, tick_delta}
on_render_3d = function(m, e) end, -- in-world drawing; e = {tick_delta}
}
Foreground & background
- Foreground (default): the script becomes a toggleable module in the Scripts category, with settings, a keybind, and config persistence. Its event callbacks fire only while the module is enabled.
- Background (
background = true): no module, no GUI entry. Its callbacks fire whenever the script is loaded. Good for pure utilities/HUDs.
Error handling
If a callback throws, the error is printed to chat (with the script + callback name) and the script's module is disabled so it can't spam. Fix the file and press Reload Scripts.
Callbacks
Define any of these as fields in your returned table. They fire automatically —
each gets the handle m first, then any extra argument listed below.
| Callback | Fires… | Extra arg |
|---|---|---|
on_load | once, when the script loads | — |
on_enable | the module is toggled on | — |
on_disable | the module is toggled off | — |
on_tick | every client tick (20/sec) | — |
on_player_tick | every player tick | — |
on_update | every rendered frame — for millisecond-precise timing (macros, click sequences) | — |
on_render_2d | every HUD frame | {width, height, tick_delta} |
on_render_3d | every world-render frame | {tick_delta} |
on_key | a key/mouse button changes state | {key, action, pressed, released} |
on_mouse_update | the aim/look updates | — |
on_mouse_move | raw mouse movement · false = cancel | {x, y} |
on_send_movement | movement packets about to send (drive silent aim here) | — |
on_attack | you attack · false = cancel | — |
on_item_use | you use an item · false = cancel | — |
on_block_break | you start breaking a block · false = cancel | — |
on_packet_send | a packet about to send · false = cancel | p |
on_packet_receive | a packet about to process · false = cancel | p |
on_chat | a chat message about to show · false = hide it | {text} (colors stripped) |
Cancelling: return Lua false from a cancellable callback to stop
the action. Returning nil (or nothing) does not cancel.
The module handle: m
Every callback receives m as its first argument — it represents
this script's own module. Use it to declare settings (which become real controls in the
ClickGUI and are saved with your config), read those settings back at runtime, and control the
module itself. Note the colon: settings are method calls, so write m:number(...),
not m.number(...).
Registering settings — call these in on_load
Each call adds one control to the module's panel. The name you give is also the
key you read it back with. Register once in on_load; they persist automatically.
m:bool(name, default) | An on/off toggle. Read back as true/false. |
m:number(name, default, min, max, step) | A slider clamped to [min, max] moving in step increments. Read back as a number. |
m:range(name, default_min, default_max, min, max, step) | A single min/max slider (one control for a whole range). Read back as {min, max} — ideal for a delay range instead of two separate settings. |
m:mode(name, default, {"A", "B", ...}) | A dropdown of string options. default is one of them; read back as the selected string. |
m:string(name, default) | A free text field. |
m:color(name, r, g, b [, a]) | A colour picker (channels 0–255). Read back as {r, g, b, a}; feed it to render.color(c.r, c.g, c.b, c.a). |
m:keybind(name, default_key) | A rebindable key control (a GLFW keycode) the user can change in the GUI. |
m:section(name) | A visual header/divider to group the settings below it. |
m:depends(child, parent, value) | Show child only while parent's value equals value — hide settings that don't apply. child may be one name or a table of names. Call it after registering both. |
on_load = function(m)
m:section("Targeting")
m:number("Range", 5.0, 1.0, 20.0, 0.5)
m:range("Delay (ms)", 40, 60, 0, 500, 1) -- one min/max slider
m:mode("Aim Point", "closest", { "closest", "head", "body" })
m:bool("Auto Range", true)
m:depends("Range", "Auto Range", false) -- hide "Range" while Auto Range is on
end
-- read a range back:
-- local d = m:get("Delay (ms)") -> { min = 40, max = 60 }
Reading & writing settings
m:get(name) | Current value of a setting, typed to match what you registered (number / bool / string / colour table). |
m:set(name, value) | Change a setting from code. |
local range = m:get("Range") -- number
local c = m:get("Color") -- -> {r, g, b, a}
m:set("Range", 8.0)
Module control
m:name() · m:description() · m:category() | This module's metadata. |
m:is_enabled() | Whether the module is currently on. |
m:set_enabled(bool) · m:toggle() | Turn it on/off, or flip it, from code. |
m:info("Silent") | Sets the small greyed suffix shown next to the module name in the ArrayList — use it to display live state (target name, mode, etc.). Pass nil to clear it. |
Sandbox
All API tables are global. Values return nil/false/0 when
you're not in a game, so guard with player.is_present() /
world.is_present() before reading state.
Scripts run with real client power (aim, attacks, packets, HTTP) — treat shared scripts like any other executable. A misconfigured combat/aim script can flag you on anticheat exactly like a misconfigured module. Test on a safe server first.
The Lua standard library is available except the parts that reach outside the game:
io and os.execute / os.exit / os.remove /
os.rename / os.tmpname / os.getenv / os.setlocale
are removed. math, string,
table, os.time/os.clock/os.date, and
pcall remain. For persistence, use the sandboxed files
or storage API.
client
Talk to the game and the player's session: print to chat, send messages, read session info (FPS, ping, name), and trigger simple actions. Everything here works without a world loaded except the actions that need a player.
| Function | What it does / how to use it |
|---|---|
client.message(text) | Prints a grey line to your chat, prefixed [Scripts]. Only you see it. Use it for status output and debugging. |
client.warn(text) | Same, styled yellow — for non-fatal problems. |
client.error(text) | Same, styled red — for errors. |
client.send_chat(text) | Sends text to the server as if you typed it. A leading / runs it as a command (client.send_chat("/spawn")). |
client.get_fps() | Current frame rate, as a number. |
client.get_ping() | Your latency to the server in milliseconds. |
client.get_username() | Your account name. |
client.get_version() | The Minecraft version string, e.g. "1.21.11". |
client.get_server_address() | The address you're connected to, or nil in singleplayer. |
client.is_in_game() | true when both a player and world exist. A cheap "am I actually playing" check. |
client.is_screen_open() | true if any GUI screen (inventory, chat, a menu) is open. |
client.get_screen_title() | The open screen's title string ("Chest", "Large Chest", a shop's name…), or nil if no screen is open. |
client.now() | Current time in milliseconds. Subtract two calls to measure elapsed time / build cooldowns. |
client.is_singleplayer() | true on a local world. |
client.in_focus() | true if the game window is focused (not alt-tabbed away). |
client.get_free_memory()client.get_total_memory()client.get_max_memory() | JVM memory figures in bytes — for a RAM readout on a HUD. |
client.copy_to_clipboard(text) | Puts text on the system clipboard. |
client.play_sound(id [, volume, pitch]) | Plays a registered sound to you, e.g. client.play_sound("entity.experience_orb.pickup"). volume and pitch default to 1.0. |
client.do_attack() | Fires one left-click (attack), same as clicking the mouse. |
client.do_item_use() | Fires one right-click (use item). |
client.random_int(lo, hi) | Random integer in [lo, hi] — handy for humanising delays. |
client.random_float(lo, hi) | Random decimal in [lo, hi). |
player
Read and control you — the local player: position, rotation, health,
movement flags, and a few actions. Always call player.is_present() first;
every other function returns nil/0/false when you're not in a game.
Presence & identity
player.is_present() | Your guard: true only when player + world exist. Return early if it's false. |
player.get_name() | Your username. |
player.get_display_name() | Formatted display name (nickname/prefixes if any). |
player.get_id() | Your entity id — use it to skip yourself when looping world.get_players(). |
Health & hunger
player.get_health() / player.get_max_health() | Current and maximum health (20 = full, one heart = 2). |
player.get_absorption() | Yellow absorption hearts on top of health. |
player.get_hurt_time() | Ticks since you were last hit (0 = not currently hurt). |
player.get_hunger() | Food level, 0–20. |
player.get_fall_distance() | Blocks fallen so far — useful for crit/fall logic. |
Position & rotation
player.get_x/y/z() | World position. y is your feet. |
player.get_eye_y() | Eye height — the point rotations and raycasts come from. |
player.get_yaw() / player.get_pitch() | Where you're looking. Yaw = horizontal (degrees), pitch = up/down (−90…90). |
player.get_velocity_x/y/z() | Current motion per axis (blocks/tick). |
Movement & combat flags
player.is_on_ground() | true when standing on a block. |
player.is_sprinting/is_sneaking() | Movement states. |
player.is_using_item() | true while eating/blocking/drawing a bow. |
player.is_in_water/is_climbing/is_gliding() | Environment/movement states (climbing = on a ladder/vine; gliding = elytra). |
player.is_blocking() | true while actively raising a shield. |
player.can_crit() | true when your next hit would critical (falling, not on ground, etc.). |
player.is_holding_weapon() | true if your main hand holds a sword/axe. |
Actions
player.set_yaw(deg) / player.set_pitch(deg) | Snap your view. Sets the visible camera — for anticheat-safe aiming use silent aim instead. |
player.set_rotation(yaw, pitch) | Set both at once. |
player.set_sprinting(bool) | Force sprint on/off. |
player.swing() | Plays the main-hand swing animation. |
player.send_chat(text) | Same as client.send_chat. |
Status effects & held items
player.has_status_effect(name) | true if the effect is active. name is "speed" or "minecraft:speed". |
player.get_status_effect(name) | Returns {amplifier, duration} (amplifier is 0-based: Speed II → 1) or nil. |
player.get_held_item() / player.get_off_hand_item() | The item in each hand as an item table (see inventory). |
world & entities
Query the loaded world and the things in it — other players, mobs, items,
blocks. Guard with world.is_present(). The entity-list functions return arrays of
entity tables (described below), which you loop over with ipairs.
| Function | What it does / how to use it |
|---|---|
world.is_present() | Your guard — true when a world is loaded. |
world.get_dimension() | Dimension id, e.g. "minecraft:overworld". |
world.get_time() | World time in ticks. |
world.get_players() | Array of every player entity table. The one you'll use most (ESP, aura, targeting). |
world.get_entities() | Array of all entities (mobs, items, projectiles, players). |
world.get_entity(id) | The entity table for a specific id, or nil. |
world.get_block(x, y, z) | Block id at a position, e.g. "minecraft:stone". |
world.is_air(x, y, z) | true if that block is empty. |
world.get_block_state(x, y, z) | A rich block table (see below) or nil — the autofarm/mining workhorse. properties holds live state (crop age, farmland moisture, waterlogged, facing…). |
world.find_blocks(query, radius [, max]) | Nearest-first array of {x, y, z, distance} for blocks matching query in a cube of half-size radius (capped at 48) around you. max defaults to 256. |
world.get_tab_players() | Array of {name, ping} from the tab list — includes players you can't see. |
world.get_tile_entities() | Array of {x, y, z, type} for loaded block entities (chests, signs, spawners…). Good for chest ESP. |
world.get_scoreboard() | Sidebar lines (best-effort: the title line). |
Entity tables
Every entity returned above is a snapshot table. Read fields directly
(ent.x, ent.health, ent.distance). Always skip dead/removed
ones and yourself:
local self_id = player.get_id()
for _, ent in ipairs(world.get_players()) do
if ent.is_player and ent.alive and not ent.removed and ent.id ~= self_id then
-- use ent.x, ent.y, ent.z, ent.health, ent.distance ...
end
end
| Field | Meaning |
|---|---|
id · uuid · name · display_name · type | Identity. type is the entity type id. |
x · y · z | Current position (feet). |
last_x · last_y · last_z | Position last tick — subtract for motion/interpolation. |
eye_y | Eye height — aim here for "head" shots. |
yaw · pitch | Where the entity is facing. |
width · height | Hitbox size. |
distance | Distance from you in blocks (−1 if no local player). |
speed | Horizontal movement speed. |
age · fall_distance · on_ground | Ticks alive, blocks fallen, and grounded flag. |
sprinting · sneaking · invisible · is_burning · in_water · in_lava | State flags. |
alive · removed | Liveness — always check these before acting. |
is_player · is_living · is_hostile · is_passive · is_attackable | Category checks for filtering targets. |
health · max_health · absorption · hurt_time | Living entities only. nil on items/projectiles. |
using_item · swing_progress · held_item | Living entities only — what they're doing / holding (item table). |
get_distance() | Method — recomputes distance live (the field is a snapshot). |
Block-state table
world.get_block_state(x, y, z) returns a snapshot of one block. properties
is a sub-table of the live block-state values (keys vary by block), each typed as a number, boolean,
or string. break_delta/instant_break account for your currently-held tool.
| Field | Meaning |
|---|---|
x · y · z · id · name | Position and the block's registry id + display name. |
is_air · is_solid · is_replaceable | Common state flags. |
luminance · hardness | Light level emitted; mining hardness at this position. |
break_delta · instant_break | Fraction mined in one tick with your held tool, and whether that breaks it instantly. |
fluid · fluid_level | Fluid id ("" if none) and its level. |
properties | Table of the block's own state values, e.g. { age = 7, moisture = 7 }. |
-- harvest ripe wheat within 6 blocks:
for _, b in ipairs(world.find_blocks("wheat", 6)) do
local s = world.get_block_state(b.x, b.y, b.z)
if s and s.properties.age == 7 then interaction.attack_block(b.x, b.y, b.z) break end
end
render
Draw on screen. render.color works anywhere, but drawing calls
only work inside a render callback: 2D shapes/text in on_render_2d, in-world
boxes in on_render_3d. Coordinates are GUI pixels with (0,0) at the
top-left; e.width/e.height (passed to on_render_2d) give the
screen size.
Colors
render.color(r, g, b [, a]) | Packs 0–255 channels into a color value you pass to every draw call. Alpha defaults to 255 (opaque). Store constants once: local white = render.color(255,255,255). |
2D — inside on_render_2d
render.text(text, x, y, color [, shadow] [, size]) | Draws text at (x, y). shadow adds a drop shadow; size is the point size (default ~18). Returns the pixel width so you can lay out the next element. |
render.measure_text(text [, size]) | Width of text without drawing — use it to centre text. |
render.fill(x1, y1, x2, y2, color) | Filled rectangle between two corners (absolute coords, not width/height). |
render.border(x, y, w, h, color) | Rectangle outline from an origin plus width/height. |
render.world_to_screen(x, y, z) | Projects a world point to the screen. Returns {x, y, depth, on_screen} or nil. Check .on_screen before drawing — this is how you place tags over entities. |
render.entity_screen_box(id [, padding]) | The entity's hitbox projected to a 2D box: {x, y, width, height, min_x, min_y, max_x, max_y, on_screen} or nil. Ready-made for 2D ESP boxes; padding inflates it slightly. |
render.measure_text · render.font_height([size]) | Text metrics — width and line height for the given size. |
render.list_fonts() | Array of available font-family names. |
render.is_2d_context() · render.is_3d_context() | true if a 2D/3D draw is currently valid — a safety check. |
3D — inside on_render_3d
render.box_3d(x1, y1, z1, x2, y2, z2, color [, line_width]) | Draws a box outline in the world between two corners — e.g. a block or hitbox highlight. line_width defaults to 1.5. |
input
Read the keyboard/mouse and send genuine key/mouse presses. "Down" checks
read raw hardware keys; "pressed" checks read the player's bound controls (so they
respect a remapped keybind). The press/click functions can drive the input three different ways
via a mode argument.
Click / key modes: "game" drives Minecraft's own input path
(attack/use/movement — lands on your real crosshair, no window focus needed);
"event" fires the client's internal button event (for trigger-bot-style hooks);
"os" injects a real OS-level press via the system (most genuine —
indistinguishable from hardware — but needs the game window focused and lands on the OS
cursor).
Reading state
input.is_key_down(keycode) | true while a raw key is held. Get a keycode with keybinds.get_key_code("F"). |
input.is_mouse_down(button) | Raw mouse button: 0 left, 1 right, 2 middle. |
input.is_attack_pressed() · is_use_pressed() · is_jump_pressed() · is_sneak_pressed() | Whether the corresponding bound control is held. |
input.is_forward_pressed() · is_back_pressed() · is_left_pressed() · is_right_pressed() | Movement keys (bound) — good for "only while moving" gates. |
Mouse output
input.left_click([mode]) · input.right_click([mode]) | One click. mode defaults to "game"; use "os" for a real hardware-level click or "event" for the internal event. |
input.mouse_press(button [, mode]) · input.mouse_release(button [, mode]) | Hold / release a button yourself. mode is "event" (default) or "os". |
input.mouse_click(button [, millis] [, mode]) | Press then release after millis (default 35). mode "event" (default), "game", or "os". |
Keyboard output
Keycodes are GLFW codes — get one from keybinds.get_key_code("W").
mode is "game" (default — drives the vanilla keybind, so movement/hotbar/use
work and no focus is needed) or "os" (a real OS keystroke, needs focus).
input.key_press(keycode [, mode]) · input.key_release(keycode [, mode]) | Hold and release a key — e.g. hold W to auto-walk. |
input.key_click(keycode [, millis] [, mode]) | Press then release after millis (default 50). |
input.key_tap(keycode) | One game-level edge press — fires a single "was pressed" (e.g. drop item, swap offhand). |
-- hold W for 500ms via the game input, then a real OS spacebar tap:
input.key_press(keybinds.get_key_code("W"))
-- ... later:
input.key_release(keybinds.get_key_code("W"))
input.key_click(keybinds.get_key_code("SPACE"), 40, "os")
rotation & silent aim
Compute the angles needed to look at something, measure how far off your current aim is, and — with the silent-aim engine — actually move your aim there smoothly and server-side. The maths helpers are cheap and safe; the silent-aim functions drive the real rotation engine and should be called every tick while aiming.
Aim maths
rotation.get_rotation_to(x, y, z) | The {yaw, pitch} that would point your eyes at a world point. |
rotation.get_rotation_to_entity(id [, part]) | Same, aimed at an entity. part is "head" (default), "body", or "feet". |
rotation.get_distance_to(x, y, z) | Distance from your eyes to a point. |
rotation.get_yaw_diff(id) · get_pitch_diff(id) | Signed angle (degrees) from your current aim to the entity — how far you'd need to turn. Yaw is wrapped to −180…180. |
rotation.get_abs_angle_diff(id) | Combined absolute off-angle — one number for "how far off am I". Good for an FOV/aim gate. |
rotation.get_angle_diff(id) | Returns {yaw, pitch, total} together. |
rotation.apply_gcd(current, target) | Snaps target to the client's real mouse-movement step, so a scripted turn looks like human mouse input rather than a perfect angle. Pass your result through this before player.set_yaw. |
Drives Dusk's rotation engine (the same one the combat modules use, with its priority
arbiter). Call it every tick from on_tick (or on_send_movement) while
you want to hold the aim, then rotation.reset() when done so it smoothly returns.
rotation.aim_at(x, y, z [, opts]) -- -> bool (got control)
rotation.aim_at_entity(id [, opts]) -- -> bool
rotation.reset([silent]) -- rotate back + release; true while still moving back
rotation.stop() -- hard stop
rotation.is_tracking() rotation.is_controlling()
rotation.get_silent_yaw() rotation.get_silent_pitch()
rotation.aim_at(x, y, z [, opts]) | Aim at a world point this tick. Returns true if your script got control of the aim (a higher-priority module can hold it). Call every tick to keep aiming. |
rotation.aim_at_entity(id [, opts]) | Aim at an entity, with hitbox point-selection (opts.point) and prediction. |
rotation.reset([silent]) | Stop aiming and rotate smoothly back to your real view; returns true while still returning. Call this when you have no target. |
rotation.stop() | Hard-release control immediately (no smooth return). |
rotation.is_tracking() | true while the engine is actively aiming. |
rotation.is_controlling() | true if your script currently holds the aim. |
rotation.get_silent_yaw() · get_silent_pitch() | The angles being sent to the server right now — handy to raycast/validate before you attack. |
Tune the aim with an opts table (all optional):
| key | default | meaning |
|---|---|---|
yaw_speed | 100 | turn rate for yaw |
pitch_speed | 100 | turn rate for pitch |
silent | true | true = server-only (view unchanged); false = moves your camera |
aim_type | "regular" | regular / blatant / windmouse |
point | "closest" | entity aim point: closest/straight/center/feet/random |
multipoint | 50 | 0–100 hitbox-shrink for multipoint selection |
random | false | jitter the aim point |
through_walls | false | allow aim points you can't see |
predict | 0 | ticks of velocity prediction |
-- silent aim at the crosshair target, held while attack is pressed:
on_tick = function(m)
if not input.is_attack_pressed() then rotation.reset() return end
local t = combat.crosshair_target(4.0) or combat.nearest_player(4.0)
if t then rotation.aim_at_entity(t.id, { yaw_speed = 40, pitch_speed = 40, silent = true })
else rotation.reset() end
end
targeting
Find out what you're looking at. The get_* functions read your live
crosshair (what the game itself is targeting); the raycast functions cast a fresh ray
from your eyes so you can test an arbitrary angle or reach.
targeting.get_type() | What the crosshair is on: "entity", "block", or "miss". |
targeting.is_entity() · targeting.is_block() | Quick boolean versions. |
targeting.get_entity() | The entity table under your crosshair, or nil. |
targeting.get_block() | The block you're pointing at: {x, y, z, side, hit_x, hit_y, hit_z} (side is the face, hit_* the exact point) or nil. |
targeting.get() | The full hit result: {type, is_entity, is_block, is_miss, x, y, z, entity?, entity_id?, block?}. |
targeting.raycast(reach [, yaw, pitch]) | Casts a ray for reach blocks (optionally at a given angle instead of your view) and returns a hit table — stops at solid blocks. |
targeting.raycast_through_walls(reach [, yaw, pitch]) | Same, but uses block outlines so it can reach through gaps a collider ray wouldn't. |
combat
Combat-timing helpers and target-finding — the building blocks of an aura or
trigger-bot. Pair can_swing() with crosshair_target() to attack only when
your hit will land at full damage.
combat.get_attack_cooldown([base]) | Attack charge, 0.0–1.0 (1.0 = fully recharged). base is the tick offset, default 0.5. |
combat.can_crit() | true if your next hit would be a critical. |
combat.can_swing() | true once the attack cooldown is full — the right moment to click for max damage. |
combat.is_holding_weapon() | true if you're holding a sword/axe. |
combat.crosshair_target([reach]) | The living entity directly under your crosshair within reach (default 3), or nil. This is what you'd hit if you clicked. |
combat.nearest_player(range [, exclude_friends]) | The closest other player within range, or nil. Pass true to skip friends. |
interaction
Actually do things to entities and blocks — attack, right-click, use items, place/break. These send the real interaction packets, so they respect reach and the server. Aim first (with your view or silent aim), then interact.
interaction.attack_entity(id) | Attacks the entity and swings your hand — a single melee hit. |
interaction.interact_entity(id [, hand]) | Right-clicks the entity (e.g. trading, naming). hand is "main" (default) or "off". |
interaction.use_item([hand]) | Uses the held item — eat, drink, throw a pearl, draw a bow. |
interaction.stop_using_item() | Releases a use in progress (fires the bow, stops eating). |
interaction.attack_block(x, y, z [, face]) | Starts/continues breaking a block. face is up/down/north/south/east/west. |
interaction.interact_block(x, y, z [, face] [, hand]) | Right-clicks a block face — open a chest, flip a lever, or place the held block against it. |
inventory
Inspect and manage your items — read slots, switch your held item, search for an item, and click slots inside open containers.
Every slot is returned as an item table: empty (bool),
count, max_count, item (registry id like
"minecraft:diamond_sword") and name (display name). A query
matches the registry id or its short path — "totem" matches
minecraft:totem_of_undying.
Non-empty items also carry durability — damageable (bool),
damage, max_damage, durability (hits left) and
durability_percent — and enchantments: enchantments is a
table like { ["minecraft:sharpness"] = 5 }, and item:get_enchantment(id)
returns a level (0 if absent), matching the full id or its short path.
inventory.get_selected_slot() | Your current hotbar slot, 0–8. |
inventory.set_selected_slot(0..8) | Switch hotbar slot. |
inventory.can_change_held_slot() | true when it's safe to switch (no screen open). |
inventory.try_set_selected_slot(0..8) | Switch only if safe; returns true if it changed. |
inventory.get_slot(index) | Item table for any inventory slot, 0–40 (hotbar 0–8, main storage, armor, offhand). |
inventory.get_hotbar() | Array of the 9 hotbar item tables. |
inventory.get_main_hand() · get_off_hand() | The item in each hand. |
inventory.find_slot(query) | First inventory index holding a matching item, or -1. |
inventory.find_hotbar_slot(query) | Same but restricted to the hotbar (0–8), or -1. |
inventory.count_item(query) | Total count of a matching item across the inventory. |
inventory.select_item(query) | If it's in your hotbar, switch to it; returns true on success. |
inventory.click_slot(slot [, button] [, action]) | Clicks a slot in the open container (like the real UI). button 0/1 = left/right; action is pickup (default), quick_move (shift-click), swap, throw, clone, or pickup_all. |
inventory.is_full() | true when the main storage (the 27 backpack slots — hotbar/armor/offhand excluded) has no free slot. |
inventory.free_slots() · inventory.used_slots() | Empty / filled slot counts in that same main storage region. |
inventory.first_empty_slot() | Full-inventory index (0–40) of the first empty slot, or -1. |
-- run a command once when the backpack fills up:
on_tick = function(m)
if player.is_present() and inventory.is_full() and not m._sent then
client.send_chat("/sellall") m._sent = true
elseif not inventory.is_full() then m._sent = false end
end
container
Read and click the slots of whatever screen is open — a chest, a shop menu,
a villager trade GUI. Slot indices are handler-relative: the container's own slots come first, then
your inventory. container.is_open() is false while only your own inventory
handler is active, so you can tell "a chest is open" from "nothing".
container.is_open() | true when a non-inventory handler (chest/shop/trade) is open. |
container.title() | The open screen's title, or nil (same as client.get_screen_title). |
container.sync_id() | The open handler's sync id, or -1. |
container.size() | Total slot count of the open handler (0 if none). |
container.get_slot(i) | Item table for handler slot i, or nil. |
container.slots() | Array of every slot's item table (index 1 = handler slot 0). |
container.find_slot(query) | First handler slot index whose item matches query, or -1. |
container.count_item(query) | Total matching count across the open handler. |
container.click(slot [, button] [, action]) | Clicks a handler slot (pickup/quick_move/swap/throw/clone/pickup_all); returns true when a handler was open. |
modules
Read and drive the client's other modules from a script — toggle KillAura, check whether ESP is on, or read another module's HUD label. Names are the module's display name.
modules.list() | Array of every module name. |
modules.is_enabled(name) | true if that module is on. |
modules.set_enabled(name, bool) | Turn a module on or off. |
modules.toggle(name) | Flip it; returns the new state. |
modules.get_info(name) | The greyed suffix a module shows in the ArrayList (e.g. its mode), or nil. |
files
Read and write files for your script's own data (configs, logs, caches). Everything
is sandboxed to the Scripts folder — a path that tries to escape it (..\,
absolute paths) is rejected. Paths are relative to that folder.
files.root() | Absolute path of the Scripts folder (for display/debugging). |
files.exists(path) | true if the file or folder exists. |
files.read(path) | File contents as a string, or nil if missing. |
files.write(path, content) | Writes (overwrites) the file, creating parent folders. Returns true on success. |
files.append(path, content) | Adds to the end of a file — good for logs. |
files.mkdirs(path) | Creates a folder (and parents). |
files.list([path]) | Array of relative paths inside a folder (defaults to the root). |
json · http · bridge · storage
Work with data: parse/build JSON, call web APIs, share state between scripts, and save state across restarts.
json — parse & build JSON
json.parse(string) | Turns a JSON string into a Lua table/value tree (objects → tables, arrays → 1-indexed tables), or nil if malformed. |
json.stringify(value) | Turns a Lua value/table back into a JSON string. |
http — call web APIs
Blocks the calling thread until the response arrives (5s connect / 10s read timeout). Fine for occasional calls; don't hammer it every tick or the game will hitch.
http.get(url [, headers]) | GET request. headers is an optional table like {["Authorization"]="…"}. Returns {status, body} or nil. Pair with json.parse(res.body). |
http.post(url, body [, headers]) | POST request with a string body. Returns {status, body} or nil. |
bridge — share data between scripts (in-memory)
A key/value store shared by every loaded script, cleared on reload/restart. Use it to let two scripts coordinate (e.g. one sets a target, another reads it).
bridge.set(key, value) · bridge.get(key) | Store and retrieve any Lua value under a string key. |
bridge.has(key) · bridge.remove(key) · bridge.clear() | Check, delete one, or wipe all. |
bridge.keys() | Array of all current keys. |
storage — persist across restarts
A string key/value store saved to Scripts/storage.json. Survives restarts. Values
are strings — wrap tables with json.stringify going in and json.parse
coming out. config.get/config.set are aliases (Raven compatibility).
storage.set(key, value) | Save a value (auto-writes the file). |
storage.get(key) | Read it back as a string, or nil. |
storage.has(key) · storage.remove(key) · storage.keys() | Check, delete, or list keys. |
util · keybinds · network
Small helpers: value/colour utilities, raw key/mouse access with key-name lookup, and other-player info from the tab list.
util — value helpers
util.color(r, g, b [, a]) | Packs a colour — identical to render.color, available outside render callbacks. |
util.color_symbol() | The Minecraft colour-code character §, e.g. util.color_symbol().."c" = red. |
util.strip(text) | Removes § colour codes from a string — clean names for comparison/display. |
util.round(number [, places]) | Rounds to places decimals (default 0). |
util.random_int(lo, hi) · util.random_double(lo, hi) | Random number helpers (same as the client.random_* pair). |
keybinds — raw input
keybinds.get_key_code(name) | Turns a key name into a GLFW keycode: "A", "SPACE", "LEFT_SHIFT", "F"… Returns -1 if unknown. Feed the result to is_key_down or input.is_key_down. (keybinds.get_key_index is a Raven-compatible alias.) |
keybinds.get_mouse_position() | Cursor position as {x, y} in GUI coords — for click-menus or drag handling. |
keybinds.is_key_down(code) · is_mouse_down(button) | Same raw checks as input. |
keybinds.left_click() · keybinds.right_click() | Simulated clicks (same as input.left_click/right_click). |
network — other players by tab name
network.get_ping(name) | That player's latency in ms, or -1 if not in the tab list. |
network.get_uuid(name) | Their UUID string. |
network.get_display_name(name) | Their formatted tab-list name. |
server · accounts · blink
Drive your session from a script: hop between servers, rotate accounts through the switcher, and hold your own packets. Together these are what an account-rotating auto-mine loop needs.
server — join / leave / switch
server.is_connected() · server.is_singleplayer() | Connection state. |
server.get_address() | The current server address, or nil. |
server.leave([reason]) | Disconnect and return to the server list. Alias: server.disconnect. |
server.join(address) | Connect to "host" or "host:port"; leaves the current server first. Returns false only if the address can't be parsed. Alias: server.connect. |
server.reconnect() | Rejoin the current/last server. |
Connecting is asynchronous — the client shows the normal connect screen. Watch
server.is_connected() (or on_chat) to know when you're actually in.
accounts — the account switcher
accounts.list() | Array of {name, uuid, cracked, active}. |
accounts.count() · accounts.current() | How many are stored; the logged-in username. |
accounts.is_busy() | true while a login/switch is in progress. |
accounts.switch(query [, cb]) | query = uuid, exact name, or 1-based index. Returns true when the request is accepted (matched, no other switch running) — login is async, so pass cb(ok), which fires on the client thread when it finishes. |
accounts.logout([cb]) | Restore the session the client launched with. |
Switching swaps your live session — do it while disconnected or expect to be kicked. The typical
flow is: finish work → server.leave() → accounts.switch(...) →
server.join(...).
-- sketch: hop to the next account and rejoin the same server
if done_mining then
local addr = server.get_address()
accounts.switch(next_account, function(ok)
if ok then server.join(addr) end -- rejoin after the session swaps
end)
end
blink — hold your own packets (fake ping / stall)
The scripting equivalent of the Blink module: holds outbound packets so the server keeps seeing you where you were. A rolling delay reads like high ping; a zero delay holds everything until you let go.
blink.start([delay_ms]) | delay_ms > 0 = rolling latency (fake ping); 0 or omitted = hold everything until flush()/stop(). |
blink.set_delay(ms) · blink.get_delay() | Change / read the rolling delay live. |
blink.flush() | Release everything held now, but keep blinking. |
blink.stop() | Release and stop holding. |
blink.is_active() · blink.size() | Whether it's holding, and the queued packet count. |
Always flushes on stop() and on script reload — held movement can't be discarded,
only delayed. Don't run this and the Blink module together; they'd fight over the same packets.
Packet payloads
The p argument given to on_packet_send /
on_packet_receive lets you inspect the raw network packet before it's sent or
processed — and return false to cancel it. It's read-only and works
by reflection, so field names come from Minecraft's obfuscation mappings: call
p:fields() first to discover what a given packet exposes.
p.name | Short class name, e.g. "PlayerMoveC2SPacket" — use this to filter for the packet you care about. |
p.class | Fully-qualified class name. |
p:fields() | Array of the packet's readable field names. |
p:get(field) | Reads a field by name — returns a number/bool/string, or its text form for complex types. |
on_packet_send = function(m, p)
if p.name == "PlayerMoveC2SPacket" then
-- inspect: client.message(table.concat(p:fields(), ", "))
-- cancel every move packet (freezes you server-side):
-- return false
end
end
Examples
The seeded scripts in your folder are the best reference. A few short ones:
Minimal HUD — coordinates
return {
name = "Coords", category = "SCRIPTS",
on_render_2d = function(m, e)
if not player.is_present() then return end
local xyz = string.format("%.1f %.1f %.1f", player.get_x(), player.get_y(), player.get_z())
render.text(xyz, 6, e.height - 20, render.color(255,255,255), true, 16)
end,
}
Player ESP tags
return {
name = "Player ESP", category = "SCRIPTS",
on_load = function(m) m:number("Max Distance", 64, 8, 256, 1) end,
on_render_2d = function(m, e)
if not player.is_present() or not world.is_present() then return end
local max, self_id = m:get("Max Distance"), player.get_id()
for _, ent in ipairs(world.get_players()) do
if ent.is_player and ent.alive and ent.id ~= self_id and ent.distance <= max then
local pt = render.world_to_screen(ent.x, ent.eye_y + 0.4, ent.z)
if pt and pt.on_screen then
local label = ent.name .. " " .. string.format("%.1f", ent.distance) .. "m"
render.text(label, pt.x - render.measure_text(label, 14) / 2, pt.y,
render.color(255,255,255), true, 14)
end
end
end
end,
}
Persist a counter across restarts
return {
name = "Launch Counter", category = "SCRIPTS",
on_enable = function(m)
local n = tonumber(storage.get("launches") or "0") + 1
storage.set("launches", tostring(n))
client.message("Enabled " .. n .. " time(s)")
end,
}
SCRIPTING.md for the plain-text version.
Module docs
Changelog
Back to home →
friends·teams·antibotDecide who not to target. Use these to filter out friends, teammates, and bots before you aim or attack.
friends.is_friend(name)trueif the name is on your friends list. Check it before targeting a player.teams.is_teammate(id)trueif that entity shares your scoreboard team.antibot.is_bot(id)trueif the client's AntiBot heuristics flag the player as fake (uses your Target Config settings).