Script command reference
The verbs available in script rows - map scripts, quest scripts, cutscenes.
A script is an array of rows; each row is { "verb", arg1, arg2, ... }.
Implementation and per-verb porting notes: src/script/Commands.lua. All
of these are records in the commands registry, registered by the engine
under the owner engine, so a mod adds a verb with
mod.commands:register(verb, fn) and replaces one with
mod.content.commands:override(verb, fn).
Generations. This page is the Gen 1 (Red, Blue, Yellow) verb set. Gold, Silver and Crystal run cart script bytecode in their own VM, and the Gen 3 games run theirs, so the engine verbs below are not rows there. What does carry across: a verb a mod registers resolves on all three generations (Registering your own verb), and Portable API 3 mods get a common verb set that the engine adapts per game (Portable script verbs).
Find the command family
| You want a script to | Start here |
|---|---|
| Show dialogue or ask a question | Text and choices |
| Branch, wait, or start another script | Flow |
| Change flags, items, or money | Flags, items, money |
| Start a battle or give a Pokémon | Battles and mons |
| Move people, objects, or the camera | Movement and staging |
| Play sound, music, or open a screen | Sound and screens |
Where rows go: a map_scripts entry or another API that accepts script
rows. Status: the verb tables are exact contracts; the traced example below
is a shape you adapt with text and flag ids that exist in your mod.
In an Args cell, commas separate values in the row after the verb. Quoted text
such as "wild" is literal text, and A | B means either listed form is
accepted. A textId may be a generated text label, a map pointer, or literal
text where that command says literal text is allowed.
A handler is fn(ctx, ...); returning a string jumps to that label
("end" ends the script). ctx carries ctx.save, ctx.overworld,
ctx.game, ctx.runner, and the branch state below.
Branch state
Check-style verbs set ctx.lastCheck; jump_if_true / jump_if_false
branch on it. choice also sets ctx.lastChoice = { index, label }.
Use label rows as jump targets - never absolute row numbers, which break
when a row is inserted.
Trace one small branch
This script gives a Potion the first time it runs and shows different text on
later runs. It is shape only: replace the flag and text with ids or literal
text appropriate to your script, then register the rows through
map_scripts.
"mod:" makes the branch state private to the mod that owns these script
rows. This works only for a mod-owned script contribution; engine-owned rows
must use the shared story-flag namespace. It is the script-row form of
mod.save.
local rows = {
{ "check_flag", "mod:got_potion" },
{ "jump_if_true", "already_got_it" },
{ "show_text", "You found a POTION!" },
{ "give_item", "POTION", 1 },
{ "set_field", "mod:got_potion", true },
{ "jump", "end" },
{ "label", "already_got_it" },
{ "show_text", "The spot is empty." },
{ "label", "end" },
}
| Row | What the runner remembers | What happens next |
|---|---|---|
check_flag |
lastCheck is true when the flag is set |
continue to the conditional jump |
jump_if_true |
reads lastCheck |
jumps to already_got_it only on later visits |
give_item and set_field |
the private value changes only after the item is given | the next visit takes the other branch; a full bag stops before that row |
label |
nothing by itself | provides a stable jump destination |
Text and choices
| Verb | Args | Does |
|---|---|---|
show_text |
textId, subs |
show a text box; textId is a generated-text label ("_PalletTownGirlText"), a map TEXT_* pointer, or a literal. subs fills {RAM:...} tokens |
ask |
textId, subs |
text, then YES/NO; result in lastCheck |
choice |
labels, opts |
N-way menu; lastChoice = { index, label }, lastCheck = (index == 1); opts.default preselects, opts.cancel is the B index |
text_opts |
opts |
TextBox options for the next show_text only (auto = true is the no-button-wait box) |
text_sound |
soundId |
arm the NEXT show_text with a trailing jingle (the ROM's sound_get_item_1 / sound_get_key_item text-command idiom): it fires once the last page has typed and the box waits on it before the button wait |
Flow
| Verb | Args | Does |
|---|---|---|
label |
name |
jump target, pre-scanned by the runner |
jump |
target |
unconditional jump to a label |
jump_if_true / jump_if_false |
target |
branch on lastCheck |
wait |
frames |
block for n frames |
wait_sound |
block until the last play_sound effect has finished (capped by a ceiling) |
|
wait_flag |
flagName, timeoutFrames |
yield until the flag sets; lastCheck false on timeout. The sync primitive for parallel scripts |
run_parallel |
rowsOrRef, opts |
start a background script (bounded slots) and continue; rowsOrRef is a row array or "MAP_ID/name" naming a map_scripts scripts entry |
Flags, items, money
| Verb | Args | Does |
|---|---|---|
set_flag / clear_flag |
name |
write the shared story-flag namespace |
check_flag |
name |
lastCheck = flag state |
check_item |
itemId |
lastCheck = bag has one |
give_item |
itemId, count, gotText |
add to bag with the received jingle; gotText = false when the script shows its own text |
take_item |
itemId, count |
remove from bag |
give_money |
amount |
add money (clamped at 0) |
check_money |
amount |
lastCheck = the player has at least amount |
take_money |
amount |
remove money (floors at 0) |
take_coins |
amount |
remove Game Corner coins (floors at 0) |
set_field |
key, value |
scratch state outside the flag namespace; "mod:key" routes to the owning mod's save.modData |
set_option |
key, value |
write save.options[key] (the Champion's Room idiom) |
Battles and mons
| Verb | Args | Does |
|---|---|---|
start_battle |
"wild", species, level or "trainer", OPP_CLASS, partyIndex |
run a battle |
static_battle |
species, level, beatFlag |
a static encounter (Snorlax/birds idiom); beatFlag set on any non-blackout end |
rival_battle |
oppClass, baseParty, offsets |
the starter-counterpick party math |
check_battle_result |
"win"|"lose"|"run"|"caught", ... |
lastCheck = result is any of the given |
give_pokemon |
species, level, skipNickname |
gift mon; emits the mutable pokemon.before_give first; lastCheck false when party and boxes are full. skipNickname = true suppresses the nickname prompt for callers that name the gift themselves |
save_end_battle_text |
textId |
stash the after-battle line the next scripted battle shows on defeat, with {PLAYER}/{RIVAL} expanded |
set_catch_rate |
slot, value |
set the catchRate of the party mon in a one-based slot |
heal_party |
full heal | |
trade |
tradeIndex, doneFlag |
an in-game NPC trade |
old_man_demo |
the Viridian catch tutorial | |
record_hall_of_fame |
Hall of Fame sequence | |
mark_seen |
species |
set the Pokedex SEEN flag |
check_dex_owned |
n |
lastCheck = at least n species owned (default 1) |
dex_rating |
Oak's seen/owned tally and rating line; blocks until the box closes |
Movement and staging
| Verb | Args | Does |
|---|---|---|
move_player |
dir, tiles |
walk the player |
move_npc |
objIndex, dir, tiles |
walk an NPC |
place_npc |
objIndex, x, y, facing |
teleport an NPC to a cell, no walk |
move_npc_to |
objIndex, tx, ty |
pathfind an NPC to a cell |
walk_npc |
objIndex | "player", dirs, opts |
chained walk along a direction list; opts.wait = false returns immediately |
march_in_place |
objIndex, on |
toggle walk-in-place; non-blocking |
face |
dir |
face the scripted NPC |
face_player |
scripted NPC faces the player | |
no_npc_face_player |
on |
stop (or restore) NPCs turning to face the player while a script runs |
face_object |
objIndex, dir |
face an arbitrary object |
face_npc |
player faces the scripted NPC | |
face_player_dir |
dir |
force the player's facing |
emote |
target, bubble, frames |
emote bubble ("shock", "question", "happy") |
warp |
mapId, x, y, facing |
scripted warp |
remember_outdoor |
mapId, x, y |
set save.lastOutdoor, the Dig / Escape Rope / blackout return point |
show_object / hide_object |
mapId, objName |
persistent object toggles |
replace_block |
bx, by, blockId |
the Cut-tree / card-key-door idiom |
ss_anne_departs |
the S.S. Anne leaves the Vermilion dock; foreground, blocks until it finishes | |
set_tile_anim |
anim | false |
override the tileset animation until the next map change |
pan_camera |
dx, dy, frames or "reset" |
offset the camera by cells (blocking) |
fade |
dir, frames |
screen fade |
Sound and screens
| Verb | Args | Does |
|---|---|---|
play_sound |
soundId |
one-shot SFX |
play_cry |
species |
a cry; a following show_text waits for it |
engage_music |
songId |
arm the trainer-engage song for the next scripted trainer battle |
play_music |
songId, opts |
switch music now; opts.keep survives the next warp |
play_once |
songId |
one-shot jingle (Music_PkmnHealed idiom); blocks until it finishes, then the map theme resumes |
play_default_music |
resume the current map's own theme after a cutscene override, keeping the bike/surf substitution rules | |
fade_music |
frames |
fade the music out (default 10) |
stop_music |
stop music | |
open_mart |
textConst |
open a mart by TEXT constant |
push_screen |
screenId, args |
instantiate through the screens registry and block until the screen pops |
Yellow support
Verbs ported for Pokemon Yellow's scripts; on Red and Blue they are safe no-ops or fall back sensibly.
| Verb | Args | Does |
|---|---|---|
load_player_starter_name |
put the chosen starter's species name in the engine string buffer for the next text substitution | |
spawn_pikachu_follower |
spawn the Pikachu follower on the current map | |
pikachu_make_way |
Oak's Lab Pikachu step-aside movement; a no-op without a Yellow follower |
Portable script verbs
mod.world:queueScript(rows, { onDone = fn }) is the one script entry point
that works on every generation for a "api": 3 mod
(Portable API 3). Rows are { verb, arg... }.
The whole list is validated before the first row runs: a bad row, a missing
native adapter or an unknown ID refuses the list with nil, reason and
nothing executes. At most 1024 rows. onDone(ok, reason) fires once;
queueing a new list replaces the running one.
Aliases: text is show_text, setflag is set_flag, clearflag is
clear_flag.
Common verbs (Gen 1, Gen 2, Gen 3), with the Gen 1 argument shapes above:
| Group | Verbs |
|---|---|
| Flow | label, jump, jump_if_true, jump_if_false, end, wait, wait_flag |
| Checks | check_flag, check_item, check_money, check_dex_owned, check_battle_result |
| State | set_flag, clear_flag, set_field, set_option, give_money, take_money, take_coins, give_item, take_item, heal_party, mark_seen, give_pokemon |
| Dialogue | show_text, ask, choice |
| World | warp, move_player, move_npc, move_npc_to, face, face_object, face_npc, face_player, face_player_dir, place_npc, show_object, hide_object, replace_block |
| Battle and shops | start_battle, static_battle, open_mart |
| Audio | play_sound, play_music, stop_music, play_cry, play_once, text_sound, wait_sound, engage_music, fade_music, play_default_music, remember_outdoor |
Adapted verbs exist on Gen 2 and Gen 3 through an engine adapter:
| Verb | Gen 1 | Gen 2 | Gen 3 |
|---|---|---|---|
emote, fade, walk_npc, text_opts, push_screen, trade |
the Gen 1 command above | adapted | adapted |
pan_camera, march_in_place |
the Gen 1 command above | adapted only where the native game has it, else refused | adapted |
On Gen 1 any verb the commands registry resolves also runs, including
verbs a mod registered. On Gen 2 and Gen 3 a mod-registered verb runs the
same way; an engine Gen 1 verb outside the two tables above (rival_battle,
dex_rating, run_parallel, ss_anne_departs, set_catch_rate and the
rest) is refused with native content adapter unavailable: <verb>, and an
unknown verb with unsupported script command: <verb>.
Notes that differ from Gen 1:
text_optson Gen 2 and Gen 3 accepts onlyauto; ashow_textoptions argument is refused there.push_screenneeds a screen registered incontent.screens;tradeneeds a native trade entry.give_item,give_pokemonandstatic_battleresolve species and item IDs through the selected game's data; an unknown ID refuses the list.warp,remember_outdoor, directions, flag names and screen IDs are validated up front.- Native script bytecode (Gen 2 VM, Gen 3 script engine) stays version-specific and is not exposed as verbs.
For one action instead of a list use mod.world:warpTo or
mod.world:startWildBattle.
Registering your own verb
mod.commands:register("shake_screen", function(ctx, frames)
-- runs like any engine verb; return a label string to jump
end)
A registered verb is also dispatched by the Gen 2 VM (a row such as
{ "mymod:shake", 4, 2 }, or { verb = ..., args = ... }) and by the Gen 3
script engine (a row whose op is the verb name). The handler receives a
context and the row's arguments; a returned label string jumps within the
script, and "end" ends it. An error in a registered verb is logged and the
row is skipped on Gen 2 and Gen 3 rather than taking the map script down.
Use a modid:verb name so the failure can be attributed.
A registered record may also be { fn = fn, foreground = true, blocking = true } - foreground verbs are illegal in parallel scripts, and a
parallel script moving an NPC takes a per-NPC lock a foreground script
preempts.
Start here
Playing
Tutorials
- Choose a tutorial
- 01 Sprite and Text Tweak
- 02 Balance Patch
- 03 New Species
- 04 New Item and Ball
- 05 New Move
- 06 NPC and Dialogue
- 07 New Map
- 08 Quest
- 09 Custom Music
- 10 New Mechanic
- 11 Custom UI Screen
- 12 Mini Total Conversion
Creative guides
Concepts
Recipes and reference
Author and developer tools
Home · Start Playing · Glossary · Troubleshooting · Repository · Community
Game data is imported locally from a supported ROM supplied by the player. Shared mods contain original work or transform recipes, never extracted game content. Check each guide's target game before using its examples.