Skip to main content

Examples

Practical, end-to-end scripts that combine the surfaces documented elsewhere. Paste any of these into the Scripts page and hit Run.

Most examples assume the bot is already connected. If you're driving a freshly-spawned bot, bracket the body with a wait-loop on client:status() == Status.IN_WORLD.


Inventory dump

List every slot with id / type / amount / name. Useful as a sanity check after a tutorial run or pack purchase.

local client = getClient()
local inv = client:inventory()

local typeNames = {
[0] = "block", [1] = "background", [2] = "seed", [3] = "water",
[4] = "wearable", [5] = "weapon", [6] = "throwable", [7] = "consumable",
[8] = "shard", [9] = "blueprint", [10] = "familiar", [11] = "food",
[12] = "wiring",
}

print(string.format("=== %s — %d slots ===", client.username, inv.slots))
print(string.format("%-5s %-12s %-8s %s", "id", "type", "amount", "name"))
print(string.rep("-", 60))

for _, item in pairs(inv.items) do
print(string.format("%-5d %-12s %-8d %s",
item.id, typeNames[item.type] or "?", item.amount, item.name or "?"))
end

Place → break (sleep-based)

Stable pattern: place a block, wait, hit it N times. Optimistic state lets client:hit see the just-placed block on the next tick, so smart_punch emits real mP+HB sequences instead of falling through to the SKIP-air branch.

local client = getClient()
local TARGET = 3316 -- RockPillar (medium-tier; drop-in any block id)
local HITS = 4

for cycle = 1, 5 do
local inv = client:inventory()
local amount = inv:count(TARGET, InventoryItemType.block)
if amount == 0 then break end

local p = client:point()
local tile = Vector2i.new(p.x - 1, p.y) -- to the LEFT
print(string.format("[c%d] inv=%d place at (%d,%d)", cycle, amount, tile.x, tile.y))

client:place(tile, TARGET, InventoryItemType.block)
sleep(250)
for h = 1, HITS do
client:hit(tile)
sleep(250)
end
end

Timing reference (4 hits, 250 ms inter-hit sleep):

MarkerTime
Hit 1 firesT = 0
Hit 4 firesT ≈ 0.75 s
Server sends DBT ≈ 1.4 s

Tune sleep(...) per block tier:

Block tier (hits_required)Hits neededRecommended sleep
Soft (~600, e.g. CaveWall, Lily)4sleep(120) ≈ 0.5 s/cycle
Medium (~1000, e.g. Soil, Brick)4–6sleep(180) ≈ 1 s/cycle
Hard (~1500, e.g. Obsidian)30+sleep(200) ≈ 6 s/cycle

Going below sleep(80) risks the server's anti-flood kicking in.


Presend hook — atomic burst

When you need place + hit packets to ride the same TCP write (e.g. duping the place-then-instant-hit micro-window), drop into a presend callback. The scheduler drains outbound_tx after the callback so any packet you queue inside lands in the current batch.

local client = getClient()
local TARGET = 1 -- SoilBlock

client:on("presend", function(batch)
local amount = client:inventory():count(TARGET, InventoryItemType.block)
if amount == 0 then return end

local p = client:point()
local tile = Vector2i.new(p.x - 1, p.y)
for i = 1, math.min(amount, 5) do
client:place(tile, TARGET, InventoryItemType.block)
for j = 1, 4 do client:hit(tile) end
end
end)

presend fires every slot tick (~250 ms / 4× per second). Anything heavy here multiplies fast. Server may disconnect on aggressive flood (the original community example shipped with a client disconnects warning). Use a counter or status-gate inside the callback if you only want it on certain ticks.


Throttled presend (every Nth tick)

Same hook, gated to fire every 10 slot ticks (~2.5 s):

local tickCount = 0
client:on("presend", function(batch)
tickCount = tickCount + 1
if tickCount % 10 ~= 0 then return end

local p = client:point()
client:send("BUp", { Bi = 33557167 }) -- punch animation
client:send("HB", { x = p.x - 1, y = p.y })
end)

Conditional injection — only when moving

Inspect the about-to-flush batch and inject extra packets only on movement ticks:

client:on("presend", function(batch)
local moving = false
for _, pkt in ipairs(batch) do
if pkt.ID == "mP" then moving = true; break end
end
if not moving then return end

local p = client:point()
client:send("HB", { x = p.x, y = p.y })
end)

Listen for destroy confirmations

Server pushes DB (Destroy Block) when a block actually breaks. Use it to trigger the next farming step instead of guessing with sleep:

local client = getClient()
local destroys = {}

client:on("p:DB", function(doc)
destroys[#destroys + 1] = {
block = doc.DBBT,
x = doc.x, y = doc.y,
ts = os.time(),
}
print(string.format("destroyed block #%d at (%d,%d)", doc.DBBT, doc.x, doc.y))
end)

-- ... rest of script

p:<ID> listeners fire for every inbound packet of that id; p (no suffix) catches all.


Tutorial gate

Skip mid-tutorial accounts before warping. The auto-driver is idempotent — calling it on a finished account returns immediately.

local c = getClient()
local a = c.automation

while a:tutorialState() == nil do task.wait(0.2) end

if a:isInTutorial() then
local err = a:tutorial()
if err then return print("tutorial failed: " .. err) end
end

c:warp("WORLD_OF_CHOICE")

Auto-collect + chat throttle

Combine setAutoCollect with a periodic chat:

local c = getClient()
c:setAutoCollect(true, 250) -- pick up drops on the bot's tile

local lines = {
"Selling rare hats DM me",
"Buy doubles 200 each",
"Need scaffolding pls",
}
local idx = 1
while true do
c:say(lines[idx])
idx = (idx % #lines) + 1
sleep(8000) -- 8 s between chats — server rate-limits faster
end

Wait for in-world, then act

Patternized startup gate — useful at the top of any script that needs the bot to be inside a world before it acts (e.g. a script run right after Add Bot, while the bot is still mid-handshake).

local c = getClient()

while c:status() ~= Status.IN_WORLD do
if c:status() == Status.ERROR or c:status() == Status.BANNED then
return print("can't proceed: " .. tostring(c:lastError()))
end
sleep(200)
end

-- Now safe to drive the bot.
print("ready: " .. c.username .. " in " .. tostring(c.world and c.world.name or "?"))

Fishing — arm + babysit

Persistent fishing: warp, walk to anchor, equip rod, cast loop. Survives reconnects via the resume watcher. See Fishing for the full reference.

local c = getClient()
local err = c.automation:armFishing({
world = "MANCINGNIH",
rod = 2410, -- FishingRodFiberglassBasic
bait = 2462, -- LureWorm
baitName = "LureWorm",
anchor = Vector2i.new(20, 30),
direction = "left",
})
if err then return print("arm failed: " .. err) end

while c.automation:isFishing() do
print("[fishing]", c.automation:fishingStatus())
sleep(2000)
end

For a fuller example with pre-flight tutorial gating + cleanup, see script_fishing_basic.lua.


See also

  • Eventsclient:on(event, ...) reference (lifecycle, packet, presend)
  • Game Client — full method surface (warp, place, hit, send, inventory, …)
  • Tutorial — auto-driver + state queries
  • Fishing — persistent fishing automation