Skip to main content
Version: TEdit 5

Scripting Guide

TEdit 5 includes a built-in scripting engine for automating world edits. Write scripts in JavaScript (Jint) or Lua to batch-process tiles, search worlds, fill areas, and more.

AI Script Assistant

Use an AI chatbot to help you write TEdit scripts. Click one of the links below to open a chat, or copy the prompt and paste it into your preferred assistant.

Copy prompt for Claude or Gemini

Since Claude and Gemini don't support URL-based prompt prefill, copy the prompt below and paste it to start your chat:

You are a TEdit 5 scripting assistant. TEdit is a Terraria world editor with a
built-in JavaScript (Jint) scripting engine.

API documentation: https://docs.tedit.dev/advanced/scripting-guide
Tile/wall/item data files:
https://github.com/TEdit/Terraria-Map-Editor/tree/main/src/TEdit.Terraria/Data
(tiles.json, walls.json, items.json, npcs.json, prefixes.json, paints.json, etc.)

Key API objects:
- tile: read/write individual tiles (getTileType, setType, setWall, setLiquid, etc.)
- batch: bulk operations (forEachTile, replaceTile, findTilesByType, etc.)
- geometry: shape helpers (line, rect, ellipse, fillRect, fillEllipse, setTiles, setWalls)
- selection: current selection bounds and point-in-selection test
- chests: chest inventory read/write
- signs: sign text read/write
- npcs: NPC management
- world: read-only world metadata (width, height, title, seed, spawnX, spawnY)
- metadata: name/ID lookups (tileId, wallId, itemId, tileName, wallName, itemName)
- log: output (print, warn, error, progress)
- draw: drawing tools (pencil, brush, fill, routeWire, routeBus for CAD wire routing)
- generate: procedural generation — trees (tree, forest, forestInSelection), worldgen structures (tileRunner, tunnel, lake, oreVein, listOreTypes, findSurface)
- finder: add results to the Find sidebar panel
- tools: UI tool access

When writing scripts:
- Use metadata.tileId("name") and metadata.wallId("name") to look up IDs by name
- Always check selection.isActive before using selection-based operations
- Use log.print() for output and log.progress() for long operations
- Scripts have full access to the loaded world data
- Framed tiles can be shifted UV coordinates to become another style of the same item
(e.g. platforms, banners, chests). Check tiles.json for frame UV values.

Help me write a JavaScript script for TEdit that:

Basics

Scripts run with full access to the loaded world data. The Scripting sidebar provides a code editor with syntax highlighting, script management, and an output log.

Hello World

log.print("Hello from TEdit scripting!");
log.print(`World: ${world.title} (${world.width}x${world.height})`);
log.print("Hello from TEdit scripting!")
log.print("World: " .. world.title)

TypeScript Autocomplete

TEdit ships a TypeScript declarations file (tedit-api.d.ts) that gives you full autocomplete and inline documentation in VS Code or any TypeScript-aware editor — even for plain .js scripts.

Setup

  1. Find the file — It's already in your TEdit Scripts folder (the same folder opened by the Open Scripts Folder button in the Scripting sidebar). You can also download it directly:

    Download tedit-api.d.ts

  2. Add a reference — Put this line at the top of your .js script:

    /// <reference path="tedit-api.d.ts" />

    As long as tedit-api.d.ts is in the same folder as your script (which it is by default), VS Code will pick it up automatically.

  3. Enjoy autocomplete — You'll get full IntelliSense for all API objects: tile, batch, world, tools, draw, generate, etc. Hover over any method to see its description and parameter types.

tip

The /// <reference> comment is ignored by TEdit's script engine — it's only used by your code editor. Your scripts will run exactly the same with or without it.

Common Recipes

Replace tiles in selection

if (!selection.isActive) {
log.error("Select an area first!");
}

const from = metadata.tileId("Dirt Block");
const to = metadata.tileId("Stone Block");
const count = batch.replaceTileInSelection(from, to);
log.print(`Replaced ${count} tiles`);

Fill a rectangle with walls

geometry.setWalls(metadata.wallId("Stone Wall"), 100, 200, 50, 30);
log.print("Filled 50x30 area with stone walls");

Find all chests containing a specific item

const chests = chests.findByItemName("Zenith");
for (const c of chests) {
log.print(`Found at (${c.x}, ${c.y}): ${c.name || "Chest"}`);
finder.addResult("Zenith Chest", c.x, c.y, "chest");
}
log.print(`Found ${chests.length} chests with Zenith`);

Draw a circle of tiles

const points = geometry.fillEllipse(500, 300, 25, 25);
const tileType = metadata.tileId("Glass Block");
for (const p of points) {
tile.setType(p.x, p.y, tileType);
}
log.print(`Placed ${points.length} glass tiles`);

Clear all of a tile type

const count = batch.clearTilesByType(metadata.tileId("Sand Block"));
log.print(`Cleared ${count} sand blocks`);

