Lua modding API reference
Board
An array of rows, top to bottom. Each column is two bits, from left to right going from least to most significant. 00 is empty, and 01, 10, and 11 are different tiles. Note that the indices are from 0 to 19; i.e. they don't start at 1.
type Board = [0...19]integer
Board:allempty
Checks whether a piece fits into a board at a given location.
Board:allempty(t: Tetromino, rotation: integer, x: integer, y: integer) -> boolean
Board:stamp
Writes a piece onto a board at a given location.
Board:stamp(t: Tetromino, rotation: integer, x: integer, y: integer) -> boolean
CoreOption
A core option. New values for these options can be defined by adding functions to the table. The parameters passed into the function and when the function is called depend on the specific option.
type CoreOption = table
CoreOption.name
The name of a core option, as used in game.options
.
CoreOption.name: string
CoreOption.value
The value of a core option. TODO: allow mutation
CoreOption.value: string
GameState
The state of the game for one player.
type GameState = table
GameState:bclear
Initiates the b-type clear sequence. This may invoke other mod functions if they listen to game.events.BCLEAR
. This may be called even from a-type, but it can't be called in a multiplayer game. Returns true if the call was successful, or false if the b-type clear was cancelled by a mod.
GameState:bclear() -> boolean
GameState.board
The currently active board. May be mutated.
GameState.board: Board
GameState.completed
Represents which lines, if any, were just cleared. Each of the lower 4 bits represents one line, from top to bottom going from least to most significant. If you just want to get a count of how many lines were cleared, use GameState:completedcount
instead.
GameState.completed: integer
GameState:completedcount
The number of lines that were just cleared.
GameState:completedcount() -> integer
GameState.cur
The currently active tetromino.
GameState.cur: Tetromino
GameState.das
The current DAS charge.
GameState.das: integer
GameState.doubles
The total number of doubles cleared.
GameState.doubles: integer
GameState.dropdelay
The amount of time that must pass before the next piece will spawn. If zero, a piece is currently active.
GameState.dropdelay: integer
GameState.drought
The number of non-I pieces that have spawned consecutively (the "drought").
GameState.drought: integer
GameState:efficiency
The current efficiency.
GameState:efficiency() -> integer
GameState.fallamt
Counts up to game.gravity(level) before game.events.GRAVITY
is triggered and the current piece is either moved down one or locked into place.
GameState.fallamt: integer
GameState.flip
The direction that the player wants to flip the piece. This is 0 if a rotation wasn't requested, -1 if a counter-clockwise rotation was requested, or 1 if a clockwise rotation was requested.
GameState.flip: integer
GameState.level
The current level.
GameState.level: integer
GameState:lines
The total number of lines cleared.
GameState:lines() -> integer
GameState:lock
Locks the currently active piece into place. This may invoke other mod functions if they listen to game.events.PRE_LOCK
or game.events.LOCKED
. Returns true if the piece actually locked; this may return false is a mod function cancels the lock.
GameState:lock() -> boolean
GameState.next
The tetromino up next.
GameState.next: Tetromino
GameState.oldrotation
The previous rotation. Only really used by the game to check whether the piece was just rotated, but it may be useful to set this sometimes.
GameState.oldrotation: integer
GameState.oldx
The previous x position. Only really used by the game to check whether the piece was just moved, but it may be useful to set this sometimes.
GameState.oldx: integer
GameState.oldy
The previous y position. Only really used by the game to check whether the piece was just moved, but it may be useful to set this sometimes.
GameState.oldy: integer
GameState.over
Whether the player has topped out.
GameState.over: boolean
GameState.player
The player for this game state: either 1 or 2.
GameState.player: integer
GameState.pushdownpts
Accumulated push-down points when soft-dropping. This value is mutable.
GameState.pushdownpts: integer
GameState.quads
The total number of tetrises (4-line clears) cleared.
GameState.quads: integer
GameState:redraw
Redraws the board. This may invoke other mod functions if they listen to game.events.REDRAW
.
GameState:redraw()
GameState.rotation
The current rotation.
GameState.rotation: integer
GameState.score
The current score.
GameState.score: integer
GameState.singles
The total number of singles cleared.
GameState.singles: integer
GameState.softdrop
If negative, this will increase, and GameState.fallamt
will remain frozen until this hits zero. If positive, the piece is being soft-dropped.
GameState.softdrop: integer
GameState.triples
The number of triples cleared.
GameState.triples: integer
GameState:trtrate
The percentage of line clears that were tetrises/quads (the "tetris rate").
GameState:trtrate() -> integer
GameState.waiting
If positive, this will decrease until it hits zero, and nothing else will happen in the main game loop. This is used when the game first starts (to emulate the delay in NES Tetris), and when b-type is cleared.
GameState.waiting: integer
GameState.x
The current x position.
GameState.x: integer
GameState.y
The current y position. Note that y increases from top to bottom.
GameState.y: integer
Image
An image created by game.newimg
.
type Image = userdata
Leaderboard
A leaderboard, usually obtained from game.leaderboards
. Its contents are mutable.
type Leaderboard = [10]LeaderboardEntry
Leaderboard.cur
When game.scr
== game.screen.LEADERBOARD
, this points to the leaderboard entry that's currently being edited. It's an error to access this value when on any other screen.
Leaderboard.cur: LeaderboardEntry
Leaderboard.type
The type of this leaderboard, either "a" or "b".
Leaderboard.type: string
LeaderboardEntry
An entry on a leaderboard. TODO: allow mutation
type LeaderboardEntry = table
LeaderboardEntry.height
The height of the b-type game this entry describes, or nil if the game wasn't played in b-type. This can be used to determine whether an entry is on the a-type or b-type leaderboard.
LeaderboardEntry.height: integer | nil
LeaderboardEntry.level
The final level of the game this entry describes.
LeaderboardEntry.level: integer
LeaderboardEntry.name
The name of the player who played the game this entry describes. The name is always exactly eight characters.
LeaderboardEntry.name: string
LeaderboardEntry.rank
The rank of an entry on its leaderboard, where first place is rank 1.
LeaderboardEntry.rank: integer
LeaderboardEntry.score
The final score of the game this entry describes.
LeaderboardEntry.score: integer
Music
Music loaded by game.loadmusic
.
type Music = integer
Option
An option registered by a mod.
type Option = table
Option.default
The default value of this option, if no value is supplied in the config file.
Option.default: string
Option.value
The current value of this option.
Option.value: string
Screen
A screen, such as game.screen.LVLSELECT
or game.screen.GAME
.
type Screen = integer
Sfx
A sound effect loaded by game.loadsfx
.
type Sfx = integer
Tetromino
A tetromino, represented by a single character string: "T", "J", "Z", "O", "S", "L", "I".
type Tetromino = string
Texture
A texture that can be drawn to. Note that not all textures support transparency.
type Texture = table
Texture:clear
Clears a texture.
Texture:clear()
Texture:cleartetromino
Clears the space a tetromino would occupy on a texture.
Texture:cleartetromino(t: Tetromino, rotation: integer, x: integer, y: integer)
Texture:drawbrick
Draws a tetromino brick on a texture. 'id' must be a non-negative number less than 4. If 0, this is a no-op. Otherwise, 'id' represents the tile to draw. If 'opacity' is specified, it must be an integer between 0 and 255 (defaults to 255).
Texture:drawbrick(
id: integer,
x: integer,
y: integer,
level: integer,
opacity?: integer
)
Texture:drawimg
Draws an image on a texture. Note that x and y are in pixels (before the texture is scaled by SDL), not tiles as they are in most other texture functions.
Texture:drawimg(
img: Image,
x: integer,
y: integer,
opts?: {
flip: nil | "h" | "v",
rotation: number,
scale: integer,
}
)
Texture:drawrect
Draws a rectangle on a texture.
Texture:drawrect(x: integer, y: integer, w: integer, h: integer, color: integer)
Texture:drawrectpx
Similar to Texture:drawrect
, but x and y are in pixels (before the texture is scaled by SDL) rather than tiles.
Texture:drawrectpx(x: integer, y: integer, w: integer, h: integer, color: integer)
Texture:drawtetromino
Draws a tetromino on a texture. If 'opacity' is specified, it must be an integer between 0 and 255 (defaults to 255).
Texture:drawtetromino(
t: Tetromino,
rotation: integer,
x: integer,
y: integer,
level: integer,
opacity?: integer
)
Texture:fillrect
Draws a rectangle on a texture, and fills it in with the given color.
Texture:fillrect(x: integer, y: integer, w: integer, h: integer, color: integer)
Texture:fillrectpx
Similar to Texture:fillrect
, but x and y are in pixels (before the texture is scaled by SDL) rather than tiles.
Texture:fillrectpx(x: integer, y: integer, w: integer, h: integer, color: integer)
Texture:write
Writes a string or number to the screen. 'width' must only be supplied if writing a number, but it can always be omitted.
Texture:write(msg: string | integer, x: integer, y: integer, width?: integer, color: integer)
game
local game = require"game"
game.TILEHEIGHT
The height of the canvas, in tiles. Each tile is 32x32 pixels.
game.TILEHEIGHT: integer
game.TILEWIDTH
The width of the canvas, in tiles. Each tile is 32x32 pixels.
game.TILEWIDTH: integer
game.__useroptions
game.__useroptions: table
game.bg
The background texture. This is cleared whenever the screen is switched, and is intended to be mostly static and infrequently updated. Transparency is not supported on this texture.
game.bg: Texture
game.chdir
Changes the current working directory, as in chdir(2)
. On POSIX systems, the PWD
environment variable is also updated to the absolute path of the new working directory. A Lua error is raised if an error occurs.
game.chdir(path: string)
game.color.BLACK
The color black.
game.color.BLACK: integer
game.color.BLUE
A blue color from the NES color palette: #3032ECFF
game.color.BLUE: integer
game.color.GREEN
A green color from the NES color palette: #287200FF
game.color.GREEN: integer
game.color.LIGHTBLUEISH
A light blue-ish color from the NES color palette: #BCBCECFF
game.color.LIGHTBLUEISH: integer
game.color.LIGHTGRAY
A light gray color from the NES color palette: #ECEEECFF
game.color.LIGHTGRAY: integer
game.color.LIGHTGREEN
A light green color from the NES color palette: #38CC6CFF
game.color.LIGHTGREEN: integer
game.color.PURPLE
A purple color from the NES color palette: #E454ECFF
game.color.PURPLE: integer
game.color.RED
A red color from the NES color palette: #982220FF
game.color.RED: integer
game.color.WHITE
The color white.
game.color.WHITE: integer
game.color.YELLOW
A yellow color from the NES color palette: #A0AA00FF
game.color.YELLOW: integer
game.color.invert
Inverts the background and foreground of a color.
game.color.invert(color: integer) -> integer
game.emptyboard
A board which is completely empty; useful when combined with Board:allempty
to check if a piece is entirely within the playfield. You shouldn't mutate this board; that would be very impolite to the other mods using it. Don't be impolite.
game.emptyboard: Board
game.events.BCLEAR
Triggers before a b-type clear. Setting GameState.waiting
to 0 will cancel the clear.
game.events.BCLEAR: integer
game.events.END_GAME
Triggers immediately before the game ends.
game.events.END_GAME: integer
game.events.FIRST_FRAME
Triggers on the first frame that a piece spawns.
game.events.FIRST_FRAME: integer
game.events.FLIP
Triggers when the player attempts to rotate a piece, immediately before the rotation is attempted. Setting GameState.flip
to 0 will cancel the rotation. This is useful if you'd like to implement your own rotation system.
game.events.FLIP: integer
game.events.FRAME
Triggers every frame, unconditionally.
game.events.FRAME: integer
game.events.GRAVITY
Triggers before a piece is shifted down or locked due to gravity. Setting GameState.fallamt
to a non-zero value or setting GameState.softdrop
to a negative value will cancel this.
game.events.GRAVITY: integer
game.events.LOCKED
Triggers after a piece locks. Data about the piece such as its position and rotation is lost; if you need this, use game.events.PRE_LOCK
.
game.events.LOCKED: integer
game.events.LVLUP
Triggers after the level is incremented.
game.events.LVLUP: integer
game.events.PAUSE
Triggers when the game is paused or unpaused. Check game.pause
to determine which.
game.events.PAUSE: integer
game.events.PRE_LOCK
Triggers before a piece locks. Line clears aren't computed at this point; if you need this, use game.events.LOCKED
.
game.events.PRE_LOCK: integer
game.events.RAW_KEYDOWN
Triggers when a key on the keyboard is pressed. A third argument is supplied to the mod function for this event: a string of the key that was pressed. This differs from game.events.RAW_TEXTINPUT
in that it only sends single key presses, regardless of any modifiers (e.g. shift) being used. You shouldn't use this unless you have a very specific use-case which calls for it.
game.events.RAW_KEYDOWN: integer
game.events.RAW_KEYUP
Triggers when a key on the keyboard is released. A third argument is supplied to the mod function for this event: a string of the key that was released. You shouldn't use this unless you have a very specific use-case which calls for it.
game.events.RAW_KEYUP: integer
game.events.RAW_MOUSEDOWN
Triggers when a button on the mouse is pressed. A third argument is supplied to the mod function for this event: a string of the button that was pressed ("Left", "Middle", "Right", "X1", "X2", or "Invalid"). You shouldn't use this unless you have a very specific use-case which calls for it.
game.events.RAW_MOUSEDOWN: integer
game.events.RAW_MOUSEMOVE
Triggers when the mouse is moved. A third argument is supplied to the mod function for this event: a table containing two integer values, which are respectively the x and y position of the cursor. You shouldn't use this unless you have a very specific use-case which calls for it.
game.events.RAW_MOUSEMOVE: integer
game.events.RAW_MOUSEUP
Triggers when a button on the mouse is released. A third argument is supplied to the mod function for this event: a string of the button that was released ("Left", "Middle", "Right", "X1", "X2", or "Invalid"). You shouldn't use this unless you have a very specific use-case which calls for it.
game.events.RAW_MOUSEDOWN: integer
game.events.RAW_MOUSEWHEEL
Triggers when the mouse wheel is scrolled. A third argument is supplied to the mod function for this event: a table containing two integer values, which are respectively the amount scrolled horizontally (positive to the right) and the amount scrolled vertically (positive up). You shouldn't use this unless you have a very specific use-case which calls for it.
game.events.RAW_MOUSEWHEEL: integer
game.events.RAW_TEXTINPUT
Triggers when something is typed on the keyboard. A third argument is supplied to the mod function for this event: a string of what was typed. This differs from game.events.RAW_KEYDOWN
in that it sends a string of what was actually typed by the user, taking modifiers (e.g. shift) into account, and supporting composition (e.g. for non-ASCII characters, but note that the builtin font only supports ASCII). You shouldn't use this unless you have a very specific use-case which calls for it.
game.events.RAW_TEXTINPUT: integer
game.events.REDRAW
Triggers after the board is redrawn.
game.events.REDRAW: integer
game.events.ROTATE
Triggers after the piece is rotated.
game.events.ROTATE: integer
game.events.SHIFT
Triggers after the piece is shifted in the x direction.
game.events.SHIFT: integer
game.events.STARTUP
Triggers when generic tetromino game is first launched. This is the first event that's ever triggered, and it's only triggered once. Additional "flags" can be supplied as command-line arguments, for instance, foo.bar=baz
will set the flag "bar" to "baz" for the option named "foo", foo.bar
will set the flag "bar" to boolean true, and !foo.bar
will set the flag "bar" to boolean false. The flags are passed to the mod function in the third parameter, as a table. The table is empty if no flags are given. Most mods won't need to use flags at all; their use case is when additional info needs to be provided to a mod that can't be provided through the option value alone (e.g. a file path).
game.events.STARTUP: integer
game.events.START_GAME
Triggers when the game starts, immediately after the current and next piece are first chosen. Note that this isn't the first event that's triggered in a game, since game.events.TICK
triggers even during the initial start delay.
game.events.START_GAME: integer
game.events.SWITCH_SCREEN
Triggers when the active screen is switched. Check game.scr
to determine which screen is now active. For most use cases you probably don't want to use this event; use a more specific event such as game.events.END_GAME
instead.
game.events.SWITCH_SCREEN: integer
game.events.TICK
Triggers every frame for all players in-game.
game.events.TICK: integer
game.events.TOPOUT
Triggers after the player tops out.
game.events.TOPOUT: integer
game.fg
The foreground texture. This is cleared whenever the screen is switched, and is intended for screen-dependent graphics that are frequently updated. This texture supports transparency.
game.fg: Texture
game.frames
The number of frames that have elapsed since startup. This effects the timing of line clears (for compatibility with NES Tetris). This value is mutable.
game.frames: integer
game.freeze
Completely freezes all game logic. The only events that are triggered when the game is frozen are game.events.FRAME
and all RAW_ events. When the game is frozen, input is read, but not acted upon; it follows that the same button can be pressed immediately before the game is frozen, and immediately after it's frozen, and it will be interpreted as two separate consecutive button presses, rather than holding the button down (this will break replays, however). game.frames
isn't updated while the game is frozen. This function can be safely called more than once; subsequent calls will have no effect. Call game.unfreeze
to resume game logic.
game.freeze()
game.gravity
Gets the gravity of a level; i.e. the value that GameState.fallamt
must reach before the current piece moves down or locks.
game.gravity(level: integer) -> integer
game.haltmusic
Halts the currently playing music if there is any. A fade-out can be optionally supplied (in milliseconds). Once the music halts, the callback function is called, if one is supplied. The callback may call game.playmusic
. Only one callback may be active at a time; if a new callback is registered while music is still fading out, the old callback will be overwritten. The callback is removed after it returns.
game.haltmusic(fadeout?: integer, callback?: function ())
game.haltsfx
Halts all currently playing sound effects.
game.haltsfx()
game.height
The B-type height, or nil if not playing on B-type. This value is mutable, but mutating it when not on game.screen.LVLSELECT
when game.type
is "b" won't have any effect. This value will never be greater than 8.
game.height: integer | nil
game.leaderboards.a
The a-type leaderboard.
game.leaderboards.a: Leaderboard
game.leaderboards.b
The b-type leaderboard.
game.leaderboards.b: Leaderboard
game.listen
Registers a listener for events in the same manner as game.newoption
, except that no visible/modifiable option is created. This should be used when the actual mod logic is implemented elsewhere, but an additional listener is needed. Mods should prefer game.newoption
whenever possible.
game.listen(events: integer, func: function (ev: integer, player: integer))
game.loadmusic
Loads music from the given path and returns an object that can be played with game.playmusic
. The working directory is set to the directory that mod.lua resides in when the file is first run, so you can use a relative path here.
game.loadmusic(path: string) -> Music
game.loadsfx
Loads a sound effect from the given path and returns an object that can be played with game.playsfx
. The working directory is set to the directory that mod.lua resides in when the file is first run, so you can use a relative path here.
game.loadsfx(path: string) -> Sfx
game.multiplayer
Whether or not the game is being played multiplayer, either local or online.
game.multiplayer: boolean
game.newboard
Creates a new board, which can be safely mutated.
game.newboard() -> Board
game.newimg
Creates a new image from the given pixel data. The data table is a table of colors (32-bit unsigned integers), and its length must be equal to width times height. The image can be drawn with Texture:drawimg
.
game.newimg(data: []integer, width: integer, height: integer) -> Image
game.newoption
Registers a new option. The second argument is a table, which must have a 'default' key, whose value is the default value of the option, and an 'events' key, whose value is the bitwise OR of all events that functions for this option will listen to. Register values for the new option by creating functions within the option table, i.e. for option foo with value bar: function foo.bar(ev, player)
. ev is the event that caused this function to be called, and player is the player this event is associated with (either 1 or 2). Some events will supply a third argument to the function; these are documented for specific events.
game.newoption(name: string, {default: string, events: integer}) -> Option
game.options.input_handler
The input_handler core option. User-defined functions are called everytime input is received. The purpose of the input handler is to return inputs which are possibly derived from the actual received inputs. The function is called with three arguments: the first argument is always 0, the second argument is the player the received input is from (either 1 or 2), and the third argument is the actual inputs received. The inputs passed into and returned from the function have the same format as those in the replay format, except the repeat bit is ignored.
game.options.input_handler: CoreOption
game.options.lines_per_level
The lines_per_level core option. TODO: actually implement
game.options.lines_per_level: CoreOption
game.options.music
The music core option. The user may define functions here (the same as with all other core options), which are called with no arguments whenever game music is to be played, or the user may define fields as Music
values instead, in which case the given music is played.
game.options.music: CoreOption
game.options.rng
The rng core option. User-defined functions are called everytime a new RNG value is needed. The function is called with two arguments: the first argument is always 0, and the second argument is the player that the RNG value is being generated for (either 1 or 2). The return value of the function is used as the new RNG value.
game.options.rng: CoreOption
game.overlay
The overlay texture. This is intended for screen-independent graphics. This texture supports transparency.
game.overlay: Texture
game.pause
Whether or not the game is currently paused. This value is mutable.
game.pause: boolean
game.pausemusic
Pauses the currently playing music if there is any. The music can be resumed with game.resumemusic
.
game.pausemusic()
game.players
Game states for both players. If game.multiplayer
is false, index 2 is invalid here.
game.players: [2]GameState
game.playgamemusic
Plays game music according to the value of the "music" core option.
game.playgamemusic()
game.playmusic
Plays music.
game.playmusic(mus: Music)
game.playsfx
Plays a sound effect. The 'pan' argument only has an effect when playing multiplayer. If 'pan' is negative, the sound plays moreso to the left, if positive, it plays moreso to the right. If zero, the sound plays equally left and right (i.e. no position changes). The default pan is zero.
game.playsfx(sfx: Sfx, pan?: integer)
game.prompt
Displays a prompt containing one or two choices. When a choice is selected, the respective callback function is called. This should, at minimum, set game.scr
to a different screen.
game.prompt(
msg: string,
choice1: string,
action1: function (),
choice2?: string,
action2?: function ()
)
game.resumemusic
Resumes music that was paused by game.pausemusic
, or does nothing if no music was paused.
game.resumemusic()
game.rng.misc
A miscellaneous 16-bit unsigned integer used by the selected RNG algorithm. The exact meaning of this number differs based on the active RNG algorithm. This value is mutable; if your mod implements an RNG algorithm that preserves data between games, this should be used so games replay correctly.
game.rng.misc: integer
game.rng.next
Runs the PRNG function to get the next random number and returns the result.
game.rng.next() -> integer
game.rng.value
The most recently generated random number.
game.rng.value: integer
game.scr
The currently active screen. This can be mutated to switch to a different screen, potentially invoking other mod functions that listen to game.events.SWITCH_SCREEN
. game.screen.PROMPT
is NOT a valid argument for this function; use game.prompt
instead.
game.scr: Screen
game.screen.CREDITS
The credits screen.
game.screen.CREDITS: Screen
game.screen.GAME
The game screen.
game.screen.GAME: Screen
game.screen.INPUT
The input mapping screen.
game.screen.INPUT: Screen
game.screen.LEADERBOARD
The leaderboard screen, where the player enters their name to save their score.
game.screen.LEADERBOARD: Screen
game.screen.LVLSELECT
The level select screen.
game.screen.LVLSELECT: Screen
game.screen.ONLINE
The online screen.
game.screen.ONLINE: Screen
game.screen.OPTIONS
The options screen.
game.screen.OPTIONS: Screen
game.screen.PROMPT
The prompt screen, used for user input and error messages.
game.screen.PROMPT: Screen
game.screen.REPLAY
The replay selection screen.
game.screen.REPLAY: Screen
game.screen.ROCKET
The rocket (statistics) screen.
game.screen.ROCKET: Screen
game.screen.TITLE
The title screen.
game.screen.TITLE: Screen
game.startlevel
The level the game was started at, or the level the game will start at if not currently in a game. This value is mutable, but mutating it when not on game.screen.LVLSELECT
won't have any effect.
game.startlevel: integer
game.type
The type of the game: either "a", "b", or nil if inapplicable.
game.type: string | nil
game.unfreeze
Resumes game logic if the game was frozen by game.freeze
. This function is a no-op if the game isn't frozen.
game.unfreeze()