Deluxe Pixel scripting reference (Luau)
Written by Deluxe Pixel 2.41 from its own command list, Luau build and examples, so it matches that version exactly.
For everything else, see the Deluxe Pixel guide. Every command is also available as JSON tool definitions (the form AI assistants see through Edit > AI access): deluxepixel_commands.json.
Getting started
Scripts are written in Luau (a fast, typed, sandboxed form of Lua 5.1). Open Edit > Script console, type a script and press Run (Ctrl+Enter). Everything a script does to a picture is one undo step.
-- A red square, then say how big the picture is.
dp.palette.set{ index = 1, colour = "#FF0000" }
dp.draw.rect{ x = 8, y = 8, w = 16, h = 16, colour = 1, filled = true }
print(`The picture is {dp.width()} x {dp.height()}`)
- Commands: every action is a command, called as
dp.<group>.<name>{ settings }- the same commands as the command palette (Ctrl+Shift+P), recorded macros and AI assistants (Edit > AI access) use. - Where scripts live: the
scriptsfolder in Deluxe Pixel's settings folder (Edit > Scripts lists them, one click each; Edit > Scripts > Open the scripts folder). Edit > Run a script runs any.luauor.luafile; the commandscript.runruns one from a macro. - Macros as scripts: Edit > Stop recording, saved as a
.luaufile, writes the recorded steps as a script. - Output:
print(...)writes to the console's Output (values separated by tabs). - Stopping: Escape stops a running script; one that runs for more than 30 seconds is stopped.
Calling commands
dp.draw.rect{ x = 10, y = 20, w = 32, h = 16, colour = 5, filled = true } -- settings by name
dp.layer.add() -- no settings
local message = dp.run("draw.pixel", { x = 1, y = 1, colour = 2 }) -- by name
local ok, why = dp.try("layer.delete") -- never stops the script
- Settings are named (a table:
{ x = 1, y = 2 }). Every setting is checked before the command runs: an unknown setting, a missing one that's needed, a value of the wrong kind or out of range all stop the script with the reason and the line (script:12: dp.draw.rect needs w (whole number).) - nothing is half done. - Values: numbers,
true/false, or text. By type:
| Type | Give | Notes |
|---|---|---|
| whole number | 5, -3 |
Within the setting's range. |
| number | 2.5 |
Within the setting's range. |
| true or false | true, false |
Also the text "yes"/"no", "on"/"off", "1"/"0". |
| text | "Sky" |
Any text. |
| choice | "Posterise" |
One of the setting's choices (capitals don't matter). |
| colour | "#FF8800" |
#RRGGBB. |
| path | "C:/Art/hero.dpx" |
A file. |
- What a command gives back: its message as text (often empty; the
info.*questions give their answer). If it can't run - no picture open, the layer locked, a colour not in the palette - the script stops with the command's own reason.dp.trygivesok, messageinstead and carries on;pcallcatches it too. - Undo: a whole script run is one undo step (labelled
Script: <name>), however many commands it runs. - Names: in Luau a command is
dp.<group>.<name>; by name (dp.run, dp.try, macros) it's"group.name"; AI assistants see it asgroup_name.
The dp helpers
Quick questions about the active picture, answered as Luau values. With no picture open (or a map in the active tab) the picture questions stop the script with "there's no picture open."
| Helper | What it does and gives back |
|---|---|
message = dp.run("group.name", { settings }) |
Runs a command by its name (as dp.group.name{ settings } does). Returns the command's message (text, often empty); if the command can't run, the script stops with the command's own error. |
ok, message = dp.try("group.name", { settings }) |
Runs a command by its name without ever stopping the script. Returns ok (true or false) and a message: the command's message, or why it couldn't run. |
dp.width() |
The picture's width in pixels (a number); a tile map's, cells x tile width. |
dp.height() |
The picture's height in pixels (a number); a tile map's, cells x tile height. |
dp.colours() |
How many colours the palette has (a number). |
dp.layers() |
How many layers the picture has (a number). |
dp.active_layer() |
The active layer's number (0 is the bottom layer). |
dp.transparent() |
The see-through colour's number. |
dp.pixel(x, y) |
The active layer's pixel at x, y. A palette picture: its colour number. A true-colour picture: "#RRGGBB", or nil where it's see-through. Outside the picture: -1. |
dp.colour(i) |
Palette colour i as "#RRGGBB". A number outside the palette stops the script ("the palette has N colours."). |
dp.commands() |
Every command's name ("layer.add"...) as a list, in the order the command palette has them. |
buf, w, h = dp.pixels() |
The whole active layer at once, as a buffer (fast: no command a pixel). A palette picture: 1 byte a pixel, its colour number; a true-colour picture: 4 bytes a pixel, R G B A. Pixel x, y is at offset (y * w + x) (times 4 for true colour). Also gives the width and height. |
dp.set_pixels(buf) |
Writes a buffer like dp.pixels() gives back to the active layer, as one undo step; with a selection, only the pixels inside it change. Stops the script if the size is wrong, a colour number isn't in the palette, or the layer is locked. |
id = dp.on("event", function(...) ... end) |
Calls the function each time the event happens ("frame", "key", "mouse", "picture"... see Events). The script keeps running after its last line until it's stopped. Returns an id for dp.off. |
dp.off(id) |
Stops one handler (or task) that dp.on (or dp.spawn) gave the id of. Returns true if there was one. |
id = dp.spawn(function(...) ... end, ...) |
Runs the function alongside Deluxe Pixel from the next frame, with the arguments given; inside it, dp.wait pauses it. The script keeps running until it's stopped. |
waited = dp.wait(seconds) |
Inside a task (dp.spawn): goes on after that many seconds (left out: the next frame), and gives back how long it was. Deluxe Pixel carries on meanwhile. |
dp.stop() |
Stops this script: its handlers and tasks end (its "stop" handlers run first). |
dp.live() |
How many handlers and tasks this script has (0: it ends after its last line). |
p = dp.pad(n) |
Game controller n (1 if left out) now, or nil if there isn't one: p.lx, p.ly, p.rx, p.ry the sticks (-1..1; y is down, as the picture's pixels), p.lt, p.rt the triggers (0..1), p.a, p.b, p.x, p.y, p.lb, p.rb, p.back, p.start, p.ls, p.rs and the d-pad p.up, p.down, p.left, p.right (true while held), p.pressed and p.released (the buttons that went down or up since the last frame, as p.pressed.a), p.name. Xbox-style pads and most other USB pads. |
dp.pads() |
How many game controllers are connected. |
img = dp.image(x, y, w, h, picture) |
Cuts an image (a character, an enemy, a bullet) from a picture, to show over the canvas with dp.show. picture is an open picture's name; left out, the map's tile set when a map is in front, else the picture in front. Its see-through colour stays see-through. Gone when the script stops. |
img = dp.tile_image(n) |
Tile n (from 0) of the tile set - the map's, when a map is in front - as an image for dp.show. |
dp.show(img, x, y, flipx, flipy) |
Shows an image over the canvas this frame, its top-left at picture (or map) pixel x, y, mirrored if flipx / flipy. Nothing is drawn into the picture: the overlay is cleared every frame, so show things from a frame handler. Positions can be fractions (it's drawn at the pixel it's in). |
dp.box(x, y, w, h, colour, filled) |
A rectangle over the canvas this frame: colour a palette number or "#RRGGBB" ("#RRGGBBAA" see-through); filled = false for just the outline. |
dp.say(x, y, text, colour) |
Text over the canvas this frame, at picture pixel x, y (a score, a message); white if colour is left out. |
dp.camera(x, y) |
Centres the view on picture (or map) pixel x, y, keeping the zoom: the view follows a character. With nothing given, gives back the centre and the size shown: x, y, w, h. |
m = dp.map_info() |
The map in front (nil if it isn't a map): m.width, m.height in cells, m.tile_w, m.tile_h in pixels, m.layers, m.layer (the active one, from 0). |
tile, attr, flipx, flipy = dp.cell(cx, cy, layer) |
A map cell (cx, cy in cells, from 0): its tile number (-1 empty) and attribute bits (solid, ladder... as the map gives them). layer left out: the active layer. nil outside the map. |
anim = dp.sprite("name", "animation", picture) |
One of the sprite system's sprites (Window > Sprite timeline) and one of its animations (the first if left out), as images for dp.show_anim: anim.frames (each image, x, y from the sprite's origin, w, h, time in seconds), anim.length, anim.loop ("loop", "pingpong", "once"). Looked for in the picture named, else the picture in front, the map's tile set, then every open picture. |
dp.show_anim(anim, x, y, t, flipx) |
Shows the frame of a dp.sprite animation that's showing t seconds in, with the sprite's origin (its hot spot) at x, y; flipx mirrors it round the origin (facing left). |
dp.sprites() |
Every sprite in the open pictures, as "picture: sprite (animation, ...)". |
dp.show_screen(img, x, y, scale) |
Shows an image this frame in the view's own pixels rather than the picture's: its top-left x, y from the view's top-left, scale screen pixels an image pixel. With x, y and scale left out it's as big as fits the view, in the middle (a first-person view from dp.raycast or dp.mode7 filling the window); x, y left out alone: centred. |
img = dp.raycast(x, y, angle, opts) |
The map in first person (as Wolfenstein 3D), standing at map pixel x, y and looking along angle (degrees: 0 right, 90 down, as the map is drawn): the cells with a tile are walls drawn with their tiles (opts.solid: only cells whose attribute has one of those bits; opts.open: tile numbers that are never walls, the floor's), the floor from the tiles of the cells that aren't walls. opts: w, h (320 x 200), fov (66), floor, ceiling (colours), ceiling_tile, floor_tiles (true), distance (64 cells), fog (0-1), fog_colour, eye (0.5 of a wall), shade (true), layer, sprites = { {image = img, x = .., y = .., size = 1 (cells high), lift = 0}, ... } standing in it, and into = img to draw into an image made before (do, every frame: view = dp.raycast(x, y, a, {into = view})). Doors: opts.doors = { {x = cell, y = cell, open = 0 to 1, along = "x" or "y" (left out: from the walls beside it)}, ... }: a thin wall across the middle of its cell, drawn with the cell's tile, sliding aside as it opens. Moving walls (push walls): opts.moving = { {x = cell, y = cell, dx = .., dy = ..}, ... }: that cell's wall moved so far, in cells. Keep them in the script and change open / dx / dy each frame. opts.gpu = true draws it on the graphics card (much faster, so it can be as big as the window: w and h up to 3840 x 2160; where there's no graphics card it's drawn as usual - dp.gpu() says which). A view drawn on the graphics card can be shown but can't be a sprite in another view. Show it with dp.show_screen. |
dp.gpu() |
True when views can be drawn on the graphics card here (dp.raycast's gpu option). |
dp.wall_at(x, y, opts) |
True if map pixel x, y is inside a wall as dp.raycast draws them: opts.solid (the attribute bits that make a wall; left out, any tile), opts.open (floor tiles), opts.layer - the same as given to dp.raycast, with its doors (solid until three quarters open) and moving walls (solid where they are now). For walking: don't step into one. |
img = dp.raised(x, y, opts) |
The map raised in 3D, centred on map pixel x, y: each cell a stack of cubes as high as its height (steps), its top drawn with its tile and its sides with the tile shaded; height 0 is flat floor, an empty cell nothing. Heights from opts.height_bits (attribute bits read as a number), opts.heights = { [tile] = height, ... }, or opts.cells = { one per cell, row by row; -1: as the others say }. opts: view = "iso" (a diamond grid: the default) or "angle" (from above, tops and fronts), w, h (320 x 200), cell_w, cell_h (a top's size on screen), step (pixels a height step), background, shade (true), layer, sprites = { {image = img, x = .., y = .., size = 1 (screen pixels an image pixel), lift = 0 (steps)}, ... } standing on the cells, into = img. Show it with dp.show_screen. (Called dp.blocks in 1.70: that name still works.) |
sx, sy, height = dp.raised_point(mx, my, x, y, opts) |
Where map pixel mx, my shows in dp.raised's view centred on x, y (on top of the cell there; opts.lift adds steps), and that cell's height - the same opts as dp.raised. For a cursor, a shadow or clicking on a cell. |
img = dp.mode7(x, y, angle, opts) |
The map laid flat and seen from above it at an angle, as the SNES's Mode 7 (kart racers, flying over a world map): the camera at map pixel x, y, looking along angle (0 right, 90 down). opts: w, h (320 x 200), fov (60), height (48 map pixels above it), horizon (the row: a third of the way down), sky, outside (colours), wrap (the map repeating for ever), fog (0-1), layer, sprites = { {image = img, x = .., y = .., size = 1, lift = 0}, ... } standing on it, into = img. Show it with dp.show_screen. |
m = dp.model("name", picture) |
One of a picture's 3D models (brought in with File > 3D model), to show while playing: m.name, m.anims (its animations' names), m.lengths (seconds), m.w, m.h (its size: a live 3D object's box if the picture has one using it, which also gives its light and look). Looked for in the picture named, else the picture in front, the map's tile set, then every open picture. |
img, w, h = dp.model_image(m, opts) |
A dp.model drawn as an image, turned and posed: opts.facing (the way it's going on the map: 0 right, 90 down, 180 left, 270 up) or opts.yaw (0 facing you), opts.directions (snap to 8 or 16 directions, as pre-rendered sprites), opts.pitch, opts.anim (a name or number), opts.time (seconds into it), opts.w, opts.h. Each pose is drawn once and kept, so call it every frame. For dp.show or as a sprite in dp.raycast and dp.mode7. |
dp.show_model(m, x, y, opts) |
Shows a dp.model over the canvas this frame, centred on picture (map) pixel x, y (opts.feet = true: standing there), turned and posed as dp.model_image says: a 3D character facing the way it walks. |
dp.cycle(on) |
Turns the view's colour cycling on (or off with false): a level's animated water and lights while playing. |
tile, attr, cx, cy = dp.cell_at(x, y, layer) |
The map cell under map pixel x, y (for collisions: what's under a character's feet), and which cell it is. nil outside the map. |
dp.key("Left") |
True while that key is held down (names as the key event gives them: "A", "7", "Space", "Enter", "Left", "LeftShift", "F5"...). For moving things smoothly in a frame handler. |
print(...) |
Writes its values to the Output, separated by tabs. |
Commands
Every command, by group: what it does, and every setting (needed ones must be given; the others have the default shown, or are left as they are).
draw
Drawing on the active layer of the active picture with colour numbers (a palette picture). Painting stays inside the selection if there is one. Each command is one undo step (a whole script run is one step too).
dp.draw.pixel{ } - Plot a pixel
Sets one pixel of the active layer to a colour number.
Changes things (one undo step). By name: "draw.pixel". For AI: draw_pixel.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
x |
whole number | needed | Across, from the left (0 is the first column). | |
y |
whole number | needed | Down, from the top. | |
colour |
whole number | needed | 0 to 255 | The colour number to paint with (the picture's see-through colour, usually 0, paints empty: info.picture says which). |
dp.draw.line{ } - Draw a line
A line between two points on the active layer (size: its thickness).
Changes things (one undo step). By name: "draw.line". For AI: draw_line.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
x0 |
whole number | needed | Start, across. | |
y0 |
whole number | needed | Start, down. | |
x1 |
whole number | needed | End, across. | |
y1 |
whole number | needed | End, down. | |
colour |
whole number | needed | 0 to 255 | The colour number to paint with (the picture's see-through colour, usually 0, paints empty: info.picture says which). |
size |
whole number | 1 |
1 to 64 | Line thickness in pixels (1: one pixel; more: a round pen that wide). |
pen |
one of the choices | round |
round, square |
The pen's shape for thicker lines. |
dp.draw.rect{ } - Draw a rectangle
A rectangle on the active layer, outlined (size: the line's thickness, centred on the edge) or filled.
Changes things (one undo step). By name: "draw.rect". For AI: draw_rect.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
x |
whole number | needed | Left edge. | |
y |
whole number | needed | Top edge. | |
w |
whole number | needed | 1 to 1,000,000 | Width in pixels. |
h |
whole number | needed | 1 to 1,000,000 | Height in pixels. |
colour |
whole number | needed | 0 to 255 | The colour number to paint with (the picture's see-through colour, usually 0, paints empty: info.picture says which). |
filled |
true or false | false |
Filled, or just the outline. | |
size |
whole number | 1 |
1 to 64 | Line thickness in pixels (1: one pixel; more: a round pen that wide). |
pen |
one of the choices | round |
round, square |
The pen's shape for thicker lines. |
dp.draw.ellipse{ } - Draw an ellipse
An ellipse (or circle) on the active layer, outlined (size: the line's thickness, centred on the edge) or filled, by its centre and radii.
Changes things (one undo step). By name: "draw.ellipse". For AI: draw_ellipse.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
cx |
whole number | needed | Centre, across. | |
cy |
whole number | needed | Centre, down. | |
rx |
whole number | needed | 0 to 1,000,000 | Radius across. |
ry |
whole number | - | 0 to 1,000,000 | Radius down (left out: the same as rx, a circle). |
colour |
whole number | needed | 0 to 255 | The colour number to paint with (the picture's see-through colour, usually 0, paints empty: info.picture says which). |
filled |
true or false | false |
Filled, or just the outline. | |
size |
whole number | 1 |
1 to 64 | Line thickness in pixels (1: one pixel; more: a round pen that wide). |
pen |
one of the choices | round |
round, square |
The pen's shape for thicker lines. |
dp.draw.fill{ } - Flood fill
Fills the area of one colour around a point on the active layer (4-connected).
Changes things (one undo step). By name: "draw.fill". For AI: draw_fill.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
x |
whole number | needed | Where, across. | |
y |
whole number | needed | Where, down. | |
colour |
whole number | needed | 0 to 255 | The colour number to paint with (the picture's see-through colour, usually 0, paints empty: info.picture says which). |
palette
The palette: setting colours and making ramps.
dp.palette.set{ } - Set a palette colour
Changes one colour of the palette (every pixel using it changes).
Changes things (one undo step). By name: "palette.set". For AI: palette_set.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
index |
whole number | needed | 0 to 255 | Which colour number. |
colour |
colour (#RRGGBB) | needed | The new colour, like #FF8800. | |
set |
whole number | 0 |
0 to 255 | Which palette set (0: the main palette). |
dp.palette.transparent{ } - See-through colour
Chooses which colour number is see-through: pixels in it show as empty (lower layers through them) and export as transparent. For a solid background colour, pick a colour you don't draw with.
Changes things (one undo step). By name: "palette.transparent". For AI: palette_transparent.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
index |
whole number | needed | 0 to 255 | The colour number to be see-through. |
dp.palette.ramp{ } - Make a colour ramp
Blends the colours between two palette entries smoothly from one to the other.
Changes things (one undo step). By name: "palette.ramp". For AI: palette_ramp.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
from |
whole number | needed | 0 to 255 | The first colour number. |
to |
whole number | needed | 0 to 255 | The last colour number. |
set |
whole number | 0 |
0 to 255 | Which palette set (0: the main palette). |
dp.palette.lock{ } - Lock the palette to its colours
Locks the palette of the picture in front to the colours it was opened with (or a given number): colours can be changed but none added, pixels in any later colours become the nearest of them, and a true-colour picture paints in them and converts back to them. One undo step.
Changes things (one undo step). By name: "palette.lock". For AI: palette_lock.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
on |
true or false | true |
Lock (true) or unlock (false). | |
colours |
whole number | - | 2 to 256 | How many colours to keep to (left out: the colours the file was opened with, or all it has now). |
layer
Layers: adding, choosing, showing, flipping, merging.
dp.layer.select{ } - Choose the active layer
Makes a layer the one painting goes to (0 is the bottom layer).
Changes things (one undo step). By name: "layer.select". For AI: layer_select.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
index |
whole number | needed | 0 to 255 | The layer's number from the bottom (0 first). |
dp.layer.show{ } - Show or hide a layer
Turns a layer's visibility on or off.
Changes things (one undo step). By name: "layer.show". For AI: layer_show.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
index |
whole number | needed | 0 to 255 | The layer's number from the bottom (0 first). |
visible |
true or false | true |
Shown or hidden. |
dp.layer.flip{ } - Flip the layer
Mirrors the active layer's pixels, across (x) or upside down (y), as one undo step.
Changes things (one undo step). By name: "layer.flip". For AI: layer_flip.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
axis |
one of the choices | needed | x, y |
x: mirror left to right; y: upside down. |
dp.layer.add{ } - New layer
A new empty layer above the active one (it becomes active).
Changes things (one undo step). By name: "layer.add". For AI: layer_add.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
name |
text | - | Its name (left out: Layer 2, 3...). |
dp.layer.duplicate() - Duplicate layer
A copy of the active layer above it.
Changes things (one undo step). By name: "layer.duplicate". For AI: layer_duplicate.
No settings.
dp.layer.delete() - Delete layer
Removes the active layer (a picture keeps at least one).
Changes things (one undo step). By name: "layer.delete". For AI: layer_delete.
No settings.
dp.layer.up() - Move layer up
Moves the active layer one place up the stack.
Changes things (one undo step). By name: "layer.up". For AI: layer_up.
No settings.
dp.layer.down() - Move layer down
Moves the active layer one place down the stack.
Changes things (one undo step). By name: "layer.down". For AI: layer_down.
No settings.
dp.layer.rename{ } - Rename layer
Gives a layer a new name.
Changes things (one undo step). By name: "layer.rename". For AI: layer_rename.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
name |
text | needed | The new name. | |
index |
whole number | - | 0 to 255 | Which layer from the bottom (left out: the active one). |
dp.layer.blend{ } - Layer blend and opacity
Sets how a layer's colours go onto the layers beneath: its blend mode and opacity. Palette pictures draw each mix in the nearest palette colour.
Changes things (one undo step). By name: "layer.blend". For AI: layer_blend.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
mode |
one of the choices | - | normal, multiply, screen, overlay, add, subtract, darken, lighten, difference |
The blend mode (left out: as it is). |
opacity |
whole number | - | 0 to 100 | How much shows, 0-100 percent (left out: as it is). |
index |
whole number | - | 0 to 255 | Which layer from the bottom (left out: the active one). |
dp.layer.merge_down() - Merge down
Merges the active layer into the one below it.
Changes things (one undo step). By name: "layer.merge_down". For AI: layer_merge_down.
No settings.
frame
Animation frames.
dp.frame.duration{ } - Frame duration
How long the current frame (or every frame) shows, in milliseconds.
Changes things (one undo step). By name: "frame.duration". For AI: frame_duration.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
ms |
whole number | needed | 1 to 65,535 | Milliseconds. |
all |
true or false | false |
Every frame, not just this one. |
dp.frame.add{ } - New frame
Adds an animation frame after the current one and goes to it.
Changes things (one undo step). By name: "frame.add". For AI: frame_add.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
copy |
true or false | true |
A copy of the current frame (or empty). |
dp.frame.delete() - Delete frame
Removes the current frame (a picture keeps at least one).
Changes things (one undo step). By name: "frame.delete". For AI: frame_delete.
No settings.
dp.frame.go{ } - Go to a frame
Shows an animation frame (0 is the first).
Changes things (one undo step). By name: "frame.go". For AI: frame_go.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
index |
whole number | needed | 0 to 1,000,000 | The frame. |
select
Selections (painting then stays inside them).
dp.select.all() - Select all
Selects the whole picture (a map: every cell of the active layer).
Changes things (one undo step). By name: "select.all". For AI: select_all.
No settings.
dp.select.none() - Deselect
Removes the selection.
Changes things (one undo step). By name: "select.none". For AI: select_none.
No settings.
dp.select.invert() - Invert selection
Selects what wasn't, and deselects what was.
Changes things (one undo step). By name: "select.invert". For AI: select_invert.
No settings.
dp.select.wand{ } - Magic wand
Selects the pixel at x, y and the pixels like it: those joined to it (side by side), or that colour anywhere with joined = false. Palette pictures compare colour numbers (with a tolerance, the colours themselves). On a map, x and y are a cell of the active layer, and the cells showing the same tile are selected.
Changes things (one undo step). By name: "select.wand". For AI: select_wand.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
x |
whole number | needed | Across. | |
y |
whole number | needed | Down. | |
tolerance |
whole number | - | 0 to 100 | How different a colour may be and still count, 0-100% (0: the same colour only). |
joined |
true or false | - | Only pixels joined to this one (false: that colour anywhere). | |
layers |
one of the choices | - | active, visible |
Look at the active layer, or at what shows (the visible layers together). |
mode |
one of the choices | replace |
replace, add, take away, overlap |
Replace the selection, add to it, or take away from it. |
dp.select.tile{ } - Select every cell of a tile
A map: selects every cell of the active layer showing this tile (flips and palette bank as given).
Changes things (one undo step). By name: "select.tile". For AI: select_tile.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
tile |
whole number | needed | 0 to ... | The tile number. |
flipx |
true or false | false |
Mirrored across. | |
flipy |
true or false | false |
Mirrored down. | |
palette |
whole number | 0 |
0 to 255 | Palette bank. |
mode |
one of the choices | replace |
replace, add, take away, overlap |
Replace the selection, add to it, or take away from it. |
dp.select.fill() - Fill the selection
Pictures: paints the selection in the left colour (with the paint mode). Maps: fills the selected cells with the tile brush, tiled from the selection's top-left corner.
Changes things (one undo step). By name: "select.fill". For AI: select_fill.
No settings.
dp.select.colour{ } - Select by colour
Selects every pixel of a colour: a colour number (palette pictures) or a colour (true colour), with the magic wand's tolerance and layers.
Changes things (one undo step). By name: "select.colour". For AI: select_colour.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
index |
whole number | - | 0 to 255 | The colour number (palette pictures). |
colour |
colour (#RRGGBB) | - | The colour (true-colour pictures). | |
tolerance |
whole number | - | 0 to 100 | How different a colour may be and still count, 0-100% (0: the same colour only). |
layers |
one of the choices | - | active, visible |
Look at the active layer, or at what shows (the visible layers together). |
mode |
one of the choices | replace |
replace, add, take away, overlap |
Replace the selection, add to it, or take away from it. |
dp.select.rect{ } - Select a rectangle
Selects a rectangle of the picture (painting then stays inside it); on a map, of the active layer's cells.
Changes things (one undo step). By name: "select.rect". For AI: select_rect.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
x |
whole number | needed | Left edge. | |
y |
whole number | needed | Top edge. | |
w |
whole number | needed | 1 to 1,000,000 | Width. |
h |
whole number | needed | 1 to 1,000,000 | Height. |
filter
Filters and adjustments, as in the Image menu.
dp.filter.apply{ } - Apply a filter
Runs a filter or colour adjustment on the active layer (inside the selection if there is one), as one undo step. Settings left out keep the filter's own defaults.
Changes things (one undo step). By name: "filter.apply". For AI: filter_apply.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
name |
one of the choices | needed | Brightness and contrast, Hue and saturation, Colour balance, Posterise, Invert, Greyscale, Fit to palette, Blur, Sharpen, Noise, Pixelate, Outline, Drop shadow, Clean up stray pixels, Grow or shrink, Levels, Curves, Replace colours, Gradient map, Rotate, Auto-shade, Exposure, White balance, Vibrance, Shadows and highlights, Split toning, Sepia, Duotone, Selective colour, Colour look-up (.cube), Film grain, Vignette, Smooth, keeping edges, Oil paint, Toon (cel shading), Pencil or ink sketch, Halftone, Posterise smoothly, Punch for few colours |
Which filter (info.filters lists them with what a, b, c, n... mean for each). |
a |
number | - | The filter's first setting (see info.filters). | |
b |
number | - | Its second setting. | |
c |
number | - | Its third setting. | |
n |
whole number | - | Its whole-number setting (levels, size, radius...). | |
colour |
whole number | - | 0 to 255 | A colour number it uses (outline, shadow). |
dx |
whole number | - | Shadow offset across. | |
dy |
whole number | - | Shadow offset down. | |
flag |
true or false | - | Its on/off setting (see info.filters). | |
dither |
one of the choices | - | None, Ordered, Floyd-Steinberg, Atkinson |
Palette pictures: how the result is fitted back to the palette. |
mix |
number | 100 |
0 to 100 | How much of the result: 0 none to 100 all. |
to |
one of the choices | pixels |
pixels, palette |
Colour adjustments on a palette picture: change the pixels (fitted back), or just the palette's colours. |
settings |
text | - | Everything at once as the Image menu's recipes save it (overrides the rest). |
What each filter's settings mean:
Filter (name) |
Its settings |
|---|---|
Brightness and contrast |
a = brightness -100..100, b = contrast -100..100, c = gamma 0.2..5. |
Hue and saturation |
a = hue -180..180, b = saturation -100..100, c = lightness -100..100. |
Colour balance |
a, b, c = red, green, blue -100..100. |
Posterise |
n = levels per channel 2..16. |
Invert |
no settings. |
Greyscale |
no settings. |
Fit to palette |
fits to the palette with dithering (dither); nothing else changes. |
Blur |
a = radius 0.5..16. |
Sharpen |
a = amount 0..300 (%), b = radius 0.5..8. |
Noise |
a = amount 0..100, flag = the same noise on every channel. |
Pixelate |
n = the size of the big pixels, 2..64. |
Outline |
colour, n = thickness 1..8, flag = inside the shapes. |
Drop shadow |
colour, dx, dy = offset. |
Clean up stray pixels |
removes stray pixels: n = 0 lone pixels only, 1 also thin spurs. |
Grow or shrink |
n = -8..8 (grow by n, or shrink by -n). |
Levels |
use settings= (channel, inblack, inwhite, c = gamma, outblack, outwhite). |
Curves |
use settings= (channel, curve = x,y pairs 0..255). |
Replace colours |
use settings= (replace = from,to pairs); a = tolerance 0..100. |
Gradient map |
brightness onto colours first..last (settings=); flag = reversed. |
Rotate |
a = degrees; flag = clean (pixel-art rotation). |
Auto-shade |
a = light from (degrees, 0 right, 90 above), b = strength, n = edge width; flag = lighten and darken. |
Exposure |
a = stops -4..4. |
White balance |
a = temperature -100 (cooler)..100 (warmer), b = tint -100 (green)..100 (magenta). |
Vibrance |
a = vibrance -100..100, b = saturation -100..100. |
Shadows and highlights |
a = lift shadows 0..100, b = recover highlights 0..100. |
Split toning |
a = shadow hue, b = highlight hue (degrees), c = balance -100..100, n = strength 0..100. |
Sepia |
a = strength 0..100. |
Duotone |
a = dark hue, b = light hue (degrees), c = saturation 0..100. |
Selective colour |
n = 0 reds, 1 yellows, 2 greens, 3 cyans, 4 blues, 5 magentas; a = hue shift -60..60, b = saturation, c = lightness -100..100. |
Colour look-up (.cube) |
a .cube file (settings= with lut=path); a = strength 0..100. |
Film grain |
a = amount 0..100, b = size 1..8, flag = colour grain. |
Vignette |
a = -100..100 (darken edges if above 0), b = clear middle 0..100, c = softness 0..100. |
Smooth, keeping edges |
n = radius 1..6, a = edge threshold 5..100. |
Oil paint |
n = radius 1..8. |
Toon (cel shading) |
n = tones 2..8, a = outlines 0..100, b = outline darkness 0..100, c = smoothing 0..4. |
Pencil or ink sketch |
n = 0 pencil, 1 ink; a = radius 1..16, b = darkness or detail 0..100; flag = keep the colours. |
Halftone |
n = 0 dots, 1 lines; a = cell size 3..32, b = angle 0..90; flag = colour. |
Posterise smoothly |
n = levels 2..16, c = smoothing radius 0..6, a = edge threshold 5..100; flag = remove specks. |
Punch for few colours |
a = contrast 0..100, b = saturation 0..100, c = de-mud 0..100. |
info
Questions: they change nothing and answer as text.
dp.info.filters() - List the filters
Each filter's name and what its settings mean (for filter.apply).
A question: changes nothing. Not written into recorded macros. By name: "info.filters". For AI: info_filters.
No settings.
For example, it answered:
Brightness and contrast: a = brightness -100..100, b = contrast -100..100, c = gamma 0.2..5.
Hue and saturation: a = hue -180..180, b = saturation -100..100, c = lightness -100..100.
Colour balance: a, b, c = red, green, blue -100..100.
Posterise: n = levels per channel 2..16.
Invert: no settings.
Greyscale: no settings.
Fit to palette: fits to the palette with dithering (dither); nothing else changes.
Blur: a = radius 0.5..16.
Sharpen: a = amount 0..300 (%), b = radius 0.5..8.
Noise: a = amount 0..100, flag = the same noise on every channel.
Pixelate: n = the size of the big pixels, 2..64.
Outline: colour, n = thickness 1..8, flag = inside the shapes.
Drop shadow: colour, dx, dy = offset.
Clean up stray pixels: removes stray pixels: n = 0 lone pixels only, 1 also thin spurs.
Grow or shrink: n = -8..8 (grow by n, or shrink by -n).
Levels: use settings= (channel, inblack, inwhite, c = gamma, outblack, outwhite).
Curves: use settings= (channel, curve = x,y pairs 0..255).
Replace colours: use settings= (replace = from,to pairs); a = tolerance 0..100.
Gradient map: brightness onto colours first..last (settings=); flag = reversed.
Rotate: a = degrees; flag = clean (pixel-art rotation).
Auto-shade: a = light from (degrees, 0 right, 90 above), b = strength, n = edge width; flag = lighten and darken.
Exposure: a = stops -4..4.
White balance: a = temperature -100 (cooler)..100 (warmer), b = tint -100 (green)..100 (magenta).
Vibrance: a = vibrance -100..100, b = saturation -100..100.
Shadows and highlights: a = lift shadows 0..100, b = recover highlights 0..100.
Split toning: a = shadow hue, b = highlight hue (degrees), c = balance -100..100, n = strength 0..100.
Sepia: a = strength 0..100.
Duotone: a = dark hue, b = light hue (degrees), c = saturation 0..100.
Selective colour: n = 0 reds, 1 yellows, 2 greens, 3 cyans, 4 blues, 5 magentas; a = hue shift -60..60, b = saturation, c = lightness -100..100.
Colour look-up (.cube): a .cube file (settings= with lut=path); a = strength 0..100.
Film grain: a = amount 0..100, b = size 1..8, flag = colour grain.
Vignette: a = -100..100 (darken edges if above 0), b = clear middle 0..100, c = softness 0..100.
Smooth, keeping edges: n = radius 1..6, a = edge threshold 5..100.
Oil paint: n = radius 1..8.
Toon (cel shading): n = tones 2..8, a = outlines 0..100, b = outline darkness 0..100, c = smoothing 0..4.
Pencil or ink sketch: n = 0 pencil, 1 ink; a = radius 1..16, b = darkness or detail 0..100; flag = keep the colours.
Halftone: n = 0 dots, 1 lines; a = cell size 3..32, b = angle 0..90; flag = colour.
Posterise smoothly: n = levels 2..16, c = smoothing radius 0..6, a = edge threshold 5..100; flag = remove specks.
Punch for few colours: a = contrast 0..100, b = saturation 0..100, c = de-mud 0..100.
dp.info.picture() - About the picture
Its size, colours, layers, frames and active layer.
A question: changes nothing. Not written into recorded macros. By name: "info.picture". For AI: info_picture.
No settings.
For example, it answered:
320 x 200, 256 colours, 1 layers (active 0: Background), 1 frames (current 0), system Generic, mode 320 x 200, 256 colours. Colour 0 is the see-through (transparent) colour: pixels in it show as empty and export as transparent (palette.transparent picks another).
dp.info.palette{ } - List the palette
Every colour number and its colour, as #RRGGBB.
A question: changes nothing. Not written into recorded macros. By name: "info.palette". For AI: info_palette.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
set |
whole number | 0 |
0 to 255 | Which palette set (0: the main palette). |
For example, it answered:
0=#000000 1=#000055 2=#0000AA 3=#0000FF 4=#002400 5=#002455 6=#0024AA 7=#0024FF 8=#004900 9=#004955 10=#0049AA 11=#0049FF 12=#006D00 13=#006D55 14=#006DAA 15=#006DFF 16=#009200 17=#009255 18=#0092AA 19=#0092FF 20=#00B600 21=#00B655 22=#00B6AA 23=#00B6FF 24=#00DB00 25=#00DB55 26=#00DBAA 27=#00DBFF 28=#00FF00 29=#00FF55 30=#00FFAA 31=#00FFFF 32=#240000 33=#240055 34=#2400AA 35=#2400FF 36=#242400 37=#242455 38=#2424AA 39=#2424FF 40=#244900 41=#244955 42=#2449AA 43=#2449FF 44=#246D00 45=#246D55 46=#246DAA 47=#246DFF 48=#249200 49=#249255 50=#2492AA 51=#2492FF 52=#24B600 53=#24B655 54=#24B6AA ...
dp.info.layers() - List the layers
Each layer from the bottom: number, name, shown or hidden, locked.
A question: changes nothing. Not written into recorded macros. By name: "info.layers". For AI: info_layers.
No settings.
For example, it answered:
0: Background (active)
dp.info.commands() - List the commands
Every command's id and title (the command palette shows them with their settings).
A question: changes nothing. Not written into recorded macros. By name: "info.commands". For AI: info_commands.
No settings.
For example, it answered:
draw.pixel (Plot a pixel); draw.line (Draw a line); draw.rect (Draw a rectangle); draw.ellipse (Draw an ellipse); draw.fill (Flood fill); palette.set (Set a palette colour); palette.transparent (See-through colour); palette.ramp (Make a colour ramp); layer.select (Choose the active layer); layer.show (Show or hide a layer); frame.duration (Frame duration); filter.apply (Apply a filter); info.filters (List the filters); layer.flip (Flip the layer); info.picture (About the picture); info.palette (List the palette); info.layers (List the layers); file.new (New picture); file.open (Open a file); ...
map
Tile maps (a map open in the active tab): cells, tiles and attributes.
dp.map.info() - About the map
The map's size in cells, its tile size, how many tiles its tile set has, and its layers.
A question: changes nothing. Not written into recorded macros. By name: "map.info". For AI: map_info.
No settings.
dp.map.cell{ } - What's in a cell
A cell of the active map layer: its tile (-1 empty), mirrors, turn, palette bank and attributes.
A question: changes nothing. Not written into recorded macros. By name: "map.cell". For AI: map_cell.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
x |
whole number | needed | 0 to 65,535 | Column. |
y |
whole number | needed | 0 to 65,535 | Row. |
dp.map.set{ } - Set map cells
Puts a tile in a cell, or in every cell of a rectangle, of the active map layer (one undo step; with cells selected, only those change).
Changes things (one undo step). By name: "map.set". For AI: map_set.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
x |
whole number | needed | 0 to 65,535 | Column. |
y |
whole number | needed | 0 to 65,535 | Row. |
w |
whole number | 1 |
1 to 65,535 | Columns wide. |
h |
whole number | 1 |
1 to 65,535 | Rows high. |
tile |
whole number | - | -1 to 1,048,574 | The tile number (-1: empty). |
flipx |
true or false | false |
Mirrored across. | |
flipy |
true or false | false |
Upside down. | |
rotate |
true or false | false |
Turned 90 degrees clockwise. | |
palette |
whole number | 0 |
0 to 255 | Palette bank. |
attr |
whole number | - | 0 to 255 | Attributes (left out: the cells keep theirs). |
random |
true or false | false |
Instead of tile: each cell one of the random tiles (tiles.random), by weight. | |
rule |
text | - | Instead of tile: an automapping rule tile (empty, ignore, nonempty, other, negate), for rules maps. | |
stamp |
text | - | Instead of tile: a saved stamp (tiles.stamp_save), its top-left at x, y (w and h: placed across that many cells, each placement one of its variations). |
dp.map.attr{ } - Set cell attributes
Sets the attributes (the game's own bits) of a cell, or a rectangle of cells, leaving their tiles.
Changes things (one undo step). By name: "map.attr". For AI: map_attr.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
x |
whole number | needed | 0 to 65,535 | Column. |
y |
whole number | needed | 0 to 65,535 | Row. |
w |
whole number | 1 |
1 to 65,535 | Columns wide. |
h |
whole number | 1 |
1 to 65,535 | Rows high. |
value |
whole number | needed | 0 to 255 | The attributes, 0-255. |
dp.map.save_tiled{ } - Save as a Tiled map
Writes the map in front as a Tiled map (mapeditor.org): .tmx (XML) or .tmj (JSON), by the file's extension, with each layer's tile set as a PNG beside it. (file.open opens Tiled maps.)
Changes things (one undo step). By name: "map.save_tiled". For AI: map_save_tiled.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
path |
file path | needed | The .tmx or .tmj file. |
dp.map.tile_art{ } - Tall tiles
Sets the size of the active layer's tiles in its tile set, when it isn't the cell's: bigger makes tall tiles (walls, trees, buildings on an isometric map), each drawn whole from its cell's bottom-left, overhanging up (and right), the ones lower down in front, as Tiled draws them. The cell's own size: ordinary tiles.
Changes things (one undo step). By name: "map.tile_art". For AI: map_tile_art.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
w |
whole number | needed | 1 to 1,024 | The tiles' width in the tile set. |
h |
whole number | needed | 1 to 1,024 | Their height. |
layer |
whole number | - | 0 to 255 | The map layer (from 0; left out: the active one). |
dp.map.layer{ } - Map layer settings
Sets a map layer's scroll (the layer pixel shown at the map's top-left) and whether it repeats in every direction (wrap: any shape; isometric maps repeat along their diamond's sides, staggered and hex maps seamlessly with an even number of shifted rows or columns). What's left out stays as it is.
Changes things (one undo step). By name: "map.layer". For AI: map_layer.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
layer |
whole number | - | 0 to 255 | The map layer (from 0; left out: the active one). |
wrap |
true or false | - | Repeat in every direction. | |
scroll_x |
whole number | - | -1,000,000 to 1,000,000 | Its scroll across. |
scroll_y |
whole number | - | -1,000,000 to 1,000,000 | Its scroll down. |
name |
text | - | Its name. |
dp.map.layer_add{ } - New map layer
A new, empty tile layer above the active one (the same size and tiles), which becomes the active layer.
Changes things (one undo step). By name: "map.layer_add". For AI: map_layer_add.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
name |
text | - | Its name (left out: Layer n). |
dp.map.save_godot{ } - Save as a Godot scene
Writes the map in front as a Godot 4 scene (.tscn, Godot 4.3 or later): a TileMapLayer for each layer, each with a TileSet of its tile set picture (a PNG beside the scene), in the map's shape (square, isometric, staggered, hexagonal or brick as Godot's half-offset squares), with the flips and turns. Put the scene and its PNGs in the Godot project's folder res ("" its top). (file.open opens Godot 4 scenes: their TileMapLayer and TileMap nodes.)
Changes things (one undo step). By name: "map.save_godot". For AI: map_save_godot.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
path |
file path | needed | The .tscn file. | |
res |
text | - | The folder in the Godot project, e.g. levels/ (left out: the top). |
dp.map.shape{ } - Map shape
Makes the map in front square, isometric, staggered or hexagonal (all its layers; the cells stay as they are, drawn in the new shape). As Tiled's orientations, stagger axis and index, and hex side length.
Changes things (one undo step). By name: "map.shape". For AI: map_shape.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
shape |
one of the choices | - | square, isometric, staggered, hexagonal, brick |
square, isometric (diamonds), staggered (isometric, every other row or column shifted), hexagonal, or brick (rectangles, every other row or column shifted half: a hex map whose straight edges are the whole tile) (left out: as it is, or square). |
stagger |
one of the choices | - | rows, columns |
Staggered and hexagonal: rows shifted (pointy-topped hexes) or columns (flat-topped). |
shifted |
one of the choices | - | odd, even |
Staggered and hexagonal: the odd rows (or columns) shifted, or the even ones. |
hex_side |
whole number | - | 0 to 256 | Hexagonal: the straight edge's length in pixels (left out: as it is, or half the tile). |
dp.map.new{ } - New tile map
A new, empty tile map painted from the active picture's tiles - or, on a map, from that map's tile set (several maps can share one tile set).
Changes things (one undo step). By name: "map.new". For AI: map_new.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
width |
whole number | 32 |
1 to 65,535 | Columns (tiles across). |
height |
whole number | 24 |
1 to 65,535 | Rows (tiles down). |
tile_w |
whole number | - | 1 to 256 | Tile width in pixels (left out: the map's, or the picture's grid). |
tile_h |
whole number | - | 1 to 256 | Tile height in pixels (left out: the map's, or the picture's grid). |
shape |
one of the choices | - | square, isometric, staggered, hexagonal, brick |
square, isometric (diamonds), staggered (isometric, every other row or column shifted), hexagonal, or brick (rectangles, every other row or column shifted half: a hex map whose straight edges are the whole tile) (left out: as it is, or square). |
stagger |
one of the choices | - | rows, columns |
Staggered and hexagonal: rows shifted (pointy-topped hexes) or columns (flat-topped). |
shifted |
one of the choices | - | odd, even |
Staggered and hexagonal: the odd rows (or columns) shifted, or the even ones. |
hex_side |
whole number | - | 0 to 256 | Hexagonal: the straight edge's length in pixels (left out: as it is, or half the tile). |
dp.map.object_layer{ } - New object layer
A new object layer above the active map layer (it becomes the active one): game objects - rectangles, ellipses, points, polygons, polylines, tiles placed anywhere, text - with names, classes and properties, shown over the map and saved with it (and in Tiled's maps as an object group).
Changes things (one undo step). By name: "map.object_layer". For AI: map_object_layer.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
name |
text | - | Its name (left out: Objects 1, 2...). | |
colour |
colour (#RRGGBB) | - | The colour its objects are drawn in. |
dp.map.image_layer{ } - New image layer
A new image layer above the active map layer: one picture (an open one by its name, or a file, which is opened) as a background or foreground, in its own colours, scrolling, linking and repeating (across, down or both) as any layer does; Tiled's image layers.
Changes things (one undo step). By name: "map.image_layer". For AI: map_image_layer.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
picture |
text | - | An open picture's name (as on its tab). | |
path |
file path | - | Or a picture file to open. | |
repeat_x |
true or false | false |
Repeat it across. | |
repeat_y |
true or false | false |
Repeat it down. | |
name |
text | - | The layer's name (left out: the picture's). |
dp.map.object_go{ } - Go to an object
Chooses an object (its layer becomes the active one) and brings it into view.
Changes things (one undo step). By name: "map.object_go". For AI: map_object_go.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
id |
whole number | needed | 1 to ... | The object. |
dp.map.template_make{ } - Make a template
Keeps an object as a template of the map: copies of it can be placed anywhere (map.template_place), and changing the template (map.template_set) changes every copy except what a copy has changed itself. The object becomes its first copy. Returns the template's id.
Changes things (one undo step). By name: "map.template_make". For AI: map_template_make.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
object |
whole number | needed | 1 to ... | The object. |
name |
text | - | The template's name (left out: the object's). |
dp.map.template_place{ } - Place a template's copy
Places a copy of a template on an object layer: it follows the template in everything but its place (and what it's given its own: map.object). Returns its id.
Changes things (one undo step). By name: "map.template_place". For AI: map_template_place.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
template |
whole number | needed | 1 to ... | The template. |
x |
number | 0 |
Across, in the layer's pixels. | |
y |
number | 0 |
Down (a tile's: its bottom-left). | |
layer |
whole number | - | 0 to 255 | The object layer (from 0; left out: the active one). |
dp.map.template_set{ } - Change a template
Changes a template, and with it every copy (except what a copy has changed itself). What's left out stays as it is.
Changes things (one undo step). By name: "map.template_set". For AI: map_template_set.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
template |
whole number | needed | 1 to ... | The template. |
name |
text | - | Its name. | |
class |
text | - | Its class. | |
w |
number | - | 0 to 10,000,000 | Width. |
h |
number | - | 0 to 10,000,000 | Height. |
rotation |
number | - | -360 to 360 | Turned, degrees. |
tile |
whole number | - | 0 to ... | Tile templates: the tile. |
text |
text | - | Text templates: the words. | |
visible |
true or false | - | Shown. |
dp.map.templates() - List templates
The map's object templates: a line each, id shape "name" class, size, and how many copies.
A question: changes nothing. Not written into recorded macros. By name: "map.templates". For AI: map_templates.
No settings.
dp.map.object_reset{ } - Back to the template
A template's copy as the template is again: what it changed is dropped (its place kept).
Changes things (one undo step). By name: "map.object_reset". For AI: map_object_reset.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
id |
whole number | needed | 1 to ... | The copy. |
dp.map.object_detach{ } - Cut loose from the template
A template's copy made an ordinary object with what it has now: the template no longer changes it.
Changes things (one undo step). By name: "map.object_detach". For AI: map_object_detach.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
id |
whole number | needed | 1 to ... | The copy. |
dp.map.object{ } - Place or change an object
Places an object on an object layer, or changes one (give its id): what's left out stays as it is (a new one: a rectangle one cell big). Places are the layer's pixels: a rectangle's, ellipse's or text's top-left, a point itself, a polygon's first point (its points from there), a tile object's bottom-left. Returns its id.
Changes things (one undo step). By name: "map.object". For AI: map_object.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
id |
whole number | - | 1 to ... | The object to change (left out: a new one). |
layer |
whole number | - | 0 to 255 | A new one's layer (from 0; left out: the active one). |
shape |
one of the choices | - | rectangle, ellipse, point, polygon, polyline, tile, text |
rectangle, ellipse, point, polygon, polyline, tile or text (a new object; left out: a rectangle). |
name |
text | - | Its name. | |
class |
text | - | Its class (what kind of thing: Enemy, Door...). | |
x |
number | - | Across, in the layer's pixels. | |
y |
number | - | Down. | |
w |
number | - | 0 to 10,000,000 | Width. |
h |
number | - | 0 to 10,000,000 | Height. |
rotation |
number | - | -360 to 360 | Turned clockwise, degrees. |
points |
text | - | Polygons and polylines: "x,y x,y ..." from its place. | |
tile |
whole number | - | 0 to ... | Tile objects: the tile's number. |
flip_x |
true or false | - | Tile objects: mirrored. | |
flip_y |
true or false | - | Tile objects: upside down. | |
text |
text | - | Text objects: the words. | |
size |
whole number | - | 4 to 512 | Text objects: pixels high. |
visible |
true or false | - | Shown. |
dp.map.object_delete{ } - Delete an object
Removes an object from its object layer.
Changes things (one undo step). By name: "map.object_delete". For AI: map_object_delete.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
id |
whole number | needed | 1 to ... | The object. |
dp.map.objects{ } - List objects
The objects on an object layer (or every one): a line each, id shape "name" class at x,y size w x h (and its tile, points or text).
A question: changes nothing. Not written into recorded macros. By name: "map.objects". For AI: map_objects.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
layer |
whole number | - | 0 to 255 | The layer (from 0; left out: every object layer). |
dp.map.collision_layer{ } - Make the map's collision an object layer
The active layer's collision, as an object layer above it: solid full cells merged into as few rectangles as can be, every other cell's shapes as they lie (turned and mirrored as the cell). For games that read a map's collision as objects (Tiled maps, Unity through SuperTiled2Unity). Square maps.
Changes things (one undo step). By name: "map.collision_layer". For AI: map_collision_layer.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
name |
text | Collision |
The object layer's name. |
dp.map.automap{ } - Automap
Applies a rules map (another open map, by its name as on its tab) to the map in front, as Tiled's automapping: its layers input<layer>, inputnot<layer>, output<layer> and output1 ... (one chosen at random), each rule a group of touching cells, the rule tiles Empty, Ignore, NonEmpty, Other, Negate; its properties MatchOutsideMap, WrapBorder, OverflowBorder, MatchInOrder, ModX, ModY, OffsetX, OffsetY, Probability, Disabled; a rule_options object layer. One undo step. x, y, w, h: only rules touching that box of cells (as while drawing). It becomes the rules map the Tiles panel uses.
Changes things (one undo step). By name: "map.automap". For AI: map_automap.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
rules |
text | - | The rules map's name (left out: the one chosen in the Tiles panel). | |
x |
whole number | - | -65,536 to 65,536 | The box's left cell. |
y |
whole number | - | -65,536 to 65,536 | Its top cell. |
w |
whole number | - | 1 to 65,536 | Its width in cells. |
h |
whole number | - | 1 to 65,536 | Its height in cells. |
while_drawing |
true or false | - | Also apply the rules after every stroke on this map from now on. |
dp.map.brush{ } - Pick up a map brush
Makes the map brush the cells of a rectangle of the active map layer, as a right-drag on the map picks them up (then tiles.stamp_save keeps it, or tiles.quick_stamp).
Changes things (one undo step). By name: "map.brush". For AI: map_brush.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
x |
whole number | needed | 0 to 65,535 | Column. |
y |
whole number | needed | 0 to 65,535 | Row. |
w |
whole number | 1 |
1 to 256 | Columns wide. |
h |
whole number | 1 |
1 to 256 | Rows high. |
dp.map.layer_lock{ } - Lock a map layer
Locks a map layer (or unlocks it), so painting, filling, map.set and automapping leave it alone (Tiled's layer lock); saved with the map.
Changes things (one undo step). By name: "map.layer_lock". For AI: map_layer_lock.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
layer |
whole number | - | 0 to 15 | The layer (left out: the active one). |
locked |
true or false | true |
Locked (true) or not. |
dp.map.focus{ } - Focus on the layer
Dims the map's other layers so the active one stands out (Ctrl+H on a map), or shows them as they are.
Changes things (one undo step). By name: "map.focus". For AI: map_focus.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
on |
true or false | true |
Focus (true) or not. |
dp.map.save_image{ } - Save the map as an image
Saves the map in front, or part of it, as a PNG of any size, as it's drawn: every chosen layer at its scroll (or laid flat), scaled up if asked; in the map's palette when one palette holds it, otherwise in full colour (layers in their own colours, see-through layers, palette banks past 255). It's written before the command returns.
Changes things (one undo step). By name: "map.save_image". For AI: map_save_image.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
path |
file path | needed | The PNG file. | |
x |
whole number | - | 0 to 65,535 | The part's first column (the active layer's cells). Left out, with y, w and h: the whole map. |
y |
whole number | - | 0 to 65,535 | Its first row. |
w |
whole number | - | 1 to 65,535 | Columns. |
h |
whole number | - | 1 to 65,535 | Rows. |
selected |
true or false | false |
Only the selected cells (their rectangle; others left empty). | |
layers |
text | - | "visible" (the default), "active", or layer numbers like "0, 2". | |
scale |
whole number | 1 |
1 to 8 | Each pixel as a block this big. |
flat |
true or false | false |
Laid flat: the layers unscrolled (no scroll, links, column scroll or copper). | |
view |
true or false | false |
What the view in front shows of the map. | |
anim_ms |
whole number | 0 |
0 to ... | Animated tiles: the frame they show this many milliseconds in. |
empty |
text | - | Empty places: "clear" (see-through, the default) or "colour" (the see-through colour, solid). | |
colours |
text | - | "auto" (a palette PNG when one palette holds it; the default) or "full" (always RGBA). | |
objects |
true or false | false |
Draw the chosen object layers' objects over it. | |
grid |
true or false | false |
Draw the active layer's cell grid over it. | |
numbers |
true or false | false |
Draw the active layer's tile numbers over it. | |
collisions |
true or false | false |
Draw the active layer's tiles' collision shapes over it. |
dp.map.to_picture{ } - Map to picture
Makes a new picture of the map in front, or part of it, as it's drawn (Tiles > Map to picture): up to 4,096 x 4,096 pixels, in the map's palette (maps one palette can't hold: map.save_image).
Changes things (one undo step). By name: "map.to_picture". For AI: map_to_picture.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
x |
whole number | - | 0 to 65,535 | The part's first column (the active layer's cells). Left out, with y, w and h: the whole map. |
y |
whole number | - | 0 to 65,535 | Its first row. |
w |
whole number | - | 1 to 65,535 | Columns. |
h |
whole number | - | 1 to 65,535 | Rows. |
selected |
true or false | false |
Only the selected cells (their rectangle; others left empty). | |
layers |
text | - | "visible" (the default), "active", or layer numbers like "0, 2". | |
scale |
whole number | 1 |
1 to 8 | Each pixel as a block this big. |
flat |
true or false | false |
Laid flat: the layers unscrolled (no scroll, links, column scroll or copper). | |
view |
true or false | false |
What the view in front shows of the map. | |
anim_ms |
whole number | 0 |
0 to ... | Animated tiles: the frame they show this many milliseconds in. |
edit
Undo, redo and deleting the selection.
dp.edit.undo() - Undo
Undoes the last change to the active picture.
Changes things (one undo step). By name: "edit.undo". For AI: edit_undo.
No settings.
dp.edit.redo() - Redo
Does again what was last undone.
Changes things (one undo step). By name: "edit.redo". For AI: edit_redo.
No settings.
dp.edit.delete() - Delete selection
Clears the selected part of the active layer to the transparent colour (a map: empties the selected cells).
Changes things (one undo step). By name: "edit.delete". For AI: edit_delete.
No settings.
colour
The colours the mouse buttons paint with.
dp.colour.left{ } - Left colour
The colour the left mouse button paints with.
Changes things (one undo step). By name: "colour.left". For AI: colour_left.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
index |
whole number | needed | 0 to 255 | Colour number. |
dp.colour.right{ } - Right colour
The colour the right mouse button paints with.
Changes things (one undo step). By name: "colour.right". For AI: colour_right.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
index |
whole number | needed | 0 to 255 | Colour number. |
tool
The painting tool.
dp.tool.set{ } - Choose a tool
The tool the mouse paints with.
Changes things (one undo step). By name: "tool.set". For AI: tool_set.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
name |
one of the choices | needed | Freehand, Dotted freehand, Airbrush, Line, Curve, Rectangle, Filled rectangle, Circle, Filled circle, Ellipse, Filled ellipse, Polygon, Filled polygon, Fill, Pick colour, Pick up brush, Select rectangle, Select lasso, Text, Pointer, Magic wand |
The tool. |
brush
The brush.
dp.brush.set{ } - Built-in brush
The brush the mouse's tools (and paint.stroke) paint with: round or square, of a size (the custom brush is put aside). draw.line, draw.rect and draw.ellipse don't use it: give them size for a thicker line.
Changes things (one undo step). By name: "brush.set". For AI: brush_set.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
shape |
one of the choices | - | round, square |
A built-in brush's shape (left out: the brush as it is). |
size |
whole number | - | 1 to 64 | Its size in pixels. |
paint
Painting as the mouse does (recorded strokes).
dp.paint.stroke{ } - Paint a stroke
Does what dragging the mouse through these points with a tool does (a recorded stroke).
Changes things (one undo step). By name: "paint.stroke". For AI: paint_stroke.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
tool |
one of the choices | needed | Freehand, Dotted freehand, Airbrush, Line, Rectangle, Filled rectangle, Circle, Filled circle, Ellipse, Filled ellipse, Fill, Pick up brush, Select rectangle, Select lasso |
The tool, as the mouse would use it (its current settings: paint mode, brush...). |
button |
one of the choices | left |
left, right |
The mouse button (left paints with the left colour). |
points |
text | needed | The points: "x,y x,y ..." (the first is where the button went down). | |
colour |
whole number | - | 0 to 255 | The colour number to paint with (left out: the button's colour as it is). |
size |
whole number | - | 1 to 64 | A built-in brush's size (left out: the brush as it is). |
shape |
one of the choices | - | round, square |
A built-in brush's shape (left out: the brush as it is). |
perfect |
true or false | - | Freehand with the 1-pixel brush: take out the doubled corners (pixel-perfect; left out: as the Tools panel has it). |
dp.paint.wrap{ } - Wrap around
Turns wrap-around on or off for the picture: it repeats in every direction on the canvas, and paint going over an edge (strokes, lines, shapes, paint.stroke) comes in at the other side, for seamless textures and tiles. Kept with the picture.
Changes things (one undo step). By name: "paint.wrap". For AI: paint_wrap.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
on |
true or false | true |
On or off. |
file
Files: new pictures, opening, saving, exporting.
dp.file.new{ } - New picture
A new picture of any size, in the palette of the system last used.
Changes things (one undo step). By name: "file.new". For AI: file_new.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
width |
whole number | 320 |
1 to 4,096 | Width in pixels. |
height |
whole number | 200 |
1 to 4,096 | Height in pixels. |
colours |
whole number | 256 |
2 to 256 | Colours in its palette. |
dp.file.open{ } - Open a file
Opens a picture, project, map or 3D model (anything File > Open opens).
Changes things (one undo step). By name: "file.open". For AI: file_open.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
path |
file path | needed | The file. |
dp.file.save{ } - Save
Saves the active picture as a .dpx project (to path, or where it was last saved).
Changes things (one undo step). By name: "file.save". For AI: file_save.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
path |
file path | - | Where (a .dpx file). Left out: where it was saved before. |
dp.file.export{ } - Export
Writes the active picture (or map) in one of the Export window's formats.
Changes things (one undo step). By name: "file.export". For AI: file_export.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
format |
text | needed | The format's name as the Export window lists it, e.g. "PNG" or "IFF ILBM" (any case; enough of it to be unique). | |
path |
file path | needed | The file to write. |
macro
Recording and playing macros.
dp.macro.record() - Record a macro
Starts writing down every command run (from the menus, keys, the command palette...) until macro.stop.
Changes things (one undo step). Not written into recorded macros. By name: "macro.record". For AI: macro_record.
No settings.
dp.macro.stop{ } - Stop recording
Stops recording, and saves the macro to path if given (else asks where).
Changes things (one undo step). Not written into recorded macros. By name: "macro.stop". For AI: macro_stop.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
path |
file path | - | Where to save it: a .dpmacro file, or a .luau script doing the same. |
dp.macro.play{ } - Play a macro
Runs the commands in a macro file, in order, stopping at the first that fails.
Changes things (one undo step). Not written into recorded macros. By name: "macro.play". For AI: macro_play.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
path |
file path | - | The macro (.dpmacro). Left out: asks. |
script
Running scripts.
dp.script.run{ } - Run a script
Runs a Luau script file: its commands on the active picture, as one undo step.
Changes things (one undo step). By name: "script.run". For AI: script_run.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
path |
file path | needed | The script (.luau). |
dp.script.console() - Script console
Opens the script console, to write and run Luau scripts.
Changes things (one undo step). Not written into recorded macros. By name: "script.console". For AI: script_console.
No settings.
block
dp.block.make{ } - Make a block map
Makes the active map layer a block map: what's painted cut into blocks of w x h cells, alike ones made one block (mirrored ones too, with flips; ones differing in at most spots cells too, those becoming spot changes). The map's cells don't change. On a block map already: its blocks found afresh.
Changes things (one undo step). By name: "block.make". For AI: block_make.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
w |
whole number | 2 |
1 to 256 | Block width in cells. |
h |
whole number | 2 |
1 to 256 | Block height in cells. |
flips |
true or false | true |
Mirrored blocks are the same block. | |
palettes |
true or false | false |
Blocks in other palette banks are the same block, placed with a palette add. | |
spots |
whole number | 0 |
0 to 65,535 | Cells a placement may differ from its block by. |
dp.block.clear() - Ordinary layer
The active layer stops being a block map (its cells stay as they are).
Changes things (one undo step). By name: "block.clear". For AI: block_clear.
No settings.
dp.block.info() - About the block map
The active layer's blocks: their size, how many, placements, spot changes, and the bytes they take against the flat map.
A question: changes nothing. Not written into recorded macros. By name: "block.info". For AI: block_info.
No settings.
dp.block.place{ } - Place a block
Places a block at placement (x, y) (in blocks, not cells: x 3 is cells 3 * the block width on), with its flips and palette add; its cells are written (spot changes there go). block -1: the placement becomes plain cells. With chunk: places chunk block at chunk placement (x, y) instead (in chunks), its blocks and their cells written.
Changes things (one undo step). By name: "block.place". For AI: block_place.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
x |
whole number | needed | 0 to 65,535 | Placement across. |
y |
whole number | needed | 0 to 65,535 | Placement down. |
block |
whole number | needed | -1 to 1,000,000 | The block's number (-1: none). |
flipx |
true or false | false |
Mirrored across. | |
flipy |
true or false | false |
Mirrored down. | |
palette |
whole number | 0 |
0 to 255 | Palette add. |
w |
whole number | 1 |
1 to 65,535 | Placements wide (the same block in each). |
h |
whole number | 1 |
1 to 65,535 | Placements high. |
attr |
whole number | 0 |
0 to 255 | The placement's attributes (Sonic: a block's solidity in its chunk, a chunk's loop flag). |
chunk |
true or false | false |
Place a chunk (x, y, w and h in chunks). |
dp.block.break{ } - Break blocks into cells
The placements in a rectangle (in blocks) become plain cells; the cells stay as they are.
Changes things (one undo step). By name: "block.break". For AI: block_break.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
x |
whole number | needed | 0 to 65,535 | Placement across. |
y |
whole number | needed | 0 to 65,535 | Placement down. |
w |
whole number | 1 |
1 to 65,535 | Placements wide. |
h |
whole number | 1 |
1 to 65,535 | Placements high. |
dp.block.from_cells{ } - New block from cells
A new block from the active layer's cells at cell (x, y) (a block's size of them, snapped to its placement), placed there. Returns its number.
Changes things (one undo step). By name: "block.from_cells". For AI: block_from_cells.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
x |
whole number | needed | 0 to 65,535 | Cell across. |
y |
whole number | needed | 0 to 65,535 | Cell down. |
dp.block.set{ } - Change a block's cell
Changes cell (x, y) of a block itself: every placement of it changes, except where they have spot changes.
Changes things (one undo step). By name: "block.set". For AI: block_set.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
block |
whole number | needed | 0 to 1,000,000 | The block's number. |
x |
whole number | needed | 0 to 255 | Cell across in the block. |
y |
whole number | needed | 0 to 255 | Cell down in the block. |
tile |
whole number | needed | -1 to 1,048,574 | The tile (-1: empty). |
flipx |
true or false | false |
Mirrored across. | |
flipy |
true or false | false |
Mirrored down. | |
palette |
whole number | 0 |
0 to 255 | Palette bank. |
attr |
whole number | 0 |
0 to 255 | Attributes. |
dp.block.export{ } - Export block map
Writes the active layer's blocks, layout and spot changes as JSON (documented in the file), for game code.
Changes things (one undo step). By name: "block.export". For AI: block_export.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
path |
file path | needed | The .json file. |
dp.block.chunks{ } - Group blocks into chunks
Groups the active block map's blocks into chunks of w x h blocks (Sonic's 16 x 16 and 8 x 8, Mega Man's 2 x 2): alike areas made one chunk (mirrored ones too, with flips; in other palettes too, with palettes; ones differing in at most spots blocks too). The cells and the block placements don't change; the layout then places chunks.
Changes things (one undo step). By name: "block.chunks". For AI: block_chunks.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
w |
whole number | 8 |
1 to 256 | Chunk width in blocks. |
h |
whole number | 8 |
1 to 256 | Chunk height in blocks. |
flips |
true or false | false |
Mirrored chunks are the same chunk. | |
palettes |
true or false | false |
Chunks in other palette banks are the same chunk, placed with a palette add. | |
spots |
whole number | 0 |
0 to 65,535 | Blocks a chunk placement may differ from its chunk by. |
dp.block.unchunk() - Ungroup chunks
The active block map forgets its chunks: its blocks are placed as they are.
Changes things (one undo step). By name: "block.unchunk". For AI: block_unchunk.
No settings.
dp.block.format{ } - Block map format
Sets the machine the active layer's blocks are for (its sizes, limits and files: block.check, block.export_format, block.import_format). The sizes, limits and compression given change the preset's (making it custom).
Changes things (one undo step). By name: "block.format". For AI: block_format.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
preset |
one of the choices | needed | free, sonic1, sonic2, sonic3k, nes, nes32, smw, zelda, metroid, custom, saved |
free (no machine), sonic1, sonic2, sonic3k, nes (Super Mario Bros. style metatiles), nes32 (Mega Man style 32 x 32), smw (SNES Map16), zelda (screens of columns), metroid (rooms of structures), custom (the current options, to change) or saved (one saved with block.format_save: give its name). |
name |
text | - | preset saved: the saved format's name. | |
blockw |
whole number | - | 1 to 256 | Block width in cells. |
blockh |
whole number | - | 1 to 256 | Block height in cells. |
chunkw |
whole number | - | 0 to 256 | Chunk width in blocks (0: none). |
chunkh |
whole number | - | 0 to 256 | Chunk height in blocks. |
banks |
whole number | - | 0 to 256 | Palette banks (0: any). |
maxblocks |
whole number | - | 0 to 16,777,216 | Most blocks (0: any). |
maxchunks |
whole number | - | 0 to 16,777,216 | Most chunks (0: any). |
pack |
one of the choices | - | none, kosinski, moduled |
Compress the files: none, kosinski or moduled (moduled Kosinski, Sonic 3 & Knuckles); left out: as the preset has them. |
dp.block.format_save{ } - Save block map format
Saves the active layer's format, with all its options, under a name: it's then in the Machine drop-down (and block.format{preset = "saved", name}) for every map. A name already saved is replaced.
Changes things (one undo step). By name: "block.format_save". For AI: block_format_save.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
name |
text | needed | The format's name. |
dp.block.format_forget{ } - Forget a saved block map format
Takes a saved format out of the Machine drop-down (maps already using it keep its options).
Changes things (one undo step). By name: "block.format_forget". For AI: block_format_forget.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
name |
text | needed | The format's name. |
dp.block.check() - Check the block map
What in the active block map doesn't fit its format (block.format): one line each, or "It all fits."
A question: changes nothing. Not written into recorded macros. By name: "block.check". For AI: block_check.
No settings.
dp.block.export_format{ } - Export block map for the machine
Writes the active block map as its format's files, beside path (a name without an extension): path.blocks.bin (the cells as the machine's tile words), path.chunks.bin (the chunks' block words), path.layout.bin, and for one-palette blocks path.palettes.bin (Zelda: path.columns.bin, .column_dirs.bin and .screens.bin for the chunks; Metroid: path.structures.bin, .rooms.bin and their _ptrs files, the layout the world map). bg: another layer (from 0) using the same blocks and chunks, written as the background (Sonic 2: each layout row's second half; otherwise path.bg.layout.bin).
Changes things (one undo step). By name: "block.export_format". For AI: block_export_format.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
path |
file path | needed | The files' name, without an extension. | |
bg |
whole number | - | 0 to 255 | The background layer. |
dp.block.import_format{ } - Import block map from the machine
Reads a level in the active layer's format (block.format) from its files beside path (a name without an extension: path.blocks.bin, path.chunks.bin, path.layout.bin, path.palettes.bin, decompressed as the format keeps them) into the active layer: its blocks, chunks and placements, and every cell, the layer sized to the layout. The map's tile set should hold the level's tiles (in order). bg: a layer (from 0) to fill with the level's background too (Sonic).
Changes things (one undo step). By name: "block.import_format". For AI: block_import_format.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
path |
file path | needed | The files' name, without an extension. | |
across |
whole number | - | 1 to 65,535 | The layout's width in placements, for layouts that don't keep it (a byte or word each). |
bg |
whole number | - | 0 to 255 | The layer for the background. |
dp.block.open_level{ } - Open a game level
Opens a level kept as a game keeps it, from its files beside path (a name without an extension: path.blocks.bin, path.chunks.bin, path.layout.bin, and path.art.bin and path.palette.bin if they're there): its art as a tile set picture (without art, each tile a plain square), and a map of its blocks and chunks drawn from it (with a background layer when the level has one).
Changes things (one undo step). By name: "block.open_level". For AI: block_open_level.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
path |
file path | needed | The files' name, without an extension. | |
preset |
one of the choices | needed | sonic1, sonic2, sonic3k, nes, nes32, smw, saved |
The level's format: sonic1, sonic2, sonic3k, nes, nes32, smw, or saved (one saved with block.format_save: give its name). |
name |
text | - | preset saved: the saved format's name. | |
across |
whole number | - | 1 to 65,535 | The layout's width, for layouts that don't keep it (a byte or word each). |
dp.block.share{ } - Share blocks with a layer
Makes another layer (from 0) a block map using the active layer's blocks (and chunks): its placements found with them, any new blocks or chunks it needs added to both layers' sets, so the two can be written together (a level's foreground and background).
Changes things (one undo step). By name: "block.share". For AI: block_share.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
layer |
whole number | needed | 0 to 255 | The other layer. |
copper
dp.copper.info() - The copper's changes
Every change the copper makes down the screen, a line each: the line it's from, and what changes (a palette colour and its new colour, or a map layer's scroll or visibility).
A question: changes nothing. Not written into recorded macros. By name: "copper.info". For AI: copper_info.
No settings.
dp.copper.colour{ } - Copper: change a colour
From a line down, a palette colour becomes another colour (until it changes again).
Changes things (one undo step). By name: "copper.colour". For AI: copper_colour.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
line |
whole number | needed | 0 to 65,535 | The line it changes on (0 is the top). |
index |
whole number | needed | 0 to 255 | The palette colour number. |
colour |
colour (#RRGGBB) | needed | Its new colour, like #2040A0. |
dp.copper.gradient{ } - Copper: a colour gradient
A palette colour ramped from one colour to another down a run of lines (a sky, water, a colour bar), a change every few lines; snapped to the machine's colours.
Changes things (one undo step). By name: "copper.gradient". For AI: copper_gradient.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
from_line |
whole number | needed | 0 to 65,535 | The first line. |
to_line |
whole number | needed | 0 to 65,535 | The last line. |
index |
whole number | needed | 0 to 255 | The palette colour number it changes. |
from |
colour (#RRGGBB) | needed | The colour at the first line. | |
to |
colour (#RRGGBB) | needed | The colour at the last line. | |
every |
whole number | 1 |
1 to 256 | A change every this many lines. |
snap |
true or false | true |
Snap the colours to what the machine can show. |
dp.copper.wave{ } - Copper: a wave
A map layer's scroll changed line by line down a run of lines as a sine wave (water, heat haze, a dream sequence).
Changes things (one undo step). By name: "copper.wave". For AI: copper_wave.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
from_line |
whole number | needed | 0 to 65,535 | The first line. |
to_line |
whole number | needed | 0 to 65,535 | The last line. |
layer |
whole number | 0 |
0 to 255 | The map layer (0 is the bottom one). |
across |
true or false | true |
Scroll across (false: up and down). | |
amplitude |
whole number | 4 |
0 to 512 | How far it moves, in pixels. |
period |
whole number | 32 |
2 to 4,096 | Lines for one whole wave. |
phase |
whole number | 0 |
-360 to 360 | Where the wave starts, in degrees. |
dp.copper.clear() - Copper: clear it
Removes every change the copper makes.
Changes things (one undo step). By name: "copper.clear". For AI: copper_clear.
No settings.
dp.copper.show{ } - Copper: on or off
Shows the copper's changes, or keeps them but doesn't show them.
Changes things (one undo step). By name: "copper.show". For AI: copper_show.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
on |
true or false | true |
Show them. |
history
dp.history.list() - List the undo history
Every undo step, oldest first: its number, name, the layer painted on, when (the clock time), and a * on the current one (steps after it can be redone).
A question: changes nothing. Not written into recorded macros. By name: "history.list". For AI: history_list.
No settings.
dp.history.goto{ } - Go to a step of the history
Undoes or redoes until step steps are done (0: the start of the history), as clicking a step in the History panel.
Changes things (one undo step). Not written into recorded macros. By name: "history.goto". For AI: history_goto.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
step |
whole number | needed | 0 to 1,000,000 | How many steps done (history.list numbers them). |
dp.history.forget{ } - Forget old history steps
Forgets the oldest steps of the undo history (frees their memory; the picture stays as it is). Can't be undone.
Changes things (one undo step). Not written into recorded macros. By name: "history.forget". For AI: history_forget.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
steps |
whole number | needed | 1 to 1,000,000 | How many of the oldest done steps to forget. |
dp.history.clearredo() - Clear the redo steps
Forgets the steps Redo would bring back. Can't be undone.
Changes things (one undo step). Not written into recorded macros. By name: "history.clearredo". For AI: history_clearredo.
No settings.
model
dp.model.open{ } - Open a 3D model
Opens a 3D model (STL, OBJ with its MTL, glTF / GLB, MagicaVoxel .vox) to render as sprites: File > 3D model to sprites shows it.
Changes things (one undo step). By name: "model.open". For AI: model_open.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
path |
file path | needed | The model file. |
dp.model.info() - About the 3D model
Its parts, colours and animations, and how it's being rendered.
A question: changes nothing. Not written into recorded macros. By name: "model.info". For AI: model_info.
No settings.
dp.model.view{ } - 3D model: view
How the model is seen: the frame size, directions, angles, zoom and perspective (only those given change).
Changes things (one undo step). By name: "model.view". For AI: model_view.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
width |
whole number | - | 8 to 512 | Frame width in pixels. |
height |
whole number | - | 8 to 512 | Frame height in pixels. |
directions |
one of the choices | - | 1, 4, 8, 16 |
Frames turning round the model. |
start |
number | - | -360 to 360 | The first frame's angle, degrees (0: its front towards you). |
pitch |
number | - | -10 to 90 | Looking down on it, degrees (0 level, 90 from above). |
zoom |
number | - | 0.1 to 8 | Zoom (1 fits it). |
perspective |
true or false | - | Perspective, or straight on (orthographic). |
dp.model.light{ } - 3D model: light
Where the light comes from and how much light is everywhere (only those given change).
Changes things (one undo step). By name: "model.light". For AI: model_light.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
yaw |
number | - | -180 to 180 | From, degrees round (relative to the view). |
pitch |
number | - | -10 to 90 | Its height, degrees. |
ambient |
number | - | 0 to 1 | Light everywhere, 0 to 1. |
dp.model.style{ } - 3D model: style
Dithering, the outline and the palette (only those given change).
Changes things (one undo step). By name: "model.style". For AI: model_style.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
dither |
true or false | - | Ordered dithering between the shades. | |
outline |
one of the choices | - | none, darkest, left |
An outline round it. |
palette |
one of the choices | - | model, picture |
The picture in front's palette, or one made for the model. |
colours |
whole number | - | 4 to 256 | Colours in a palette made for the model. |
dp.model.animation{ } - 3D model: animation
Which of a glTF model's animations to render, in how many frames.
Changes things (one undo step). By name: "model.animation". For AI: model_animation.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
name |
text | - | Its name or number (model.info lists them); "none" for the model as it stands. | |
frames |
whole number | - | 1 to 64 | Frames for each direction. |
loop |
true or false | - | It goes round (the last moment isn't made again). |
dp.model.colour{ } - 3D model: a colour
Changes one of the model's colours (its materials: model.info lists them).
Changes things (one undo step). By name: "model.colour". For AI: model_colour.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
material |
text | needed | Its name or number. | |
colour |
colour (#RRGGBB) | needed | The new colour. |
dp.model.pose{ } - 3D model: pose a part
Moves, turns (round its own middle) or scales one of the model's parts - an arm raised, a head turned. What's given is set; the rest of its pose stays.
Changes things (one undo step). By name: "model.pose". For AI: model_pose.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
part |
text | needed | The part's name or number (model.info lists them). | |
x |
number | - | Moved across (the model's units). | |
y |
number | - | Moved up. | |
z |
number | - | Moved towards you. | |
yaw |
number | - | Turned round up, degrees. | |
pitch |
number | - | Tipped forward, degrees. | |
roll |
number | - | Rolled, degrees. | |
scale |
number | - | 0.01 to 100 | Its size (1 as it is). |
pivot |
one of the choices | - | middle, top, bottom |
What it turns round: its middle, top (a shoulder, a hip) or bottom. |
dp.model.pose_reset{ } - 3D model: unpose
Puts a part (or every part) back as the model has it.
Changes things (one undo step). By name: "model.pose_reset". For AI: model_pose_reset.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
part |
text | - | The part's name or number (left out: every part). |
dp.model.frames() - 3D model to frames
Renders the model with the settings now into a new picture: a frame for each direction (and each moment of its animation).
Changes things (one undo step). By name: "model.frames". For AI: model_frames.
No settings.
dp.model.stamp{ } - 3D model into the picture
Renders the model (its first frame: the start angle, the animation's first moment) in the picture in front's colours, onto a new layer at x, y (its top-left): a 3D object put into a scene.
Changes things (one undo step). By name: "model.stamp". For AI: model_stamp.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
x |
whole number | - | Where its left edge goes. | |
y |
whole number | - | Where its top goes. |
dp.model.layer{ } - 3D model on a live 3D layer
Puts the model itself into the picture in front, on a new layer (its top-left at x, y; left out: centred), with the settings now. It stays 3D: model.object and the Layers panel change how it's turned, lit and posed; flattening the layer makes it pixels.
Changes things (one undo step). By name: "model.layer". For AI: model_layer.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
x |
whole number | - | Where its left edge goes (left out: centred). | |
y |
whole number | - | Where its top goes (left out: centred). |
dp.model.object{ } - Live 3D object: change it
Turns, lights, animates or moves a live 3D object (model.layer) in the picture in front: the one on the active layer (the topmost, or number n from the bottom). Only those given change; calls in a row are one undo step. A script calling it each frame animates it.
Changes things (one undo step). By name: "model.object". For AI: model_object.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
n |
whole number | - | 1 to 4,096 | Which model object on the layer, 1 the bottom (left out: the top one). |
x |
whole number | - | Its left edge. | |
y |
whole number | - | Its top. | |
width |
whole number | - | 1 to 2,048 | Its box, pixels. |
height |
whole number | - | 1 to 2,048 | Its box, pixels. |
yaw |
number | - | -100,000 to 100,000 | Turned round, degrees (0: its front towards you). |
pitch |
number | - | -10 to 90 | Looking down on it, degrees. |
zoom |
number | - | 0.1 to 8 | Zoom (1 fits it). |
light_yaw |
number | - | -180 to 180 | The light from, degrees round. |
light_pitch |
number | - | -10 to 90 | The light's height, degrees. |
ambient |
number | - | 0 to 1 | Light everywhere, 0 to 1. |
animation |
whole number | - | 0 to 1,000 | Its animation, 1 the first (0: none, as it stands). |
time |
number | - | 0 to 100,000 | The animation's moment, seconds (it loops). |
outline |
whole number | - | -1 to 255 | A colour number round it (-1: none). |
dither |
true or false | - | Dither between shades. | |
perspective |
true or false | - | Perspective, or straight on. | |
visible |
true or false | - | Shown. |
dp.model.object_pose{ } - Live 3D object: pose a part
Poses one part of a live 3D object (model.layer) on the active layer: moved, turned, scaled. What's given is set; the rest of its pose stays. Calls in a row are one undo step, so a script can animate an arm frame by frame.
Changes things (one undo step). By name: "model.object_pose". For AI: model_object_pose.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
part |
text | needed | The part's name or number (1 the first). | |
n |
whole number | - | 1 to 4,096 | Which model object on the layer, 1 the bottom (left out: the top one). |
x |
number | - | Moved across (the model's units). | |
y |
number | - | Moved up. | |
z |
number | - | Moved towards the front. | |
yaw |
number | - | Turned round up, degrees. | |
pitch |
number | - | Tipped forward, degrees. | |
roll |
number | - | Rolled, degrees. | |
scale |
number | - | 0.01 to 100 | Its size (1 as it is). |
pivot |
one of the choices | - | middle, top, bottom |
What it turns round: its middle, top (a shoulder, a hip) or bottom. |
reset |
true or false | - | Put the part back as the model has it first. |
dp.model.texture{ } - 3D model: a texture
Puts a texture on one of the model's colours (its materials: model.info lists them): the picture in front as it is now (its selection, if it has one) - paint a texture in Deluxe Pixel - or an image file; or takes it off. Models without texture coordinates get a box projection.
Changes things (one undo step). By name: "model.texture". For AI: model_texture.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
material |
text | needed | The material's name or number. | |
from |
one of the choices | picture |
picture, file, none |
Where the texture comes from. |
path |
file path | - | The image file (from = file). |
picture
dp.picture.convert{ } - Convert true colour / palette
Changes the picture in front between true colour and a palette, as one undo step: as the Image menu's Machines section does. opened_palette keeps it to the colours of the file as it was opened.
Changes things (one undo step). By name: "picture.convert". For AI: picture_convert.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
to |
one of the choices | needed | true_colour, palette, opened_palette |
true_colour (24-bit, any colour), palette (the colours used, merged to the machine's number), or opened_palette (every pixel one of the colours of the palette the picture was opened with, in its order). |
dither |
one of the choices | None |
None, Ordered, Floyd-Steinberg, Atkinson |
opened_palette: how colours between the palette's are made. |
prop
dp.prop.list{ } - List properties
The properties of the map (or picture), a map layer, a tile or a map object: a line each, name (type) = value; a class's members in braces.
A question: changes nothing. Not written into recorded macros. By name: "prop.list". For AI: prop_list.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
target |
one of the choices | map |
map, layer, tile, object, template |
Whose: the map's (or picture's) own, a map layer's, a tile's, or a map object's. |
layer |
whole number | - | 0 to 255 | target = layer: which (from 0, the bottom; left out: the active one). |
tile |
whole number | - | 0 to ... | target = tile: the tile's number (in the active layer's tile set). |
object |
whole number | - | 1 to ... | target = object: the object's id (left out: the chosen one); target = template: the template's. |
dp.prop.set{ } - Set a property
Gives the map (or picture), a map layer, a tile or a map object a property, or changes one's value. A class's member: name it "wave.count". type is bool, int, float, string, color, file, or the name of an enum or class (Property types); left out, an existing property keeps its type, and a new one is text.
Changes things (one undo step). By name: "prop.set". For AI: prop_set.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
target |
one of the choices | map |
map, layer, tile, object, template |
Whose: the map's (or picture's) own, a map layer's, a tile's, or a map object's. |
name |
text | needed | The property's name. | |
value |
text | - | Its value (flags: values joined by commas). | |
type |
text | - | Its type (left out: as it is, or text for a new one). | |
layer |
whole number | - | 0 to 255 | target = layer: which (from 0, the bottom; left out: the active one). |
tile |
whole number | - | 0 to ... | target = tile: the tile's number (in the active layer's tile set). |
object |
whole number | - | 1 to ... | target = object: the object's id (left out: the chosen one); target = template: the template's. |
dp.prop.remove{ } - Remove a property
Takes a property away from the map (or picture), a map layer, a tile or a map object.
Changes things (one undo step). By name: "prop.remove". For AI: prop_remove.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
target |
one of the choices | map |
map, layer, tile, object, template |
Whose: the map's (or picture's) own, a map layer's, a tile's, or a map object's. |
name |
text | needed | The property's name. | |
layer |
whole number | - | 0 to 255 | target = layer: which (from 0, the bottom; left out: the active one). |
tile |
whole number | - | 0 to ... | target = tile: the tile's number (in the active layer's tile set). |
object |
whole number | - | 1 to ... | target = object: the object's id (left out: the chosen one); target = template: the template's. |
reference
dp.reference.add{ } - Add a reference image
A picture file (PNG, JPEG, GIF, TIFF, BMP...) to look at while drawing: shown beside the picture or over it, colours picked from it with Pick colour, never part of the picture (not a layer; not exported). Kept with the .dpx.
Changes things (one undo step). By name: "reference.add". For AI: reference_add.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
path |
file path | needed | The picture file. | |
place |
one of the choices | beside |
beside, over |
Beside the picture (to its right, as tall as it) or over it (fitted, to trace). |
opacity |
whole number | 100 |
5 to 100 | How solid it shows, 5-100%. |
dp.reference.set{ } - Change a reference image
Moves, sizes, shows or hides a reference image (numbered from 0 in the order added; the last is on top).
Changes things (one undo step). By name: "reference.set". For AI: reference_set.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
index |
whole number | 0 |
0 to 255 | Which one. |
x |
whole number | - | Its left edge, in picture pixels. | |
y |
whole number | - | Its top edge, in picture pixels. | |
size |
number | - | 1 to 10,000 | How big it shows, % (100: one image pixel to one picture pixel). |
opacity |
whole number | - | 5 to 100 | How solid it shows, 5-100%. |
visible |
true or false | - | Shown or hidden. | |
place |
one of the choices | - | beside, over |
Put it beside the picture or fit it over it (before x, y and size, which can adjust it). |
dp.reference.remove{ } - Remove a reference image
Takes a reference image away (the picture is untouched).
Changes things (one undo step). By name: "reference.remove". For AI: reference_remove.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
index |
whole number | needed | 0 to 255 | Which one (from 0). |
sheet
dp.sheet.open{ } - Open a sprite sheet
Opens a packed sprite sheet by its data file (Gideros .txt, TexturePacker JSON hash / array, Aseprite JSON, a libGDX .atlas, Unity .tpsheet, Godot SpriteFrames .tres, or a TexturePacker .tps project): the sheet picture, with each region as a sprite (walk_0001-style frames and Aseprite tags as animations).
Changes things (one undo step). By name: "sheet.open". For AI: sheet_open.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
path |
file path | needed | The data file or .tps. | |
set |
true or false | false |
Open every sheet of its multipack set (name0.json, name1.json ... beside it). |
dp.sheet.nine{ } - Set 9-patch borders
Makes a sprite of the picture in front a 9-patch (9-slice): the borders in from each edge (of the whole sprite, before any trimming) that stay as drawn while the middle stretches. All 0 makes it an ordinary sprite again. They're saved with the sheet's data (TexturePacker JSON, Aseprite, libGDX, Unity).
Changes things (one undo step). By name: "sheet.nine". For AI: sheet_nine.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
sprite |
text | needed | The sprite's name. | |
cel |
whole number | -1 |
-1 to 65,535 | Which of its cels (from 0); -1: every one. |
left |
whole number | 0 |
0 to 4,096 | Pixels in from the left. |
top |
whole number | 0 |
0 to 4,096 | Pixels in from the top. |
right |
whole number | 0 |
0 to 4,096 | Pixels in from the right. |
bottom |
whole number | 0 |
0 to 4,096 | Pixels in from the bottom. |
dp.sheet.save{ } - Save a sprite sheet's data
Writes the picture in front's sprites as a sheet's data file, and the sheet as a PNG beside it. A sheet of a multipack set saves the whole set (phaser and libgdx: one data file; the others: name0, name1 ...).
Changes things (one undo step). By name: "sheet.save". For AI: sheet_save.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
path |
file path | needed | The data file to write. | |
format |
one of the choices | gideros |
gideros, json-hash, json-array, aseprite, libgdx, unity, godot, phaser, tps |
The data format (tps: a TexturePacker project, with a JSON hash beside it). |
png |
true or false | true |
Also save the sheet picture as a PNG beside it. |
dp.sheet.pack{ } - Pack a sprite sheet
Packs the picture in front's frames, layers or sprites, or a folder of pictures, into a new sheet picture (its regions as sprites).
Changes things (one undo step). By name: "sheet.pack". For AI: sheet_pack.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
from |
one of the choices | frames |
frames, layers, sprites, folder |
What to pack. |
folder |
file path | - | The folder (from = folder). | |
trim |
true or false | true |
Trim see-through edges. | |
aliases |
true or false | true |
Identical sprites stored once. | |
padding |
whole number | 1 |
0 to 64 | Pixels between sprites. |
border |
whole number | 0 |
0 to 64 | Pixels round the sheet. |
extrude |
whole number | 0 |
0 to 8 | Edge pixels repeated round each sprite. |
pot |
true or false | true |
A power-of-two size. | |
square |
true or false | false |
Square. | |
max |
whole number | 2048 |
16 to 16,384 | The largest the sheet may be, each way. |
rotate |
true or false | false |
Sprites may be turned 90 degrees clockwise to fit better. | |
multipack |
true or false | false |
More than one sheet when they don't fit on one. | |
polygons |
true or false | false |
Packed by their outlines instead of their rectangles (no rotate or extrude; TexturePacker JSON only). |
target
dp.target.add{ } - Add a machine target
Adds a machine target to the picture in front: a computer it's made for, shown as that machine would show it (fitted to its screen unless fit = false), kept up to date as the picture changes, and exported in its formats (target.export). Saved with the picture.
Changes things (one undo step). By name: "target.add". For AI: target_add.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
machine |
text | needed | The machine (a system profile's name, e.g. "ZX Spectrum", "C64", "ST"). | |
name |
text | - | The target's name (left out: the machine's, made unique). | |
mode |
text | - | The screen mode (as the system profile names it; left out: its first). | |
dither |
text | - | none, ordered, floyd-steinberg (the default) or atkinson. | |
strength |
number | - | 0 to 100 | Dithering strength, 0-100. |
fix_clashes |
true or false | - | Modes with colour cells: choose each cell's colours so nothing clashes. | |
contrast |
number | - | 0 to 100 | Before converting: contrast, 0-100. |
saturation |
number | - | 0 to 100 | Saturation, 0-100. |
demud |
number | - | 0 to 100 | De-mud, 0-100. |
fit |
true or false | - | Fitted to the machine's screen (true, the default) or the picture's own size. | |
sprites |
text | - | Sprites: the machine's sprite hardware (as the Timeline's hardware check names it, e.g. "C64 (multicolour)", "NES", "Spectrum Next"; "none" for none): every frame, the sprites kept to its colours and checked. | |
split_sprites |
true or false | - | Sprites: cels that are no size the sprite hardware has shown as poses of its sprites (pieces of one of its sizes). | |
tiles |
text | - | Tile maps: the machine's tile hardware (e.g. "Mega Drive", "SNES", "Spectrum Next", "NES"; "none" for none): its palette banks, mirrors and tile limit. | |
reduce_tiles |
true or false | - | Tile maps: merge the most alike tiles until they fit the tile hardware's limit. | |
show |
true or false | true |
Show it in a canvas of its own. |
dp.target.set{ } - Change a machine target
Changes a machine target's settings (the ones given): its canvas follows. Targets can be named by part of their name.
Changes things (one undo step). By name: "target.set". For AI: target_set.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
name |
text | needed | The target. | |
machine |
text | - | Another machine. | |
rename |
text | - | A new name. | |
mode |
text | - | The screen mode (as the system profile names it; left out: its first). | |
dither |
text | - | none, ordered, floyd-steinberg (the default) or atkinson. | |
strength |
number | - | 0 to 100 | Dithering strength, 0-100. |
fix_clashes |
true or false | - | Modes with colour cells: choose each cell's colours so nothing clashes. | |
contrast |
number | - | 0 to 100 | Before converting: contrast, 0-100. |
saturation |
number | - | 0 to 100 | Saturation, 0-100. |
demud |
number | - | 0 to 100 | De-mud, 0-100. |
fit |
true or false | - | Fitted to the machine's screen (true, the default) or the picture's own size. | |
sprites |
text | - | Sprites: the machine's sprite hardware (as the Timeline's hardware check names it, e.g. "C64 (multicolour)", "NES", "Spectrum Next"; "none" for none): every frame, the sprites kept to its colours and checked. | |
split_sprites |
true or false | - | Sprites: cels that are no size the sprite hardware has shown as poses of its sprites (pieces of one of its sizes). | |
tiles |
text | - | Tile maps: the machine's tile hardware (e.g. "Mega Drive", "SNES", "Spectrum Next", "NES"; "none" for none): its palette banks, mirrors and tile limit. | |
reduce_tiles |
true or false | - | Tile maps: merge the most alike tiles until they fit the tile hardware's limit. |
dp.target.remove{ } - Remove a machine target
Removes a machine target from the picture (and closes its canvas).
Changes things (one undo step). By name: "target.remove". For AI: target_remove.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
name |
text | needed | The target. |
dp.target.list() - List machine targets
The picture's machine targets: name, machine, mode and settings.
Changes things (one undo step). By name: "target.list". For AI: target_list.
No settings.
dp.target.freeze{ } - Freeze a machine target
Freezes a machine target (frozen = false: unfreezes it). Frozen, it no longer follows the picture: its picture as it is now (touch-ups and all) is the finished art for that machine, painted on freely and saved with the picture. Unfrozen, it's made again from the picture, and every pixel the frozen picture had that the new conversion doesn't becomes a touch-up.
Changes things (one undo step). By name: "target.freeze". For AI: target_freeze.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
name |
text | needed | The target. | |
frozen |
true or false | true |
Frozen (true) or following the picture again. |
dp.target.clear_touches{ } - Clear a target's touch-ups
Forgets a machine target's touch-ups (pixels fixed by hand): it's made again as converted.
Changes things (one undo step). By name: "target.clear_touches". For AI: target_clear_touches.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
name |
text | needed | The target. |
dp.target.export_all{ } - Export all machine targets
Exports every machine target of the picture again, each the way it was last exported (its format and file; into folder, if given, keeping the files' names). Targets never exported are listed: export each once (the Export window, or target.export).
Changes things (one undo step). By name: "target.export_all". For AI: target_export_all.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
folder |
file path | - | Write them all into this folder instead. |
dp.target.show{ } - Show a machine target
Shows a machine target in its own canvas (in front), made from the picture as it is now.
Changes things (one undo step). By name: "target.show". For AI: target_show.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
name |
text | needed | The target. |
dp.target.export{ } - Export a machine target
Writes a machine target in one of the Export window's formats (as file.export does), made from the picture as it is now.
Changes things (one undo step). By name: "target.export". For AI: target_export.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
name |
text | needed | The target. | |
format |
text | needed | The format's name as the Export window lists it. | |
path |
file path | needed | The file to write. |
terrain
dp.terrain.list() - List terrain sets
The tile set's terrain sets (on a map, the active layer's tile set): a line each, number, name, kind, its terrains (1 up) and how many tiles take part.
A question: changes nothing. Not written into recorded macros. By name: "terrain.list". For AI: terrain_list.
No settings.
dp.terrain.new{ } - New terrain set
Makes a terrain set on the tile set (on a map, the active layer's), and chooses it.
Changes things (one undo step). By name: "terrain.new". For AI: terrain_new.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
name |
text | Terrains |
Its name. | |
kind |
one of the choices | corner |
corner, edge, mixed |
corner: transitions (16 tiles); edge: paths, roads, fences (16); mixed: the 47-tile blob set. |
terrains |
text | Grass |
Its terrains' names, joined by commas. |
dp.terrain.remove{ } - Remove a terrain set
Takes a terrain set away from the tile set.
Changes things (one undo step). By name: "terrain.remove". For AI: terrain_remove.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
set |
text | - | The terrain set: its name or number from 0 (left out: the one chosen in Terrains). |
dp.terrain.layout{ } - Set up terrain tiles from a layout
Sets the terrains of 16 or 47 tiles one after another, laid out in a ready-made order, terrain a over b (b none: empty).
Changes things (one undo step). By name: "terrain.layout". For AI: terrain_layout.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
set |
text | - | The terrain set: its name or number from 0 (left out: the one chosen in Terrains). | |
layout |
one of the choices | corner16 |
corner16, edge16, blob47 |
corner16: tile n has terrain a at the corners of n's bits (1 top-right, 2 bottom-right, 4 bottom-left, 8 top-left); edge16: n's bits are the sides (1 top, 2 right, 4 bottom, 8 left); blob47: the 47 blob masks in order, smallest first. |
first |
whole number | 0 |
0 to 1,048,574 | The first tile's number. |
a |
text | 1 |
The terrain (name or number from 1). | |
b |
text | none |
The other terrain (or none). |
dp.terrain.tile{ } - Set a tile's terrains
Which terrain is at each of a tile's 8 places, in Tiled's order: top, top-right, right, bottom-right, bottom, bottom-left, left, top-left; numbers from 1 (0 none), joined by commas. Places the set's kind doesn't use are kept as 0. places left out takes the tile out of the set.
Changes things (one undo step). By name: "terrain.tile". For AI: terrain_tile.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
set |
text | - | The terrain set: its name or number from 0 (left out: the one chosen in Terrains). | |
tile |
whole number | needed | 0 to 1,048,574 | The tile's number. |
places |
text | - | 8 numbers, joined by commas. |
dp.terrain.paint{ } - Paint a terrain
Paints a terrain into a cell, or every cell of a rectangle, of the active map layer; the tiles there and round about are chosen from the set (one undo step; with cells selected, only those change). terrain none takes it away.
Changes things (one undo step). By name: "terrain.paint". For AI: terrain_paint.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
set |
text | - | The terrain set: its name or number from 0 (left out: the one chosen in Terrains). | |
terrain |
text | needed | The terrain: its name, or number from 1 (none: erase). | |
x |
whole number | needed | 0 to 65,535 | Column. |
y |
whole number | needed | 0 to 65,535 | Row. |
w |
whole number | 1 |
1 to 65,535 | Columns wide. |
h |
whole number | 1 |
1 to 65,535 | Rows high. |
tiles
dp.tiles.shape_template{ } - New tile set for a map shape
A new picture of tiles to paint for a map of this shape: each tile's cell shape (diamond, hex, brick, square) outlined in the left colour, the corners outside it see-through. Left out: the map in front's shape and tile size.
Changes things (one undo step). By name: "tiles.shape_template". For AI: tiles_shape_template.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
shape |
one of the choices | - | square, isometric, staggered, hexagonal, brick |
square, isometric, staggered, hexagonal or brick (left out: the map in front's shape). |
stagger |
one of the choices | - | rows, columns |
Hexagonal and brick: rows shifted or columns. |
tile_w |
whole number | - | 2 to 256 | Tile width in pixels. |
tile_h |
whole number | - | 2 to 256 | Tile height in pixels. |
hex_side |
whole number | - | 0 to 256 | Hexagonal: the straight edge's length in pixels (left out: half the tile). |
count |
whole number | 64 |
1 to 4,096 | How many tiles. |
columns |
whole number | 8 |
1 to 256 | Tiles across. |
dp.tiles.blocks3d{ } - 3D cubes from tiles
Each tile of the picture in front on the top and sides of a 3D cube, rendered in the same colours into a new picture of cubes, in the same order: isometric (2:1) or three-quarter view tiles.
Changes things (one undo step). By name: "tiles.blocks3d". For AI: tiles_blocks3d.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
tile_width |
whole number | 16 |
1 to 512 | The tiles' width. |
tile_height |
whole number | 16 |
1 to 512 | The tiles' height. |
view |
one of the choices | isometric |
isometric, three-quarter, custom |
How the cubes are seen. |
yaw |
number | 45 |
-180 to 180 | Custom view: turned, degrees. |
pitch |
number | 30 |
0 to 90 | Custom view: looking down, degrees. |
height |
number | 1 |
0.05 to 8 | The cube's height, as a fraction of its width. |
sides |
one of the choices | same |
same, below, plain |
The sides' tile. |
width |
whole number | 0 |
0 to 1,024 | Each cube's cell width (0: automatic). |
cell_height |
whole number | 0 |
0 to 1,024 | Each cube's cell height (0: automatic). |
outline |
whole number | -1 |
-1 to 255 | A colour number round each cube (-1: none). |
dither |
true or false | - | Dither between shades. | |
top_as_drawn |
true or false | - | The top exactly as the tile, not shaded (the default). |
dp.tiles.animate{ } - Animate a tile
Makes a tile of the active map layer's tile set an animated tile (Tiled's tile animations): wherever it's on a map, it shows the frames in turn, each for its time, round and round. Give frames as "tile:ms, tile:ms, ...", or count: that many tiles from it on. Replaces any animation it had. One undo step (on the tile set).
Changes things (one undo step). By name: "tiles.animate". For AI: tiles_animate.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
tile |
whole number | needed | 0 to 1,048,575 | The tile animated (its number in the set). |
frames |
text | - | Its frames: "12:100, 13:100, 14:200" (a frame without :ms lasts ms). | |
count |
whole number | - | 2 to 4,096 | Or this many tiles from it on (2 or more). |
ms |
whole number | 150 |
1 to 600,000 | How long each frame lasts when not given. |
dp.tiles.animation_remove{ } - Stop animating a tile
The tile shows just itself again (on every map using its tile set).
Changes things (one undo step). By name: "tiles.animation_remove". For AI: tiles_animation_remove.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
tile |
whole number | needed | 0 to 1,048,575 | The animated tile's number. |
dp.tiles.animations() - List animated tiles
The active map layer's tile set's animated tiles, a line each: "tile: frame:ms, frame:ms, ...".
A question: changes nothing. Not written into recorded macros. By name: "tiles.animations". For AI: tiles_animations.
No settings.
dp.tiles.animation_play{ } - Play animated tiles
Animated tiles play on every map (on), or each shows its own tile (off).
Changes things (one undo step). By name: "tiles.animation_play". For AI: tiles_animation_play.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
on |
true or false | true |
Play or stop. | |
restart |
true or false | false |
Every animation from its first frame. |
dp.tiles.random{ } - Random tiles
Sets the random tiles the map brush paints with (Tiled's random mode): every cell painted - brush, line, box, fill, Fill selection, map.set with random = true - is one of them, by weight, optionally mirrored or turned at random. Give tiles as "3, 4, 5" or with weights "3:4, 4:1, 5:1"; on = false turns it off (the brush paints as it is).
Changes things (one undo step). By name: "tiles.random". For AI: tiles_random.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
tiles |
text | - | The tiles, "3, 4, 5" or "3:4, 4:1" (tile:weight). Left out: as they are. | |
on |
true or false | true |
Paint random tiles (true) or not. | |
flip_x |
true or false | - | Mirror across at random. | |
flip_y |
true or false | - | Mirror down at random. | |
rotate |
true or false | - | Turn a quarter at random (square tiles). |
dp.tiles.collision{ } - Set a tile's collision
Gives a tile of the active map layer's tile set collision shapes, in its own pixels (Tiled's tile collision editor): a ready-made shape (full, top_half, bottom_half, left_half, right_half, slope_up, slope_down, platform), boxes "x, y, w, h; x, y, w, h", or a polygon "x, y, x, y, x, y...". Replaces what it had unless add = true. Every cell holding the tile is solid there (turned and mirrored as the cell). Saved with the tile set, and in Tiled's maps and Godot's scenes. One undo step.
Changes things (one undo step). By name: "tiles.collision". For AI: tiles_collision.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
tile |
whole number | needed | 0 to 1,048,575 | The tile's number in the set. |
preset |
text | - | A ready-made shape: full, top_half, bottom_half, left_half, right_half, slope_up, slope_down, platform. | |
boxes |
text | - | Boxes: "x, y, w, h" each, separated by ;. | |
polygon |
text | - | A polygon's corners: "x, y, x, y, x, y...". | |
add |
true or false | false |
Add to the shapes it has (false: replace them). |
dp.tiles.collision_clear{ } - Clear a tile's collision
The tile has no collision shapes (nothing solid).
Changes things (one undo step). By name: "tiles.collision_clear". For AI: tiles_collision_clear.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
tile |
whole number | needed | 0 to 1,048,575 | The tile's number in the set. |
dp.tiles.collisions() - List tiles' collision
Each tile of the active map layer's tile set with collision shapes, a line each: its shapes in its pixels.
A question: changes nothing. Not written into recorded macros. By name: "tiles.collisions". For AI: tiles_collisions.
No settings.
dp.tiles.stamp_save{ } - Save a stamp
Keeps the map brush (picked up from a map, or a block of tiles) as a named stamp (Tiled's stamps), saved for every map in the person's own folder (stamps.json). A stamp of that name already: the brush replaces it.
Changes things (one undo step). By name: "tiles.stamp_save". For AI: tiles_stamp_save.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
name |
text | needed | Its name. |
dp.tiles.stamp_add_variation{ } - Add a stamp variation
Adds the map brush to a stamp as another variation: wherever the stamp is placed, one of its variations is, at random (rock clusters, trees, cracks that don't repeat).
Changes things (one undo step). By name: "tiles.stamp_add_variation". For AI: tiles_stamp_add_variation.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
name |
text | needed | The stamp. |
dp.tiles.stamp_use{ } - Use a stamp
Makes a saved stamp the map brush (with its variations, one at random per placement).
Changes things (one undo step). By name: "tiles.stamp_use". For AI: tiles_stamp_use.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
name |
text | needed | The stamp. |
dp.tiles.stamp_delete{ } - Forget a stamp
Forgets a saved stamp.
Changes things (one undo step). By name: "tiles.stamp_delete". For AI: tiles_stamp_delete.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
name |
text | needed | The stamp. |
dp.tiles.stamps() - List stamps
The saved stamps: each one's name, size and how many variations.
Changes things (one undo step). By name: "tiles.stamps". For AI: tiles_stamps.
No settings.
dp.tiles.quick_stamp{ } - Quick stamp
Keeps the map brush as quick stamp 1-9 (as Ctrl+1-9 on a map), or with use = true makes quick stamp 1-9 the brush (as 1-9).
Changes things (one undo step). By name: "tiles.quick_stamp". For AI: tiles_quick_stamp.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
slot |
whole number | needed | 1 to 9 | 1 to 9. |
use |
true or false | false |
Bring it back (true) rather than keep the brush. |
types
dp.types.enum{ } - Make an enum
Makes (or changes) an enum for properties: one of a list of values, or with flags = true, any of them at once (as bits). Kept with the map or picture in front.
Changes things (one undo step). By name: "types.enum". For AI: types_enum.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
name |
text | needed | Its name. | |
values |
text | needed | Its values, joined by commas. | |
flags |
true or false | false |
Any of the values at once. | |
attributes |
true or false | false |
(Maps, flags) name the cells' 8 attribute bits with it. |
dp.types.class{ } - Make a class
Makes (or changes) a class for properties: named members with their default values, written "count:int=3; kind:Kind=Enemy; first:Spawn" (a member of a class type takes that class's members).
Changes things (one undo step). By name: "types.class". For AI: types_class.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
name |
text | needed | Its name. | |
members |
text | needed | Its members (see above). |
dp.types.remove{ } - Remove an enum or class
Takes an enum or class away (properties of that type keep their values).
Changes things (one undo step). By name: "types.remove". For AI: types_remove.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
name |
text | needed | Its name. |
voxel
dp.voxel.open_layers{ } - Open voxels as layers
Opens a MagicaVoxel .vox model as a new picture, a layer for each slice (the bottom layer the lowest; up to 32), in its colours. (Opening a .vox otherwise shows it as a 3D model.)
Changes things (one undo step). By name: "voxel.open_layers". For AI: voxel_open_layers.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
path |
file path | needed | The .vox file. |
dp.voxel.save_layers{ } - Save layers as voxels
Saves the picture in front's layers (those shown) as a MagicaVoxel .vox model, a layer a slice (the bottom layer the lowest), up to 256 x 256: palette pictures, the colours kept.
Changes things (one undo step). By name: "voxel.save_layers". For AI: voxel_save_layers.
| Setting | Type | Needed / default | Range or choices | What it is |
|---|---|---|---|---|
path |
file path | needed | The .vox file to write. |
dp.voxel.layers_to_model() - Layers as a 3D model
The picture in front's layers (those shown) as a voxel model in the 3D window, a layer a slice: turn and light it, render frames, or put it on a live 3D layer.
Changes things (one undo step). By name: "voxel.layers_to_model". For AI: voxel_layers_to_model.
No settings.
Luau in Deluxe Pixel
- Luau: luau-lang/luau commit 1eca9fda3e4753a1592000f6cfdf659aaa778b7d (6 October 2026): Common, Ast, Bytecode, Compiler, VM and CodeGen. MIT licence (Roblox; parts Lua.org, PUC-Rio).
- Native code: Run (Ctrl+Enter) compiles a script to the computer's own machine code (Luau's code generator) when the processor supports it (x64 with SSE4.1, or ARM64), so a script's own maths, loops, tables and buffers run several times faster than interpreted; commands take the same time either way. Script console > Options > Native code turns it off. Debugging always uses the interpreter (breakpoints and stepping need it).
- Compiled with: optimisation level 1 (level 0 when debugging, so every line is where it's written), full debug information (line numbers and local variables' names, for errors and the debugger).
- Language: all of Luau:
continue, compound assignment (+=,-=,*=,/=,//=,%=,^=,..=),if ... then ... elseexpressions, string interpolation with backticks (`x is {x}`), floor division//, generalised iteration (for k, v in t do), numbers with_separators and0bbinary, type annotations (local n: number,type Point = { x: number }; the console's checks read them, they don't slow anything).
Libraries
What a script can use - everything listed is there in this build:
- bit32:
arshift,band,bnot,bor,btest,bxor,byteswap,countlz,countrz,extract,lrotate,lshift,replace,rrotate,rshift - buffer:
copy,create,fill,fromstring,len,readbits,readf32,readf64,readi16,readi32,readi8,readinteger,readstring,readu16,readu32,readu8,tostring,writebits,writef32,writef64,writei16,writei32,writei8,writeinteger,writestring,writeu16,writeu32,writeu8 - coroutine:
close,create,isyieldable,resume,running,status,wrap,yield - debug (only looking: no hooks or changing variables):
info,traceback - integer:
add,arshift,band,bnot,bor,bswap,btest,bxor,clamp,countlz,countrz,create,div,extract,fromstring,ge,gt,idiv,le,lrotate,lshift,lt,max,maxsigned,min,minsigned,mod,mul,neg,rem,replace,rrotate,rshift,sub,tonumber,udiv,uge,ugt,ule,ult,urem - math:
abs,acos,asin,atan,atan2,ceil,clamp,cos,cosh,deg,e,exp,floor,fmod,frexp,huge,isfinite,isinf,isnan,ldexp,lerp,log,log10,map,max,min,modf,nan,noise,phi,pi,pow,rad,random,randomseed,round,sign,sin,sinh,sqrt,sqrt2,tan,tanh,tau - os (no files, programs or environment: only the time):
clock,date,difftime,time - string:
byte,char,find,format,gmatch,gsub,len,lower,match,pack,packsize,rep,reverse,split,sub,unpack,upper - table:
clear,clone,concat,create,find,foreach,foreachi,freeze,getn,insert,isfrozen,maxn,move,pack,remove,sort,unpack - utf8:
char,charpattern,codepoint,codes,len,offset - vector:
abs,angle,ceil,clamp,create,cross,dot,floor,lerp,magnitude,max,min,normalize,one,sign,zero - Built in (globals):
_G,_VERSION,assert,error,gcinfo,getfenv,getmetatable,ipairs,newproxy,next,pairs,pcall,print,rawequal,rawget,rawlen,rawset,select,setfenv,setmetatable,tonumber,tostring,type,typeof,unpack,xpcall
Left out on purpose
- Not there:
io,require,load,loadstring,dofile,loadfile,module,package,collectgarbage. - The sandbox: scripts can't read or write files, use the network, start programs, or load other code. The libraries and the
dptable are read-only (a script can't replacemath.floorordp.width). Each run starts with fresh globals: nothing carries over from one run to the next. - Limits: a run is stopped after 30 seconds (time paused in the debugger doesn't count), or when Escape is pressed.
- Not built in: Luau's type checker and linter (Analysis) - the console's own checks cover the mistakes that matter for Deluxe Pixel's scripts - and
require(loading other script files).
Luau's optional features in this build
Luau keeps new features and fixes behind switches ("fast flags"), off unless the program using it turns them on. Deluxe Pixel turns on every one that Luau's own tools do: all but the experimental ones. In this build:
58 of 60 on.
| Feature | On |
|---|---|
LuauAllowGlobalDeclarationToBeCalledClass |
yes |
LuauBackedgeHeapCheck |
yes |
LuauBufferCage |
yes |
LuauBytecodeCostModel |
yes |
LuauCallFeedback |
yes |
LuauCallLuauTm |
yes |
LuauCodeGenFastpcall |
yes |
LuauCodegenA64ForgLoopArray |
yes |
LuauCodegenBufferInteger |
yes |
LuauCodegenConstPropMinOffset |
yes |
LuauCodegenInteger3 |
yes |
LuauCodegenIntegerCompare |
yes |
LuauCodegenLimitVersions |
yes |
LuauCodegenNoLinearFastpcall |
yes |
LuauCodegenNopPadding |
yes |
LuauCodegenPropagateFallbackTags |
yes |
LuauCodegenX64IntSpillRestore |
yes |
LuauCompileCleanBlockDeadClose |
yes |
LuauCompileConcatTargetTop |
yes |
LuauCompileEmitVectorDouble |
yes |
LuauCompileFastpcall |
yes |
LuauCompileLoopUnrollZero |
yes |
LuauCompileMoveElision |
yes |
LuauCompileNoFoldVectorEqW |
yes |
LuauCompileRecursiveAliases |
yes |
LuauCompileRefactorFeedback |
yes |
LuauCompileReuseLocalRegs |
yes |
LuauCompileUndoEmitAdjust |
yes |
LuauCostModel |
yes |
LuauEasyStateInit |
yes |
LuauEmitCallFeedback |
yes |
LuauEnumMoreEdges |
yes |
LuauExperimentalIfLocalSyntax |
no (experimental) |
LuauExportValueSyntax |
yes |
LuauExportedTypesParticipateInScc |
yes |
LuauFastpcall |
yes |
LuauFastpcallInterrupt |
yes |
LuauFrozenMetaButterfly |
yes |
LuauGcHeapShrinkFix |
yes |
LuauGcTraceUdata |
yes |
LuauIntegerBufferFastcalls |
yes |
LuauIntegerFastcalls |
yes |
LuauIntegerLibrary |
yes |
LuauIntegerType2 |
yes |
LuauLoadRemapOptionalUserdata |
yes |
LuauNewPointerEncode |
yes |
LuauOptimizeExportTable |
yes |
LuauOptimizeStringGsub |
yes |
LuauOptimizeStringSplit |
yes |
LuauPcallOptimize |
yes |
LuauPromoteProto |
yes |
LuauSandboxFreezesVectorMetatable |
yes |
LuauSingleTypeOptionalPackReturnsAttributeParens |
yes |
LuauSolverV2 |
no (experimental) |
LuauSplitTableLookups |
yes |
LuauTableArrayAdjustCheck |
yes |
LuauTableArrayShrinkOrder |
yes |
LuauTableMoveTimeoutFix |
yes |
LuauTableRobustOom |
yes |
LuauVirtualBcBuilder |
yes |
The script console
- Editor: Luau colours, line numbers, bracket matching, find and replace (Ctrl+F), several cursors (Ctrl+D selects the next match), comment lines (Ctrl+/), indent (Ctrl+] / Ctrl+[), undo and redo.
- Checks as you type: syntax errors (Luau's own parser), and the mistakes that would only show when run: a misspelt command or group ("dp.draw.rec isn't a command. Did you mean dp.draw.rect?"), a setting a command doesn't have, settings it needs, a value of the wrong kind or out of range, a misspelt library function (
math.flor), a global that's never set (pirnt), a local that's never used. Underlined, marked by the line number, listed under Problems. - Autocomplete (as you type, or Ctrl+Space): after
dp.the groups and helpers, afterdp.draw.its commands, inside a command's{ }its settings, aftermath.(any library) its functions, otherwise keywords, globals and the script's own names. - Hover help: a command's description and settings, a setting's meaning inside
{ }, a helper's use; while paused, a variable's value. - Errors: the line is highlighted red with the message; the Output shows where it was called from, each line clickable.
- Layout: the editor and the Output, Problems, Variables and Call stack panels are docked windows: drag the split, reorder the tabs, or drag a panel anywhere (Options > Reset the console's layout puts them back).
The debugger
| Key | |
|---|---|
| Ctrl+Enter, Ctrl+F5 | Run (native code, no stopping) |
| F5 | Debug: run, stopping at breakpoints; Continue when paused |
| F9, or click left of a line number | Breakpoint on or off |
| F10 | Step over (when nothing's running: start, stopped at the first line) |
| F11 | Step into |
| Shift+F11 | Step out |
| Shift+F5 | Stop the paused script |
| Pause, or F6 | Pause a debugged script where it is (even an endless loop) |
| Escape | Stop any running script |
While paused: the line is highlighted, Variables shows the local variables (and the globals the script made, tables shown in brief), Call stack shows the functions it's in (click one for its variables), and hovering a variable shows its value. The picture can be looked at meanwhile; each stretch the script runs between pauses is one undo step.
Events: scripts that keep running
Most scripts run once, top to bottom. A script can instead give Deluxe Pixel a function to call when something happens - every frame, a key, the mouse over the picture - with dp.on, or start a task that runs alongside with dp.spawn. Then it keeps running after its last line: the menu bar says Running: name (Stop), and Escape, the script console's Stop, Edit > Stop running scripts, dp.stop() or running it again ends it. Plugins use plugin.on (the same) and their handlers last while the plugin is loaded.
local x = 0
dp.on("frame", function(dt, t) -- every frame, about 60 a second
x = (x + 1) % dp.width()
dp.draw.pixel{ x = x, y = 10, colour = 2 }
end)
dp.on("key", function(e)
if e.key == "Space" then print("jump!") return true end -- true: used up (the tools don't see it)
end)
dp.spawn(function() -- a task: it can wait without holding anything up
for i = 1, 3 do dp.wait(1) print("tick", i) end
end)
- Each call has a quarter of a second at most; a handler that fails or takes longer stops its script (the error goes to the console's Output).
- What handlers change is one undo step, joined frame after frame while nothing else happens: one Undo takes back a whole run.
- The events, and what the handler is given:
| Event | The handler gets | When |
|---|---|---|
frame |
dt, t |
Every frame (about 60 a second): dt is the seconds since the last frame, t the seconds since the script started. |
key |
e: key, ctrl, shift, alt |
A key went down (not while typing in a box). e.key is its name: "A", "7", "Space", "Enter", "Left", "F5"... Return true and Deluxe Pixel doesn't act on it. |
keyup |
e: key, ctrl, shift, alt |
A key was let go. |
mouse |
e: x, y, left, right, middle, pressed, released, wheel |
The pointer moved over the picture, a button went down or up, or the wheel turned. x, y are the picture's pixels; pressed / released name the button ("left", "right", "middle"). Return true on a press and the tool doesn't paint. |
pad |
e: pad, button, pressed, connected |
A game controller's button went down (pressed = true) or up (false), or a controller was plugged in or out (connected = true / false; button is nil). e.pad is its number for dp.pad. |
picture |
e: name, width, height, map |
Another picture (or map) became the active one. |
layer |
e: index, name |
Another layer became the active one (0 is the bottom). |
colour |
e: left, right |
The left or right colour changed. |
palette |
e: colours |
The active picture's palette changed. |
selection |
e: empty, x, y, w, h |
The selection changed (x, y, w, h: the box round it). |
open |
e: path |
A file was opened. |
save |
e: path |
The active picture was saved. |
stop |
nothing | This script (or plugin) is stopping: tidy up. (Its last chance: it can't start anything new.) |
Plugins
A plugin is Luau that adds to Deluxe Pixel itself - menu items, panels and filters - rather than running once. Plugins > Manage plugins turns them on and off, reloads them after editing, makes a new one from a template and adds the examples; Plugins > Open the plugins folder shows where they live (plugins in the settings folder).
A plugin's files
Each plugin is a folder with two files:
plugins/star-tools/plugin.json
plugins/star-tools/main.luau
plugin.json says who it is (all optional; main defaults to main.luau):
{
"name": "Star tools",
"version": "1.0",
"author": "You",
"description": "What it does.",
"main": "main.luau"
}
main.luau runs once when the plugin loads. Unlike a script, what it makes lives on while the plugin is loaded: its local variables and functions keep their values between calls (a panel's settings, say). It has everything a script has (dp.*, the libraries, the same sandbox) plus the plugin table. Saving main.luau in the script console reloads the plugin; so does Reload in Manage plugins.
plugin.menu
plugin.menu{ label = "Scatter stars", help = "Scatters stars on the active layer.", run = function() ... end }
A menu item in the Plugins menu (a plugin with several is a submenu named after it). It's a command too, named plugin.<plugin>_<label> (plugin.star_tools_scatter_stars), so the command palette, macros, scripts (dp.plugin.star_tools_scatter_stars()) and AI assistants can run it. Each run is one undo step; it may run for up to 30 seconds (Escape stops it).
plugin.panel
local count = 200
plugin.panel{
title = "Stars",
draw = function(ui)
count = ui.slider_int("How many", count, 10, 2000)
if ui.button("Scatter stars") then scatter() end
end,
}
A window of its own (dockable anywhere; Plugins > Panels shows or hides it). draw runs every frame and lays out the panel with ui's widgets, which give back their values: keep them in the plugin's variables. A draw that fails, or takes over half a second, stops the panel and shows why (Try again, or reload the plugin).
| Widget | Gives back |
|---|---|
ui.text(...) |
- (text, wrapped) |
ui.button(label) |
true when pressed |
ui.checkbox(label, value) |
the value (true or false) |
ui.slider(label, value, min, max) |
the value (a number) |
ui.slider_int(label, value, min, max) |
the value (a whole number) |
ui.colour(label, index) |
a palette colour number (a swatch that opens the palette) |
ui.choice(label, index, { "a", "b", ... }) |
the chosen item's number (1 first) |
ui.input(label, text) |
the text |
ui.separator() |
- (a line) |
ui.same_line() |
- (the next widget goes on the same line) |
plugin.filter
plugin.filter{
name = "Scanlines",
help = "Every few rows in one colour.",
settings = {
{ name = "colour", label = "Line colour", type = "colour", default = 0 },
{ name = "every", label = "Every", type = "int", min = 2, max = 8, default = 2 },
},
apply = function(px, w, h, s, info)
for y = 0, h - 1, s.every do
for x = 0, w - 1 do buffer.writeu8(px, y * w + x, s.colour) end
end
end,
}
A filter in Image > Plugin filters (and Plugins > Filters): a dialog with its settings and a live preview, then Apply (one undo step) or Cancel. It's a command too, its settings as the command's (checked the same way).
- Settings: each
{ name, label, help, type, min, max, default };typeisnumber,int,bool,colour(a palette colour number, 0 to 255) orchoice(withchoices = { "...", ... }, the default by its text). - apply(px, w, h, s, info):
pxis the active layer as a buffer, laid out asdp.pixels()gives it (1 byte a pixel, the colour number; 4 for true colour, R G B A);w,hits size;sthe settings by name (numbers, true/false, or a choice's text);info.truecolour,info.colours(how many in the palette),info.bytes(1 or 4). Changepxin place, or give back a new buffer the same size. Deluxe Pixel writes the result inside the selection, if there is one.error("...")stops it with that message.
The example plugins
Manage plugins > Add the example plugins puts these in the plugins folder:
star-tools/plugin.json
{
"name": "Star tools",
"version": "1.0",
"author": "Kage Games",
"description": "A Stars panel to scatter stars in a range of colours, and the same as a menu item (and a command)."
}
star-tools/main.luau
-- Star tools: a panel and a menu item. This file runs once when the plugin loads; these locals keep the
-- panel's settings for as long as it's loaded.
local count = 200
local first, last = 13, 15
local crosses = true
local function scatter()
local w, h = dp.width(), dp.height()
local lo, hi = math.min(first, last), math.max(first, last)
for _ = 1, count do
local x, y = math.random(0, w - 1), math.random(0, h - 1)
local c = math.random(lo, hi)
dp.draw.pixel{ x = x, y = y, colour = c }
-- now and then a bigger one: a little cross
if crosses and math.random() < 0.08 and x > 0 and y > 0 and x < w - 1 and y < h - 1 then
dp.draw.line{ x0 = x - 1, y0 = y, x1 = x + 1, y1 = y, colour = c }
dp.draw.line{ x0 = x, y0 = y - 1, x1 = x, y1 = y + 1, colour = c }
end
end
end
plugin.panel{
title = "Stars",
draw = function(ui)
count = ui.slider_int("How many", count, 10, 2000)
first = ui.colour("From colour", first)
last = ui.colour("To colour", last)
crosses = ui.checkbox("Some as little crosses", crosses)
if ui.button("Scatter stars") then scatter() end
ui.same_line()
ui.text("(on the active layer)")
end,
}
plugin.menu{
label = "Scatter stars",
help = "Scatters stars on the active layer, with the Stars panel's settings.",
run = scatter,
}
retro-filters/plugin.json
{
"name": "Retro filters",
"version": "1.0",
"author": "Kage Games",
"description": "Scanlines and a colour shift, written in Luau: each gets the whole layer at once as a buffer."
}
retro-filters/main.luau
-- Retro filters: filters get the active layer as a buffer (as dp.pixels gives it: 1 byte a pixel, the colour
-- number, or 4 for a true-colour picture, R G B A), its size, the settings and a little about the picture.
-- They change the buffer (or give back a new one); Deluxe Pixel writes it back as one undo step, inside the
-- selection if there is one. Image > Plugin filters shows them with their settings and a live preview.
plugin.filter{
name = "Scanlines",
help = "Every few rows in one colour (or darker, in true colour), like an old TV.",
settings = {
{ name = "colour", label = "Line colour", type = "colour", default = 0 },
{ name = "every", label = "Every", type = "int", min = 2, max = 8, default = 2 },
{ name = "darken", label = "True colour: darker by %", type = "int", min = 0, max = 100, default = 50 },
},
apply = function(px, w, h, s, info)
local keep = 1 - s.darken / 100
for y = 0, h - 1, s.every do
for x = 0, w - 1 do
if info.truecolour then
local o = (y * w + x) * 4
for c = 0, 2 do
buffer.writeu8(px, o + c, math.floor(buffer.readu8(px, o + c) * keep))
end
else
buffer.writeu8(px, y * w + x, s.colour)
end
end
end
end,
}
plugin.filter{
name = "Colour shift",
help = "Shifts the colour numbers in a range round by an amount (palette pictures): frames for colour cycling.",
settings = {
{ name = "first", label = "First colour", type = "colour", default = 16 },
{ name = "last", label = "Last colour", type = "colour", default = 31 },
{ name = "by", label = "Shift by", type = "int", min = -32, max = 32, default = 1 },
},
apply = function(px, w, h, s, info)
if info.truecolour then error("Colour shift is for palette pictures.") end
local lo, hi = math.min(s.first, s.last), math.max(s.first, s.last)
local span = hi - lo + 1
for i = 0, w * h - 1 do
local c = buffer.readu8(px, i)
if c >= lo and c <= hi then buffer.writeu8(px, i, lo + (c - lo + s.by) % span) end
end
end,
}
layer-tools/plugin.json
{
"name": "Layer tools",
"version": "1.0",
"author": "Kage Games",
"description": "Menu items that work on the whole layer at once with dp.pixels and dp.set_pixels."
}
layer-tools/main.luau
-- Layer tools: dp.pixels() gives the whole active layer as a buffer; dp.set_pixels() writes one back (one undo
-- step, inside the selection). Much faster than a command a pixel.
plugin.menu{
label = "Mirror the left half",
help = "Copies the left half of the active layer onto the right, mirrored: symmetry.",
run = function()
local px, w, h = dp.pixels()
local bytes = buffer.len(px) // (w * h)
for y = 0, h - 1 do
for x = 0, w // 2 - 1 do
buffer.copy(px, (y * w + (w - 1 - x)) * bytes, px, (y * w + x) * bytes, bytes)
end
end
dp.set_pixels(px)
end,
}
plugin.menu{
label = "Colour usage",
help = "Prints how many pixels use each colour on the active layer (the script console's Output).",
run = function()
local px, w, h = dp.pixels()
if buffer.len(px) ~= w * h then
print("Colour usage is for palette pictures.")
return
end
local counts = table.create(256, 0)
for i = 0, w * h - 1 do
local c = buffer.readu8(px, i) + 1
counts[c] += 1
end
for c = 1, 256 do
if counts[c] > 0 then print(`colour {c - 1} {dp.colour(c - 1)}: {counts[c]} pixels`) end
end
end,
}
Examples
The console's examples (File > Examples), by category. Those marked needs a map want a tile map in the active tab.
Basics
Hello, picture
-- What's open: the picture, its palette and layers. dp.info.* answer as text;
-- dp.width() and friends answer as numbers.
print(dp.info.picture())
print(`{dp.width()} x {dp.height()} pixels, {dp.colours()} colours, {dp.layers()} layers`)
print(`Colour 1 is {dp.colour(1)}; the pixel at 0, 0 is colour {dp.pixel(0, 0)}`)
Shapes
-- Rectangles, ellipses and a line, outlined and filled.
local w, h = dp.width(), dp.height()
dp.draw.rect{ x = 2, y = 2, w = w // 3, h = h // 3, colour = 1 }
dp.draw.rect{ x = 6, y = 6, w = w // 3 - 8, h = h // 3 - 8, colour = 2, filled = true }
dp.draw.ellipse{ cx = w // 2, cy = h // 2, rx = h // 4, colour = 3 }
dp.draw.ellipse{ cx = w * 3 // 4, cy = h * 3 // 4, rx = w // 8, ry = h // 10, colour = 4, filled = true }
dp.draw.line{ x0 = 0, y0 = h - 1, x1 = w - 1, y1 = 0, colour = 5 }
Starburst
-- Lines from the middle out past every edge, cycling through colours 1 to 15.
local cx, cy = dp.width() // 2, dp.height() // 2
local reach = math.max(dp.width(), dp.height())
local n = 48
for i = 0, n - 1 do
local a = i / n * math.pi * 2
dp.draw.line{ x0 = cx, y0 = cy, x1 = cx + math.floor(math.cos(a) * reach), y1 = cy + math.floor(math.sin(a) * reach), colour = 1 + i % 15 }
end
Rings
-- Filled circles from the biggest down, each a different colour: a target.
local cx, cy = dp.width() // 2, dp.height() // 2
for r = math.min(cx, cy) - 1, 1, -3 do
dp.draw.ellipse{ cx = cx, cy = cy, rx = r, colour = 1 + (r // 3) % 15, filled = true }
end
Flood fill
-- Outlines, then a flood fill inside each: a fill takes the area of one colour around a point.
local w, h = dp.width(), dp.height()
dp.draw.rect{ x = 2, y = 2, w = w // 3, h = h // 2, colour = 1 }
dp.draw.ellipse{ cx = w * 2 // 3, cy = h // 2, rx = w // 6, ry = h // 4, colour = 2 }
dp.draw.fill{ x = 4, y = 4, colour = 3 }
dp.draw.fill{ x = w * 2 // 3, y = h // 2, colour = 4 }
Checkerboard
-- A checkerboard of colours 1 and 2 in 8 x 8 squares, on a new layer.
dp.layer.add{ name = "Checkerboard" }
for y = 0, dp.height() - 1, 8 do
for x = 0, dp.width() - 1, 8 do
local c = ((x + y) // 8) % 2 + 1
dp.draw.rect{ x = x, y = y, w = 8, h = 8, colour = c, filled = true }
end
end
Patterns
Diagonal stripes
-- Stripes 4 pixels wide at 45 degrees, in colours 1 to 4.
local w, h = dp.width(), dp.height()
for i = -h, w - 1 do
dp.draw.line{ x0 = i, y0 = 0, x1 = i + h - 1, y1 = h - 1, colour = 1 + (i // 4) % 4 }
end
Dithered blend
-- Colour 1 blending into colour 2 across the picture with a 4 x 4 Bayer (ordered) dither:
-- the way 8-bit games faked gradients with two colours.
local bayer = { 0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5 }
local w, h = dp.width(), dp.height()
for y = 0, h - 1 do
for x = 0, w - 1 do
local level = x / math.max(1, w - 1) * 16
local threshold = bayer[(y % 4) * 4 + x % 4 + 1]
dp.draw.pixel{ x = x, y = y, colour = if level > threshold then 2 else 1 }
end
end
Bricks
-- A brick wall: mortar first, then bricks with every other row offset by half a brick.
local bw, bh = 16, 8
local w, h = dp.width(), dp.height()
dp.draw.rect{ x = 0, y = 0, w = w, h = h, colour = 2, filled = true }
for row = 0, h // bh do
local offset = if row % 2 == 0 then 0 else bw // 2
for col = -1, w // bw do
dp.draw.rect{ x = col * bw + offset + 1, y = row * bh + 1, w = bw - 1, h = bh - 1, colour = 3 + (row + col) % 2, filled = true }
end
end
Polka dots
-- Dots on a staggered grid.
local step = 12
local row = 0
for y = step // 2, dp.height() - 1, step do
local start = if row % 2 == 0 then step // 2 else step
for x = start, dp.width() - 1, step do
dp.draw.ellipse{ cx = x, cy = y, rx = 3, colour = 5, filled = true }
end
row += 1
end
Tartan
-- Bands across and down, woven: where they cross the colours alternate pixel by pixel.
local bands = { { at = 0, size = 10, colour = 4 }, { at = 14, size = 4, colour = 2 }, { at = 22, size = 8, colour = 6 } }
local period = 32
local function band(p: number): number
for _, b in bands do
if p % period >= b.at and p % period < b.at + b.size then return b.colour end
end
return 1
end
for y = 0, dp.height() - 1 do
for x = 0, dp.width() - 1 do
dp.draw.pixel{ x = x, y = y, colour = if (x + y) % 2 == 0 then band(x) else band(y) }
end
end
Stars
-- Scatter 200 single-pixel stars in colours 13 to 15.
for _ = 1, 200 do
dp.draw.pixel{ x = math.random(0, dp.width() - 1), y = math.random(0, dp.height() - 1), colour = math.random(13, 15) }
end
Palette
Colour ramp
-- Blend colours 16 to 31 from dark blue to pale yellow, then show them as bars.
dp.palette.set{ index = 16, colour = "#102040" }
dp.palette.set{ index = 31, colour = "#F0E8A0" }
dp.palette.ramp{ from = 16, to = 31 }
local bw = math.max(1, dp.width() // 16)
for i = 0, 15 do
dp.draw.rect{ x = i * bw, y = 0, w = bw, h = dp.height(), colour = 16 + i, filled = true }
end
Rainbow
-- Colours 16 to 31 all the way round the colour wheel (hue, saturation, value to #RRGGBB).
local function hsv(hue: number, sat: number, val: number): string
local i = math.floor(hue * 6)
local f = hue * 6 - i
local p, q, t = val * (1 - sat), val * (1 - f * sat), val * (1 - (1 - f) * sat)
local r, g, b = val, t, p
i %= 6
if i == 1 then r, g, b = q, val, p
elseif i == 2 then r, g, b = p, val, t
elseif i == 3 then r, g, b = p, q, val
elseif i == 4 then r, g, b = t, p, val
elseif i == 5 then r, g, b = val, p, q end
return string.format("#%02X%02X%02X", math.floor(r * 255 + 0.5), math.floor(g * 255 + 0.5), math.floor(b * 255 + 0.5))
end
for i = 0, 15 do
dp.palette.set{ index = 16 + i, colour = hsv(i / 16, 0.8, 1) }
end
local bw = math.max(1, dp.width() // 16)
for i = 0, 15 do
dp.draw.rect{ x = i * bw, y = 0, w = bw, h = dp.height(), colour = 16 + i, filled = true }
end
Sunset sky
-- A sky gradient from several key colours: ramps between them, then one band of each colour down
-- the picture.
local keys = { { 48, "#1A0B2E" }, { 54, "#5B2A6E" }, { 58, "#C2456B" }, { 61, "#F2865A" }, { 63, "#FAD47A" } }
for i, k in keys do
dp.palette.set{ index = k[1], colour = k[2] }
if i > 1 then dp.palette.ramp{ from = keys[i - 1][1], to = k[1] } end
end
local h = dp.height()
for y = 0, h - 1 do
dp.draw.line{ x0 = 0, y0 = y, x1 = dp.width() - 1, y1 = y, colour = 48 + math.floor(y / h * 16) }
end
Grey ramp
-- Colours 32 to 47 as greys from black to white.
dp.palette.set{ index = 32, colour = "#000000" }
dp.palette.set{ index = 47, colour = "#FFFFFF" }
dp.palette.ramp{ from = 32, to = 47 }
print(dp.info.palette())
Show the palette
-- Every palette colour as a swatch, 16 to a row.
local n = dp.colours()
local size = math.max(2, math.min(dp.width() // 16, dp.height() // math.ceil(n / 16)))
for i = 0, n - 1 do
dp.draw.rect{ x = (i % 16) * size, y = (i // 16) * size, w = size, h = size, colour = i, filled = true }
end
Count colours
-- How many pixels of each colour the active layer uses.
local counts = {}
for y = 0, dp.height() - 1 do
for x = 0, dp.width() - 1 do
local c = dp.pixel(x, y)
counts[c] = (counts[c] or 0) + 1
end
end
for c, n in counts do print(`colour {c} {dp.colour(c)}: {n} pixels`) end
Unused colours
-- The palette colours the active layer doesn't use (free for something else).
local used = {}
for y = 0, dp.height() - 1 do
for x = 0, dp.width() - 1 do
used[dp.pixel(x, y)] = true
end
end
local unused = {}
for i = 0, dp.colours() - 1 do
if not used[i] then table.insert(unused, i) end
end
print(`{#unused} of {dp.colours()} colours aren't used on the active layer:`)
print(table.concat(unused, ", "))
Swap two colours
-- Colours 1 and 2 swap places on the active layer.
local a, b = 1, 2
for y = 0, dp.height() - 1 do
for x = 0, dp.width() - 1 do
local c = dp.pixel(x, y)
if c == a then dp.draw.pixel{ x = x, y = y, colour = b }
elseif c == b then dp.draw.pixel{ x = x, y = y, colour = a } end
end
end
Generative
String art
-- Straight lines between points round a circle make a curve: point i to point 2i is a cardioid.
local cx, cy = dp.width() // 2, dp.height() // 2
local r = math.min(cx, cy) - 2
local n = 120
local function at(i: number): (number, number)
local a = i / n * math.pi * 2
return cx + math.floor(math.cos(a) * r + 0.5), cy + math.floor(math.sin(a) * r + 0.5)
end
for i = 0, n - 1 do
local x0, y0 = at(i)
local x1, y1 = at(i * 2)
dp.draw.line{ x0 = x0, y0 = y0, x1 = x1, y1 = y1, colour = 1 + i % 15 }
end
Sierpinski triangle
-- The chaos game: jump halfway to a random corner, again and again, and a fractal appears.
local w, h = dp.width(), dp.height()
local corners = { { w // 2, 0 }, { 0, h - 1 }, { w - 1, h - 1 } }
local x, y = w / 2, h / 2
for i = 1, 20000 do
local c = corners[math.random(1, 3)]
x, y = (x + c[1]) / 2, (y + c[2]) / 2
if i > 10 then dp.draw.pixel{ x = math.floor(x), y = math.floor(y), colour = 1 + i % 3 } end
end
Mandelbrot
-- The Mandelbrot set, coloured by how quickly each point escapes. The maths runs as native code
-- (Run, Ctrl+Enter): try Debug to see the difference the interpreter makes.
local w, h = dp.width(), dp.height()
local maxIter = 32
for py = 0, h - 1 do
for px = 0, w - 1 do
local cr = px / w * 3.5 - 2.5
local ci = py / h * 2.4 - 1.2
local zr, zi, n = 0, 0, 0
while zr * zr + zi * zi < 4 and n < maxIter do
zr, zi = zr * zr - zi * zi + cr, 2 * zr * zi + ci
n += 1
end
dp.draw.pixel{ x = px, y = py, colour = if n == maxIter then 0 else 1 + n % 15 }
end
end
Plasma
-- Old-school demo plasma: sine waves added together, mapped onto colours 1 to 15.
local w, h = dp.width(), dp.height()
for y = 0, h - 1 do
for x = 0, w - 1 do
local v = math.sin(x / 8) + math.sin(y / 6) + math.sin((x + y) / 10) + math.sin(math.sqrt(x * x + y * y) / 8)
dp.draw.pixel{ x = x, y = y, colour = 1 + math.floor((v + 4) / 8 * 15) % 15 }
end
end
Maze
-- A maze carved by a random walk that backs up when it's stuck (recursive backtracking),
-- on a grid of 4-pixel cells.
local cell = 4
local cols, rows = (dp.width() - 1) // (cell * 2), (dp.height() - 1) // (cell * 2)
dp.draw.rect{ x = 0, y = 0, w = dp.width(), h = dp.height(), colour = 1, filled = true }
local seen = {}
local function key(c: number, r: number): number return r * cols + c end
local function open(c: number, r: number)
dp.draw.rect{ x = (c * 2 + 1) * cell, y = (r * 2 + 1) * cell, w = cell, h = cell, colour = 2, filled = true }
end
local stack = { { 0, 0 } }
seen[key(0, 0)] = true
open(0, 0)
while #stack > 0 do
local top = stack[#stack]
local c, r = top[1], top[2]
local choices = {}
for _, d in { { 1, 0 }, { -1, 0 }, { 0, 1 }, { 0, -1 } } do
local nc, nr = c + d[1], r + d[2]
if nc >= 0 and nc < cols and nr >= 0 and nr < rows and not seen[key(nc, nr)] then
table.insert(choices, { nc, nr })
end
end
if #choices == 0 then
table.remove(stack)
else
local n = choices[math.random(1, #choices)]
seen[key(n[1], n[2])] = true
-- knock through the wall between the two cells, then step in
dp.draw.rect{ x = (c + n[1] + 1) * cell, y = (r + n[2] + 1) * cell, w = cell, h = cell, colour = 2, filled = true }
open(n[1], n[2])
table.insert(stack, n)
end
end
Mountains
-- Four ranges of hills, back to front, each made by midpoint displacement: halve the gaps,
-- nudge each new point up or down a bit less each time.
local w, h = dp.width(), dp.height()
local function ridge(base: number, rough: number): { number }
local size = 1
while size < w do size *= 2 end
local pts = table.create(size + 1, base)
pts[1], pts[size + 1] = base + math.random(-rough, rough), base + math.random(-rough, rough)
local step, amount = size, rough
while step > 1 do
local half = step // 2
for i = 1, size, step do
pts[i + half] = (pts[i] + pts[i + step]) / 2 + math.random(-amount, amount)
end
step = half
amount = math.max(1, amount // 2)
end
return pts
end
for range = 0, 3 do
local pts = ridge(h * (0.35 + range * 0.15), math.max(1, h // 5))
for x = 0, w - 1 do
local top = math.clamp(math.floor(pts[x + 1]), 0, h - 1)
dp.draw.line{ x0 = x, y0 = top, x1 = x, y1 = h - 1, colour = 4 + range }
end
end
Lissajous
-- A Lissajous figure: x and y follow sine waves at different speeds (3 : 4).
local cx, cy = dp.width() / 2, dp.height() / 2
local px, py = -1, -1
for i = 0, 2000 do
local t = i / 2000 * math.pi * 2
local x = math.floor(cx + math.sin(3 * t + math.pi / 2) * (cx - 2))
local y = math.floor(cy + math.sin(4 * t) * (cy - 2))
if i > 0 then dp.draw.line{ x0 = px, y0 = py, x1 = x, y1 = y, colour = 1 + (i // 200) % 15 } end
px, py = x, y
end
Voronoi cells
-- Each pixel takes the colour of its nearest seed point: cells like cracked mud or stained glass.
-- (Uses Luau's vector type for the distances.)
local w, h = dp.width(), dp.height()
local seeds = {}
for i = 1, 12 do seeds[i] = vector.create(math.random(0, w - 1), math.random(0, h - 1), 0) end
for y = 0, h - 1 do
for x = 0, w - 1 do
local p = vector.create(x, y, 0)
local best, bestDistance = 1, math.huge
for i, s in seeds do
local d = vector.magnitude(p - s)
if d < bestDistance then best, bestDistance = i, d end
end
dp.draw.pixel{ x = x, y = y, colour = best }
end
end
Game of Life
-- One step of Conway's Game of Life on the active layer: any colour but 0 is alive. Run it
-- again for the next generation (the picture wraps round at the edges).
local w, h = dp.width(), dp.height()
local alive = {}
for y = 0, h - 1 do
for x = 0, w - 1 do alive[y * w + x] = dp.pixel(x, y) ~= 0 end
end
for y = 0, h - 1 do
for x = 0, w - 1 do
local n = 0
for dy = -1, 1 do
for dx = -1, 1 do
if (dx ~= 0 or dy ~= 0) and alive[((y + dy) % h) * w + (x + dx) % w] then n += 1 end
end
end
local now = alive[y * w + x]
local live = n == 3 or (now and n == 2)
if live ~= now then dp.draw.pixel{ x = x, y = y, colour = if live then 1 else 0 } end
end
end
Spiral
-- Points round a widening spiral, each a dot of the next colour.
local cx, cy = dp.width() / 2, dp.height() / 2
for i = 0, 600 do
local a = i / 20
local r = i / 600 * math.min(cx, cy)
dp.draw.pixel{ x = math.floor(cx + math.cos(a) * r), y = math.floor(cy + math.sin(a) * r), colour = 1 + i % 15 }
end
Layers and animation
A scene in layers
-- Three layers, each with its own part of a scene: hide or move them afterwards.
local w, h = dp.width(), dp.height()
dp.layer.add{ name = "Sky" }
dp.draw.rect{ x = 0, y = 0, w = w, h = h, colour = 6, filled = true }
dp.layer.add{ name = "Hills" }
dp.draw.ellipse{ cx = w // 4, cy = h, rx = w // 3, ry = h // 3, colour = 5, filled = true }
dp.draw.ellipse{ cx = w * 3 // 4, cy = h, rx = w // 3, ry = h // 4, colour = 4, filled = true }
dp.layer.add{ name = "Sun" }
dp.draw.ellipse{ cx = w * 4 // 5, cy = h // 5, rx = math.max(2, h // 10), colour = 7, filled = true }
print(dp.info.layers())
Mirror the layer
-- Left-right symmetry: a copy of the active layer, flipped across and merged back down.
-- Draw on the left half first, then run it.
dp.layer.duplicate()
dp.layer.flip{ axis = "x" }
dp.layer.merge_down()
Every other layer
-- Shows the layers with even numbers and hides the odd ones.
for i = 0, dp.layers() - 1 do
dp.layer.show{ index = i, visible = i % 2 == 0 }
end
print(dp.info.layers())
Bouncing ball
-- A 12-frame animation of a ball bouncing on the floor (play it with the Frames panel).
local w, h = dp.width(), dp.height()
local frames = 12
local r = math.max(3, h // 10)
for f = 0, frames - 1 do
if f > 0 then dp.frame.add{ copy = false } end
local up = math.abs(math.sin(f / frames * math.pi))
local y = math.floor(h - r - 2 - up * (h - 2 * r - 4))
dp.draw.rect{ x = 0, y = h - 1, w = w, h = 1, colour = 3, filled = true }
dp.draw.ellipse{ cx = w // 2, cy = y, rx = r, ry = if f == 0 then r * 3 // 4 else r, colour = 2, filled = true }
dp.frame.duration{ ms = 60 }
end
dp.frame.go{ index = 0 }
Spinning bar
-- An 8-frame loop of a bar turning round its middle.
local cx, cy = dp.width() // 2, dp.height() // 2
local r = math.min(cx, cy) - 2
for f = 0, 7 do
if f > 0 then dp.frame.add{ copy = false } end
local a = f / 8 * math.pi
local dx, dy = math.floor(math.cos(a) * r), math.floor(math.sin(a) * r)
dp.draw.line{ x0 = cx - dx, y0 = cy - dy, x1 = cx + dx, y1 = cy + dy, colour = 1 + f }
dp.frame.duration{ ms = 80 }
end
dp.frame.go{ index = 0 }
Paint inside a selection
-- Painting stays inside the selection: a big circle only fills the selected rectangle.
local w, h = dp.width(), dp.height()
dp.select.rect{ x = w // 4, y = h // 4, w = w // 2, h = h // 2 }
dp.draw.ellipse{ cx = w // 4, cy = h // 4, rx = w // 3, colour = 9, filled = true }
dp.select.none()
Filters
Posterise and outline
-- Filters by name; dp.info.filters() says what each one's settings mean.
dp.filter.apply{ name = "Posterise", n = 4 }
dp.filter.apply{ name = "Outline", colour = 1, n = 1 }
Old photo
-- Sepia, film grain and darkened corners.
dp.filter.apply{ name = "Sepia", a = 80 }
dp.filter.apply{ name = "Film grain", a = 25, b = 1 }
dp.filter.apply{ name = "Vignette", a = 60, b = 30, c = 50 }
Cartoon
-- Flat cel-shaded tones with dark outlines.
dp.filter.apply{ name = "Toon (cel shading)", n = 4, a = 60, b = 80, c = 1 }
Sprite outline and shadow
-- A one-pixel outline in colour 1 round everything that isn't see-through, and a shadow
-- in colour 2 two pixels down and right.
dp.filter.apply{ name = "Outline", colour = 1, n = 1 }
dp.filter.apply{ name = "Drop shadow", colour = 2, dx = 2, dy = 2 }
Retro look
-- Fewer levels per channel, then fitted back to the palette with an ordered dither.
dp.filter.apply{ name = "Posterise", n = 3 }
dp.filter.apply{ name = "Fit to palette", dither = "Ordered" }
List the filters
-- Every filter and what its settings (a, b, c, n, colour, flag...) mean.
print(dp.info.filters())
Tile maps
About the map (needs a map)
-- The map's size, tile size and layers, and what's in the top-left cell (open a map first).
print(dp.map.info())
print(dp.map.cell{ x = 0, y = 0 })
Border of tiles (needs a map)
-- Tile 1 all round the edge of the active map layer, tile 0 inside.
local a, b = string.match(dp.map.info(), "(%d+) x (%d+) cells")
local cols, rows = tonumber(a) or 0, tonumber(b) or 0
dp.map.set{ x = 0, y = 0, w = cols, h = rows, tile = 0 }
dp.map.set{ x = 0, y = 0, w = cols, h = 1, tile = 1 }
dp.map.set{ x = 0, y = rows - 1, w = cols, h = 1, tile = 1 }
dp.map.set{ x = 0, y = 0, w = 1, h = rows, tile = 1 }
dp.map.set{ x = cols - 1, y = 0, w = 1, h = rows, tile = 1 }
Random ground (needs a map)
-- Every cell a random tile from 2 to 5, sometimes mirrored, so the ground doesn't repeat.
local a, b = string.match(dp.map.info(), "(%d+) x (%d+) cells")
local cols, rows = tonumber(a) or 0, tonumber(b) or 0
for y = 0, rows - 1 do
for x = 0, cols - 1 do
dp.map.set{ x = x, y = y, tile = math.random(2, 5), flipx = math.random() < 0.5 }
end
end
Mark solid cells (needs a map)
-- Attribute 1 (say, "solid") on every cell that has a tile, 0 on empty ones: the game's own bits.
local a, b = string.match(dp.map.info(), "(%d+) x (%d+) cells")
local cols, rows = tonumber(a) or 0, tonumber(b) or 0
local solid = 0
for y = 0, rows - 1 do
for x = 0, cols - 1 do
local tile = tonumber(string.match(dp.map.cell{ x = x, y = y }, "tile=(%-?%d+)"))
local value = if tile and tile >= 0 then 1 else 0
dp.map.attr{ x = x, y = y, value = value }
solid += value
end
end
print(`{solid} of {cols * rows} cells marked solid`)
Events and games
Bouncing square
-- dp.on("frame", ...) runs every frame, so the script keeps going after its last line:
-- a square bounces across the picture. Up and Down move it (the key is used up, so the tools
-- don't see it). Escape, Stop, or the "Running" note on the menu bar stops it; one Undo
-- takes back everything it drew.
local x, y, dx, size = 4, dp.height() // 3, 1, 8
local under = dp.pixel(0, 0) -- (the colour to rub out with: the background's)
local function square(colour)
dp.draw.rect{ x = x, y = y, w = size, h = size, colour = colour, filled = true }
end
dp.on("frame", function(dt, t)
square(under)
x += dx * 2
if x < 0 or x + size >= dp.width() then dx = -dx end
square(2)
end)
dp.on("key", function(e)
if e.key == "Up" or e.key == "Down" then
square(under)
y = math.clamp(y + (if e.key == "Up" then -size else size), 0, dp.height() - size)
return true
end
end)
dp.on("stop", function() square(under) end) -- tidy up when it's stopped
Move with the keys or a pad
-- Smooth movement for a game: a frame handler reads the keys held down (dp.key) and a game
-- controller (dp.pad), and moves by speed x dt, so it's the same speed however fast the
-- frames come. Arrow keys, the left stick or the d-pad move the square; A (or Space) jumps it.
-- (It rubs itself out with the background's colour, so it scratches what it crosses: drawing
-- moving things over the picture without touching it is what's coming next.)
local x, y, size, speed = dp.width() / 2, dp.height() / 2, 6, 80 -- speed: pixels a second
local under = dp.pixel(0, 0)
local function square(colour)
dp.draw.rect{ x = math.floor(x), y = math.floor(y), w = size, h = size, colour = colour, filled = true }
end
dp.on("frame", function(dt)
local dx, dy = 0, 0
if dp.key("Left") then dx -= 1 end
if dp.key("Right") then dx += 1 end
if dp.key("Up") then dy -= 1 end
if dp.key("Down") then dy += 1 end
local p = dp.pad(1)
if p then
dx += p.lx + (if p.left then -1 elseif p.right then 1 else 0)
dy += p.ly + (if p.up then -1 elseif p.down then 1 else 0)
end
if dx ~= 0 or dy ~= 0 then
square(under)
x = math.clamp(x + dx * speed * dt, 0, dp.width() - size)
y = math.clamp(y + dy * speed * dt, 0, dp.height() - size)
end
square(3)
end)
local function jump()
square(under)
y = math.max(0, y - size * 3)
end
dp.on("key", function(e) if e.key == "Space" then jump() return true end end)
dp.on("pad", function(e)
if e.button == "a" and e.pressed then jump() end
if e.button == nil then print(`pad {e.pad} {if e.connected then "plugged in" else "unplugged"}`) end
end)
dp.on("stop", function() square(under) end)
print(`{dp.pads()} game controller(s) connected. Escape stops it.`)
Sprites over the picture
-- dp.image cuts a piece of the picture as an image; dp.show shows it over the canvas each frame
-- without drawing into the picture - nothing to rub out, nothing to undo. Three copies of the
-- top-left corner bounce about; dp.say writes over it. Escape stops it, and the picture is as it was.
local size = math.min(16, dp.width() // 4, dp.height() // 4)
local img = dp.image(0, 0, size, size)
local balls = {}
for i = 1, 3 do
table.insert(balls, { x = math.random(0, dp.width() - size), y = math.random(0, dp.height() - size),
dx = math.random(30, 90) * (if i % 2 == 0 then -1 else 1), dy = math.random(30, 90) })
end
local bounces = 0
dp.on("frame", function(dt)
for _, b in balls do
b.x += b.dx * dt
b.y += b.dy * dt
if b.x < 0 or b.x > dp.width() - size then b.dx = -b.dx; bounces += 1 end
if b.y < 0 or b.y > dp.height() - size then b.dy = -b.dy; bounces += 1 end
b.x = math.clamp(b.x, 0, dp.width() - size)
b.y = math.clamp(b.y, 0, dp.height() - size)
dp.show(img, b.x, b.y, b.dx < 0) -- mirrored when it's going left
end
dp.box(0, 0, dp.width(), dp.height(), "#FFFFFF80", false) -- a frame round the picture
dp.say(2, 2, `bounces: {bounces}`)
end)
Platform game (needs a map)
-- A tiny platform game over the tile map in front - make one first (open a picture of a level,
-- then Tiles > Make tile set from this image). Arrow keys or a pad move, Space / Up / A jump; the
-- view follows. Solid cells: those with attribute bit 1 set or, on a map without attributes, any
-- tile but the one in the top-left corner (the sky). Nothing is drawn into the map. A sprite
-- called "Hero" (Window > Sprite timeline, in any open picture) is the hero if there is one; its
-- origin is put at its feet. Tiles > Play this level runs it; F9 plays again.
local m = dp.map_info()
assert(m, "Put a tile map in front first (Tiles > Make tile set from this image).")
local tw, th = m.tile_w, m.tile_h
local sky = dp.cell(0, 0)
local useAttr = false
for cy = 0, math.min(m.height, 64) - 1 do
for cx = 0, math.min(m.width, 64) - 1 do
local _, a = dp.cell(cx, cy)
if a ~= 0 then useAttr = true end
end
end
local function solid(px, py)
local tile, attr = dp.cell_at(px, py)
if tile == nil then return false end -- outside the map: open (fall off the bottom)
if useAttr then return bit32.btest(attr, 1) end
return tile ~= -1 and tile ~= sky
end
local w, h = math.max(4, tw // 2), math.max(4, th - 2) -- the hero: a box (dp.image + dp.show for a sprite)
local function hits(px, py)
return solid(px, py) or solid(px + w - 1, py) or solid(px, py + h - 1) or solid(px + w - 1, py + h - 1)
or solid(px, py + h // 2) or solid(px + w - 1, py + h // 2)
end
local hasSprite, heroAnim = pcall(dp.sprite, "Hero")
dp.cycle(true) -- the level's colour cycling, if it has any
local startX, startY = tw * 2, 0
local x, y, vy, onGround, facing = startX, startY, 0, false, 1
local gravity, jumpSpeed, runSpeed = 700, 280, 100 -- pixels a second (a second squared)
dp.on("frame", function(dt, t)
dt = math.min(dt, 1 / 30)
local dir = 0
if dp.key("Left") then dir -= 1 end
if dp.key("Right") then dir += 1 end
local p = dp.pad(1)
if p then dir += p.lx + (if p.left then -1 elseif p.right then 1 else 0) end
dir = math.clamp(dir, -1, 1)
if dir ~= 0 then facing = math.sign(dir) end
-- across: only where it's clear
local nx = x + dir * runSpeed * dt
if not hits(nx, y) then x = nx end
-- up and down: falling, landing on what's solid, bumping heads
vy = math.min(vy + gravity * dt, 500)
local ny = y + vy * dt
if hits(x, ny) then
local step = math.sign(vy)
for _ = 1, 32 do -- (close the gap, a pixel at a time)
if hits(x, y + step) then break end
y += step
end
onGround = vy > 0
vy = 0
else
y = ny
onGround = false
end
if y > m.height * th + 64 then x, y, vy = startX, startY, 0 end -- fell off: start again
if hasSprite then
dp.show_anim(heroAnim, x + w / 2, y + h, if dir ~= 0 then t else 0, facing < 0)
else
dp.box(x, y, w, h, "#E04040")
dp.box(x + (if facing > 0 then w - 3 else 1), y + 2, 2, 2, "#FFFFFF") -- an eye, the way it faces
end
dp.camera(x + w / 2, y + h / 2)
end)
local function jump()
if onGround then vy = -jumpSpeed; onGround = false end
end
dp.on("key", function(e)
if e.key == "Space" or e.key == "Up" then jump() return true end
if e.key == "Left" or e.key == "Right" or e.key == "Down" then return true end -- (not the tools')
end)
dp.on("pad", function(e) if e.button == "a" and e.pressed then jump() end end)
print("Arrow keys or a pad to move, Space or A to jump. Escape stops it.")
Walk round the map in 3D (needs a map)
-- The map in first person, as Wolfenstein 3D: the cells are walls drawn with their own tiles,
-- the floor is the most common tile (or, if the map has attributes, the cells with bit 1 set are
-- the walls). Arrow keys or a pad: up and down walk, left and right turn. M: the map from above.
local m = dp.map_info()
local tw, th = m.tile_w, m.tile_h
-- What's a wall: cells with attribute bit 1 if any have it, else every tile but the floor's.
local count, floor, attrs = {}, -1, false
for cy = 0, math.min(m.height, 128) - 1 do
for cx = 0, math.min(m.width, 128) - 1 do
local tile, a = dp.cell(cx, cy)
if a and bit32.btest(a, 1) then attrs = true end
if tile and tile >= 0 then
count[tile] = (count[tile] or 0) + 1
if floor < 0 or count[tile] > count[floor] then floor = tile end
end
end
end
local walls = if attrs then { solid = 1 } else { open = { floor } }
-- Start in the middle of the first open cell.
local x, y
for cy = 0, m.height - 1 do
for cx = 0, m.width - 1 do
if not x and not dp.wall_at((cx + 0.5) * tw, (cy + 0.5) * th, walls) then x, y = (cx + 0.5) * tw, (cy + 0.5) * th end
end
end
if not x then error("every cell is a wall: paint some floor (the most common tile) first.") end
local angle, view, fromAbove = 0, nil, false
local gpu = dp.gpu()
local walk, turn = 3 * tw, 120 -- cells a second (as pixels), degrees a second
local function clear(px, py) -- a little room round the player, so walls aren't touched
local r = tw * 0.2
return not (dp.wall_at(px - r, py - r, walls) or dp.wall_at(px + r, py - r, walls)
or dp.wall_at(px - r, py + r, walls) or dp.wall_at(px + r, py + r, walls))
end
dp.on("frame", function(dt)
dt = math.min(dt, 1 / 20)
local go, spin = 0, 0
if dp.key("Up") then go += 1 end
if dp.key("Down") then go -= 1 end
if dp.key("Left") then spin -= 1 end
if dp.key("Right") then spin += 1 end
local p = dp.pad(1)
if p then go -= p.ly; spin += p.lx end
angle = (angle + math.clamp(spin, -1, 1) * turn * dt) % 360
local r = math.rad(angle)
local step = math.clamp(go, -1, 1) * walk * dt
local nx, ny = x + math.cos(r) * step, y + math.sin(r) * step
if clear(nx, y) then x = nx end -- (sliding along walls: each way on its own)
if clear(x, ny) then y = ny end
if fromAbove then
view = dp.mode7(x, y, angle, { w = 320, h = 200, height = th * 3, fog = 0.6, into = view })
else
-- (on the graphics card when there is one: three times as sharp, as fast)
local opts = { w = if gpu then 960 else 320, h = if gpu then 600 else 200, gpu = gpu,
fog = 0.7, distance = 24, ceiling = "#202838", into = view }
for k, v in walls do opts[k] = v end
view = dp.raycast(x, y, angle, opts)
end
dp.show_screen(view)
dp.camera(x, y)
end)
dp.on("key", function(e)
if e.key == "M" then fromAbove = not fromAbove return true end
if e.key == "Up" or e.key == "Down" or e.key == "Left" or e.key == "Right" then return true end
end)
print("Up and down walk, left and right turn, M flies over it. Escape stops it.")
Doors and push walls in 3D (needs a map)
-- Walking round the map in first person with doors that slide open and walls that can be pushed,
-- as Wolfenstein 3D. Walls are the cells with attribute bit 1; doors the cells with bit 2; push
-- walls the cells with bits 1 and 4 (set them in the Map layers panel's attributes). Arrow keys
-- walk and turn; Space opens the door or pushes the wall in front.
local m = dp.map_info()
local tw, th = m.tile_w, m.tile_h
local doors, pushes, byCell = {}, {}, {}
local x, y
for cy = 0, m.height - 1 do
for cx = 0, m.width - 1 do
local tile, a = dp.cell(cx, cy)
a = a or 0
if bit32.btest(a, 2) then
local d = { x = cx, y = cy, open = 0, target = 0 }
table.insert(doors, d)
byCell[cy * m.width + cx] = d
elseif bit32.btest(a, 1) and bit32.btest(a, 4) then
local p = { x = cx, y = cy, dx = 0, dy = 0, ex = 0, ey = 0 }
table.insert(pushes, p)
byCell[cy * m.width + cx] = p
elseif not bit32.btest(a, 1) and tile and tile >= 0 and not x then
x, y = (cx + 0.5) * tw, (cy + 0.5) * th
end
end
end
if not x then error("there's no open cell (one with a tile and without attribute bit 1) to start in.") end
local angle, view = 0, nil
local function opts() return { solid = 1, doors = doors, moving = pushes } end
local function clear(px, py)
local r, o = tw * 0.2, opts()
return not (dp.wall_at(px - r, py - r, o) or dp.wall_at(px + r, py - r, o)
or dp.wall_at(px - r, py + r, o) or dp.wall_at(px + r, py + r, o))
end
dp.on("frame", function(dt)
dt = math.min(dt, 1 / 20)
local go, spin = 0, 0
if dp.key("Up") then go += 1 end
if dp.key("Down") then go -= 1 end
if dp.key("Left") then spin -= 1 end
if dp.key("Right") then spin += 1 end
angle = (angle + spin * 120 * dt) % 360
local r = math.rad(angle)
local nx, ny = x + math.cos(r) * go * 3 * tw * dt, y + math.sin(r) * go * 3 * th * dt
if clear(nx, y) then x = nx end
if clear(x, ny) then y = ny end
for _, d in doors do -- doors slide at one cell a second
if d.open < d.target then d.open = math.min(d.target, d.open + dt) end
if d.open > d.target then d.open = math.max(d.target, d.open - dt) end
end
for _, p in pushes do -- push walls move at half a cell a second, two cells at most
if p.ex ~= 0 or p.ey ~= 0 then
local fx, fy = p.dx + p.ex * dt * 0.5, p.dy + p.ey * dt * 0.5
local lead = math.ceil(math.max(math.abs(fx), math.abs(fy)) - 0.000001) -- the cells it reaches into
local cx, cy = p.x + p.ex * lead, p.y + p.ey * lead
if lead > 2 or (lead > 0 and dp.wall_at((cx + 0.5) * tw, (cy + 0.5) * th, { solid = 1 })) then
p.dx, p.dy, p.ex, p.ey = p.ex * (lead - 1), p.ey * (lead - 1), 0, 0 -- stopped: against a wall, or far enough
else
p.dx, p.dy = fx, fy
end
end
end
local o = opts()
o.fog, o.distance, o.ceiling, o.into = 0.6, 24, "#202838", view
o.gpu = dp.gpu() -- (sharper on the graphics card)
o.w, o.h = if o.gpu then 960 else 320, if o.gpu then 600 else 200
view = dp.raycast(x, y, angle, o)
dp.show_screen(view)
end)
dp.on("key", function(e)
if e.key == "Space" then
local r = math.rad(angle)
local ax, ay = x / tw + math.cos(r), y / th + math.sin(r) -- a cell ahead: the nearest door or push wall to there
local thing, best = nil, 1.0
for cell, t in byCell do
local d = math.sqrt((t.x + 0.5 - ax) ^ 2 + (t.y + 0.5 - ay) ^ 2)
if d < best then thing, best = t, d end
end
if thing and thing.target ~= nil then
thing.target = if thing.target > 0 then 0 else 1
elseif thing and thing.ex == 0 and thing.ey == 0 and thing.dx == 0 and thing.dy == 0 then
local ax, ay = math.cos(r), math.sin(r) -- pushed the way the player faces, straight along a row
if math.abs(ax) > math.abs(ay) then thing.ex = if ax > 0 then 1 else -1 else thing.ey = if ay > 0 then 1 else -1 end
end
return true
end
if e.key == "Up" or e.key == "Down" or e.key == "Left" or e.key == "Right" then return true end
end)
print("Arrow keys walk and turn, Space opens doors and pushes walls. Escape stops it.")
The map raised in 3D (needs a map)
-- The map drawn as stacked cubes of different heights, seen isometrically (Tab: from above at an angle).
-- Heights come from the attribute bits 1, 2 and 4 read as a number (0-7) if the map has any;
-- otherwise every tile but the most common (the floor) is 2 high. Arrow keys move round.
local m = dp.map_info()
local tw, th = m.tile_w, m.tile_h
local count, floor, attrs = {}, -1, false
for cy = 0, math.min(m.height, 128) - 1 do
for cx = 0, math.min(m.width, 128) - 1 do
local tile, a = dp.cell(cx, cy)
if a and bit32.band(a, 7) ~= 0 then attrs = true end
if tile and tile >= 0 then
count[tile] = (count[tile] or 0) + 1
if floor < 0 or count[tile] > count[floor] then floor = tile end
end
end
end
local heights = {}
if not attrs then for tile in count do if tile ~= floor then heights[tile] = 2 end end end
local x, y = m.width * tw / 2, m.height * th / 2
local iso, view = true, nil
local player = dp.image(0, 0, tw, th) -- the tile set's first tile stands in for a player
dp.on("frame", function(dt)
local dx, dy = 0, 0
if dp.key("Left") then dx -= 1 end
if dp.key("Right") then dx += 1 end
if dp.key("Up") then dy -= 1 end
if dp.key("Down") then dy += 1 end
x = math.clamp(x + dx * 4 * tw * dt, 0, m.width * tw - 1)
y = math.clamp(y + dy * 4 * th * dt, 0, m.height * th - 1)
local o = { w = 320, h = 200, view = if iso then "iso" else "angle", background = "#101820", into = view,
sprites = { { image = player, x = x, y = y, size = 1 } } }
if attrs then o.height_bits = 7 else o.heights = heights end
view = dp.raised(x, y, o)
dp.show_screen(view)
local _, _, h = dp.raised_point(x, y, x, y, o)
dp.say(4, 4, `cell {math.floor(x / tw)}, {math.floor(y / th)} height {h}`)
end)
dp.on("key", function(e)
if e.key == "Tab" then iso = not iso return true end
if e.key == "Up" or e.key == "Down" or e.key == "Left" or e.key == "Right" then return true end
end)
print("Arrow keys move, Tab switches between isometric and from above at an angle. Escape stops it.")
Fly over the map (Mode 7) (needs a map)
-- The map laid flat and flown over, as the SNES's Mode 7 (a kart track, a world map):
-- left and right steer, up speeds up, down slows down. It wraps round at the edges.
local m = dp.map_info()
local x, y = m.width * m.tile_w / 2, m.height * m.tile_h / 2
local angle, speed, view = 270, 40, nil
dp.on("frame", function(dt)
dt = math.min(dt, 1 / 20)
if dp.key("Left") then angle -= 90 * dt end
if dp.key("Right") then angle += 90 * dt end
if dp.key("Up") then speed = math.min(speed + 80 * dt, 300) end
if dp.key("Down") then speed = math.max(speed - 80 * dt, 0) end
local r = math.rad(angle)
x += math.cos(r) * speed * dt
y += math.sin(r) * speed * dt
view = dp.mode7(x, y, angle, { w = 320, h = 200, height = 24 + speed / 6, wrap = true, fog = 0.5, sky = "#5080D0", into = view })
dp.show_screen(view)
dp.say(4, 4, `speed {math.floor(speed)}`)
end)
dp.on("key", function(e)
if e.key == "Up" or e.key == "Down" or e.key == "Left" or e.key == "Right" then return true end
end)
print("Left and right steer, up and down change speed. Escape stops it.")
A 3D character on the map (needs a map)
-- A 3D model (File > 3D model, brought into any open picture) walking round the map from above,
-- turned to face the way it goes and playing its first animation while it moves - drawn live, as
-- pre-rendered sprites were, in 8 directions. Arrow keys or a pad.
local name = "robot" -- the model's name (as File > 3D model gave it)
local ok, model = pcall(dp.model, name)
if not ok then error(model .. " Change `name` at the top to one of yours.") end
local m = dp.map_info()
local x, y = m.width * m.tile_w / 2, m.height * m.tile_h / 2
local facing, t = 90, 0
dp.on("frame", function(dt)
local dx, dy = 0, 0
if dp.key("Left") then dx -= 1 end
if dp.key("Right") then dx += 1 end
if dp.key("Up") then dy -= 1 end
if dp.key("Down") then dy += 1 end
local p = dp.pad(1)
if p then dx += p.lx; dy += p.ly end
local moving = math.abs(dx) + math.abs(dy) > 0.2
if moving then
facing = math.deg(math.atan2(dy, dx))
x += dx * 60 * dt
y += dy * 60 * dt
t += dt
end
dp.show_model(model, x, y, { facing = facing, directions = 8, feet = true,
anim = if #model.anims > 0 then 1 else nil, time = if moving then t else 0 })
dp.camera(x, y)
end)
dp.on("key", function(e)
if e.key == "Up" or e.key == "Down" or e.key == "Left" or e.key == "Right" then return true end
end)
print("Arrow keys or a pad to walk. Escape stops it.")
Paint with the keys
-- A cursor the arrow keys move a pixel at a time (Shift: 8); Space paints with the left
-- colour, and the number keys pick colours 1 to 9. A tiny "etch a sketch".
local x, y, colour = dp.width() // 2, dp.height() // 2, 1
local function show() print(`cursor {x}, {y}, colour {colour}`) end
dp.on("key", function(e)
local step = if e.shift then 8 else 1
if e.key == "Left" then x -= step
elseif e.key == "Right" then x += step
elseif e.key == "Up" then y -= step
elseif e.key == "Down" then y += step
elseif e.key == "Space" then dp.draw.pixel{ x = x, y = y, colour = colour }
elseif tonumber(e.key) and tonumber(e.key) > 0 then colour = math.min(tonumber(e.key), dp.colours() - 1)
else return false end -- anything else: Deluxe Pixel's as usual
x = math.clamp(x, 0, dp.width() - 1)
y = math.clamp(y, 0, dp.height() - 1)
show()
return true
end)
show()
Mouse trail
-- The mouse event: hold the left button over the picture for a rainbow trail (the press
-- is used up, so the tool doesn't paint too). The right button paints as usual.
local colour = 1
dp.on("mouse", function(e)
if e.left then
dp.draw.ellipse{ cx = e.x, cy = e.y, rx = 2, colour = colour, filled = true }
colour = colour % (dp.colours() - 1) + 1
end
if e.pressed == "left" then return true end
end)
print("Hold the left button over the picture. Escape stops it.")
Tasks and waiting
-- dp.spawn runs a function alongside Deluxe Pixel; inside it, dp.wait(seconds) pauses it
-- without holding anything up. Two at once: rain falling, and a clock ticking.
dp.spawn(function()
for i = 1, 40 do
local x = math.random(0, dp.width() - 1)
dp.draw.line{ x0 = x, y0 = 0, x1 = x, y1 = math.random(2, dp.height() // 3), colour = 4 }
dp.wait(0.1)
end
print("rain: done") -- a task that finishes simply ends
end)
dp.spawn(function()
for second = 1, 5 do
dp.wait(1)
print(`tick {second}`)
end
end)
Watching what changes
-- Events for what the person does: another picture, layer, colour, palette or selection.
-- Try them while it runs (Escape stops it).
dp.on("picture", function(e) print(`picture: {e.name} ({e.width} x {e.height}){if e.map then ", a map" else ""}`) end)
dp.on("layer", function(e) print(`layer {e.index}: {e.name}`) end)
dp.on("colour", function(e) print(`colours: left {e.left}, right {e.right}`) end)
dp.on("palette", function(e) print(`the palette changed ({e.colours} colours)`) end)
dp.on("selection", function(e)
if e.empty then print("nothing selected") else print(`selected {e.w} x {e.h} at {e.x}, {e.y}`) end
end)
dp.on("save", function(e) print("saved " .. e.path) end)
print("Watching...")
Learning Luau
Debugging
-- Click left of a line number for a breakpoint (the red dot), then Debug (F5).
-- When it stops: the variables are below; F10 steps over a line, F11 into a function,
-- Shift+F11 out of it, F5 carries on. Hover a variable to see its value.
local function spiral(cx: number, cy: number, turns: number)
local points = {}
for i = 0, turns * 32 do
local a = i / 32 * math.pi * 2
local r = i / 4
table.insert(points, vector.create(cx + math.cos(a) * r, cy + math.sin(a) * r, 0))
end
return points
end
local pts = spiral(dp.width() / 2, dp.height() / 2, 3)
for i, p in pts do
dp.draw.pixel{ x = math.floor(p.x), y = math.floor(p.y), colour = 1 + i % 15 }
end
print(`{#pts} points`)
Errors and dp.try
-- A command that can't run stops the script with why; dp.try runs one without stopping.
local tooBig = 9999
local ok, why = dp.try("draw.pixel", { x = 0, y = 0, colour = tooBig })
print("ok:", ok, "why:", why)
-- pcall catches any error, a command's included:
local none = 0
local fine, err = pcall(function() dp.draw.rect{ x = 0, y = 0, w = none, h = 4, colour = 1 } end)
print("fine:", fine, "error:", err)
-- error() stops the script with your own message: take the -- off the next line and Run
-- (the line goes red, and the Output says where it was called from).
-- error("stopped on purpose")
Tables and strings
-- A quick tour: tables as lists and as records, string functions, interpolation.
local colours = { "red", "green", "blue" }
table.insert(colours, "yellow")
print(#colours, table.concat(colours, ", "))
local sprite = { name = "Hero", w = 16, h = 24, frames = 4 }
for k, v in sprite do print(k, v) end
print(string.upper(sprite.name), string.rep("=", 10))
print(`{sprite.name} is {sprite.w} x {sprite.h}, {sprite.frames} frames`)
local words = string.split("one two three", " ")
print(#words, words[2])
local sorted = { 5, 3, 9, 1 }
table.sort(sorted)
print(table.concat(sorted, " < "))
Functions and types
-- Functions are values; type annotations are optional (the checks use them, the VM ignores them).
local function clamp(v: number, lo: number, hi: number): number
return math.max(lo, math.min(hi, v))
end
local function twice(f: (number) -> number): (number) -> number
return function(x) return f(f(x)) end
end
local addTen = function(x: number) return x + 10 end
print(clamp(300, 0, 255), twice(addTen)(1))
type Point = { x: number, y: number }
local p: Point = { x = 3, y = 4 }
print(`distance: {math.sqrt(p.x ^ 2 + p.y ^ 2)}`)
Buffers
-- buffer: a block of bytes, compact and fast. The active layer read into one, then counted.
local w, h = dp.width(), dp.height()
local b = buffer.create(w * h)
for y = 0, h - 1 do
for x = 0, w - 1 do
local c = dp.pixel(x, y)
buffer.writeu8(b, y * w + x, if type(c) == "number" and c >= 0 then c else 0)
end
end
local counts = table.create(256, 0)
for i = 0, w * h - 1 do
local c = buffer.readu8(b, i) + 1
counts[c] += 1
end
print(`{buffer.len(b)} bytes; colour 0 is used {counts[1]} times`)
Whole layer at once
-- dp.pixels() gives the whole active layer as a buffer (1 byte a pixel: its colour number; 4 for true colour);
-- dp.set_pixels() writes it back as one undo step (inside the selection). Far faster than a command a pixel:
-- here every colour number goes up by one, the last wrapping round to 0.
local px, w, h = dp.pixels()
if buffer.len(px) ~= w * h then error("This example is for palette pictures.") end
local top = dp.colours() - 1
for i = 0, w * h - 1 do
local c = buffer.readu8(px, i)
buffer.writeu8(px, i, if c == top then 0 else c + 1)
end
dp.set_pixels(px)
print(`{w * h} pixels in one go`)
Vectors (3D)
-- vector: 3D maths built into Luau (x, y, z), fast as native code. A cube's corners turned
-- and projected onto the picture, edges drawn as lines.
local cx, cy = dp.width() / 2, dp.height() / 2
local size = math.min(cx, cy) / 2
local corners = {}
for i = 0, 7 do
corners[i + 1] = vector.create(if i % 2 == 0 then -1 else 1, if (i // 2) % 2 == 0 then -1 else 1, if i // 4 == 0 then -1 else 1)
end
local function turn(v: vector, a: number): vector
local x = v.x * math.cos(a) - v.z * math.sin(a)
local z = v.x * math.sin(a) + v.z * math.cos(a)
local y = v.y * math.cos(a * 0.7) - z * math.sin(a * 0.7)
return vector.create(x, y, v.y * math.sin(a * 0.7) + z * math.cos(a * 0.7))
end
local function project(v: vector): (number, number)
local p = 3 / (v.z + 4)
return math.floor(cx + v.x * size * p), math.floor(cy + v.y * size * p)
end
local edges = { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 }, { 1, 3 }, { 2, 4 }, { 5, 7 }, { 6, 8 }, { 1, 5 }, { 2, 6 }, { 3, 7 }, { 4, 8 } }
for _, e in edges do
local x0, y0 = project(turn(corners[e[1]], 0.6))
local x1, y1 = project(turn(corners[e[2]], 0.6))
dp.draw.line{ x0 = x0, y0 = y0, x1 = x1, y1 = y1, colour = 1 }
end
print(`corner 1 is {vector.magnitude(corners[1])} from the middle`)
Native speed
-- Luau compiles scripts to native code (Run, Ctrl+Enter): maths and loops run fast.
-- Debug (F5) uses the interpreter instead: compare the times.
local t = os.clock()
local sum = 0
for i = 1, 10_000_000 do
sum += math.sqrt(i) * 0.5
end
print(`sum {math.floor(sum)} in {math.floor((os.clock() - t) * 1000)} ms`)
Every command
-- Every command a script can use (dp.<group>.<name>; hover one in the editor for its help).
for _, id in dp.commands() do print(id) end