Hackware

Switch to dark theme

Scripting

The template language behind HUD text elements: variables, functions, arithmetic, method chains, and visibility conditions.

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.

Quote text arguments

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

VariableDescription
client.versionThe client's version.
client.commitThe client's git commit hash.
client.fpsYour current frames per second.
client.pingYour latency to the server, in milliseconds.
client.via.enabledWhether ViaVersion is actively translating your connection.
client.via.target_versionThe server version ViaVersion is translating to (e.g. 1.19.2).
client.via.target_protocolThe server's protocol number under ViaVersion (e.g. 760).

Player

VariableDescription
player.speedYour horizontal speed in blocks per second.
player.vertical_speedYour vertical speed in blocks per second.
player.real_speedYour speed in all directions in blocks per second.
player.healthYour current health (0–20).
player.hungerYour current hunger (0–20).
player.saturationYour current saturation (0–20).
player.absorptionYour current absorption hearts (0–20).
player.airYour current air supply (0–300).
player.xp_levelYour current experience level.
player.xp_progressProgress to the next level (0–1).
player.xp_totalYour total experience.
player.gamemodeYour game mode (Survival, Creative, …).
player.sprintingWhether you're sprinting (true/false).
player.sneakingWhether you're sneaking.
player.swimmingWhether you're swimming.
player.flyingWhether you're flying (creative or spectator).
player.glidingWhether you're gliding with an elytra.
player.in_waterWhether you're in water (or rain).
player.in_lavaWhether you're in lava.
player.in_liquidWhether you're in any liquid.

Position

VariableDescription
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.dimensionThe 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.nameThe opposite dimension's name (Nether ↔ Overworld).

Rotation

VariableDescription
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

VariableDescription
player.inventory.main_handThe item held in your main hand.
player.inventory.off_handThe item held in your off hand.
player.inventory.helmetThe item in your helmet slot.
player.inventory.chestplateThe item in your chestplate slot.
player.inventory.leggingsThe item in your leggings slot.
player.inventory.bootsThe item in your boots slot.
FunctionDescription
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

VariableDescription
server.tpsThe server's ticks per second (0–20).
server.msptThe server's tick time in milliseconds (0–50).
server.connected_atTimestamp (epoch ms) when you connected.
server.connected_sinceMilliseconds since you connected.
server.last_tick_atTimestamp of the server's last tick.
server.last_tick_durationDuration of the server's last tick, in ms.
server.last_tick_sinceMilliseconds since the server's last tick.
server.last_disconnect_atTimestamp of your last disconnect.
server.last_disconnect_sinceMilliseconds since your last disconnect.
server.last_disconnect_reasonThe reason for your most recent disconnect.
server.addressThe server's IP address (may differ from the hostname behind a proxy).
server.hostnameThe hostname you connected to.
server.portThe port you connected to.
server.brandThe server's brand (Paper, Spigot, Velocity, …).
server.versionThe server's game version.
server.protocolThe server's protocol number.
server.playersThe number of players currently online.
server.queue_secondsEstimated 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

FunctionDescription
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

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

FunctionDescription
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/s

Methods

Methods chain onto any value, a variable, a function result, or a string literal, with .method(args):

MethodDescription
.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: FORWARD types from the start and backspaces off the end; BACKWARD types 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 > 0

The 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.gliding passes 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 ***.