---
title: "Examples"
description: "Four complete modules to copy: HUD text, a 2D ESP, auto totem, and a command driving a renderer."
---

> Documentation Index
> Fetch the complete documentation index at: https://aesthetic-docs.pages.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Examples

## Spammer with HUD text

Settings, tick logic and 2D drawing in one module.

```lua title="spammer.lua"
local module = ui.create("code", "Lua Example")

local interval = module:slider("Interval", 5, 200, 40, 1, "t")
local message = module:input("Message", "Hello from Lua!")
local mode = module:combo("Mode", "Client", "Chat")
local show_hud = module:switch("HUD text", true)
local color = module:color_picker("Color", 0xFF4FF2A6)

local ticks = 0

module:event("enable", function()
ticks = 0
end)

module:event("tick", function()
ticks = ticks + 1
if ticks % interval:get() == 0 then
    if mode:get() == "Chat" then
        chat.say(message:get())
    else
        print(message:get())
    end
end
end)

module:event("render_2d", function(render)
if not show_hud:get() or not player then return end
local label = message:get() .. "  |  " .. client.fps() .. " fps"
local w = render.text_metrics(label, 9).width + 16
render.rect(8, render.height() - 40, w, 22, render.paint(0x90101010), 7)
render.text(label, 16, render.height() - 34, 9, render.paint(color:get()))
end)
```

## 2D ESP

Target filtering, Box and Corner styles, health bar, names and distance. The entity list is
built once per tick and the render callback only projects and draws it, which is the split
[Performance](/performance) argues for.

<details>
<summary>Full source — <code>esp2d.lua</code>, ~130 lines</summary>

```lua title="esp2d.lua"
local module = ui.create("draw-square", "Lua ESP 2D")

local targets = module:selectable(
"Targets",
{ "Players", "Mobs", "Items", "Other" },
{ "Players" }
)
local style = module:combo("Style", "Box", "Corner")
local thickness = module:slider("Thickness", 1, 5, 1, 1)
local maxDistance = module:slider("Max distance", 16, 512, 128, 8, "m")
local color = module:color_picker("Color", 0xFF4FF2A6)
local fill = color:create():switch("Fill", false)
local overlays = module:label("Overlays"):create()
local names = overlays:switch("Names", true)
local healthBar = overlays:switch("Health bar", true)
local distanceTag = overlays:switch("Distance", false)

local tracked = {}

local function classify(e)
if e:type() == "minecraft:player" then
    return "Players"
elseif e:type() == "minecraft:item" then
    return "Items"
elseif e:health() then
    return "Mobs"
end
return "Other"
end

module:event("enable", function()
tracked = {}
end)

module:event("disable", function()
tracked = {}
end)

module:event("tick", function()
local selected = {}
for _, t in ipairs(targets:get()) do
    selected[t] = true
end
local limit = maxDistance:get()
local list = {}
for _, e in ipairs(world:entities()) do
    if not e:is_self() and e:distance() <= limit and selected[classify(e)] then
        list[#list + 1] = e
    end
end
tracked = list
end)

local function healthColor(frac)
local r = math.floor(255 * (1 - frac))
local g = math.floor(255 * frac)
return 0xFF000000 + r * 0x10000 + g * 0x100
end

local function drawCorners(render, x, y, w, h, accent, stroke)
local len = math.min(w, h) * 0.3
render.line(x, y, x + len, y, accent, stroke)
render.line(x, y, x, y + len, accent, stroke)
render.line(x + w - len, y, x + w, y, accent, stroke)
render.line(x + w, y, x + w, y + len, accent, stroke)
render.line(x, y + h - len, x, y + h, accent, stroke)
render.line(x, y + h, x + len, y + h, accent, stroke)
render.line(x + w, y + h - len, x + w, y + h, accent, stroke)
render.line(x + w - len, y + h, x + w, y + h, accent, stroke)
end

module:event("render_2d", function(render)
local count = #tracked
if count == 0 or not world then return end

local argb = color:get()
local stroke = thickness:get()
local corner = style:get() == "Corner"
local drawFill = fill:get()
local drawNames = names:get()
local drawHealth = healthBar:get()
local drawDistance = distanceTag:get()

local accent = render.paint(argb)
local fillPaint = render.paint((argb % 0x1000000) + 0x28000000)
local boxPaint = render.paint(argb):stroke(stroke, "inside")
local backPaint = render.paint(0x80000000):stroke(stroke + 2, "inside")
local barBack = render.paint(0xA0101010)

for i = 1, count do
    local e = tracked[i]
    local x, y, w, h = projection.entity_box(e:id())
    if x then
        if drawFill then
            render.rect(x, y, w, h, fillPaint)
        end
        if corner then
            drawCorners(render, x, y, w, h, accent, stroke)
        else
            render.rect(x - 1, y - 1, w + 2, h + 2, backPaint)
            render.rect(x, y, w, h, boxPaint)
        end
        local health = e:health()
        if drawHealth and health then
            local frac = health / e:max_health()
            if frac > 1 then frac = 1 end
            render.rect(x - 5, y, 2, h, barBack)
            render.rect(x - 5, y + h * (1 - frac), 2, h * frac,
                        render.paint(healthColor(frac)))
        end
        if drawNames then
            local name = e:name()
            local tw = render.text_metrics(name, 9).width
            render.text(name, x + (w - tw) / 2, y - 13, 9, accent)
        end
        if drawDistance then
            local tag = string.format("%dm", math.floor(e:distance() + 0.5))
            local tw = render.text_metrics(tag, 8).width
            render.text(tag, x + (w - tw) / 2, y + h + 3, 8)
        end
    end
end
end)
```