Show progress for long operations

const total = world.width * world.height;
let processed = 0;

batch.forEachTile((x, y) => {
// ... your logic here ...
processed++;
if (processed % 100000 === 0) {
log.progress(processed / total);
}
});
log.progress(1.0);
log.print("Done!");

More Examples

Read tile information at a position

const x = 500, y = 300;
log.print(`Active: ${tile.isActive(x, y)}`);
log.print(`Type: ${metadata.tileName(tile.getTileType(x, y))}`);
log.print(`Wall: ${metadata.wallName(tile.getWall(x, y))}`);
log.print(`Paint: ${tile.getPaint(x, y)}`);
log.print(`Liquid: type=${tile.getLiquidType(x, y)} amount=${tile.getLiquidAmount(x, y)}`);
log.print(`Frame: U=${tile.getFrameU(x, y)} V=${tile.getFrameV(x, y)}`);
log.print(`Slope: ${tile.getSlope(x, y)}`);

Paint all tiles in selection

if (!selection.isActive) { log.error("Select an area first!"); }

const paintColor = 13; // Illuminant paint
batch.forEachInSelection((x, y) => {
if (tile.isActive(x, y)) {
tile.setPaint(x, y, paintColor);
}
});
log.print("Painted all tiles in selection");

Paint walls in selection

if (!selection.isActive) { log.error("Select an area first!"); }

const paintColor = 12; // Deep paint colors start around 12
batch.forEachInSelection((x, y) => {
if (tile.getWall(x, y) > 0) {
tile.setWallPaint(x, y, paintColor);
}
});
log.print("Painted all walls in selection");

Fill selection with water

if (!selection.isActive) { log.error("Select an area first!"); }

batch.forEachInSelection((x, y) => {
tile.setLiquid(x, y, 255, 1); // 255 = full, 1 = water
});
log.print("Filled selection with water");

Create a lava moat

// Draw a lava-filled trench at a position
const startX = 400, startY = 290, width = 60, depth = 6;

// Clear the trench
geometry.clearTiles(startX, startY, width, depth);

// Fill with lava
for (let x = startX; x < startX + width; x++) {
for (let y = startY; y < startY + depth; y++) {
tile.setLiquid(x, y, 255, 2); // 2 = lava
}
}
log.print(`Lava moat: ${width}x${depth} at (${startX}, ${startY})`);

Add slopes to a platform

if (!selection.isActive) { log.error("Select an area first!"); }

const stoneId = metadata.tileId("Stone Block");
batch.forEachInSelection((x, y) => {
if (tile.isActive(x, y) && tile.getTileType(x, y) === stoneId) {
// Check if tile has air above — slope the top surface
if (!tile.isActive(x, y - 1)) {
// Left edge gets SlopeTopLeft, right edge gets SlopeTopRight
if (tile.isActive(x - 1, y) && !tile.isActive(x + 1, y)) {
tile.setSlope(x, y, "SlopeTopRight");
} else if (!tile.isActive(x - 1, y) && tile.isActive(x + 1, y)) {
tile.setSlope(x, y, "SlopeTopLeft");
}
}
}
});
log.print("Added slopes to exposed stone edges");

Wire a region with red wire

if (!selection.isActive) { log.error("Select an area first!"); }

let count = 0;
batch.forEachInSelection((x, y) => {
if (tile.isActive(x, y)) {
tile.setWire(x, y, 1, true); // 1 = red wire
count++;
}
});
log.print(`Added red wire to ${count} tiles`);

Route a wire between two points

// Route a single red wire with a 90° elbow
draw.setWire(true, false, false, false);
draw.routeWire(100, 200, 150, 220, '90', 'h');
log.print("Routed red wire");

Route a bus of parallel wires

// Route 5 parallel green wires with 45° miter routing
draw.setWire(false, false, true, false);
const count = draw.routeBus(5, 100, 200, 200, 250, '45', 'auto');
log.print(`Placed ${count} wire tiles in 5-wire bus`);

Route tile paths (platforms, tracks)

// Route a stone block path using wire routing geometry
draw.setTile(metadata.tileId("Stone Block"));
draw.routeWire(50, 50, 100, 80, '90', 'v');
log.print("Routed stone path");

Connect two points with colored wire bus

// Red + blue bus from spawn to dungeon
draw.setWire(true, true, false, false);
draw.routeBus(3, world.spawnX, world.spawnY - 5, world.dungeonX, world.dungeonY, '90', 'auto');
log.print("Connected spawn to dungeon with wire bus");

Copy a column of tiles downward

// Duplicate the top 10 rows at x=500 downward by 10 tiles
const x = 500;
for (let y = 0; y < 10; y++) {
tile.copy(x, y, x, y + 10);
}
log.print("Copied column");

Count tiles by type in selection

if (!selection.isActive) { log.error("Select an area first!"); }

