# Wsamiaw Scripting — JavaScript API

Wsamiaw 1.8.9 embeds **Rhino 1.7.14**. Each `*.js` file in `config/Wsa/scripts/` is compiled when the client boots and again on `.script reload`. A script can listen to engine events and register real ClickGUI modules (settings, keybind, ArrayList, config).

This page is the full reference: every global, every method, what it returns when you are not in a world, and the mistakes that silently break scripts.

## Contents

1. [Language rules](#language-rules)
2. [Files, load order, reload](#files-load-order-reload)
3. [Cloud and Local](#cloud-and-local)
4. [Commands](#commands)
5. [Modules](#modules)
6. [Settings](#settings)
7. [Events](#events)
8. [client](#client)
9. [player](#player)
10. [world](#world)
11. [inventory](#inventory)
12. [util](#util)
13. [render](#render)
14. [theme](#theme)
15. [mc and Packages](#mc-and-packages)
16. [Packets](#packets)
17. [Errors](#errors)
18. [Examples](#examples)
19. [Cursor screen](#cursor-screen)
20. [Full examples](#full-examples)
21. [Security](#security)

## Language rules

Write **ES5**. Use `var` and `function`. Arrow functions, `let`, `const`, classes, and template strings are not part of the supported subset even if a particular Rhino build accepts some of them.

```js
var m = modules.register({
  name: "Example",
  onTick: function(e) {
    if (!player.inGame()) return;
    client.print("hp " + player.health());
  }
});
```

### Java values and `===`

Rhino wraps Java booleans, numbers, and strings. `===` and `!==` compare by identity and **fail** against those wrappers.

| Do | Don't |
|---|---|
| `==` / `!=` | `===` / `!==` |
| `String(e.getType())` | `e.getType() === "PRE"` |
| `m.settingNum("Range")` | `m.setting("Range")` in arithmetic |
| `m.settingBool("Fast")` | `if (m.setting("Fast") === true)` |

`setting()` returns the raw Java value. Prefer `settingNum`, `settingBool`, `settingStr`, `settingMode`.

### Null and the main menu

`mc.thePlayer` and `mc.theWorld` are `null` on the main menu and while connecting. The helpers (`player`, `world`, `inventory`, `client.inGame()`) return a safe empty value instead of throwing. Raw `mc` access does not.

| Helper when not in a world | Returns |
|---|---|
| `player.inGame()` | `false` |
| `player.x()` / `y()` / `z()` / motion / health | `0` |
| `player.name()` / `heldItem()` | `""` |
| `player.distance(...)` / `distanceTo` | `-1` |
| `world.loaded()` | `false` |
| `world.players()` | empty array |
| `world.block(x,y,z)` | `"minecraft:air"` |
| `inventory.find` / `findHotbar` | `-1` |
| `client.ping()` | `-1` |

## Files, load order, reload

```
config/Wsa/scripts/YourScript.js
```

- The folder is created on first launch.
- Only files ending in `.js` are loaded. Names are sorted alphabetically.
- The script **name** is the file name without `.js`. `client.getScriptName()` returns that.
- Top-level code runs once at load, on the client thread. Do not assume a world exists there.
- `.script reload` unloads every script, disables its modules, then loads the folder again. In-memory variables are gone. ClickGUI settings on script modules are not written to the config by the script engine itself; put defaults in the `settings` array.
- A script that throws while loading is skipped. The error is printed in chat as `[script] name load failed: ...`. Other files still load.
- A handler that throws is reported the same way. The script stays loaded and the next event still runs.

Two ways to receive events:

| Style | When it runs |
|---|---|
| `events.on("tick", fn)` | Every tick, even if you never registered a module |
| `onTick` inside `modules.register` | Only while **that module is enabled** |

Use the module form for anything the user should be able to turn off.

## Cloud and Local

In game: **Cloud** tab, **Scripts**, then the **Cloud** / **Local** toggle. Chat commands do the same job for files already on disk.

| Tab | What it does |
|---|---|
| Cloud | Browse community scripts. **Apply** downloads, saves a local copy, and runs it. **Del** removes your own upload. Cards show download counts. |
| Local | Files in `config/Wsa/scripts`. **Apply** loads that file. **Del** deletes it from disk. |

Only run scripts you wrote or have read. See [Security](#security).

## Commands

Aliases: `.scripts`, `.js`.

| Command | Action |
|---|---|
| `.script` | Prints the usage line |
| `.script list` | Each loaded script, `on` or `off`, plus the last load error |
| `.script reload` | Unload all, load every `*.js` again. Prints how many succeeded |
| `.script load name` | Load `config/Wsa/scripts/name.js`. The `.js` suffix is optional |
| `.script unload name` | Disable its modules and drop the script |
| `.script eval code...` | Run one expression in a **fresh** scope that only has `mc`. It does not see `player`, `modules`, or your script's variables |
| `.script folder` / `.script dir` | Print the absolute path to `config/Wsa/scripts` |

`eval` is for a quick probe (`mc.thePlayer.getHealth()`). It is not a way to register a module.

## Modules

```js
var m = modules.register({
  name: "HelloMod",
  category: "Misc",
  description: "Says hi in local chat",
  bind: 0,
  settings: [],
  onEnable: function() { client.print("HelloMod on"); },
  onDisable: function() { client.print("HelloMod off"); }
});
```

`modules.register(desc)` returns the module, or `null` if `name` is missing. The module shows up in ClickGUI under `category`, in ArrayList while enabled, and in the keybind list.

| Field | Type | Required | Meaning |
|---|---|---|---|
| `name` | string | yes | ClickGUI and ArrayList label. A second register with the same name replaces the old module |
| `category` | string | no | `Combat`, `Movement`, `Render`, `Player`, `Misc`, `Settings`. Anything else is kept as typed. Default `Misc` |
| `description` | string | no | Tooltip. Default empty |
| `bind` | int | no | LWJGL key code. `0` means unbound. Example: `57` is space |
| `settings` | array | no | See [Settings](#settings) |
| `onEnable` | function() | no | Module just turned on |
| `onDisable` | function() | no | Module just turned off |
| `onTick` | function(e) | no | Client tick, module enabled |
| `onUpdate` | function(e) | no | Pre/post motion update |
| `onPacket` | function(e) | no | Packet send/receive |
| `onRender2D` | function(e) | no | HUD pass |
| `onRender3D` | function(e) | no | World pass |
| `onAttack` | function(e) | no | Local player attacks an entity |
| `onKey` | function(e) | no | A key went down |
| `onStrafe` | function(e) | no | Strafe input for this tick |
| `onJump` | function(e) | no | Jump started |
| `onKnockback` | function(e) | no | Velocity from a hit |
| `onWorld` | function(e) | no | A world was loaded |
| `onClick` | function(e) | no | Left mouse |
| `onRightClick` | function(e) | no | Right mouse |
| `onPost` | function(e) | no | After the motion update |
| `onHitBlock` | function(e) | no | Block hit |
| `onMove` | function(e) | no | Movement-input hook. The event itself has no fields |
| `onSafeWalk` | function(e) | no | Sneak-at-edge |
| `onWindowClick` | function(e) | no | Inventory slot click |

### Runtime methods

| Call | Returns |
|---|---|
| `m.isEnabled()` | boolean |
| `m.setEnabled(true/false)` | toggles and runs `onEnable` / `onDisable` |
| `m.getName()` | string |
| `m.settingNum(name)` | number, or `0` if missing |
| `m.settingBool(name)` | boolean, or `false` |
| `m.settingStr(name)` | string. For a mode this is the **index**, not the label |
| `m.settingMode(name)` | mode label (`"Fast"`), or the string value for other types |
| `m.settingOptions(name)` | array of mode labels, or empty |
| `m.setSetting(name, value)` | `true` if the value was applied |
| `m.setting(name)` | raw Java value. Avoid in comparisons |

`modules.list()` returns the names of **script** modules only, not built-in modules. `modules.get(name)` returns the script module or `null`. Built-in modules go through `client.mod(name)`.

Setting names are matched loosely: case, spaces, and `-` are ignored. `"Range"`, `"range"`, and `"ran ge"` hit the same setting.

## Settings

Each entry is an object. Unknown `type` values are skipped.

| type | Fields | Stored as | Read with |
|---|---|---|---|
| `bool` or `boolean` | `value` | boolean | `settingBool` |
| `number`, `float`, `double` | `value`, `min`, `max` | float. Default min `0`, max `100` | `settingNum` |
| `int` or `integer` | `value`, `min`, `max` | int | `settingNum` |
| `mode` or `list` | `value` (index), `options` (string array) | index | `settingMode` for the label, `settingNum` for the index |
| `color` | `value` ARGB int | int | `settingNum` (may be negative; that is normal for colors above `0x7FFFFFFF`) |
| `text` or `string` | `value` | string | `settingStr` |

```js
settings: [
  {name: "Fast", type: "bool", value: true},
  {name: "Range", type: "number", value: 3.0, min: 1.0, max: 6.0},
  {name: "Count", type: "int", value: 2, min: 0, max: 10},
  {name: "Mode", type: "mode", value: 0, options: ["Slow", "Fast"]},
  {name: "Tint", type: "color", value: 0xFF7C5CFF},
  {name: "Label", type: "text", value: "hi"}
]
```

`setSetting` accepts:

| Type | Accepted value |
|---|---|
| bool | `true` / `false` or the strings `"true"` / `"false"` |
| number / int / color | number, or a numeric string |
| mode | index (`0`) **or** the label (`"Fast"`, case-insensitive) |
| text | anything, converted with `String(...)` |

`setSetting` returns `false` if the name is unknown, the value is `null`, or a mode label does not match.

Color literals above `0x7FFFFFFF` do not fit in a JS 32-bit int as a positive number. `0xFF7C5CFF` becomes negative. That is the correct ARGB value. Prefer `util.rgba(r, g, b, a)` when you build a color in code.

## Events

Global listeners:

```js
events.on("packet", function(e) { });
events.once("world", function(e) { client.print("joined"); });
events.off("tick"); // remove all tick listeners on this script file
```

The name is lowercased. `events.on("Packet", fn)` is the same as `"packet"`. You can register more than one listener; they run in registration order. Listeners survive until reload. They run whether or not a module is enabled. `once` removes the handler after the first call. `off` clears every handler for that event name on the current script.

Module handlers (the `onTick` fields) run only while that module is enabled, and they run in addition to global listeners.

### update

`e.getType()` is `PRE` then `POST` once per client tick, around the movement packet.

| Method | Meaning |
|---|---|
| `getYaw()` / `getPitch()` | Rotation at the start of the update |
| `getNewYaw()` / `getNewPitch()` | Rotation that will be sent |
| `setRotation(yaw, pitch, priority)` | Replace the outgoing rotation. Only on `PRE`. A higher `priority` wins over a lower one already applied this tick |
| `isRotated()` | Some module already set a rotation |
| `getPreYaw()` | Yaw used for the render-side previous rotation |

Compare the type with `String(e.getType()) == "PRE"`.

### tick

`e.getType()` is `PRE` / `POST`. Cancellable via `setCancelled(true)`. Use this for timers and "once per tick" work. Do not draw here.

### packet

`e.getType()` is `SEND` or `RECEIVE`.

| Method | Meaning |
|---|---|
| `getPacket()` | The vanilla packet |
| `setPacket(p)` | Replace it before it is processed |
| `isCancelled()` / `setCancelled(true)` | Drop it |

Identify a packet with `instanceof`, not with a class-name string compare. See [Packets](#packets).

### render2d / render3d

Both have `getPartialTicks()` (0–1, position between ticks).

Call `render.*` only from these events. `render2d` is screen space (scaled pixels, origin top-left). `render3d` is world space; `blockBox` / `entityBox` / `line3d` already subtract the camera.

### attack

| Method | Meaning |
|---|---|
| `getTarget()` | The entity being attacked |
| `setCancelled(true)` | Cancel the swing's attack |

### key

`getKey()` is the LWJGL key code. This fires for key presses the client already received. Module keybinds are processed separately; this does not replace `bind`.

### strafe

The movement scalars for this tick. Writing them changes how far the player moves.

| Method | Meaning |
|---|---|
| `getStrafe()` / `setStrafe(v)` | Side input |
| `getForward()` / `setForward(v)` | Forward input |
| `getFriction()` / `setFriction(v)` | Speed multiplier vanilla computed |

### jump

No fields. Fires when a jump starts.

### knockback

`getX()` / `getY()` / `getZ()` are the velocity that will be applied. `setX` / `setY` / `setZ` also mark the event cancelled, which tells the engine to use your values instead of vanilla's.

### world

No fields. A world finished loading (join or dimension change). Player data may already be available; if `player.inGame()` is still false, wait for the next tick.

### click / rightclick / hitblock

Cancellable. No extra fields. `setCancelled(true)` swallows the click or the block hit.

### post

No fields. Runs after the motion update. Read position here if you need the post-move coordinates.

### move

No fields. The movement-input hook. Read `mc.thePlayer.movementInput` if you need the raw key state.

### safewalk

| Method | Meaning |
|---|---|
| `isSafeWalk()` | Edge-sneak is currently requested |
| `setSafeWalk(true/false)` | Force it on or off for this tick |

### windowclick

Inventory click before it is sent.

| Method | Meaning |
|---|---|
| `getWindowsId()` | Open container id |
| `getSlotId()` | Slot index |
| `getMouseButtonClicked()` | 0 left, 1 right |
| `getMode()` | Click mode (pickup, quick move, …) |
| `setCancelled(true)` | Drop the click |

## client

| Call | Returns | Notes |
|---|---|---|
| `client.print(value)` | | Local chat only. Color codes with `&` work (`&c`, `&7`, `&r`). Nothing is sent to the server |
| `client.say(text)` | | Sends a chat line to the server. Other players see it |
| `client.mod(name)` | module or `null` | Built-in or script module. `isEnabled()` / `setEnabled(bool)` |
| `client.modules()` | string array | Every module name |
| `client.getScriptName()` | string | This file's name without `.js` |
| `client.fps()` | int | Debug FPS counter |
| `client.ping()` | int | Tab-list ping in ms, or `-1` |
| `client.inGame()` | boolean | Player and world both exist |
| `client.scriptsFolder()` | string | Absolute path to `config/Wsa/scripts` |
| `client.server()` | string | Server IP, `singleplayer`, or empty on main menu |
| `client.singlePlayer()` | boolean | Integrated server / LAN SP |
| `client.screen()` | string | Open GUI class simple name, or empty in-game |
| `client.tick()` | int | Local player `ticksExisted`, or `0` |

`chat("hello")` is the same as `client.say`: it **sends to the server**. Use `client.print` for logs.

```js
var hud = client.mod("HUD");
if (hud != null) hud.setEnabled(true);
```

Module name match is case-insensitive. `"HUD"`, `"killaura"`, `"KillAura"` all work if that module exists.

## player

All reads return a zero/empty value on the main menu. See the table in [Language rules](#language-rules).

| Call | Returns | Notes |
|---|---|---|
| `player.inGame()` | boolean | |
| `player.name()` | string | In-game name |
| `player.x()` `y()` `z()` | number | Feet position, this tick |
| `player.renderX(partial)` `renderY` `renderZ` | number | Interpolated feet. Pass `e.getPartialTicks()` |
| `player.motionX()` `motionY()` `motionZ()` | number | Current velocity |
| `player.yaw()` `pitch()` | number | Degrees |
| `player.health()` `maxHealth()` `absorption()` | number | Half-hearts are already converted; 20 is full health |
| `player.food()` | int | 0–20 |
| `player.armor()` | int | Armor bar value |
| `player.air()` | int | Breath, 300 is full |
| `player.onGround()` `sprinting()` `sneaking()` | boolean | |
| `player.hurtTime()` | int | Ticks of damage flash left. 0 means not recently hit |
| `player.heldSlot()` | int | Hotbar index 0–8 |
| `player.heldItem()` | string | Registry id, or `""` if the hand is empty |
| `player.setSlot(n)` | | Hotbar only. Clamped to 0–8. Does not send a held-item packet by itself |
| `player.distance(x, y, z)` | number | Blocks from your feet. `-1` if not in game |
| `player.distanceTo(name)` | number | Blocks to that player. `-1` if they are not in the world |
| `player.swing()` | | Play the swing animation |
| `player.jump()` | | Vanilla jump |
| `player.say(text)` | | Server chat. Same as `chat()` |
| `player.ticksExisted()` | int | Ticks since spawn |
| `player.inWater()` / `inLava()` | boolean | Fluid state |
| `player.fallDistance()` | number | Fall distance for no-fall logic |
| `player.setSprinting(bool)` / `setSneaking(bool)` | | Client-side flags |

`y()` is the feet, not the eyes.

## world

| Call | Returns | Notes |
|---|---|---|
| `world.loaded()` | boolean | |
| `world.time()` | number | World time (ticks) |
| `world.dimension()` | int | `0` overworld, `-1` nether, `1` end |
| `world.players()` | string array | Other players in the world. You are not included |
| `world.playerCount()` | int | Length of that list |
| `world.playersNear(range)` | string array | Other players within `range` blocks |
| `world.entity(name)` | entity or `null` | Pass this to `render.entityBox`. Case-insensitive name |
| `world.block(x, y, z)` | string | Registry id, e.g. `minecraft:stone` |
| `world.air(x, y, z)` | boolean | True for `minecraft:air` |
| `world.solid(x, y, z)` | boolean | Block has a solid material |
| `world.replaceable(x, y, z)` | boolean | Air / replaceable (scaffold-style) |

Coordinates for `block` / `air` are block coordinates (`Math.floor` of the position).

```js
var names = world.playersNear(8);
for (var i = 0; i < names.length; i++) {
  client.print(names[i] + " " + player.distanceTo(names[i]).toFixed(1));
}
```

## inventory

Slot map (vanilla):

| Slots | Contents |
|---|---|
| 0–8 | Hotbar. Slot 0 is the leftmost |
| 9–35 | Main inventory |
| 36–39 | Armor: boots, leggings, chest, helmet |

| Call | Returns | Notes |
|---|---|---|
| `inventory.item(slot)` | string | Registry id, or `""` |
| `inventory.count(slot)` | int | Stack size, or `0` |
| `inventory.size()` | int | |
| `inventory.find(id)` | int | First matching slot, or `-1` |
| `inventory.findHotbar(id)` | int | First match in 0–8, or `-1` |
| `inventory.total(id)` | int | Sum of stack sizes |

`id` matches the full registry id (`minecraft:diamond_sword`) or a suffix (`diamond_sword`). Matching is case-insensitive.

```js
var slot = inventory.findHotbar("ender_pearl");
if (slot != -1) player.setSlot(slot);
```

`setSlot` only changes the selected hotbar index. It does not move items between slots.

## util

| Call | Returns | Notes |
|---|---|---|
| `util.rgba(r, g, b, a)` | int | Channels 0–255. This is the color format every `render` call expects |
| `util.rgb(r, g, b)` | int | Alpha 255 |
| `util.clamp(v, min, max)` | number | |
| `util.lerp(a, b, t)` | number | `t` is not clamped |
| `util.wrapDegrees(deg)` | number | Result is in `[-180, 180)` |
| `util.ms()` | number | `currentTimeMillis` |
| `util.timer()` | timer | A new stopwatch, already started |
| `util.distance(x1,y1,z1,x2,y2,z2)` | number | 3D distance |
| `util.floor(v)` / `ceil(v)` | int | Math floor/ceil |
| `util.random(min,max)` | number | Uniform random in range |
| `util.randomInt(min,maxInclusive)` | int | Random integer in range |

Timer:

| Call | Meaning |
|---|---|
| `timer.elapsed()` | Milliseconds since `reset` or creation |
| `timer.hasPassed(ms)` | `elapsed() >= ms` |
| `timer.reset()` | Start again from now |

```js
var cooldown = util.timer();
events.on("tick", function() {
  if (!cooldown.hasPassed(1500)) return;
  cooldown.reset();
  client.print("1.5s");
});
```

One timer per cadence. Don't reuse the same timer for two unrelated delays.

## render

Call these from `render2d` or `render3d` only. Colors are ARGB ints from `util.rgba` or hex. A missing alpha byte is treated as opaque by the 3D helpers (`0xRRGGBB` still draws).

Screen helpers (`screenWidth`, `screenHeight`, `textWidth`) are safe to call any time; they don't draw.

### 2D

| Call | Meaning |
|---|---|
| `render.rect(x1, y1, x2, y2, color)` | Filled rectangle, **two corners**, not x/y/width/height |
| `render.rounded(x, y, w, h, radius, color)` | Filled round rect. This one is x, y, width, height |
| `render.outline(x1, y1, x2, y2, width, fill, line)` | Filled rect plus a border. Corners, like `rect` |
| `render.line(x1, y1, x2, y2, width, color)` | Screen-space line |
| `render.circle(x, y, radius, color)` | Filled circle, 32 segments |
| `render.text(s, x, y, color)` | Vanilla font, shadow |
| `render.textNoShadow(s, x, y, color)` | Vanilla font |
| `render.centerText(s, cx, y, color)` | Horizontally centered on `cx` |
| `render.textWidth(s)` | Pixel width |
| `render.pill(x, y, w, h, bg, accent, text, textColor)` | Round row, 2px accent bar on the left, text padded 7px |
| `render.image(path, x, y, w, h)` | See below |
| `render.push(x, y, scale, rotationDeg)` | Translate to `(x,y)`, rotate degrees, scale. Later draws are in that space |
| `render.pop()` | Undo one `push`. Always pair them |
| `render.screenWidth()` / `screenHeight()` | Scaled GUI size |
| `render.time()` | Seconds since epoch, fractional |
| `render.sin(v)` / `render.cos(v)` | Radians |

`rect` vs `rounded`: `rect` takes the opposite corner. `rounded` takes a size. Mixing them up draws a huge or inverted box.

`image` looks up a classpath path (`assets/wsamiaw/logo.png`) or a `domain:path` resource location (`minecraft:textures/gui/icons.png`). The bitmap is decoded once and cached for the life of the script. A missing file draws nothing. Filtering is linear.

`push` / `pop` use the GL matrix. If you `push` and throw before `pop`, later HUD draws stay transformed until the next frame resets GL. Keep the pair in the same function with no early return between them, or `pop` on every exit.

### 3D

Call from `render3d`. Coordinates are block/world coordinates. The helpers subtract the camera.

| Call | Meaning |
|---|---|
| `render.blockBox(x, y, z, argb)` | Filled 1×1×1 cube at that block |
| `render.blockOutline(x, y, z, argb, width)` | Wire cube, line width in pixels |
| `render.entityBox(entity, argb, width)` | Wire box around an entity from `world.entity(name)`. `null` draws nothing |
| `render.line3d(x1, y1, z1, x2, y2, z2, argb, width)` | World-space line |

```js
onRender3D: function(e) {
  var names = world.playersNear(32);
  var color = util.rgba(124, 92, 255, 180);
  for (var i = 0; i < names.length; i++) {
    render.entityBox(world.entity(names[i]), color, 1.5);
  }
}
```

## theme

`theme` reads and writes the **current** theme object. Color and metric writes are pushed to modules immediately. Call `theme.saveCurrent()` to write `current.json`, or `theme.save(name)` to also store a named file under `config/Wsa/themes`.

| Call | Meaning |
|---|---|
| `theme.list()` | File names, no extension |
| `theme.current()` | Active theme name |
| `theme.load(name)` | Load that file and make it current. `false` if the file is missing |
| `theme.reload()` | Re-read `current.json` from disk |
| `theme.save(name)` | Write a named copy and select it |
| `theme.saveCurrent()` | Write `current.json` only |
| `theme.getBgAlpha()` / `setBgAlpha(0..1)` | ArrayList plate alpha |
| `theme.getColor(key)` / `setColor(key, value)` | Palette. Value is an ARGB int or `"#RRGGBB"` / `"#AARRGGBB"` |
| `theme.getMetric(key)` / `setMetric(key, number)` | Layout numbers, clamped |
| `theme.getHudNumber(key)` / `setHudNumber(key, number)` | HUD floats |
| `theme.getHudFlag(key)` / `setHudFlag(key, bool)` | HUD booleans |

Color keys (aliases in parentheses):

| Key | What it paints |
|---|---|
| `bg` | Window background |
| `header` (`headerBg`) | Header bar |
| `accent` | Accent |
| `accentHover` (`accent_hover`) | Accent hover |
| `text` | Primary text |
| `muted` (`textDim`, `text_dim`) | Secondary text |
| `switchOn` / `switchOff` | Toggle |
| `arrayBg` (`array_bg`) | ArrayList plate |
| `panelBorder` | Panel border |
| `panelHighlight` | Panel highlight |
| `hoverBg` | Row hover |

Metric keys, with the clamp applied by `setMetric`:

| Key | Clamp |
|---|---|
| `radius` | 2–20 |
| `panelW` | 50–600 |
| `headerH` | 10–80 |
| `rowH` | 10–60 |
| `gap` | 0–30 |
| `fontSize` | passed through the theme's font-size normalizer |

HUD number keys: `arrayTextScale` (clamped 1–2), `arrayBgAlpha` / `bgAlpha` (0–1), `pillAlpha` (0–1).

HUD flags: `arrayAccentBar` (`accentBar`), `arrayShadow` (`shadow`).

```js
theme.setColor("accent", util.rgba(124, 92, 255, 255));
theme.setBgAlpha(0.55);
theme.saveCurrent();
```

`setColor("notAKey", ...)` does nothing. `getColor` of an unknown key returns white (`0xFFFFFFFF`).

## mc and Packages

`mc` is `Minecraft.getMinecraft()`. Common fields:

| Field | Meaning |
|---|---|
| `mc.thePlayer` | Local player, or `null` |
| `mc.theWorld` | Client world, or `null` |
| `mc.gameSettings` | Options |
| `mc.currentScreen` | Open screen, or `null` in game |
| `mc.getNetHandler()` | Network. `addToSendQueue(packet)` sends |

`Packages.fully.qualified.Class` reaches any Java class Rhino can see. Construct packets with `new Packages.net.minecraft.network.play.client.C01PacketChatMessage("hi")`.

Prefer `player` / `world` / `inventory` / `render` when a helper exists. Drop to `mc` when you need a field the helper does not wrap.

## Packets

Read and cancel on the `packet` event. Send with `mc.getNetHandler().addToSendQueue(packet)`.

```js
events.on("packet", function(e) {
  if (String(e.getType()) != "RECEIVE") return;
  var p = e.getPacket();
  var S02 = Packages.net.minecraft.network.play.server.S02PacketChat;
  if (p instanceof S02) {
    var text = p.getChatComponent().getUnformattedText();
    if (text.indexOf("Victory") != -1) client.say("gg");
  }
});
```

Sending:

```js
var C01 = Packages.net.minecraft.network.play.client.C01PacketChatMessage;
mc.getNetHandler().addToSendQueue(new C01("hello"));
```

That is a real chat message. `client.say("hello")` does the same thing and is shorter. `client.print` does not send a packet.

### Serverbound (you send)

| Class | What it is |
|---|---|
| `C01PacketChatMessage` | Chat or a command (`/spawn`) |
| `C02PacketUseEntity` | Attack or interact with an entity |
| `C03PacketPlayer` and its inner position/look classes | Movement |
| `C07PacketPlayerDigging` | Dig, drop, stop using |
| `C08PacketPlayerBlockPlacement` | Place or use item |
| `C09PacketHeldItemChange` | Hotbar slot. Prefer this over `player.setSlot` if the server must see the change |
| `C0APacketAnimation` | Swing |
| `C0BPacketEntityAction` | Sprint, sneak, horse |
| `C0DPacketCloseWindow` | Close a container |
| `C0EPacketClickWindow` | Inventory click |
| `C0FPacketConfirmTransaction` | Container transaction |
| `C10PacketCreativeInventoryAction` | Creative slot |
| `C13PacketPlayerAbilities` | Flying / creative abilities |
| `C16PacketClientStatus` | Respawn, stats request |

### Clientbound (server sends)

| Class | What it is |
|---|---|
| `S02PacketChat` | Chat. `getChatComponent().getUnformattedText()` |
| `S04PacketEntityEquipment` | Armor / held item of an entity |
| `S06PacketUpdateHealth` | Health, food, saturation |
| `S08PacketPlayerPosLook` | Teleport / rotation correction |
| `S12PacketEntityVelocity` | Knockback velocity |
| `S13PacketDestroyEntities` | Entity removed |
| `S14PacketEntity` | Relative move |
| `S18PacketEntityTeleport` | Entity teleport |
| `S23PacketBlockChange` | One block |
| `S27PacketExplosion` | Explosion and knockback |
| `S29PacketSoundEffect` | Sound |
| `S2APacketParticles` | Particles |
| `S45PacketTitle` | Title / subtitle |
| `S48PacketResourcePackSend` | Server resource pack |

Cancelling `S08` or movement packets desyncs you. Read them unless you are deliberately replacing the movement.

## Errors

Load failures and handler exceptions are printed locally:

```
[script] MyScript load failed: syntax error
[script] MyScript error: JavaException: ...
```

The handler is skipped for that call only. Fix the file and `.script reload`.

`.script list` shows the last **load** error on the script. Handler errors are not stored there; they only appear in chat at the moment they happen.

An infinite loop in `onTick` freezes the client. There is no timeout. Keep tick work to a few checks.

### Troubleshooting cheat sheet

| You see / symptom | What to do |
|---|---|
| `load failed: syntax error` | ES5 only (`var`, `function`). No `let`/`const`/arrows/template strings. |
| Module not in ClickGUI | `modules.register` needs `name`; check `.script list` for red load error. |
| `onTick` never runs | Enable the script module, or use `events.on("tick", …)` globally. |
| `=== "PRE"` always false | Use `==` and `String(e.getType()) == "PRE"`. |
| HUD draws nothing | Only call `render.*` from `render2d` / `onRender2D`; check `player.inGame()`. |
| `rect` huge or inverted | `rect` uses two corners; `rounded` uses x,y,width,height. |
| `push` without `pop` | GL matrix stays transformed — always pair in the same function. |
| `eval` cannot see my vars | Eval scope is only `mc` — use a real `.js` file. |
| Wrong script folder | Run `.script folder` — default is `config/Wsa/scripts` next to the game directory. |
| Cloud script acts weird | `.script unload name`, delete the file, reload; read code before Apply. |
| Kicked after packet script | Do not cancel `S08`/movement unless you know the server; test on alt first. |

## Examples

### Local status row

```js
var hud = modules.register({
  name: "StatusRow",
  category: "Render",
  settings: [
    {name: "X", type: "number", value: 8, min: 0, max: 1000},
    {name: "Y", type: "number", value: 8, min: 0, max: 1000}
  ],
  onRender2D: function(e) {
    if (!player.inGame()) return;
    var text = player.name()
      + "  " + player.health().toFixed(1) + "hp"
      + "  " + client.ping() + "ms"
      + "  " + world.playerCount() + " near";
    var x = hud.settingNum("X");
    var y = hud.settingNum("Y");
    var w = render.textWidth(text) + 16;
    render.pill(x, y, w, 16, util.rgba(16, 16, 22, 180), theme.getColor("accent"), text, util.rgb(255, 255, 255));
  }
});
```

### Mode setting

```js
var m = modules.register({
  name: "ModeDemo",
  category: "Misc",
  settings: [
    {name: "Mode", type: "mode", value: 0, options: ["Slow", "Fast"]}
  ],
  onTick: function() {
    if (m.settingMode("Mode") == "Fast" && player.onGround()) player.jump();
  }
});
m.setSetting("Mode", "Slow");
```

### Pearl slot

```js
events.on("key", function(e) {
  if (e.getKey() != 45) return; // K
  var slot = inventory.findHotbar("ender_pearl");
  if (slot == -1) client.print("&cno pearl");
  else player.setSlot(slot);
});
```

### Chat trigger

Global, so it runs even with no module enabled. `client.say` sends `gg` to the server once per matching chat line.

```js
events.on("packet", function(e) {
  if (String(e.getType()) != "RECEIVE") return;
  var p = e.getPacket();
  var S02 = Packages.net.minecraft.network.play.server.S02PacketChat;
  if (!(p instanceof S02)) return;
  var text = p.getChatComponent().getUnformattedText();
  if (text.indexOf("Victory") != -1) client.say("gg");
});
```

### Cooldown

```js
var clock = util.timer();
events.on("tick", function(e) {
  if (String(e.getType()) != "PRE") return;
  if (!clock.hasPassed(2000)) return;
  clock.reset();
  client.print("still alive, fps " + client.fps());
});
```

## Cursor screen

Raven opens an empty screen with `render.showGui()` so the mouse is free while the game keeps running. The same calls exist here: `render.showGui()`, `ui.show()`, and `render.closeGui()` / `ui.close()` / `client.closeScreen()`. Esc closes it and fires `guiclose`. The screen does not pause the game.

`mouse` events use scaled pixels, origin top-left, the same space as `render.rect`. Button `0` is left, `1` right, `2` middle. Button `-1` is the wheel: `pressed` is true when the wheel goes up, and `wheel` is the raw delta.

| Call | Meaning |
|---|---|
| `ui.show()` / `render.showGui()` | Free the cursor |
| `ui.close()` / `render.closeGui()` | Close that screen |
| `ui.isOpen()` | The cursor screen is up |
| `input.mouseX()` / `input.mouseY()` | Scaled cursor |
| `input.mouseDown(button)` | Button is held |
| `input.keyDown(code)` | LWJGL key is held |
| `input.key("W")` | Name to key code (`SPACE`, `LSHIFT`, …) |
| `input.hover(x, y, w, h)` | Cursor is inside that rectangle |
| `events.on("mouse", fn)` | `e.button`, `e.pressed`, `e.x`, `e.y`, `e.wheel` |
| `events.on("guikey", fn)` | Typed key while the screen is open. `e.code`, `e.character`. Backspace is code `14` |
| `events.on("guiclose", fn)` | Esc or `ui.close()` |

In-game clicks (no screen) also emit `mouse`, so a CPS counter works with the mouse captured. Drawing still belongs in `render2d`. Copy any example below into `config/Wsa/scripts/` and run `.script reload`. They are documentation, not files shipped with the client.

## Full examples
Paste one file into `config/Wsa/scripts/` and run `.script reload`.

### Billiards

Drag behind the cue ball and release. Enabling the module frees the cursor. Esc closes the table.

```js
// Billiards — drag behind the cue ball and release to shoot.
// Enable the module. Esc, or disabling it, closes the table.

var game = modules.register({
  name: "Billiards",
  category: "Render",
  description: "Drag behind the cue ball, release to shoot",
  onEnable: function() { ui.show(); rack(); },
  onDisable: function() { ui.close(); }
});

var balls = [];
var pocketed = 0;
var inHand = false;
var dragging = false;
var ox = 0, oy = 0, fw = 0, fh = 0;
var lastMs = 0;
var BR = 7;

function rack() {
  balls = [];
  pocketed = 0;
  inHand = false;
  dragging = false;
  lastMs = util.ms();
  var box = table();
  ox = box.x; oy = box.y; fw = box.w; fh = box.h;
  var colors = [
    util.rgb(240, 210, 60),
    util.rgb(40, 90, 200),
    util.rgb(190, 40, 40),
    util.rgb(120, 40, 160),
    util.rgb(230, 120, 30),
    util.rgb(20, 20, 20)
  ];
  balls.push({x: box.x + box.w * 0.28, y: box.y + box.h * 0.5, vx: 0, vy: 0, color: util.rgb(245, 245, 245), cue: true, alive: true});
  var i, row, col, n = 0;
  var startX = box.x + box.w * 0.68;
  var startY = box.y + box.h * 0.5;
  for (row = 0; row < 3; row++) {
    for (col = 0; col <= row; col++) {
      balls.push({
        x: startX + row * (BR * 2 + 1),
        y: startY + (col - row / 2) * (BR * 2 + 1),
        vx: 0, vy: 0,
        color: colors[n % colors.length],
        cue: false,
        alive: true
      });
      n++;
    }
  }
}

function table() {
  var sw = render.screenWidth();
  var sh = render.screenHeight();
  var w = Math.min(540, sw - 80);
  var h = Math.min(w * 0.56, sh - 90);
  return {x: (sw - w) / 2, y: (sh - h) / 2 + 6, w: w, h: h};
}

function fit() {
  var box = table();
  if (fw < 1) {
    ox = box.x; oy = box.y; fw = box.w; fh = box.h;
    return box;
  }
  if (Math.abs(box.w - fw) > 1 || Math.abs(box.h - fh) > 1) {
    var i;
    for (i = 0; i < balls.length; i++) {
      var b = balls[i];
      var u = (b.x - ox) / fw;
      var v = (b.y - oy) / fh;
      b.x = box.x + u * box.w;
      b.y = box.y + v * box.h;
    }
  } else {
    var dx = box.x - ox;
    var dy = box.y - oy;
    var j;
    for (j = 0; j < balls.length; j++) {
      balls[j].x += dx;
      balls[j].y += dy;
    }
  }
  ox = box.x; oy = box.y; fw = box.w; fh = box.h;
  return box;
}

function pockets(box) {
  var r = BR * 1.85;
  var x = box.x, y = box.y, w = box.w, h = box.h;
  return [
    {x: x, y: y}, {x: x + w / 2, y: y}, {x: x + w, y: y},
    {x: x, y: y + h}, {x: x + w / 2, y: y + h}, {x: x + w, y: y + h}
  ];
}

function speed(b) {
  return Math.sqrt(b.vx * b.vx + b.vy * b.vy);
}

function asleep() {
  var i;
  for (i = 0; i < balls.length; i++) {
    if (balls[i].alive && speed(balls[i]) > 14) return false;
  }
  return true;
}

function cue() {
  var i;
  for (i = 0; i < balls.length; i++) if (balls[i].cue && balls[i].alive) return balls[i];
  return null;
}

function step(dt) {
  var box = {x: ox, y: oy, w: fw, h: fh};
  var holes = pockets(box);
  var sub = 8;
  var h = dt / sub;
  var s, i, j;
  for (s = 0; s < sub; s++) {
    for (i = 0; i < balls.length; i++) {
      var b = balls[i];
      if (!b.alive) continue;
      b.x += b.vx * h;
      b.y += b.vy * h;
      var damp = Math.exp(-1.7 * h);
      b.vx *= damp;
      b.vy *= damp;
      if (speed(b) < 8) { b.vx = 0; b.vy = 0; }
      if (b.x < box.x + BR) { b.x = box.x + BR; b.vx = Math.abs(b.vx) * 0.86; }
      if (b.x > box.x + box.w - BR) { b.x = box.x + box.w - BR; b.vx = -Math.abs(b.vx) * 0.86; }
      if (b.y < box.y + BR) { b.y = box.y + BR; b.vy = Math.abs(b.vy) * 0.86; }
      if (b.y > box.y + box.h - BR) { b.y = box.y + box.h - BR; b.vy = -Math.abs(b.vy) * 0.86; }
    }
    for (i = 0; i < balls.length; i++) {
      for (j = i + 1; j < balls.length; j++) {
        var a = balls[i], c = balls[j];
        if (!a.alive || !c.alive) continue;
        var dx = c.x - a.x, dy = c.y - a.y;
        var dist = Math.sqrt(dx * dx + dy * dy);
        var min = BR * 2;
        if (dist >= min || dist < 0.001) continue;
        var nx = dx / dist, ny = dy / dist;
        var overlap = (min - dist) / 2;
        a.x -= nx * overlap; a.y -= ny * overlap;
        c.x += nx * overlap; c.y += ny * overlap;
        var rel = (a.vx - c.vx) * nx + (a.vy - c.vy) * ny;
        if (rel > 0) {
          a.vx -= rel * nx; a.vy -= rel * ny;
          c.vx += rel * nx; c.vy += rel * ny;
        }
      }
    }
    for (i = 0; i < balls.length; i++) {
      var ball = balls[i];
      if (!ball.alive) continue;
      var p;
      for (p = 0; p < holes.length; p++) {
        var hx = ball.x - holes[p].x, hy = ball.y - holes[p].y;
        if (hx * hx + hy * hy < (BR * 1.55) * (BR * 1.55)) {
          ball.alive = false;
          ball.vx = 0; ball.vy = 0;
          if (ball.cue) inHand = true;
          else pocketed++;
          break;
        }
      }
    }
  }
}

function shoot() {
  var c = cue();
  if (c == null) return;
  var dx = c.x - input.mouseX();
  var dy = c.y - input.mouseY();
  var len = Math.sqrt(dx * dx + dy * dy);
  if (len < 6) return;
  var power = Math.min(len, 150) / 150 * 780;
  c.vx = dx / len * power;
  c.vy = dy / len * power;
}

function inside(box, x, y) {
  return x > box.x + BR && y > box.y + BR && x < box.x + box.w - BR && y < box.y + box.h - BR;
}

events.on("guiclose", function() {
  if (game.isEnabled()) game.setEnabled(false);
});

events.on("mouse", function(e) {
  if (!game.isEnabled() || !ui.isOpen() || e.button != 0) return;
  var box = table();
  if (e.pressed) {
    if (input.hover(box.x + box.w - 62, box.y - 22, 62, 16)) { rack(); return; }
    if (inHand && inside(box, e.x, e.y)) {
      var c = cue();
      if (c != null) { c.alive = true; c.x = e.x; c.y = e.y; c.vx = 0; c.vy = 0; inHand = false; }
      return;
    }
    if (asleep() && cue() != null) dragging = true;
  } else if (dragging) {
    shoot();
    dragging = false;
  }
});

events.on("render2d", function() {
  if (!game.isEnabled()) return;
  var now = util.ms();
  var dt = (now - lastMs) / 1000;
  lastMs = now;
  if (dt > 0.05) dt = 0.05;
  if (dt < 0) dt = 0;
  if (!dragging && !inHand) step(dt);
  var box = fit();
  var holes = pockets(box);

  render.rounded(box.x - 16, box.y - 28, box.w + 32, box.h + 44, 16, util.rgba(18, 16, 14, 210));
  render.rounded(box.x - 8, box.y - 8, box.w + 16, box.h + 16, 12, util.rgba(92, 58, 32, 255));
  render.rounded(box.x, box.y, box.w, box.h, 8, util.rgba(18, 92, 58, 255));

  var p;
  for (p = 0; p < holes.length; p++) {
    render.circle(holes[p].x, holes[p].y, BR * 1.7, util.rgba(8, 8, 10, 255));
  }

  var i;
  for (i = 0; i < balls.length; i++) {
    var b = balls[i];
    if (!b.alive) continue;
    render.circle(b.x + 1.2, b.y + 1.6, BR, util.rgba(0, 0, 0, 70));
    render.circle(b.x, b.y, BR, b.color);
    render.circle(b.x - 2, b.y - 2, BR * 0.28, util.rgba(255, 255, 255, 90));
  }

  var c = cue();
  if (dragging && c != null) {
    var mx = input.mouseX(), my = input.mouseY();
    var dx = c.x - mx, dy = c.y - my;
    var len = Math.sqrt(dx * dx + dy * dy);
    if (len > 1) {
      var ux = (mx - c.x) / len, uy = (my - c.y) / len;
      var pull = Math.min(len, 150);
      render.line(c.x - ux * BR, c.y - uy * BR, c.x - ux * 90, c.y - uy * 90, 1.2, util.rgba(255, 255, 255, 90));
      render.line(c.x + ux * (BR + 3), c.y + uy * (BR + 3), c.x + ux * (BR + 28 + pull * 0.35), c.y + uy * (BR + 28 + pull * 0.35), 3, util.rgb(196, 150, 90));
      render.rounded(box.x, box.y + box.h + 10, box.w * (pull / 150), 4, 2, util.rgb(230, 200, 90));
    }
  }

  var label = inHand ? "click the felt to place the cue" : (asleep() ? "drag behind the cue, release to shoot" : "rolling");
  render.text(label, box.x, box.y - 20, util.rgb(235, 230, 220));
  render.rounded(box.x + box.w - 62, box.y - 22, 62, 16, 5, input.hover(box.x + box.w - 62, box.y - 22, 62, 16) ? util.rgba(255, 255, 255, 40) : util.rgba(0, 0, 0, 80));
  render.text("reset", box.x + box.w - 50, box.y - 18, util.rgb(255, 255, 255));
  render.text(pocketed + " pocketed", box.x + box.w - 130, box.y + box.h + 16, util.rgb(210, 205, 190));
});
```

### Keystrokes

WASD, space, mouse buttons, and CPS. Each key eases in instead of popping.

```js
// Keystrokes — WASD, space, mouse buttons, and left-click CPS.
// The keys ease in and out instead of popping.

var hud = modules.register({
  name: "Keystrokes",
  category: "Render",
  description: "WASD, mouse buttons, and CPS",
  settings: [
    {name: "X", type: "number", value: 12, min: 0, max: 1000},
    {name: "Y", type: "number", value: 80, min: 0, max: 1000},
    {name: "Scale", type: "number", value: 1, min: 0.6, max: 1.8}
  ]
});

var press = {W: 0, A: 0, S: 0, D: 0, SPACE: 0, LMB: 0, RMB: 0};
var clicks = [];
var lastMs = 0;

events.on("mouse", function(e) {
  if (!hud.isEnabled() || e.button != 0 || !e.pressed) return;
  clicks.push(util.ms());
});

function fade(name, down, dt) {
  var target = down ? 1 : 0;
  var speed = down ? 18 : 12;
  press[name] = press[name] + (target - press[name]) * Math.min(1, dt * speed);
}

function keyBox(x, y, w, h, label, amount) {
  var bg = util.rgba(12, 12, 16, 150 + amount * 70);
  var fg = amount > 0.45 ? util.rgb(255, 255, 255) : util.rgb(176, 176, 186);
  render.rounded(x, y, w, h, 5, bg);
  if (amount > 0.04) {
    render.rounded(x, y, 2, h, 1, util.rgba(124, 92, 255, amount));
  }
  render.centerText(label, x + w / 2, y + (h - 8) / 2, fg);
}

events.on("render2d", function() {
  if (!hud.isEnabled()) return;
  var now = util.ms();
  var dt = lastMs == 0 ? 0.016 : (now - lastMs) / 1000;
  lastMs = now;
  if (dt > 0.05) dt = 0.05;
  if (dt < 0) dt = 0;
  var next = [];
  var i;
  for (i = 0; i < clicks.length; i++) if (now - clicks[i] < 1000) next.push(clicks[i]);
  clicks = next;

  fade("W", input.keyDown(input.key("W")), dt);
  fade("A", input.keyDown(input.key("A")), dt);
  fade("S", input.keyDown(input.key("S")), dt);
  fade("D", input.keyDown(input.key("D")), dt);
  fade("SPACE", input.keyDown(input.key("SPACE")), dt);
  fade("LMB", input.mouseDown(0), dt);
  fade("RMB", input.mouseDown(1), dt);

  var s = hud.settingNum("Scale");
  var x = hud.settingNum("X");
  var y = hud.settingNum("Y");
  var u = 22 * s;
  var g = 3 * s;
  keyBox(x + u + g, y, u, u, "W", press.W);
  keyBox(x, y + u + g, u, u, "A", press.A);
  keyBox(x + u + g, y + u + g, u, u, "S", press.S);
  keyBox(x + (u + g) * 2, y + u + g, u, u, "D", press.D);
  keyBox(x, y + (u + g) * 2, u * 3 + g * 2, u * 0.7, "space", press.SPACE);
  var row = y + (u + g) * 2 + u * 0.7 + g;
  var half = (u * 3 + g * 2 - g) / 2;
  keyBox(x, row, half, u * 0.85, "LMB", press.LMB);
  keyBox(x + half + g, row, half, u * 0.85, "RMB", press.RMB);
  render.text(clicks.length + " CPS", x, row + u * 0.85 + 4, util.rgb(210, 210, 220));
});
```

### ArmorStatus

Durability bars for boots, leggings, chest, and helmet.

```js
// Armor — durability for boots, leggings, chest, and helmet.
// The bar goes from green to red as the piece wears down.

var hud = modules.register({
  name: "ArmorStatus",
  category: "Render",
  description: "Armor durability bars",
  settings: [
    {name: "X", type: "number", value: 8, min: 0, max: 1000},
    {name: "Y", type: "number", value: 180, min: 0, max: 1000}
  ]
});

var LABELS = ["Boots", "Legs", "Chest", "Helm"];
var SLOTS = [36, 37, 38, 39];

events.on("render2d", function() {
  if (!hud.isEnabled() || !player.inGame()) return;
  var x = hud.settingNum("X");
  var y = hud.settingNum("Y");
  var i;
  var shown = 0;
  for (i = 0; i < SLOTS.length; i++) {
    var max = inventory.maxDamage(SLOTS[i]);
    if (max <= 0) continue;
    var left = max - inventory.damage(SLOTS[i]);
    if (left < 0) left = 0;
    var frac = left / max;
    var row = y + shown * 16;
    var name = inventory.displayName(SLOTS[i]);
    if (name == "") name = LABELS[i];
    var color = util.rgb((1 - frac) * 220, frac * 200, 70);
    render.rounded(x, row, 108, 14, 4, util.rgba(12, 12, 16, 160));
    render.rounded(x + 2, row + 10, 104 * frac, 2, 1, color);
    render.text(name, x + 4, row + 1, util.rgb(235, 235, 240));
    render.text(Math.round(frac * 100) + "%", x + 78, row + 1, color);
    shown++;
  }
  if (shown == 0) {
    render.text("no armor", x, y, util.rgb(160, 160, 170));
  }
});
```

### Session

Session clock, facing, coordinates, ping, fps, hits taken, and nearby players.

```js
// Session — clock, facing, coordinates, ping, and how many hits you have taken.

var hud = modules.register({
  name: "Session",
  category: "Render",
  description: "Session clock, coords, facing, hits taken",
  settings: [
    {name: "X", type: "number", value: 8, min: 0, max: 1000},
    {name: "Y", type: "number", value: 28, min: 0, max: 1000}
  ]
});

var clock = util.timer();
var hits = 0;
var hurting = false;

function facing() {
  var yaw = player.yaw() % 360;
  if (yaw < 0) yaw += 360;
  var names = ["S", "SW", "W", "NW", "N", "NE", "E", "SE"];
  var index = Math.floor((yaw + 22.5) / 45) % 8;
  return names[index];
}

function clockText() {
  var sec = Math.floor(clock.elapsed() / 1000);
  var m = Math.floor(sec / 60);
  var s = sec % 60;
  var mm = m < 10 ? "0" + m : "" + m;
  var ss = s < 10 ? "0" + s : "" + s;
  return mm + ":" + ss;
}

events.on("render2d", function() {
  if (!hud.isEnabled()) return;
  var hurt = player.hurtTime() > 0;
  if (hurt && !hurting) hits++;
  hurting = hurt;

  var x = hud.settingNum("X");
  var y = hud.settingNum("Y");
  var lines = [
    "session  " + clockText(),
    player.inGame() ? (facing() + "   " + player.x().toFixed(1) + "  " + player.y().toFixed(1) + "  " + player.z().toFixed(1)) : "not in a world",
    "ping " + client.ping() + "   fps " + client.fps(),
    "hits taken  " + hits + "    nearby  " + (player.inGame() ? world.playerCount() : 0)
  ];
  var width = 0;
  var i;
  for (i = 0; i < lines.length; i++) width = Math.max(width, render.textWidth(lines[i]));
  render.rounded(x, y, width + 14, lines.length * 11 + 8, 6, util.rgba(14, 14, 18, 170));
  for (i = 0; i < lines.length; i++) {
    render.text(lines[i], x + 7, y + 5 + i * 11, i == 0 ? theme.getColor("accent") : util.rgb(230, 230, 235));
  }
});
```

### QuickMenu

Clickable module list. Type to filter, scroll to move, click a row to toggle. Esc closes it.

```js
// QuickMenu — a clickable module list. Type to filter, scroll to move, click to toggle.
// Enable the module. Esc closes it.

var menu = modules.register({
  name: "QuickMenu",
  category: "Render",
  description: "Click a module to toggle it",
  onEnable: function() { ui.show(); },
  onDisable: function() { ui.close(); }
});

var query = "";
var scroll = 0;
var ROW = 16;
var VISIBLE = 12;

events.on("guiclose", function() {
  if (menu.isEnabled()) menu.setEnabled(false);
});

function shown() {
  var names = client.modules();
  var out = [];
  var q = query.toLowerCase();
  var i;
  for (i = 0; i < names.length; i++) {
    var name = String(names[i]);
    if (q == "" || name.toLowerCase().indexOf(q) != -1) out.push(name);
  }
  return out;
}

events.on("guikey", function(e) {
  if (!menu.isEnabled()) return;
  if (e.code == 14) {
    if (query.length > 0) query = query.substring(0, query.length - 1);
    scroll = 0;
    return;
  }
  var ch = String(e.character);
  if (ch.length != 1) return;
  var code = ch.charCodeAt(0);
  if (code < 32 || code > 126) return;
  if (query.length < 20) query += ch;
  scroll = 0;
});

events.on("mouse", function(e) {
  if (!menu.isEnabled() || !ui.isOpen()) return;
  var rows = shown();
  if (e.button == -1) {
    scroll -= e.wheel > 0 ? 1 : -1;
    if (scroll < 0) scroll = 0;
    var max = rows.length - VISIBLE;
    if (max < 0) max = 0;
    if (scroll > max) scroll = max;
    return;
  }
  if (e.button != 0 || !e.pressed) return;
  var box = panel();
  var i;
  for (i = 0; i < VISIBLE && scroll + i < rows.length; i++) {
    var ry = box.y + 28 + i * ROW;
    if (e.x >= box.x + 6 && e.x <= box.x + box.w - 6 && e.y >= ry && e.y <= ry + ROW - 2) {
      var mod = client.mod(rows[scroll + i]);
      if (mod != null && rows[scroll + i] != "QuickMenu") mod.setEnabled(!mod.isEnabled());
      return;
    }
  }
});

function panel() {
  var sw = render.screenWidth();
  var sh = render.screenHeight();
  var w = 220;
  var h = 28 + VISIBLE * ROW + 8;
  return {x: (sw - w) / 2, y: (sh - h) / 2, w: w, h: h};
}

events.on("render2d", function() {
  if (!menu.isEnabled()) return;
  var box = panel();
  var rows = shown();
  var max = rows.length - VISIBLE;
  if (max < 0) max = 0;
  if (scroll > max) scroll = max;

  render.rounded(box.x, box.y, box.w, box.h, 10, util.rgba(14, 14, 18, 210));
  var title = query == "" ? "modules" : query;
  render.text(title, box.x + 10, box.y + 8, theme.getColor("accent"));
  render.text(rows.length + "", box.x + box.w - 22, box.y + 8, util.rgb(160, 160, 170));

  var i;
  for (i = 0; i < VISIBLE && scroll + i < rows.length; i++) {
    var name = rows[scroll + i];
    var ry = box.y + 28 + i * ROW;
    var mod = client.mod(name);
    var on = mod != null && mod.isEnabled();
    var hot = input.hover(box.x + 6, ry, box.w - 12, ROW - 2);
    if (hot || on) {
      render.rounded(box.x + 6, ry, box.w - 12, ROW - 2, 4, on ? util.rgba(124, 92, 255, hot ? 70 : 40) : util.rgba(255, 255, 255, 18));
    }
    render.text(name, box.x + 12, ry + 3, on ? util.rgb(255, 255, 255) : util.rgb(186, 186, 196));
  }
});
```

## Security

Scripts are not sandboxed. `Packages` can call any Java method the game can call, send packets, and toggle modules. A script from Cloud runs with the same power as one you wrote.

- Read a script before Apply.
- `chat()`, `client.say()`, and `player.say()` are visible to the server and to other players.
- `client.print()` stays on your client.
- Cancelling movement, teleport, or transaction packets can kick you or desync inventory.
- Unload a bad script with `.script unload name` or delete the file and `.script reload`.
