Hackware runs a small scripting language anywhere the client accepts a template: HUD text elements, macros ($echo evaluates its argument), and modules that build text, Spammer runs every chat line through it. A template is plain text with {...} blocks in it; everything outside the braces renders as written (color codes included), and everything inside is evaluated fresh each time it's used, every frame for a HUD element, every send for a chat line:
FPS: {client.fps} | Ping: {client.ping}ms | TPS: {server.tps}The same language powers custom visibility conditions, so a HUD element can show itself only when player.health <= 10, or when you're standing in the Nether.
Templates
Writing Templates
Open the ClickGUI and switch to the HUD Editor tab. Right-click to open the New menu and add a Text element, then open its properties and edit the Text setting. The editor is syntax-highlighted and has IntelliSense: it autocompletes variable and function names as you type, and shows each function's signature and per-argument help.
Every text element re-evaluates its template each frame, so values are always live, a speed readout updates as you move, a counter counts.
Expressions
Inside {...} you can write more than a variable name. The expression grammar supports:
- Variables: dotted names like
player.health,server.tps - Arithmetic:
+,-,*,/, unary minus, and parentheses - Numbers:
2,0.5 - Strings: double-quoted, with JavaScript-style escapes (
\n,\t,\r,\\,\") - Function calls:
{round(client.ping / 2)} - Method chains:
{player.inventory.main_hand.split(" ").random()}
Expressions nest: a {variable} placeholder inside a larger block resolves first, so {round({client.ping} / 2)} and {round(client.ping / 2)} both work.
Numeric results format cleanly, whole numbers render without a decimal point, and trailing zeros are trimmed.
Arguments that aren't numbers or variables must be quoted. Item and block ids contain a :, which ends a bare name, {world.count_in_range("minecraft:chest")} works, {world.count_in_range(minecraft:chest)} does not.
Missing Values
A variable that has no value right now, player.health in the main menu, server.tps in singleplayer, an empty hand for player.inventory.main_hand, renders as ???. The template keeps re-evaluating, so the real value appears the moment it exists.
Reference
Client
| Variable | Description |
|---|---|
client.version | The client's version. |
client.commit | The client's git commit hash. |
client.fps | Your current frames per second. |
client.ping | Your latency to the server, in milliseconds. |
client.via.enabled | Whether ViaVersion is actively translating your connection. |
client.via.target_version | The server version ViaVersion is translating to (e.g. 1.19.2). |
client.via.target_protocol | The server's protocol number under ViaVersion (e.g. 760). |
Player
| Variable | Description |
|---|---|
player.speed | Your horizontal speed in blocks per second. |
player.vertical_speed | Your vertical speed in blocks per second. |
player.real_speed | Your speed in all directions in blocks per second. |
player.health | Your current health (0–20). |
player.hunger | Your current hunger (0–20). |
player.saturation | Your current saturation (0–20). |
player.absorption | Your current absorption hearts (0–20). |
player.air | Your current air supply (0–300). |
player.xp_level | Your current experience level. |
player.xp_progress | Progress to the next level (0–1). |
player.xp_total | Your total experience. |
player.gamemode | Your game mode (Survival, Creative, …). |
player.sprinting | Whether you're sprinting (true/false). |
player.sneaking | Whether you're sneaking. |
player.swimming | Whether you're swimming. |
player.flying | Whether you're flying (creative or spectator). |
player.gliding | Whether you're gliding with an elytra. |
player.in_water | Whether you're in water (or rain). |
player.in_lava | Whether you're in lava. |
player.in_liquid | Whether you're in any liquid. |
Position
| Variable | Description |
|---|---|
player.position.x * | Your X coordinate (block-floored). |
player.position.y * | Your Y coordinate (block-floored). |
player.position.z * | Your Z coordinate (block-floored). |
player.position.dimension | The dimension you're in, Overworld, Nether or The End. |
player.position.biome * | The biome you're standing in. |
player.position.opposite.x * | The opposite dimension's X coordinate. |
player.position.opposite.y * | The opposite dimension's Y coordinate. |
player.position.opposite.z * | The opposite dimension's Z coordinate. |
player.position.opposite.name | The opposite dimension's name (Nether ↔ Overworld). |
Rotation
| Variable | Description |
|---|---|
player.rotation.direction * | The compass direction you're facing (North, South East, …). |
player.rotation.axis * | The axis you're facing (X+, Z-, X+ Z+, …). |
player.rotation.pitch * | Head pitch in degrees (0 = level, 90 = down, -90 = up). |
player.rotation.yaw * | Head yaw in degrees (0 = South, 90 = West, 180 = North, -90 = East). |
* marks location-revealing values, masked as *** when the template renders censored. See Censoring.
Inventory
| Variable | Description |
|---|---|
player.inventory.main_hand | The item held in your main hand. |
player.inventory.off_hand | The item held in your off hand. |
player.inventory.helmet | The item in your helmet slot. |
player.inventory.chestplate | The item in your chestplate slot. |
player.inventory.leggings | The item in your leggings slot. |
player.inventory.boots | The item in your boots slot. |
| Function | Description |
|---|---|
player.inventory.item_count(type) | How many of an item you carry, across hotbar, inventory, off hand and armor. |
player.inventory.item_durability(item) | Remaining durability of all matching items. |
player.inventory.item_durability_unbreaking(item) | Remaining durability of all matching items, scaled by Unbreaking. |
player.inventory.item_max_durability(item) | Maximum durability of all matching items. |
player.inventory.item_max_durability_unbreaking(item) | Maximum durability, scaled by Unbreaking. |
Pearls: {player.inventory.item_count("minecraft:ender_pearl")}
Elytra: {player.inventory.item_durability("minecraft:elytra")} / {player.inventory.item_max_durability("minecraft:elytra")}Server
| Variable | Description |
|---|---|
server.tps | The server's ticks per second (0–20). |
server.mspt | The server's tick time in milliseconds (0–50). |
server.connected_at | Timestamp (epoch ms) when you connected. |
server.connected_since | Milliseconds since you connected. |
server.last_tick_at | Timestamp of the server's last tick. |
server.last_tick_duration | Duration of the server's last tick, in ms. |
server.last_tick_since | Milliseconds since the server's last tick. |
server.last_disconnect_at | Timestamp of your last disconnect. |
server.last_disconnect_since | Milliseconds since your last disconnect. |
server.last_disconnect_reason | The reason for your most recent disconnect. |
server.address | The server's IP address (may differ from the hostname behind a proxy). |
server.hostname | The hostname you connected to. |
server.port | The port you connected to. |
server.brand | The server's brand (Paper, Spigot, Velocity, …). |
server.version | The server's game version. |
server.protocol | The server's protocol number. |
server.players | The number of players currently online. |
server.queue_seconds | Estimated seconds left in queue (0 if not queued). |
Timestamps and durations are plain numbers, so they compose with functions:
Online for {format_time(server.connected_since / 1000)}World
| Function | Description |
|---|---|
world.count_in_range(block) | Count how many of a block are loaded within your render distance. |
Chests nearby: {world.count_in_range("minecraft:chest")}Scans are cached for a quarter second and skip chunk sections that can't contain the target, so rare blocks are cheap, counting something as common as stone still touches a lot of blocks, use it sparingly.
System
| Function | Description |
|---|---|
system.read_file(path) | Read a file's contents from disk. Re-read at most once a second; files over 64 KB are refused. |
Paths resolve against the hackware config folder by default, rant.txt means .minecraft/hackware/rant.txt. Two prefixes change the base: ~ is your home folder and @ is the instance folder, so @/hackware/rant.txt is the same file. Absolute paths work as-is.
Line breaks are kept, which makes read_file pair naturally with .split():
{system.read_file("quotes.txt").split("\n").random()}Functions
Top-level functions, callable without a namespace:
| Function | Description |
|---|---|
format(num, precision) | Format with thousands separators and a max number of decimals: {format(12345.678, 1)} → 12,345.7. |
round(n, precision) | Round to a number of decimal places (default 0). |
floor(n, precision) | Round down. |
ceil(n, precision) | Round up. |
format_time(seconds, max_units) | Format seconds as 1h 2m 3s; max_units caps how many units show (0 = all). |
smooth(value, window) | Smooth a jittery value over the last window samples (default 5). |
avg(value, window) | Average over the last window samples, same as smooth. |
min(value, window) | Minimum over the last window samples. |
max(value, window) | Maximum over the last window samples. |
The windowed functions keep their history per call site, so two smooth(...) calls in one template track independently:
{smooth(player.speed, 10)} b/sMethods
Methods chain onto any value, a variable, a function result, or a string literal, with .method(args):
| Method | Description |
|---|---|
.split(separator) | Split into a list, like JavaScript's split. An empty separator splits into single characters. |
.random(ticks) | Pick a random item from a list. ticks is optional, see below. |
.sequential(ticks) | Step through a list one item at a time, looping. ticks is optional. |
With a tick count, .random(40) picks a new item every 40 ticks (20 ticks = 1 second). Without one, the selection changes once per typewriter cycle instead: each time the text finishes erasing, the list moves on, so the element types out one line after another.
Selections are keyed to the call site: two .random() calls in one template, or the same template on two elements, pick independently.
The Typewriter Effect
Text elements have an Effect setting. TYPEWRITER types the text out one character at a time, holds it, erases it the same way, and starts over:
- Effect Direction:
FORWARDtypes from the start and backspaces off the end;BACKWARDtypes from the end and eats away from the front. - Effect Speed: characters typed (and erased) per second.
- Effect Delay: pause at each end of the cycle.
The template keeps re-evaluating while the animation runs, so a live counter still counts mid-type. The animation is wall-clock driven, the cadence doesn't change with your FPS. And as noted above, finishing a cycle advances any .random() / .sequential() selection in the template, which is how a rotating-messages element is built:
{system.read_file("quotes.txt").split("\n").sequential()}Conditions
Every HUD element can carry visibility conditions (in its properties window): Key Held, Module Enabled, Module Disabled, and Custom. The element renders only while all of its conditions pass. A visibility keybind can also be set to hold-to-show, hold-to-hide, or toggle.
Custom Expressions
A Custom condition is a single expression using the same variables as templates, without the braces:
player.health <= 10
player.position.dimension == "The End"
player.inventory.item_count("minecraft:totem_of_undying") < 2
server.queue_seconds > 0The comparison operators are ==, !=, >, <, >=, <=:
==and!=compare as text, quote strings with spaces ("The End").>,<,>=,<=compare numerically.- The bare keyword
null(unquoted) tests availability:server.last_disconnect_reason != null. - An expression with no operator at all is a truthy check:
player.glidingpasses when the value isn't empty,false,0, or missing.
A condition whose variable is unavailable fails, so an element gated on player.* disappears cleanly in the main menu.
Censoring
Variables marked * in the reference, coordinates, biome, facing, are location-revealing. Whenever a template is evaluated while its element's conditions are not met (the element is hidden in normal play, but still drawn while you're arranging things in the HUD editor), those values are masked as *** instead of evaluated.
The practical pattern: give your coordinates element a Key Held condition. On screen it only appears while you hold the key, and everywhere else, including the HUD editor and anything a stream captures while you fiddle with your layout, the numbers read ***.