const counts = {};
batch.forEachInSelection((x, y) => {
if (tile.isActive(x, y)) {
const id = tile.getTileType(x, y);
counts[id] = (counts[id] || 0) + 1;
}
});

// Sort and display top 10
const sorted = Object.entries(counts)
.map(([id, n]) => ({ name: metadata.tileName(Number(id)), count: n }))
.sort((a, b) => b.count - a.count);

for (const t of sorted.slice(0, 10)) {
log.print(`${t.name}: ${t.count}`);
}

Find tiles with a custom predicate

// Find tiles that are painted deep red
const results = batch.findTiles((x, y) => {
return tile.isActive(x, y) && tile.getPaint(x, y) === 12;
});

finder.clear();
for (const p of results) {
finder.addResult("Painted tile", p.x, p.y, "tile");
}
log.print(`Found ${results.length} painted tiles (max 10,000)`);

Locate multi-tile sprites (anchorOnly)

// Find all heart crystals — anchorOnly returns one result per sprite
const heartId = metadata.tileId("Heart Crystal");
const hearts = batch.findTilesByType(heartId, true);

finder.clear();
for (const p of hearts) {
finder.addResult("Heart Crystal", p.x, p.y, "tile");
}
log.print(`Found ${hearts.length} heart crystals`);

Find and highlight tiles by wall type

const wallId = metadata.wallId("Lihzahrd Brick Wall");
const results = batch.findTilesByWall(wallId);

finder.clear();
for (const p of results) {
finder.addResult("Lihzahrd Wall", p.x, p.y, "wall");
}
log.print(`Found ${results.length} Lihzahrd wall tiles`);

Replace walls world-wide

const from = metadata.wallId("Dirt Wall");
const to = metadata.wallId("Stone Wall");
const count = batch.replaceWall(from, to);
log.print(`Replaced ${count} dirt walls with stone walls`);

Draw a line of tiles between two points

const points = geometry.line(100, 300, 250, 280);
const tileType = metadata.tileId("Obsidian Brick");
for (const p of points) {
tile.setType(p.x, p.y, tileType);
}
log.print(`Placed ${points.length} tiles in a line`);

Draw a rectangle outline

const points = geometry.rect(200, 200, 40, 20);
const tileType = metadata.tileId("Gray Brick");
for (const p of points) {
tile.setType(p.x, p.y, tileType);
}
log.print(`Drew rectangle outline: ${points.length} tiles`);

Draw an ellipse ring

const points = geometry.ellipse(300, 300, 30, 20);
const tileType = metadata.tileId("Glass Block");
for (const p of points) {
tile.setType(p.x, p.y, tileType);
}
log.print(`Drew ellipse outline: ${points.length} tiles`);

Clear tiles in a rectangular area

geometry.clearTiles(100, 200, 50, 30);
log.print("Cleared 50x30 area");

Create a selection from script

// Select a 100x50 region starting at (200, 300)
selection.set(200, 300, 100, 50);
log.print(`Selection: (${selection.x}, ${selection.y}) ${selection.width}x${selection.height}`);

Check if spawn is inside selection

if (!selection.isActive) { log.error("Select an area first!"); }

if (selection.contains(world.spawnX, world.spawnY)) {
log.print("Spawn point is inside the selection");
} else {
log.print("Spawn point is outside the selection");
}

List all chests and their contents

const allChests = chests.getAll();
log.print(`Total chests: ${chests.count}`);

for (const c of allChests.slice(0, 20)) {
log.print(`--- Chest at (${c.x}, ${c.y}): ${c.name || "Chest"} ---`);
for (const item of c.items) {
if (item.id > 0) {
log.print(` [${item.slot}] ${metadata.itemName(item.id)} x${item.stack}`);
}
}
}

Add an item to every chest

const itemId = metadata.itemId("Torch");
let added = 0;

for (const c of chests.getAll()) {
if (chests.addItem(c.x, c.y, itemId, 99, 0)) {
added++;
}
}
log.print(`Added torches to ${added} chests`);

Clear a specific slot from all chests

let cleared = 0;
for (const c of chests.getAll()) {
chests.clearItem(c.x, c.y, 0); // Clear slot 0 (first slot)
cleared++;
}
log.print(`Cleared slot 0 from ${cleared} chests`);

Set a chest slot to a specific item

// Place a Zenith in slot 0 of the chest at (500, 300)
const zenithId = metadata.itemId("Zenith");
chests.setItem(500, 300, 0, zenithId, 1, 81); // prefix 81 = Legendary
log.print("Placed Legendary Zenith in chest");

Find chests by name

// Find all chests named "Loot" (case-insensitive substring match)
const results = chests.findByName("Loot");
finder.clear();
for (const c of results) {
log.print(`"${c.name}" at (${c.x}, ${c.y})`);
finder.addResult(c.name, c.x, c.y, "chest");
}
log.print(`Found ${results.length} chests matching "Loot"`);

Rename a chest