</details>

## Auto totem

```lua title="auto-totem.lua"
local module = ui.create("shield-heart", "Lua Auto Totem")

module:event("tick", function()
if inventory.item(45):id() ~= "minecraft:totem_of_undying" then
    local totem = inventory.find("minecraft:totem_of_undying")
    if totem then inventory.swap(totem, 45) end
end
end)
```

## Waypoints

A [client command](/commands) owns the data (subcommands, completion over the saved names,
chat replies) and the module draws it in the world. The two halves have different lifetimes:
`.wp` answers as soon as the script loads, the markers appear once the module is enabled.

```lua title="waypoints.lua"
local module = ui.create("map-pin", "Lua Waypoints")
local color = module:color_picker("Color", 0xFF4FF2A6)
local limit = module:slider("Draw distance", 32, 1024, 512, 32, "m")

local points = {}

local function saved_names()
local list = {}
for name in pairs(points) do
    list[#list + 1] = name
end
table.sort(list)
return list
end

commands.register({
name = "wp",
aliases = { "waypoint" },
description = "Marks spots you want to find again",
usage = {
    ".wp add <name>  — remember where you stand",
    ".wp del <name>  — forget one",
    ".wp list        — everything saved, with distances",
},
params = {
    { name = "subcommand", type = "add|del|list" },
    { name = "name", optional = true },
},
execute = function(ctx, args)
    local here = player and player:position():floor()
    local sub, name = args[1], args[2]

    if not here then
        ctx:error("join a world first")
    elseif sub == "add" and name then
        points[name] = here
        ctx:success(("%s saved at %d %d %d"):format(name, here.x, here.y, here.z))
    elseif sub == "del" and name then
        if not points[name] then
            ctx:error("no waypoint called " .. name)
            return
        end
        points[name] = nil
        ctx:success(name .. " removed")
    elseif sub == "list" then
        local names = saved_names()
        if #names == 0 then
            ctx:info("nothing saved yet")
            return
        end
        for _, saved in ipairs(names) do
            local away = math.floor(here:distance(points[saved]) + 0.5)
            ctx:info(("%s — %dm"):format(saved, away))
        end
    else
        ctx:error("usage: " .. ctx.prefix .. "wp <add|del|list> [name]")
    end
end,
complete = function(_, args)
    if #args == 1 then return { "add", "del", "list" } end
    if #args == 2 and args[1] == "del" then return saved_names() end
    return {}
end,
})

module:event("render_3d", function(render)
if not player then return end
local eye = player:eye_position()
local far = limit:get()
for name, pos in pairs(points) do
    if eye:distance(pos) <= far then
        render.box(pos, pos + vec3(1, 1, 1), color:get(), 1.5, true)
        render.text(name, pos + vec3(0.5, 1.5, 0.5), color:get(), 1, true)
    end
end
end)
```

The waypoints live in the script's own table, so a reload clears them. Persist them with `io`,
or declare them as a module setting.

Source: https://aesthetic-docs.pages.dev/examples/index.md
