Official documentation · v0.1.0TaleOS Wiki
Build, program and automate your Hytale world with Lua computers, mobile terminals, robots, displays and shared storage.
TaleOS is a server-side Hytale mod that adds programmable Computers, Turtles, Pocket Computers, multi-block Monitors, PenDrives, USB drives, NAS units, wireless Modems and Speakers. Players only need the vanilla client.
This wiki documents the features that are actually present in TaleOS 0.1.0. It is inspired by the usefulness of computer-mod documentation, not a promise of drop-in compatibility with other mods.
New to TaleOS?Craft your first Computer, open the terminal and run a Lua program.
Crafting recipesEvery machine, peripheral and upgrade, with exact ingredients.
Automate with TurtlesMovement, mining, combat, inventories, crafting and upgrades.
Draw on MonitorsText, pixels, 16 colors and shared displays up to 8×8 blocks.
Lua API referenceFunction signatures, return values, errors and examples.
Run a secure serverHTTP rules, modem limits, languages, persistence and logs.
Server-side only. Install the JAR in Server/mods/ and restart. Assets are delivered by the server; players do not install a separate client mod.
How to use this wiki
Use the sidebar to browse by topic, or press / and search for a function such as turtle.dig, fs.write or term.redirect. Code blocks include a copy button. APIs list Lua arguments and return values from left to right.
1. Install the mod
- Download
taleos-0.1.0.jar from CurseForge.
- Copy it into the Hytale server's
Server/mods/ directory.
- Restart the server. TaleOS requires Java 25 and is built against Hytale server 0.6.2.
- Join with the vanilla client. No client installation is required.
2. Craft and place a Computer
At a Workbench → Survival, combine 4 copper bars + 4 Deadwood planks. Place the Computer and press F to open its terminal.
After booting, you should see TaleOS v0.1 (TaleOS) and the tale> prompt.
3. Try the shell
id
help
echo Hello, Hytale!
lua
In the Lua REPL, enter:
print("Hello from Lua!")
tale.getComputerID()
exit
The REPL first tries to evaluate your input as an expression; if that fails, it runs it as a statement. It prints returned non-nil values. Type exit to return to the shell.
4. Create a program
Run edit hello.lua. Enter this program, save it, and use Save and run (or save and type hello):
local id = os.getComputerID()
print("Hello from computer #" .. id)
for i = 1, 3 do
print("Count: " .. i)
os.sleep(1)
end
The editor offers save, save-and-run, exit and Tab completion. The terminal also supports completion; the running-program Stop button terminates a program without killing the shell.
5. Run automatically at boot
Create startup.lua in the computer's root directory. TaleOS executes it before the shell, both after a server restart and after reboot. Errors are printed and the shell still starts.
-- startup.lua
print("Computer #" .. os.getComputerID() .. " is ready")
Next step: place a Monitor next to the Computer and run reloj, or craft a Turtle and try go forward 3.
| Item | Ingredients | Crafting time | Purpose |
TaleOS_Computer | 4 copper bars + 4 Deadwood planks | 5 s | Fixed programmable Lua computer. |
TaleOS_Turtle | 6 copper bars + 2 Deadwood planks | 5 s | Mobile robot with inventory and two upgrade slots. |
TaleOS_Monitor | 2 copper bars + 2 Deadwood planks | 3 s | Colored text and pixel display; connects into rectangles. |
TaleOS_PocketComputer | 1 TaleOS Computer + 2 copper bars | 5 s | Portable terminal with persistent identity and files. |
TaleOS_PenDrive | 2 copper bars + 1 Deadwood plank | 3 s | Portable 1 MiB storage medium. |
TaleOS_USB_Drive | 4 copper bars + 1 Deadwood plank | 4 s | Single-PenDrive reader for adjacent machines. |
TaleOS_NAS | 6 copper bars + 2 Deadwood planks | 6 s | Eight-PenDrive network storage unit. |
TaleOS_Modem | 4 copper bars + 1 Deadwood plank | 3 s | Wireless rednet/GPS peripheral or Turtle upgrade. |
TaleOS_Speaker | 2 copper bars + 2 Deadwood planks | 3 s | Positional SoundEvent playback or Turtle upgrade. |
TaleOS_Crafting_Upgrade | 2 copper bars + 2 Deadwood planks | 3 s | Enables Turtle crafting. |
TaleOS does not add special tools. Use ordinary Hytale pickaxes, shovels, weapons and other registered tools in Turtle upgrade slots. A tool appropriate for the target block is required for digging. Tools and weapons do not lose durability when used by a Turtle.
Stacking and identity
Computers, Turtles, Pocket Computers and PenDrives have persistent identity and do not stack. Monitors, USB drives, NAS units, Modems and Speakers can stack up to 25 before identity is attached. Breaking a Computer or Turtle creates an item labeled with its ID; placing it again preserves its files and state.
Using the terminal
Press F on a Computer or Turtle. Type at the prompt and press Enter. The output area keeps a scrollable transcript. Completion is available with Tab in the terminal and editor. Use Stop to terminate the currently running program; the shell itself remains available.
The native Lua terminal is a 51×19 character grid. It supports cursor positioning, scrolling, independent foreground/background colors per cell, blit, clipped windows and redirection to Monitors.
Persistent files
Each machine has a jailed filesystem with a 1 MiB quota. Computer files live in mods/TaleOS/computers/<id>/. The read-only rom/ mount contains TaleOS and built-in programs. PenDrives have independent 1 MiB quotas.
Shell paths are relative to the current directory. Absolute paths start with /. Both . and .. are normalized, and attempts to escape the computer's filesystem are rejected.
Identity when broken
Breaking a Computer or Turtle produces an item labeled Computer #ID or Turtle #ID. Its ID, files, inventory and equipped upgrades are retained when placed again. These items do not stack, preventing two placed machines from sharing one live disk.
Lua sandbox and budgets
TaleOS uses LuaJ with a per-tick instruction budget (200,000 bytecodes by default). Infinite loops, including loops hidden in pcall, xpcall or child coroutines, are killed without stopping the server.
Scripts cannot access Java, host files, host I/O or native process execution. luajava, io, debug, package, require, dofile and loadfile are unavailable. The standard os table is replaced by a safe TaleOS version.
Loaded chunks
World reads and Turtle actions require loaded chunks. With no players nearby, a chunk may unload and block reads return no block. Server owners can use vanilla keep-loaded settings for automation areas; see Administration.
Arguments separated by spaces are passed to programs as strings. Your own name.lua in the current directory can be run with name. Built-in ROM programs take precedence when names collide.
| Command | Description |
help | Show command and API help. |
id | Print the persistent computer ID. |
ls [directory] | List a directory; defaults to the current directory. Directory names end in /. |
cd [directory] | Change directory. With no argument, go to /. Supports .. and absolute paths. A numeric ID is a shortcut for nas/<id> when applicable. |
cat <file> | Print a UTF-8 file. |
mkdir <directory> | Create a directory, including required parents. |
rm <path> | Delete a file or directory. ROM mounts cannot be changed. |
echo [text…] | Print its arguments. |
clear | Clear the terminal. |
lua | Open the Lua mini-REPL; use exit to leave. |
edit <file> | Open the visual editor. Save, save-and-run or exit. |
wget <url> [file] | Download a text resource over HTTP. If omitted, the filename comes from the URL. Never executes the download automatically. |
reloj | Display a centered HH:MM:SS clock on an adjacent Monitor. Stop it with the Stop button. |
gps [locate] | Locate this machine using at least four GPS references. |
gps host <x> <y> <z> | Serve a GPS reference at fixed coordinates until stopped. |
reboot | Restart TaleOS and run startup.lua again. |
shutdown | Shut down this machine. |
Turtle-only commands
| Command | Description |
go <direction> [count] | Move or turn. Directions: forward, back, up, down, left, right. Aliases: fd, bk, dn, lt, rt, forwards. Count defaults to 1. |
turn <left|right> [count] | Turn in place without moving. |
tunnel [length] | Dig a 3×3 cross-section for the given number of sections; defaults to 1. Uses the appropriate equipped tool. |
excavate <length> | Dig and move through a 1×3 face for a positive integer length. |
equip <left|right> | Swap the selected inventory item into an upgrade slot. |
unequip <left|right> | Move an upgrade into the selected empty inventory slot. |
Examples
mkdir programs
cd programs
edit counter.lua
counter
cd ..
ls /
cat /startup.lua
Output and arguments
print(...) converts values to text, separates them with tabs and writes a newline. A program invoked as demo one 2 receives "one" and "2" through ....
local name, countText = ...
countText = tonumber(countText) or 1
print("Hello, " .. (name or "world"))
for i = 1, countText do
print("Iteration " .. i)
end
Errors and action results
Filesystem errors raise Lua errors and can be caught with pcall. Turtle actions usually return true or false, reason. Peripheral method errors may also raise Lua errors; handle them when hardware may be absent.
local ok, reason = turtle and turtle.forward()
if turtle and not ok then
print("Blocked: " .. tostring(reason))
end
local wrote, err = pcall(fs.write, "status.txt", "ready")
if not wrote then print(err) end
Waiting for events
Use os.pullEvent() to wait for any event or os.pullEvent("timer") to accept only one kind. os.pullEventRaw does not filter. See the event reference for exact fields.
local timer = os.startTimer(2)
while true do
local event, id = os.pullEvent()
if event == "timer" and id == timer then
print("Two seconds elapsed")
break
end
end
Two tasks at once
parallel.waitForAny returns when one function finishes; waitForAll waits for every function. Tasks cooperate through events and yields rather than preemptive threads.
local function ticker()
while true do
os.sleep(1)
print("tick")
end
end
local function listener()
local event, side = os.pullEvent("peripheral")
print("Attached on " .. side)
end
parallel.waitForAny(ticker, listener)
Startup and persistence
Save durable state with fs.write or fs.append; do not rely only on Lua variables. Put unattended behavior in startup.lua. A startup program that loops forever still receives events and can be stopped from the terminal.
Paths are strings. The shell-wrapped API resolves relative paths against the current directory; / selects the root. Missing files generally return nil on reads, while invalid writes, missing directories, read-only mounts and quota errors raise Lua errors prefixed with fs:.
| Function | Returns | Behavior |
fs.exists(path) | boolean | Whether a file or directory exists. |
fs.isDir(path) | boolean | Whether the path exists and is a directory. |
fs.list(path) | table | A numerically indexed array of child names. Names of directories include a trailing /. |
fs.read(path) | string | nil | Read the entire UTF-8 file, or nil if it does not exist. |
fs.write(path, contents) | true | Create or replace a file. Both arguments are strings. Parent directories must exist. |
fs.append(path, contents) | true | Append text to a file, creating it if necessary. |
fs.mkdir(path) | true | Create a directory and required parents. |
fs.delete(path) | true | Delete a file or directory. ROM and active mount protections apply. |
fs.getSize(path) | number | File size in bytes; directories report 0. |
fs.getCapacity([path]) | number | Total quota in bytes for the current filesystem or the filesystem containing path. |
fs.getFreeSpace([path]) | number | Remaining bytes in that filesystem's quota. |
Example: append a log
fs.mkdir("logs")
fs.append("logs/activity.txt", os.date() .. " started\n")
if fs.exists("logs/activity.txt") then
print(fs.read("logs/activity.txt"))
print(fs.getSize("logs/activity.txt") .. " bytes")
end
print(fs.getFreeSpace("logs") .. " bytes free")
Mounted media
USB media appear as usb, usb2, etc. NAS media appear as nas/<PenDriveID>. Use fs.getCapacity("nas/22") or fs.getFreeSpace("usb") to query a specific medium. Removing a PenDrive removes its mount and subsequent accesses fail rather than reading stale data.
Lua APIos · System & events
Identity, timers, sleep, events and computer lifecycle.
| Function | Returns | Behavior |
os.getComputerID() | number | The persistent numeric ID of this machine. |
os.startTimer(seconds) | number | Start a timer and return its ID. A timer event fires when it expires. |
os.cancelTimer(id) | nil | Cancel a pending timer. Unknown or completed IDs are ignored. |
os.sleep([seconds]) | nil | Wait using a timer; omitted duration means 0. Other events are consumed while waiting for this timer. |
os.queueEvent(name, ...) | boolean | Queue a custom event. Returns false when the bounded 256-event queue is full. |
os.pullEvent([filter]) | event fields… | Wait for the next event, skipping events that do not match the optional name. |
os.pullEventRaw() | event fields… | Wait for the next event without filtering. Used internally by parallel, rednet and GPS. |
os.reboot() | does not return | Restart the machine and run startup again. |
os.shutdown() | does not return | Shut down the machine. |
os.clock() | number | Safe LuaJ clock value, intended for measuring elapsed execution time. |
os.time([table]) | number | Lua time function based on real/server time, not Hytale's day cycle. |
os.date([format[, time]]) | string | table | Format real/server time. os.date("*t") returns fields such as hour, min and sec. |
os.openEditor(path) | true | Request the visual editor for the player using the terminal. Normally invoked with the edit program. |
Two kinds of sleep: os.sleep(seconds) uses real timers. tale.sleep(ticks) pauses for server ticks (20 ticks ≈ one second at normal tick rate).
One-shot and repeating timers
local id = os.startTimer(5)
print("Timer #" .. id)
local event, fired = os.pullEvent("timer")
if fired == id then print("Five seconds elapsed") end
os.queueEvent("my_event", 42, { ready = true })
local name, value, data = os.pullEvent("my_event")
print(name, value, data.ready)
The first return value is always the event name. Subsequent values depend on the event. Events are delivered in FIFO order and the queue is bounded to 256 entries.
| Event | Additional fields | When it fires |
stdin | line | A player or the admin command submits a line to the shell. Primarily used internally by TaleOS. |
timer | timerID | A timer from os.startTimer expires. |
peripheral | name | A peripheral becomes available on a side or alias. |
peripheral_detach | name | A peripheral is removed or becomes unavailable. |
monitor_resize | name | A connected Monitor structure changes size or is split. |
modem_message | side, channel, replyChannel, payload, distance | An open channel receives a wireless message on a loaded receiver in the same world. |
usb | side | A PenDrive is mounted. Side identifies the USB drive, or nas for NAS-backed media. |
usb_eject | side | A mounted PenDrive is ejected or its mount disappears. |
| custom name | custom values… | Your program calls os.queueEvent(name, ...). Strings, numbers, booleans, tables and nil are supported. |
Reading a modem message
local event, side, channel, replyTo, payload, distance =
os.pullEvent("modem_message")
print("Received on " .. side .. ", channel " .. channel)
print("Reply channel: " .. replyTo)
print("Distance: " .. distance)
Not currently generated: mouse, keyboard, monitor-touch, redstone, disk/floppy, websocket, alarm and multishell events are not part of TaleOS 0.1.0.
Coordinates are one-based: 1,1 is the upper-left cell. Terminal color arguments accept either a color name or a single power-of-two value from colors. print writes to the current redirect target.
| Function | Returns | Behavior |
term.write(value) | nil | Write text at the cursor and advance it, wrapping and scrolling as needed. |
term.blit(text, fg, bg) | nil | Write equal-length strings of text and hexadecimal foreground/background color digits. |
term.clear() | nil | Fill the terminal with spaces using current colors and move the cursor to 1,1. |
term.clearLine() | nil | Clear the current row and move the cursor to its first column. |
term.scroll(lines) | nil | Scroll by an integer amount; positive values move content upward. |
term.getSize() | width, height | Dimensions in character cells. The native terminal is 51×19. |
term.setCursorPos(x, y) | nil | Set the one-based cursor position. |
term.getCursorPos() | x, y | Read the current position. |
term.setCursorBlink(boolean) | nil | Enable or disable cursor blinking. |
term.getCursorBlink() | boolean | Read the cursor blink state. |
term.setTextColor(color) | nil | Set foreground for subsequent writes. |
term.getTextColor() | number | Current foreground as a power-of-two color value. |
term.setBackgroundColor(color) | nil | Set background for subsequent writes. |
term.getBackgroundColor() | number | Current background as a power-of-two color value. |
term.isColor() | boolean | Whether this terminal supports color. |
term.native() | table | The original native terminal object. |
term.current() | table | The current redirect target. |
term.redirect(target) | previous target | Send terminal methods and print output to a terminal, window or Monitor. Restore with the returned object. |
Using blit
Each character in the color strings is a hexadecimal digit representing a palette index: 0 is black, f is white, 5 is lime. All three strings must have the same length.
term.clear()
term.setCursorPos(1, 1)
term.blit("TaleOS", "555555", "000000")
term.setCursorPos(1, 3)
term.setTextColor(colors.lime)
term.setBackgroundColor(colors.black)
term.write("Colored terminal text")
Always restore redirects
local previous = term.redirect(term.native())
-- Work with the selected target here.
term.redirect(previous)
colors and colours are the same table. Each named value is a power of two. Terminal functions accept these values; Monitor pixel functions use the equivalent names.
| Color | Value | Hex | Blit digit |
colors.black | 1 | #14161c | 0 |
colors.navy | 2 | #2a3a6a | 1 |
colors.blue | 4 | #3a5fce | 2 |
colors.cyan | 8 | #3cb5b5 | 3 |
colors.green | 16 | #3f9e4d | 4 |
colors.lime | 32 | #7cc050 | 5 |
colors.yellow | 64 | #e0c040 | 6 |
colors.orange | 128 | #d8832c | 7 |
colors.red | 256 | #cc4040 | 8 |
colors.pink | 512 | #e08fb0 | 9 |
colors.magenta | 1024 | #b84fb8 | a |
colors.purple | 2048 | #7a4fc0 | b |
colors.brown | 4096 | #7a5a38 | c |
colors.gray | 8192 | #707880 | d |
colors.silver | 16384 | #b8c0c8 | e |
colors.white | 32768 | #e8e8e8 | f |
A window has its own cursor, colors, visibility and dimensions. Drawing is clipped to the window and does not move or scroll its parent's cursor.
| Function | Returns | Behavior |
window.create(parent, x, y, width, height[, visible]) | window | Create a one-based child region. Width and height must be positive; visibility defaults to true. |
win.setVisible(boolean) | nil | Show or hide the window without destroying its cursor or settings. |
win.isVisible() | boolean | Read visibility. |
win.reposition(x, y[, width[, height]]) | nil | Move and optionally resize the window, resetting its cursor to 1,1. |
Windows also implement the terminal methods: write, blit, clear, clearLine, scroll, getSize, setCursorPos, getCursorPos, setCursorBlink, getCursorBlink, setTextColor, getTextColor, setBackgroundColor, getBackgroundColor and isColor.
Two independent panels
term.clear()
local left = window.create(term.current(), 1, 1, 24, 8, true)
local right = window.create(term.current(), 27, 1, 24, 8, true)
left.setTextColor(colors.lime)
left.write("Status: online")
right.setTextColor(colors.yellow)
right.write("Storage: 80%")
local previous = term.redirect(left)
print("This appears in the left panel")
term.redirect(previous)
| Function | Returns | Behavior |
parallel.waitForAny(...) | number | Run all supplied functions and return the one-based index of the first that finishes. Other tasks stop being resumed. |
parallel.waitForAll(...) | nil | Run all supplied functions until every one has finished. |
Functions execute cooperatively as coroutines. Long-running tasks should wait for events, timers or world calls. An error in one task is propagated to the caller.
local function listen()
while true do
local event, side = os.pullEvent("peripheral")
print("Peripheral attached: " .. side)
end
end
local function heartbeat()
while true do
print("alive")
os.sleep(5)
end
end
parallel.waitForAll(listen, heartbeat)
| Function | Returns | Behavior |
shell.dir() | string | Current directory relative to root; the root is an empty string. |
shell.resolve(path) | string | Resolve relative or absolute paths and normalize . and ... |
shell.setDir(path) | nil | Set the current directory after resolving it. This does not itself verify that the directory exists. |
print("Current directory: /" .. shell.dir())
print(shell.resolve("../shared/data.txt"))
print(shell.resolve("/nas/22/program.lua"))
Relative coordinates are offsets from the machine: 0,-1,0 is the block immediately below. World calls run safely on the server tick thread and are limited to 32 world calls per machine per tick.
| Function | Returns | Behavior |
tale.getBlock([dx[, dy[, dz]]]) | string | nil | Hytale block ID at the relative position. Defaults to 0,-1,0. Nil means air or an unloaded/inaccessible chunk. |
tale.detect([dx[, dy[, dz]]]) | boolean | True when a block ID is present at the relative position. |
tale.chat(message) | true | Broadcast a message to the machine's world chat. |
tale.time() | number | Real epoch seconds. This is not the Hytale world-day time. |
tale.sleep([ticks]) | nil | Park the machine for at least one server tick; defaults to 1. Use 20 for roughly one second. |
tale.monitor([dx, dy, dz]) | Monitor | nil | Get a Monitor object. With no offset, search the six neighboring faces in right, left, top, bottom, front, back order. |
tale.getComputerID() | number | Low-level equivalent of os.getComputerID. |
tale.setPrompt(text) | nil | Change the prompt displayed next to the terminal input. |
tale.language() | string | Configured UI language: en or es. |
tale.tr(text) | string | Translate a known built-in string; unknown text is returned unchanged. |
tale.pullEvent([filter]) | event fields… | Low-level event wait behind the safer os.pullEvent functions. |
tale.reboot() / tale.shutdown() | does not return | Low-level lifecycle functions behind the os equivalents. |
Inspect the ground
local id = tale.getBlock(0, -1, 0)
if id then
print("Block below: " .. id)
else
print("Air or unloaded chunk")
end
if tale.detect(1, 0, 0) then
tale.chat("There is a block beside computer #" .. os.getComputerID())
end
The API exists only when HTTP is enabled in mods/TaleOS/config.json. TaleOS 0.1.0 supports text GET requests, not POST, WebSockets, proxies or Git cloning.
| Function | Returns | Behavior |
http.get(url) | response | nil, error | Perform a GET request, following validated redirects. On success returns a response object; on failure returns nil and a reason. |
response.readAll() | string | Read the complete response body. |
response.getResponseCode() | number, string | HTTP status code and reason phrase. |
response.close() | nil | Close the response. Call after reading it. |
Download and save a Lua file
local response, err = http.get(
"https://raw.githubusercontent.com/user/repo/main/program.lua")
if not response then error(err) end
local code, reason = response.getResponseCode()
if code < 200 or code >= 300 then
response.close()
error("HTTP " .. code .. " " .. reason)
end
local body = response.readAll()
response.close()
fs.write("program.lua", body)
print("Saved " .. #body .. " bytes")
Or use the shell: wget https://raw.githubusercontent.com/user/repo/main/program.lua program.lua, then run program.
Security: private, loopback, LAN, link-local and reserved addresses are denied by default. Timeouts, response sizes, redirects, per-computer concurrency and global bandwidth are limited. See
Configuration.
Place a Monitor beside a Computer or Turtle. Monitors are output devices: they do not open an interface with F. Use tale.monitor(), peripheral.wrap("side") or peripheral.find("monitor") from Lua.
Dimensions and multi-block displays
Each Monitor provides 40×24 pixels and a 10×4 character terminal. Compatible Monitors with the same facing join into rectangles up to 8×8 blocks: 320×192 pixels and 80×32 terminal cells. Every block renders its own crop of the shared canvas.
Resizing preserves compatible content. Splitting a structure creates independent canvases and queues monitor_resize for affected attachments.
Getting a Monitor
local mon = tale.monitor() -- search all six neighbors
if not mon then error("No adjacent Monitor") end
-- Or by peripheral side:
local bySide = peripheral.wrap("right")
-- Or all attached Monitors:
local first, second = peripheral.find("monitor")
Pixel methods
Pixel coordinates are one-based. Pixel colors are palette names such as "black", "lime" or "white". The built-in microfont uses 3×5 glyphs.
| Function | Returns | Behavior |
mon.getSize() | width, height | Size in pixels, including all connected blocks. |
mon.getTerminalSize() | width, height | Size in 4×6-pixel character cells. |
mon.setPixel(x, y, colorName) | nil | Set one pixel. |
mon.getPixel(x, y) | string | Read a pixel's palette name. |
mon.fill(x, y, width, height, colorName) | nil | Fill a rectangle whose upper-left corner is x,y. |
mon.clear([colorName]) | nil | With a color, clear the pixel canvas. Without arguments, clear the character terminal. |
mon.write(x, y, text[, colorName]) | nil | Draw microfont text at pixel coordinates; default color is white. |
Terminal methods
Monitors also support write(text), blit, clearLine, scroll, setCursorPos, getCursorPos, setCursorBlink, getCursorBlink, setTextColor, getTextColor, setBackgroundColor, getBackgroundColor and isColor. Their cursor and color operations use character cells, not pixels.
local mon = tale.monitor()
mon.clear("navy")
mon.fill(5, 5, 20, 3, "blue")
mon.write(5, 10, "TALEOS", "lime")
-- Use it as a normal terminal:
local previous = term.redirect(mon)
term.setTextColor(colors.white)
print("Hello on the Monitor")
term.redirect(previous)
Built-in clock
Place a Monitor adjacent to a Computer and run reloj. The program clears the canvas once per second, redraws all eight HH:MM:SS characters and sleeps for 20 ticks. Stop it from the terminal.
Hardware guideTurtles
Mobile Lua computers that move, mine, fight, build and manage items.
Inventory and upgrades
Press F on a Turtle to combine its terminal, 4×4 inventory, your inventory and two upgrade slots. Lua slots are numbered 1–16, left-to-right and top-to-bottom. The selected slot is highlighted and defaults to slot 1.
The left and right upgrade slots accept ordinary tools and weapons, TaleOS_Modem, TaleOS_Speaker and TaleOS_Crafting_Upgrade. Equipped hardware appears on the 3D chassis and tools swing when used. Tools and weapons do not consume durability.
Equipping from Lua or the shell
-- Select a slot containing a pickaxe, then:
equip left
-- Select an empty slot to retrieve it:
unequip left
turtle.select(1)
local ok, reason = turtle.equipLeft()
if not ok then print(reason) end
turtle.select(2)
turtle.equipRight()
Action results
Most physical actions return true on success or false, reason on failure. Always check the result in unattended programs. Movement and turning are animated, and a Turtle retains its running program, ID, files, inventory and upgrades while moving.
for i = 1, 10 do
while turtle.detect() do
local ok, reason = turtle.dig("right")
if not ok then error(reason) end
tale.sleep(2)
end
local moved, reason = turtle.forward()
if not moved then error(reason) end
end
Mining and combat
turtle.dig requires a suitable tool with sufficient gathering quality. Without a side argument it tries equipped tool slots; pass "left" or "right" to choose explicitly. Drops enter the Turtle inventory when possible.
turtle.attack uses an equipped weapon against a damageable target in the selected direction. It returns false when there is no valid target or a solid block obstructs the attack.
Crafting
Equip a Crafting Upgrade and arrange ingredients in the 16 inventory slots. turtle.craft([limit]) searches supported ordinary tier-0 Crafting recipes and creates up to limit crafts (default 64). Input and output stacks, including relevant metadata, are planned before changes are committed.
Useful built-ins
go forward 10 — move ten blocks.
turn right 2 — turn around.
tunnel 5 — dig five sections of a 3×3 tunnel.
excavate 8 — dig eight sections of a 1×3 face.
No fuel management needed. turtle.getFuelLevel() and turtle.getFuelLimit() return "unlimited"; refuel is unnecessary and reports that this Turtle does not use fuel.
This table exists only on Turtles. Unless noted otherwise, action functions return true or false, reason. Direction suffixes Up and Down select those faces; unsuffixed world actions use the front.
Movement and turning
| Function | Returns | Behavior |
turtle.forward() / back() | true | false, reason | Move one block horizontally in the corresponding direction. |
turtle.up() / down() | true | false, reason | Move one block vertically. |
turtle.turnLeft() / turnRight() | true | false, reason | Rotate 90 degrees without changing position. |
Detection, inspection and comparison
| Function | Returns | Behavior |
turtle.detect[Up/Down]() | boolean | Whether a block is present. |
turtle.inspect[Up/Down]() | true, detail | false, reason | Inspect a block. Detail currently contains name, the Hytale block ID. |
turtle.compare[Up/Down]() | boolean | Compare the target block's ID with the block key of the selected item. |
turtle.compareTo(slot) | boolean | false, reason | Compare item IDs in the selected slot and another slot. Two empty slots compare equal. |
local ok, info = turtle.inspectDown()
if ok then print("Standing on " .. info.name) end
if turtle.compareDown() then print("Selected item matches the ground") end
Inventory
| Function | Returns | Behavior |
turtle.select(slot) | true | false, reason | Select a slot from 1 to 16. |
turtle.getSelectedSlot() | number | Current one-based selected slot. |
turtle.getItemCount([slot]) | number | Items in the given or selected slot; empty/invalid slots report 0. |
turtle.getItemSpace([slot]) | number | Remaining stack capacity; an empty valid slot reports 64. |
turtle.getItemDetail([slot]) | table | nil | Fields: name, count, maxCount, and when applicable durability, maxDurability. |
turtle.transferTo(slot[, count]) | true | false, reason | Move items from the selected slot to another slot. Count defaults to the whole stack. |
Upgrades and fuel
| Function | Returns | Behavior |
turtle.equipLeft() / equipRight() | true | false, reason | Swap the selected item with the corresponding upgrade slot. |
turtle.equip() | true | false, reason | Alias for equipRight. |
turtle.unequip() | true | false, reason | Move the right upgrade into the selected empty slot. |
turtle.getEquippedLeft() / getEquippedRight() | table | nil | Equipped item name/count and durability fields when applicable. |
turtle.getFuelLevel() / getFuelLimit() | "unlimited" | Turtles do not require fuel. |
turtle.refuel() | false, reason | Reports that refueling is unnecessary. |
Digging, attacking, crafting and placing
| Function | Returns | Behavior |
turtle.dig[Up/Down]([side]) | true | false, reason | Break a block with an appropriate tool. Side is optional "left" or "right". |
turtle.attack[Up/Down]([side]) | true | false, reason | Attack a damageable entity with an equipped weapon. |
turtle.craft([limit]) | true | false, reason | Craft using inventory ingredients and a Crafting Upgrade. Limit is 1–64; default 64. |
turtle.place[Up/Down]() | true | false, reason | Place the selected item if it is a block and the destination is empty. |
World item transfer
| Function | Returns | Behavior |
turtle.suck[Up/Down]([count]) | true | false, reason | Pull items from an adjacent container or collect nearby dropped items into inventory. |
turtle.drop[Up/Down]([count]) | true | false, reason | Push selected items into an adjacent container or drop them into the world. Count defaults to the whole stack. |
Small builder example
for i = 1, 8 do
if turtle.getItemCount() == 0 then
error("Selected slot is empty")
end
local ok, reason = turtle.placeDown()
if not ok then error(reason) end
ok, reason = turtle.forward()
if not ok then error(reason) end
end
PenDrives
Each PenDrive has a persistent numeric ID, an optional label and an independent 1 MiB filesystem. Its ID appears as PenDrive #ID in storage interfaces. PenDrives do not stack and cannot be inserted in two places at once.
USB drives
Insert one PenDrive into a USB Drive adjacent to a Computer or Turtle. It mounts at usb, usb2, etc., depending on how many drives are attached. The usb_drive peripheral can inspect, rename, locate and eject media.
local drive = peripheral.find("usb_drive")
if drive and drive.isPenDrivePresent() then
print("Drive #" .. drive.getPenDriveID())
print("Mounted at " .. drive.getMountPath())
print(fs.read(drive.getMountPath() .. "/readme.txt"))
end
usb_drive methods
| Method | Returns | Behavior |
isPenDrivePresent() | boolean | Whether media is inserted. |
getPenDriveID() | number | nil | Persistent medium ID. |
getPenDriveLabel() | string | Current label; requires media. |
setPenDriveLabel([label]) | true | Set or clear the label; requires media. |
getMountPath() | string | nil | Path visible to this computer; requires media. |
eject() | boolean | Safely eject the PenDrive and remove its mount. |
Stackable NAS units
Each NAS holds 8 PenDrives. Face-connected NAS units form one storage component. Every running Computer, Turtle or Pocket Computer within a spherical 16-block range of that component receives mounts at nas/<PenDriveID>.
All connected machines see the same live files: a program saved to nas/22/startup.lua can be read or executed by another machine with access to PenDrive 22. Removing media or moving out of range unmounts it; stale mounts are removed automatically.
ls nas
cd 22 -- shortcut for cd nas/22
ls
cat LEEME_NAS.txt
cd /
cat nas/22/shared-program.lua
Run a mounted program from the shell by using a path that resolves to its Lua file, or load its source explicitly in your own program. For portable startup logic, copy the desired program to the machine's own startup.lua.
Media events
usb and usb_eject fire with a side name when media mounts or unmounts. NAS media use nas as the event side. See Events.
Craft with one Computer and two copper bars. Use the Pocket Computer item to open the mobile terminal. It has the same shell, Lua APIs, editor, filesystem and 1 MiB local quota as other machines.
Persistent identity
A Pocket Computer keeps its ID and files while carried. Duplicate stacks cannot start multiple sessions on the same disk, protecting the filesystem from concurrent writers.
When execution suspends
Execution and NAS access suspend when the item is dropped, its holder disconnects or changes worlds. Returning to a valid held state resumes the mobile session with the same persistent identity.
NAS on the move
Nearby connected NAS units are discovered around the player using the same 16-block range. Mounts follow the player and are removed when media leaves range or the Pocket Computer suspends.
Mobile context: wireless and NAS positioning follows the holder. World-dependent calls still require a valid loaded world context.
Lua APIperipheral API
Discover and call Monitors, Modems, inventories, USB drives and Speakers.
Peripheral names are usually side strings: left, right, top, bottom, front, back. Turtle upgrades use their corresponding side. Coordinate aliases use @dx,dy,dz for directly addressed neighbors.
| Function | Returns | Behavior |
peripheral.getNames() | table | Array of currently available peripheral names. |
peripheral.getType(name) | string | nil | monitor, modem, inventory, usb_drive, speaker, or nil. |
peripheral.getMethods(name) | table | nil | Array of callable method names. |
peripheral.wrap(name) | table | nil | Create an object whose methods call this peripheral. |
peripheral.find(type) | zero or more tables | Return every wrapped peripheral of this type as separate return values. |
peripheral.call(name, method, ...) | values… | Call a method directly without wrapping first. |
Discovery example
for _, name in ipairs(peripheral.getNames()) do
print(name .. " is " .. tostring(peripheral.getType(name)))
for _, method in ipairs(peripheral.getMethods(name)) do
print(" " .. method)
end
end
local speaker = peripheral.find("speaker")
if speaker then speaker.playSound("TaleOS_Boot_Beep") end
Listen for peripheral and peripheral_detach to react to hardware changes.
Wrap a supported container as an inventory peripheral. Slots are one-based. Turtles can additionally move items with suck and drop.
| Method | Returns | Behavior |
inv.size() | number | Total number of slots. |
inv.list() | table | Map of occupied slot numbers to basic stacks: name and count. |
inv.getItemDetail(slot) | table | nil | Detailed stack: name, count, maxCount, optional durability/maxDurability and safe metadata. |
inv.getItemLimit(slot) | number | Maximum stack size for the item in this slot. |
local chest = peripheral.find("inventory")
if chest then
print("Slots: " .. chest.size())
for slot, stack in pairs(chest.list()) do
print(slot, stack.name, stack.count)
end
end
Place a Modem adjacent to a Computer or equip it on a Turtle. Messages reach loaded receivers in the same world within the configured range (64 blocks by default). Payloads are deep-copied and bounded; each receiver is limited to 32 messages per tick by default.
modem methods
| Method | Returns | Behavior |
modem.open(channel) | nil | Listen on a channel from 0 to 65535. |
modem.close(channel) | nil | Stop listening on that channel. |
modem.closeAll() | nil | Close every channel on this modem. |
modem.isOpen(channel) | boolean | Whether the channel is open. |
modem.transmit(channel, replyChannel, payload) | nil | Send a payload with a reply channel. Listeners queue modem_message. |
modem.isWireless() | true | TaleOS Modems are wireless. |
rednet functions
Rednet manages channels and protocols for you. It opens your computer ID and rednet.CHANNEL_BROADCAST (65535) when rednet.open succeeds.
| Function | Returns | Behavior |
rednet.open(side) | boolean | Open rednet on a wireless Modem. |
rednet.isOpen(side) | boolean | Whether rednet is open on that side. |
rednet.close(side) / closeAll() | nil | Close rednet channels on one/all managed Modems. |
rednet.send(receiver, message[, protocol]) | boolean | Send to a computer ID/channel (0–65535), optionally tagged with a protocol. |
rednet.broadcast(message[, protocol]) | boolean | Send to all open rednet receivers in range. |
rednet.receive([protocol[, timeout]]) | sender, message, protocol | nil | Wait for a matching message; timeout is optional seconds. Nil on timeout. |
rednet.host(protocol, hostname) | nil | Register a hostname under a protocol; requires an open Modem. |
rednet.unhost(protocol) | nil | Remove all names hosted by this computer under that protocol. |
rednet.lookup(protocol[, hostname[, timeout]]) | ID | table | Find a specific host ID, or all host IDs when hostname is nil. |
rednet.run() | does not return | Answer DNS lookups. The normal TaleOS shell already runs this in parallel. |
Two-computer chat
-- Receiver:
rednet.open("right")
local sender, message = rednet.receive("demo", 10)
if sender then print(sender .. ": " .. tostring(message)) end
-- Sender (replace 8 with the receiver's ID):
rednet.open("left")
rednet.send(8, "Hello!", "demo")
GPS uses channel gps.CHANNEL = 65534. A locating machine needs a wireless Modem and responses from at least four non-coplanar references with consistent distances.
| Function | Returns | Behavior |
gps.locate([timeout]) | x, y, z | nil, error | Request references and solve a 3D position. Default timeout is 2 seconds. |
gps.host(x, y, z) | does not return | Serve fixed reference coordinates until the program is stopped. |
gps.trilaterate(references) | x, y, z | nil, error | Solve from an array of at least four tables containing finite x, y, z and non-negative distance. |
Set up a GPS network
- Place four Computers with wireless Modems at known, non-coplanar positions.
- Run
gps host <x> <y> <z> on each, using its actual coordinates.
- Attach a Modem to the locating machine and run
gps locate.
local x, y, z = gps.locate(3)
if x then
print(string.format("Position: %.2f %.2f %.2f", x, y, z))
else
print("GPS failed: " .. tostring(y))
end
Geometry matters. Four hosts in one plane do not provide a unique 3D solution. Add a host above or below that plane and keep all reference chunks loaded.
Place a Speaker beside a Computer or equip it on a Turtle. TaleOS plays existing registered SoundEvents; arbitrary microphone input, note synthesis and streaming audio are not included.
| Method | Returns | Behavior |
speaker.playSound(name[, volume[, pitch]]) | true | false, reason | Play a SoundEvent. Volume defaults to 1 and accepts 0–3. Pitch defaults to 1 and accepts 0.5–2. |
Sound names may contain letters, digits, underscore, dot, slash and hyphen, up to 256 characters. Unknown SoundEvents return false. Playback is limited to one sound per equipped Speaker per tick.
local speaker = peripheral.find("speaker")
if not speaker then error("No Speaker attached") end
local ok, reason = speaker.playSound("TaleOS_Boot_Beep", 1, 1)
if not ok then print(reason) end
TaleOS creates mods/TaleOS/config.json relative to the server working directory. Edit it while the server is stopped, then restart. Keep a backup before making changes.
Complete default configuration
{
"language": "en",
"http": {
"enabled": true,
"max_requests": 16,
"timeout_seconds": 30,
"max_redirects": 16,
"bandwidth": {
"global_download": 33554432
},
"rules": [
{ "host": "$private", "action": "deny" },
{ "host": "*", "action": "allow", "max_download": 16777216 }
]
},
"modem": {
"range": 64,
"max_messages_per_tick": 32
}
}
English or Spanish
Set "language": "en" (default) or "language": "es". This translates TaleOS interfaces, commands, Turtle messages, peripherals, errors and admin output. Lua API names and text produced by user programs do not change.
HTTP settings
| Setting | Default | Meaning |
http.enabled | true | Expose http.get and wget. False removes the API. |
http.max_requests | 16 | Concurrent requests per computer; 0 removes this per-computer limit. |
http.timeout_seconds | 30 | Request timeout, allowed range 0–60. |
http.max_redirects | 16 | Maximum redirects per request. |
http.bandwidth.global_download | 33554432 | Global download throughput budget: 32 MiB/s. |
http.rules | deny private, allow public | Ordered host rules; first match wins. |
Host rules
Rules support domains, IPs, wildcards such as *.example.com, CIDR ranges and the special $private group. Every DNS result and redirect destination is checked again. max_download limits bytes for a matching rule; 16777216 is 16 MiB.
"rules": [
{ "host": "$private", "action": "deny" },
{ "host": "raw.githubusercontent.com", "action": "allow", "max_download": 16777216 },
{ "host": "*.example.com", "action": "allow", "max_download": 1048576 },
{ "host": "*", "action": "deny" }
]
Fail closed. Invalid HTTP configuration disables HTTP; an invalid rule denies all addresses. Do not broaden access to private infrastructure unless you understand the risk of server-side requests.
Wireless settings
modem.range is the same-world wireless radius in blocks, and modem.max_messages_per_tick limits delivery work. Both accept values from 1 to 256. Invalid modem settings use safe defaults (64 and 32).
Persistent server data
mods/TaleOS/computers/ — machine files.
mods/TaleOS/pendrives/ — removable media.
mods/TaleOS/logs/ — per-computer transcripts.
mods/TaleOS/ids.json and turtles.json — identity and Turtle state.
The /taleos command
/taleos <world> <x> <y> <z> <shell line…>
Boot the computer at that block if needed and queue one shell line. This is useful for automated tests and server administration. From the server console, omit the slash as required by Hytale's command parser.
taleos creative_test 191 123 -64 id
taleos creative_test 191 123 -64 ls nas
taleos creative_test 199 123 -65 __turtle id
Administrative diagnostics available at the same coordinates include __probe for chunk/block diagnostics, __blockid for block IDs, __stop to request program termination and __nasdebug to log NAS media locations. Turtle commands can be prefixed with __turtle .
Computer logs
Every machine writes a timestamped transcript to mods/TaleOS/logs/computer-<id>.log. It includes boots, submitted commands, shell output and program errors. Use these logs instead of relying on screenshots when diagnosing automation.
Keep automation loaded
Machines need loaded chunks for world reads, wireless delivery and physical actions. Hytale's vanilla keep-loaded command can maintain an automation area:
/world settings keeploaded set <minX> <minZ> <maxX> <maxZ> --world=<world>
Use keep-loaded sparingly. Large permanently loaded areas increase memory and tick cost. Bound the region to the machines that need it.
Backups and updates
Back up mods/TaleOS/ together with the world before updating or migrating a server. Do not delete identity files while keeping machine disks: IDs bind placed blocks, portable items, PenDrives and their data.
Built-in smoke test
Starting the JVM with -Dtaleos.selftest=true runs a startup smoke test: boot to the TaleOS prompt, submit an ID command and verify that an infinite loop is killed by its instruction budget while the server remains alive.
- Early Access, built for Hytale server 0.6.2 and Java 25.
- Server-side installation; vanilla clients receive the assets.
- English and Spanish interfaces; English by default.
- 1 MiB quota per machine and per PenDrive.
- 51×19 native terminal; 16-color palette.
Relationship to other computer mods
TaleOS is a clean-room implementation inspired by the programmability of computer mods. Familiar names such as fs, term, peripheral, rednet and turtle do not imply complete API or program compatibility. Check signatures and return values in this wiki when porting a program.
Not implemented in 0.1.0
- Floppy disks and disk drives—TaleOS uses PenDrives, USB readers and NAS instead.
- Redstone APIs, command computers, printers, wired networks and energy/fluid peripherals.
- Multishell/tabbed program management, mouse input, monitor touch and paint/image file formats.
- HTTP POST, WebSockets, proxies, arbitrary binary downloads and Git cloning.
- Speaker audio streaming or arbitrary user audio; only registered SoundEvents.
- Lua
require, package, host io, Java access and native file access.
- Automatic chunk loading by computers themselves.
Operational limits
Lua instruction budgets kill runaway code, world calls are limited per tick, HTTP is asynchronous and validated, event queues are bounded, and network messages are size/rate limited. These protections reduce server risk but do not replace sensible permissions, backups and resource monitoring.
1. Monitor status board
Save as status.lua with a Monitor adjacent to the Computer:
local mon = tale.monitor()
if not mon then error("Place a Monitor next to this Computer") end
while true do
local width = mon.getSize()
local text = "ONLINE #" .. os.getComputerID()
mon.clear("navy")
mon.fill(1, 1, width, 1, "blue")
mon.write(2, 2, text, "lime")
mon.write(2, 10, os.date("%H:%M:%S"), "white")
tale.sleep(20)
end
2. Peripheral event logger
fs.mkdir("logs")
print("Watching peripherals…")
while true do
local event, name = os.pullEvent()
if event == "peripheral" or event == "peripheral_detach" then
local line = os.date() .. " " .. event .. " " .. name .. "\n"
fs.append("logs/peripherals.txt", line)
print(line)
end
end
3. Safe dig-and-place routine
For a Turtle with a suitable tool in the right upgrade slot and blocks in slot 1:
local function check(ok, reason)
if not ok then error(reason or "Action failed") end
end
turtle.select(1)
for step = 1, 10 do
turtle.turnRight()
if turtle.detectDown() then
check(turtle.digDown("right"))
tale.sleep(2)
end
check(turtle.placeUp())
turtle.turnLeft()
check(turtle.forward())
print("Step " .. step .. "/10")
end
4. Networked doorbell
-- Computer beside a Speaker:
rednet.open("right")
local speaker = peripheral.find("speaker")
while true do
local sender, message = rednet.receive("doorbell")
if message == "ring" then
print("Bell from #" .. sender)
if speaker then speaker.playSound("TaleOS_Boot_Beep") end
end
end
-- Another Computer:
rednet.open("left")
rednet.broadcast("ring", "doorbell")
5. Shared NAS configuration
local path = "nas/22/settings.txt"
if not fs.exists(path) then
fs.write(path, "motd=Hello from shared storage")
end
local settings = fs.read(path)
print(settings)
Adapt the PenDrive ID to one shown by ls nas. Any machine in range can read the same file.