// Set the name/label of the chest at (500, 300)
chests.setName(500, 300, "My Storage");
log.print("Chest renamed");

List all signs and their text

log.print(`Total signs: ${signs.count}`);
for (const s of signs.getAll()) {
const preview = s.text.substring(0, 50);
log.print(`(${s.x}, ${s.y}): "${preview}"`);
}

Update sign text at a position

signs.setText(500, 300, "Welcome to my base!");
log.print("Sign updated");

Number all signs sequentially

const allSigns = signs.getAll();
for (let i = 0; i < allSigns.length; i++) {
const s = allSigns[i];
signs.setText(s.x, s.y, `Sign #${i + 1}`);
}
log.print(`Numbered ${allSigns.length} signs`);

List all town NPCs

log.print(`Total NPCs: ${npcs.count}`);
for (const npc of npcs.getAll()) {
log.print(`${npc.name} at (${npc.x}, ${npc.y}) home: (${npc.homeX}, ${npc.homeY})`);
finder.addResult(npc.name, npc.x, npc.y, "npc");
}

Move an NPC home

npcs.setHome("Guide", 500, 290);
log.print("Moved Guide's home to (500, 290)");

Display world information

log.print(`Title: ${world.title}`);
log.print(`Seed: ${world.seed}`);
log.print(`Size: ${world.width} x ${world.height}`);
log.print(`Spawn: (${world.spawnX}, ${world.spawnY})`);
log.print(`Surface: ${world.surfaceLevel}`);
log.print(`Rock layer: ${world.rockLevel}`);

Search for items by partial name

const keyword = "sword";
const allItems = metadata.allItems();
const matches = allItems.filter(i =>
i.name.toLowerCase().includes(keyword.toLowerCase())
);

log.print(`Items matching "${keyword}":`);
for (const item of matches.slice(0, 20)) {
log.print(` [${item.id}] ${item.name}`);
}
log.print(`${matches.length} total matches`);

Look up IDs by name

log.print(`Dirt Block = tile ${metadata.tileId("Dirt Block")}`);
log.print(`Stone Wall = wall ${metadata.wallId("Stone Wall")}`);
log.print(`Zenith = item ${metadata.itemId("Zenith")}`);
log.print(`Tile 0 = ${metadata.tileName(0)}`);
log.print(`Wall 1 = ${metadata.wallName(1)}`);
log.print(`Item 1 = ${metadata.itemName(1)}`);

Build results in the Find panel

finder.clear();

// Find all demon altars
const altarId = metadata.tileId("Demon Altar");
const altars = batch.findTilesByType(altarId, true);

for (const p of altars) {
finder.addResult("Demon Altar", p.x, p.y, "tile",
`at depth ${p.y - world.surfaceLevel}`);
}
log.print(`Found ${altars.length} altars — check Find panel`);
finder.navigateFirst();

Read the tile picker and place matching tiles

// Place whatever tile the user has selected in the tile picker
const pickerTile = tools.getTilePickerTile();
if (pickerTile < 0) {
log.error("No tile selected in the tile picker!");
}

if (!selection.isActive) { log.error("Select an area first!"); }

batch.forEachInSelection((x, y) => {
tile.setType(x, y, pickerTile);
});
log.print(`Filled selection with ${metadata.tileName(pickerTile)}`);

Read the current tile picker values

const currentTile = tools.getTilePickerTile();
const currentWall = tools.getTilePickerWall();
log.print(`Tile picker: ${metadata.tileName(currentTile)} (${currentTile})`);
log.print(`Wall picker: ${metadata.wallName(currentWall)} (${currentWall})`);

Build a simple house

const x = 400, y = 290;
const w = 12, h = 7;
const wallType = metadata.tileId("Wood");
const wallBg = metadata.wallId("Wood Wall");

// Floor and ceiling
geometry.setTiles(wallType, x, y, w, 1); // ceiling
geometry.setTiles(wallType, x, y + h - 1, w, 1); // floor

// Left and right walls
for (let dy = 0; dy < h; dy++) {
tile.setType(x, y + dy, wallType);
tile.setType(x + w - 1, y + dy, wallType);
}

// Background wall
geometry.setWalls(wallBg, x + 1, y + 1, w - 2, h - 2);

// Clear interior
geometry.clearTiles(x + 1, y + 1, w - 2, h - 2);

// Place platforms for a door area (3 wide in the middle of the floor)
const platId = metadata.tileId("Wood Platform");
const doorX = x + Math.floor(w / 2) - 1;
for (let dx = 0; dx < 3; dx++) {
tile.setType(doorX + dx, y + h - 1, platId);
}

log.print(`Built ${w}x${h} house at (${x}, ${y})`);

Build an NPC hotel

Builds a four-floor hotel above the spawn point with a room for every town NPC (all 26). Each room meets housing requirements: wood frame, background walls, torch, work bench, chair, and a wooden door. Existing NPCs are assigned to their rooms via npcs.setHome().

note

The scripting API cannot spawn new NPCs — it can only manage NPCs already present in the world. Rooms are pre-built for all 26 NPCs so they have valid housing when they arrive.

// === NPC Hotel Builder ===
// Four-floor hotel — one room for every town NPC (26 total).
// 7 rooms per floor × 4 floors = 28 rooms (26 used + 2 spare).
// Each room: 12×7 exterior = 10×5 interior, well above 60-tile minimum.
// Uses sprites.placeByName() for furniture — handles multi-tile placement
// and frame UVs automatically.

// ── All 26 town NPCs (pre-Hardmode + Hardmode) ──
const townNpcNames = [
// Pre-Hardmode (18)
"Guide", "Merchant", "Nurse", "Demolitionist", "Dye Trader",
"Angler", "Zoologist", "Dryad", "Painter", "Golfer",
"Arms Dealer", "Tavernkeep", "Stylist", "Goblin Tinkerer",
"Witch Doctor", "Clothier", "Mechanic", "Party Girl",
// Hardmode (8)
"Wizard", "Tax Collector", "Truffle", "Pirate",
"Steampunker", "Cyborg", "Santa Claus", "Princess"
];
const npcCount = townNpcNames.length; // 26

// ── Layout ──
const roomW = 12; // room width (including walls)
const roomH = 7; // room height (including floor/ceiling)
const nFloors = 4;
const perFloor = Math.ceil(npcCount / nFloors); // 7

// Adjacent rooms share a 1-tile dividing wall; floors share a ceiling/floor.
const totalW = perFloor * (roomW - 1) + 1; // 78
const totalH = roomH * nFloors - (nFloors - 1); // 25

// Center the hotel above spawn
const baseX = world.spawnX - Math.floor(totalW / 2);
const baseY = world.spawnY - totalH;

// ── Tile / Wall IDs ──
const wood = metadata.tileId("Wood");
const woodWall = metadata.wallId("Wood Wall");
const PLATFORM = 19;

// ── Paint colors (Terraria paint IDs) ──
// 1=Red 2=Orange 3=Yellow 4=Lime 5=Green 6=Teal 7=Cyan 8=Sky Blue
// 9=Blue 10=Purple 11=Violet 12=Pink 13–24=Deep variants 25=Black
// 26=White 27=Gray 28=Brown
const paintColors = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
25, 26, 27, 28
];

log.print(`Building ${npcCount}-room hotel (${perFloor} per floor)`);
log.print(`Location: (${baseX}, ${baseY}) Size: ${totalW}×${totalH}`);

// ── 1. Select and clear build area ──
selection.set(baseX, baseY, totalW, totalH);
geometry.clearTiles(baseX, baseY, totalW, totalH);

// ── 2. Build structure (wood frame) ──
// Horizontal slabs: top ceiling, intermediate floors, ground floor
geometry.setTiles(wood, baseX, baseY, totalW, 1);
for (let f = 1; f < nFloors; f++) {
geometry.setTiles(wood, baseX, baseY + f * (roomH - 1), totalW, 1);
}
geometry.setTiles(wood, baseX, baseY + totalH - 1, totalW, 1);

// Left and right outer walls
for (let y = baseY; y < baseY + totalH; y++) {
tile.setType(baseX, y, wood);
tile.setType(baseX + totalW - 1, y, wood);
}

// Dividing walls between rooms (full height)
for (let r = 1; r < perFloor; r++) {
const wx = baseX + r * (roomW - 1);
for (let y = baseY; y < baseY + totalH; y++) {
tile.setType(wx, y, wood);
}
}

// ── 3. Per-room: walls → paint → platforms → sprites → NPC ──
const existingNpcs = {};
for (const n of npcs.getAll()) { existingNpcs[n.name] = true; }

let npcIdx = 0;
let assigned = 0;
for (let f = 0; f < nFloors; f++) {
const floorTopY = baseY + f * (roomH - 1);
const standY = floorTopY + roomH - 2; // row just above solid floor
const doorTopY = floorTopY + roomH - 4; // door anchor row (top of 1×3)

for (let r = 0; r < perFloor; r++) {
const rx = baseX + r * (roomW - 1);
const ix = rx + 1; // first interior column
const roomNum = f * perFloor + r;
const paint = paintColors[roomNum % paintColors.length];

// ── a. Background walls + paint ──
// Fill full room interior; extend up into shared slab for platform walls.
const wallTop = f > 0 ? floorTopY : floorTopY + 1;
const wallBot = floorTopY + roomH - 2;
for (let wy = wallTop; wy <= wallBot; wy++) {
for (let wx = rx + 1; wx < rx + roomW - 1; wx++) {
tile.setWall(wx, wy, woodWall);
tile.setWallPaint(wx, wy, paint);
}
}

// ── b. Platforms in shared slab (inter-floor access) ──
if (f > 0) {
const platX = rx + Math.floor(roomW / 2) - 1;
for (let i = 0; i < 3; i++) {
tile.setType(platX + i, floorTopY, PLATFORM);
tile.setFrameUV(platX + i, floorTopY, 0, 0);
}
}

// ── c. Sprites: door, torch, work bench, chair ──
// sprites.placeByName() handles multi-tile UV layout automatically.
// Door between adjacent rooms (in the shared dividing wall)
if (r > 0) {
sprites.placeByName("Wooden Door", rx, doorTopY);
}
// Entry door on the left outer wall
if (r === 0) {
sprites.placeByName("Wooden Door", baseX, doorTopY);
}

if (npcIdx < npcCount) {
sprites.placeByName("Torch", ix + 7, standY);
sprites.placeByName("Wooden Work Bench", ix + 1, standY);
sprites.placeByName("Wooden Chair", ix + 4, standY);

// ── d. Assign NPC home (solid tile they stand ON) ──
// Platforms are at rx+5..rx+7, so use rx+9 (always solid).
const npcName = townNpcNames[npcIdx];
const homeX = ix + 8;
const homeY = standY + 1; // the solid floor row
if (existingNpcs[npcName]) {
npcs.setHome(npcName, homeX, homeY);
log.print(`Room ${npcIdx + 1}: ${npcName} (paint ${paint}) ✓`);
assigned++;
} else {
log.print(`Room ${npcIdx + 1}: ${npcName} (paint ${paint}) — not yet in world`);
}
npcIdx++;
}
}
}

selection.clear();
log.print(`\nHotel complete! ${npcCount} rooms built, ${assigned} NPCs assigned.`);
Room layout diagram (showing 2 of 4 floors)
  12 tiles wide per room (shared walls)
├───────────┤
... ... (floors 4 & 3 above)
WW===WWWWWWWWW===WWWWWWW ── shared slab (platforms)
W..........DW..........W ┐
W..........DW..........W │ Floor 2 (7 tall)
W.WW.C..T..DW.WW.C..T..W │ WW=bench C=chair
W..........WW..........W │ T=torch D=door
WW===WWWWWWWWW===WWWWWWW ── shared slab (platforms)
W..........DW..........W ┐
W..........DW..........W │ Floor 1 (7 tall)
W.WW.C..T..DW.WW.C..T..W │
W..........WW..........W ┘
DWWWWWWWWWWWWWWWWWWWWWWW ── ground floor
D = entry door on left wall of each floor

7 rooms per floor × 4 floors = 28 rooms (26 NPCs + 2 spare)

Place a single tree

// Place an oak tree at position (500, 300)
var placed = generate.tree("oak", 500, 300);
log.print("Tree placed: " + placed);

Generate a mixed forest in selection

if (!selection.isActive) { log.error("Select an area first!"); }

var count = generate.forestInSelection(["oak", "sakura", "diamond"], 0.15);
log.print("Placed " + count + " trees");

List available tree types

var types = generate.listTreeTypes();
for (var i = 0; i < types.length; i++) {
log.print(types[i].name + " (tile " + types[i].tileId + ")");
}

Find the surface at a column

var surface = generate.findSurface(500, 0, world.height - 1);
if (surface >= 0) {
log.print("Surface at y=" + surface);
finder.clear();
finder.addResult("Surface", 500, surface, "tile");
finder.navigateFirst();
} else {
log.print("No surface found");
}

Checkerboard pattern in selection

if (!selection.isActive) { log.error("Select an area first!"); }

const tileA = metadata.tileId("Ebonstone Block");
const tileB = metadata.tileId("Pearlstone Block");

batch.forEachInSelection((x, y) => {
tile.setType(x, y, (x + y) % 2 === 0 ? tileA : tileB);
});
log.print("Checkerboard pattern applied");

Drain all liquids in selection

if (!selection.isActive) { log.error("Select an area first!"); }

let count = 0;
batch.forEachInSelection((x, y) => {
if (tile.getLiquidAmount(x, y) > 0) {
tile.setLiquid(x, y, 0, 0);
count++;
}
});
log.print(`Drained liquid from ${count} tiles`);

Strip all paint in selection

if (!selection.isActive) { log.error("Select an area first!"); }

let count = 0;
batch.forEachInSelection((x, y) => {
if (tile.getPaint(x, y) > 0) {
tile.setPaint(x, y, 0);
count++;
}
if (tile.getWallPaint(x, y) > 0) {
tile.setWallPaint(x, y, 0);
count++;
}
});
log.print(`Stripped paint from ${count} tiles/walls`);

Remove all wires in selection

if (!selection.isActive) { log.error("Select an area first!"); }

let count = 0;
batch.forEachInSelection((x, y) => {
for (let color = 1; color <= 4; color++) {
if (tile.getWire(x, y, color)) {
tile.setWire(x, y, color, false);
count++;
}
}
});
log.print(`Removed ${count} wire segments`);

API Reference

tile — Tile Read/Write

MethodDescription
isActive(x, y) → boolCheck if tile is active
getTileType(x, y) → intGet tile type ID
getWall(x, y) → intGet wall type ID
getPaint(x, y) → intGet tile paint color
getWallPaint(x, y) → intGet wall paint color
getLiquidAmount(x, y) → intGet liquid amount (0–255)
getLiquidType(x, y) → intGet liquid type (0=none, 1=water, 2=lava, 3=honey)
getFrameU(x, y) → intGet sprite frame U
getFrameV(x, y) → intGet sprite frame V
getSlope(x, y) → stringGet slope type
getWire(x, y, color) → boolCheck wire (1=red, 2=blue, 3=green, 4=yellow)
getTileEcho(x, y) → boolCheck if tile has echo (invisible) coating
getWallEcho(x, y) → boolCheck if wall has echo (invisible) coating
getTileIlluminant(x, y) → boolCheck if tile has illuminant (full bright) coating
getWallIlluminant(x, y) → boolCheck if wall has illuminant (full bright) coating
setActive(x, y, active)Set tile active state
setType(x, y, type)Set tile type (activates tile)
setWall(x, y, wallType)Set wall type
setPaint(x, y, color)Set tile paint color
setWallPaint(x, y, color)Set wall paint color
setLiquid(x, y, amount, type)Set liquid amount and type
setWire(x, y, color, enabled)Set wire state
setSlope(x, y, slope)Set slope (None, HalfBrick, SlopeTopRight, etc.)
setFrameUV(x, y, u, v)Set sprite frame coordinates
setTileEcho(x, y, value)Set tile echo (invisible) coating
setWallEcho(x, y, value)Set wall echo (invisible) coating
setTileIlluminant(x, y, value)Set tile illuminant (full bright) coating
setWallIlluminant(x, y, value)Set wall illuminant (full bright) coating
clear(x, y)Reset tile to empty
copy(fromX, fromY, toX, toY)Copy tile properties

batch — Bulk Operations

MethodDescription
forEachTile(callback)Iterate entire world — callback(x, y)
forEachInSelection(callback)Iterate selection — callback(x, y)
findTiles(predicate) → [{x, y}]Find tiles matching predicate (max 10,000)
findTilesByType(type, anchorOnly?) → [{x, y}]Find by type; anchorOnly returns sprite origins
findTilesByWall(wallType) → [{x, y}]Find by wall type (max 10,000)
replaceTile(from, to) → intReplace all tiles of one type
replaceTileInSelection(from, to) → intReplace in selection only
replaceWall(from, to) → intReplace all walls of one type
clearTilesByType(type) → intClear all tiles of type

geometry — Shapes

MethodDescription
line(x1, y1, x2, y2) → [{x, y}]Line coordinates
rect(x, y, w, h) → [{x, y}]Rectangle outline
ellipse(cx, cy, rx, ry) → [{x, y}]Ellipse outline
fillRect(x, y, w, h) → [{x, y}]Filled rectangle
fillEllipse(cx, cy, rx, ry) → [{x, y}]Filled ellipse
setTiles(type, x, y, w, h)Fill rect with tiles
setWalls(type, x, y, w, h)Fill rect with walls
clearTiles(x, y, w, h)Clear tiles in rect

selection — Selection

Property/MethodDescription
isActive → boolWhether selection is active
x, y, width, heightSelection bounds
left, top, right, bottomSelection edges
set(x, y, w, h)Create selection
clear()Remove selection
contains(x, y) → boolPoint-in-selection test

chests — Chest Inventory

MethodDescription
count → intTotal chests
getAll() → [...]All chests
getAt(x, y) → {...}Chest at position
findByItem(id) → [...]Find by item ID
findByItemName(name) → [...]Find by item name
setName(x, y, name)Set chest name/label
findByName(name) → [...]Find chests by name (case-insensitive substring match)
setItem(x, y, slot, id, stack, prefix)Set slot contents
clearItem(x, y, slot)Clear slot
addItem(x, y, id, stack, prefix) → boolAdd to first empty slot

signs — Sign Text

MethodDescription
count → intTotal signs
getAll() → [...]All signs
getAt(x, y) → {...}Sign at position
setText(x, y, text)Update text

npcs — Town NPCs

MethodDescription
count → intTotal NPCs
getAll() → [...]All NPCs with positions
setHome(name, x, y)Set NPC home

world — World Metadata (read-only)

PropertyDescription
width, heightWorld size in tiles
titleWorld name
seedWorld seed
spawnX, spawnYSpawn coordinates
surfaceLevel, rockLevelLayer boundaries

metadata — Game Data Lookup

MethodDescription
tileId(name) → intTile ID by name (-1 if not found)
wallId(name) → intWall ID by name
itemId(name) → intItem ID by name
tileName(id) → stringTile name by ID
wallName(id) → stringWall name by ID
itemName(id) → stringItem name by ID
allTiles(), allWalls(), allItems()All definitions

log — Output

MethodDescription
print(msg)Log info
warn(msg)Log warning
error(msg)Log error
progress(value)Update progress bar (0.0–1.0)

finder — Find Sidebar Integration

MethodDescription
clear()Clear results
addResult(name, x, y, type, extraInfo?)Add result (max 1,000)
navigate(index)Navigate to result
navigateFirst()Go to first result

tools — UI Tools

MethodDescription
listTools() → [string]Available tool names
copySelection()Copy selection to clipboard
getTilePickerTile() → intCurrent tile picker value
getTilePickerWall() → intCurrent wall picker value

tileEntities — Tile Entity Management

Property/MethodDescription
count → intTotal tile entities
weaponRackCount → intWeapon rack count
itemFrameCount → intItem frame count
mannequinCount → intMannequin / display doll count
hatRackCount → intHat rack count
foodPlatterCount → intFood platter count
logicSensorCount → intLogic sensor count
trainingDummyCount → intTraining dummy count
pylonCount → intTeleportation pylon count
getAll() → [...]All tile entities
getAt(x, y) → {...}Entity at position
getAllByType(typeName) → [...]Get by type name
getAllWeaponRacks() → [...]All weapon racks
getAllItemFrames() → [...]All item frames
getAllMannequins() → [...]All mannequins
getAllHatRacks() → [...]All hat racks
getAllFoodPlatters() → [...]All food platters
findByItem(itemId) → [...]Find entities containing item
setItem(x, y, itemId, prefix?, stack?)Set item (weapon rack, item frame, food platter)
clearItem(x, y)Clear item from single-item entity
setEquipment(x, y, slot, itemId, prefix?)Set mannequin equipment slot
clearEquipment(x, y, slot)Clear mannequin equipment slot
setDye(x, y, slot, dyeId, prefix?)Set mannequin dye slot
clearDye(x, y, slot)Clear mannequin dye slot
setWeapon(x, y, itemId, prefix?)Set mannequin weapon
clearWeapon(x, y)Clear mannequin weapon
setPose(x, y, poseId)Set mannequin pose (0–8)
getPose(x, y) → intGet mannequin pose
setHatRackItem(x, y, slot, itemId, prefix?)Set hat rack item
clearHatRackItem(x, y, slot)Clear hat rack item
setHatRackDye(x, y, slot, dyeId, prefix?)Set hat rack dye
clearHatRackDye(x, y, slot)Clear hat rack dye
setLogicSensor(x, y, logicCheck, on)Set logic sensor
setTrainingDummyNpc(x, y, npcId)Set training dummy NPC

See the full API reference for details and examples.

sprites — Multi-tile Sprite Placement

MethodDescription
listSprites() → [{tileId, name, styleCount}]List all sprite sheets
getStyles(tileId) → [{index, name, width, height}]Get styles for a tile type
place(tileId, styleIndex, x, y) → boolPlace sprite by tile ID and style
placeByName(name, x, y) → boolPlace first style matching name

draw — Drawing Tools

MethodDescription
setTile(tileType)Set tile type to paint
setWall(wallType)Set wall type to paint
setErase(bool)Enable/disable eraser mode
setPaintMode(mode)Set mode: 'tile', 'wire', 'liquid'
setTileColor(color)Set tile paint color (0 = off)
setWallColor(color)Set wall paint color (0 = off)
setBrickStyle(style)'full', 'half', 'topright', etc.
setActuator(enabled, inactive?)Set actuator state
setTileCoating(echo?, illuminant?)Set tile coating
setWallCoating(echo?, illuminant?)Set wall coating
setLiquid(type, amount?)Type: 'water'/'lava'/'honey'/'shimmer'
setWire(red?, blue?, green?, yellow?)Set wire paint flags
setTileMask(mode, tileType?)'off', 'match', 'empty', 'notMatching'
setWallMask(mode, wallType?)Set wall mask mode
setBrush(width, height, shape?)Shape: 'square', 'round', 'right', 'left'
setBrushOutline(outline, enabled)Set brush outline
reset()Reset picker and brush to defaults
pencil(x1, y1, x2, y2)Draw 1px line
brush(x1, y1, x2, y2)Draw brush-width line
fill(x, y)Flood fill from point
hammer(x1, y1, x2, y2)Auto-slope tiles along line

generate — Procedural Tree & Forest Generation

MethodDescription
listTreeTypes() → [{name, tileId}]List supported tree types
tree(type, x, y) → boolPlace a single tree at ground position
forest(types[], x, y, w, h, density?) → intPlace random trees in rectangle (density 0.0–1.0, default 0.15)
forestInSelection(types[], density?) → intPlace random trees in current selection
findSurface(x, yStart, yEnd) → intScan downward for first solid tile; returns y or -1

Tree types: oak, palm, mushroom, topaz, amethyst, sapphire, emerald, ruby, diamond, amber, sakura, willow, ash