-
Notifications
You must be signed in to change notification settings - Fork 2.7k
The Basics of Scripting
Originally published by Avara on PokeCommunity.
If you already know XSE scripting from binary hacking, you should be able to pick up scripting in the decomps without too much trouble. However, if you're totally new to ROM hacking and want to begin with Pokéemerald, this guide will hopefully be of some use to you!
To start, you don't need any special scripting tools such as XSE - all you need is a decent text editor (such as Notepad++ or VSCode) and PoryMap to get started! It's easy to modify existing scripts - you simply edit the “scripts.inc” file as needed and hit "Save".
If you're using PoryScript, all of the below will still apply, but you will be working with a slightly different syntax. We'll touch on this in the first section, but for the full syntax changes, see PoryScript's Readme.
Table of Contents:
If you look in “data\maps”, you'll find folders pertaining to each in-game map. Each of these map folders has their own “scripts.inc” file, where the scripts for each map are contained. You can also open a map's scripts by clicking "Open Map Scripts" in the top right corner of PoryMap's Events tab.
You can technically name your labels whatever you like so long as they are unique, but it's recommended that you stick to some kind of pattern when you're naming them so you don't confuse yourself. In this guide, I'll be using Script_Something
for script labels and Text_Something
for text labels for simplicity's sake, but if you have a look at existing scripts, you'll notice that a lot of labels are along the lines of MapName_Something
/MapName_SomethingText
.
To assign a script to a map object, you'll want to highlight it and put the script's label in the "Script" box outlined in red above.
In the project, you'll usually be including scripts in script.inc
files inside each map folder. You'll also find other *.inc
files and *.s
files with script in them as well. In pokeemerald, scripts are implemented as assembler macros, and will all turn into a lot of byte code upon compiling. You don't need to know the details to work with scripts, though. Here's an example of what a sample script.inc
file looks like:
DewfordTown_House2_MapScripts::
.byte 0
DewfordTown_House2_EventScript_Boy::
lock
faceplayer
msgbox DewfordTown_House2_Text_BrawlySoCool, MSGBOX_DEFAULT
release
end
DewfordTown_House2_Text_BrawlySoCool:
.string "Wow, you bothered to cross the sea\n"
.string "to visit DEWFORD?\p"
.string "Did you maybe come here because you\n"
.string "heard about BRAWLY?\p"
.string "He's so cool…\n"
.string "Everyone idolizes him.$"
Note how the labels are followed by one or two colons. One colon indicates that the label is private. Two indicates that the label is global. If you want to reference your label outside this file (like to paste it into PoryMap like above), it needs to be global.
If you're using PoryScript, the files you'll be editing will be called script.pory
files, or any file with the .pory
extension. PoryScript will take these .pory
files and turn them into .inc
files for you. PoryScript's syntax is different, so that it can better understand your script code. Here's the same script file as above in PoryScript format:
mapscripts DewfordTown_House2_MapScripts {}
script DewfordTown_House2_EventScript_Boy {
lock
faceplayer
msgbox (DewfordTown_House2_Text_BrawlySoCool, MSGBOX_DEFAULT)
release
}
text DewfordTown_House2_Text_BrawlySoCool {
"Wow, you bothered to cross the sea\n"
"to visit DEWFORD?\p"
"Did you maybe come here because you\n"
"heard about BRAWLY?\p"
"He's so cool…\n"
"Everyone idolizes him.$"
}
Note that the main difference is that any parameters for commands must be surrounded in parenthesis. PoryScript also does a lot of if statement stuff for you, but we'll cover that later.
Here is an example of a simple text script:
Script_SimpleMsgScript::
lock
faceplayer
msgbox Text_SimpleMsgScript, MSGBOX_DEFAULT
release
end
Text_SimpleMsgScript:
.string "Hi!\n"
.string "My name is Adam.\l"
.string "What's your name?\p"
.string "Nice to meet you, {PLAYER}!$"
Let's break down the script:
-
lock
waits for the player to stop moving and then prevents the NPC being spoken to from moving.- Alternatively,
lockall
stops all NPCs on the map from moving. - Note: the player is prevented from moving while a script is executing, regardless of the use of
lock
.
- Alternatively,
-
faceplayer
makes the NPC or object the player just talked to face the player. -
msgbox
we'll get into below. -
release
cleans up: it unlocks the NPC that was locked, aborts any runningapplymovement
s (see below), and closes the message box.- If you used
lockall
instead, you'll have to usereleaseall
. - If a script ends and the player can move around while a message box is on-screen, you probably forgot to
release
orreleaseall
.
- If you used
-
end
indicates the end of the script (obviously).
For the text part:
-
.string
indicates a line of in-game text. -
{PLAYER}
is a "placeholder" that displays the player's name. -
\n
moves text printing to the next line. -
\l
scrolls down to the next line after the player presses a button. -
\p
clears the display after the player presses a button. Good for new sentences. -
$
indicates the end of the text. Make sure this is at the end of your text, otherwise the game will continue to print garbage afterwards. - Generally your text should use
\n
once then use\l
until a\p
is used, then repeat.
The msgbox
line contains our label for the text to be displayed, as well as the type of msgbox.
-
MSGBOX_DEFAULT
- If you don't supply a type, it defaults to this type. This type will display the text and then wait for the player to press any button before continuing the script. -
MSGBOX_NPC
- This type of msgbox doeslock
,faceplayer
, andrelease
for you. Used most often with NPCs. -
MSGBOX_SIGN
- This type of msgbox doeslock
andrelease
for you. Used most often with signs. -
MSGBOX_YESNO
- This type of msgbox presents a Yes/No option once it's finished printing. (See also: theyesnobox
command.)
Here's an example of MSGBOX_NPC
:
Script_SimpleNPCScript::
msgbox Text_SimpleNPCScript, MSGBOX_NPC
end
Text_SimpleNPCScript:
.string "Hi!\n"
.string "My name is Adam.$"
Here's an example of MSGBOX_SIGN
:
Script_SignpostScript::
msgbox Text_SignpostScript, MSGBOX_SIGN
end
Text_SignpostScript:
.string "Pokémon Herb Shop\n"
.string "Bitter taste, better cure!$"
Here's an example for MSGBOX_YESNO
:
Script_SimpleYesNoScript::
lock
faceplayer
msgbox Text_SimpleYesNoScriptQuestion, MSGBOX_YESNO
compare VAR_RESULT, NO
goto_if_eq Script_PlayerAnsweredNo
msgbox Text_PlayerAnsweredYes, MSGBOX_DEFAULT
release
end
Script_PlayerAnsweredNo::
msgbox Text_PlayerAnsweredNo, MSGBOX_DEFAULT
release
end
Text_SimpleYesNoScriptQuestion:
.string "Dragon-type Pokémon are the best!\n"
.string "Wouldn't you agree?$"
Text_PlayerAnsweredYes:
.string "Yeah! Dragons rule!$"
Text_PlayerAnsweredNo:
.string "I guess you've never raised one.$"
A few more notes about MSGBOX_YESNO
:
- The
compare VAR_RESULT
line checks the result of the player's answer - 0 for "No", 1 for "Yes" (or you can use the constantsNO
andYES
, orFALSE
andTRUE
respectively). More about vars later! - The
goto_if_eq
line means the script will go to a new label if the result is equal to what we specified above, in this case, 0. If it's not equal to that, the script will continue as normal.
Think of a flag as an on or off switch - it can be either set ("ON") or cleared ("OFF"). They can be used to track progress, whether the player has completed a certain event or to control visibility of objects on a map.
Setting and clearing flags is nice and simple; to set a flag we use:
setflag [Flag]
To clear a flag we use:
clearflag [Flag]
To check if a flag is set, we do:
goto_if_set [Flag], [Script Label]
(PoryScript can use a common if
function, such as if (var(FLAG_NAME))
. PoryScript will convert such if statements and blocks into a goto_if_set
command for you.)
If you open include\constants\flags.h
, you'll find a list of all the in-game flags. Some flags have special uses, e.g. for the activation of the Running Shoes or certain menus. In this demo script, we'll set the flag that activates the Pokédex menu:
Script_CheckDexFlag::
lock
faceplayer
goto_if_set FLAG_SYS_POKEDEX_GET, Script_DexFlagSet
msgbox Text_DexFlagClear, MSGBOX_DEFAULT
setflag FLAG_SYS_POKEDEX_GET
release
end
Script_DexFlagSet::
msgbox Text_DexFlagSet, MSGBOX_DEFAULT
release
end
Text_DexFlagClear:
.string "Pokédex flag is cleared.$"
Text_DexFlagSet:
.string "Pokédex flag is set.$"
If the flag is set, the script jumps ahead to the Script_DexFlagSet
label and the string "Pokédex flag is set" will display. If the flag is cleared, the script continues, displays "Pokédex flag is cleared" and then sets the flag before the end.
The list of variables can be found in include\constants\vars.h
.
setvar [Variable], [Value]
As you could probably guess, the setvar
command sets the given variable to the given value. If you wanted to add to a variable's existing value, you'd use addvar:
addvar [Variable], [Value]
Let's say I wanted to increase the value of VAR_ASH_GATHER_COUNT
by 500 - I'd have addvar VAR_ASH_GATHER_COUNT, 500
. Similarly, if I wanted to subtract from a variable's existing value, I'd use subvar
, which is set up exactly the same:
subvar [Variable], [Value]
To check the value of a variable, we'd use the following:
compare [Variable], [Value]
goto_if_[Condition] [Script Label]
A list of the different conditions for goto_if
/call_if
:
-
goto_if_lt
= Less Than -
goto_if_eq
= Equal To -
goto_if_gt
= Greater Than -
goto_if_le
= Less Than or Equal To -
goto_if_ge
= Greater Than or Equal To
So far we've covered how to give an object a script that runs when you interact with it. If you're itching to go on that, you can skip to the Basic Commands section below and start playing around with scripting. When you want to do more advanced things, like make a cutscene start upon entering a map, continue onward.
Map scripts are scripts run when something happens in a given map. Every map has a map scripts header. Here's an example from the game:
MossdeepCity_StevensHouse_MapScripts::
map_script MAP_SCRIPT_ON_LOAD, MossdeepCity_StevensHouse_OnLoad
map_script MAP_SCRIPT_ON_TRANSITION, MossdeepCity_StevensHouse_OnTransition
map_script MAP_SCRIPT_ON_FRAME_TABLE, MossdeepCity_StevensHouse_OnFrame
.byte 0
MossdeepCity_StevensHouse_OnLoad:
call_if_unset FLAG_SYS_GAME_CLEAR, MossdeepCity_StevensHouse_EventScript_HideStevensNote
end
MossdeepCity_StevensHouse_EventScript_HideStevensNote::
setmetatile 6, 4, METATILE_GenericBuilding_TableEdge, TRUE
return
MossdeepCity_StevensHouse_OnTransition:
call_if_eq VAR_STEVENS_HOUSE_STATE, 2, MossdeepCity_StevensHouse_EventScript_SetStevenPos
end
MossdeepCity_StevensHouse_EventScript_SetStevenPos::
setobjectxyperm LOCALID_STEVEN, 6, 5
setobjectmovementtype LOCALID_STEVEN, MOVEMENT_TYPE_FACE_UP
return
MossdeepCity_StevensHouse_OnFrame:
map_script_2 VAR_STEVENS_HOUSE_STATE, 1, MossdeepCity_StevensHouse_EventScript_StevenGivesDive
.2byte 0
In PoryScript, there's a special kind of block called the mapscripts
block. If you converted the above to PoryScript, it would look something like this:
mapscripts MossdeepCity_StevensHouse_MapScripts {
MAP_SCRIPT_ON_LOAD: MossdeepCity_StevensHouse_OnLoad
MAP_SCRIPT_ON_TRANSITION {
if (var(VAR_STEVENS_HOUSE_STATE) == 2) {
setobjectxyperm (LOCALID_STEVEN, 6, 5)
setobjectmovementtype (LOCALID_STEVEN, MOVEMENT_TYPE_FACE_UP)
}
}
MAP_SCRIPT_ON_FRAME_TABLE [
VAR_STEVENS_HOUSE_STATE, 1: MossdeepCity_StevensHouse_EventScript_StevenGivesDive
]
}
script(local) MossdeepCity_StevensHouse_OnLoad {
if (!flag(FLAG_SYS_GAME_CLEAR)) {
setmetatile (6, 4, METATILE_GenericBuilding_TableEdge, TRUE)
}
}
The Map Scripts are a series of special scripts that are run at given times during a map's lifecycle. In vanilla, these events are:
-
ON_DIVE_WARP
- Run after the player chooses to dive/emerge when surfing. Not used often. -
ON_TRANSITION
- Run after a map transition has started. This means after a fade out or when walking between maps that are connected.- The game uses this event to set map-specific flags/vars, update object template attributes, and set the weather.
-
ON_LOAD
- Run after the game has loaded the map's layout into memory, before it is drawn to the screen.- Usually used to conditionally set metatiles. If you set metatiles outside of an
ON_LOAD
script, you will need to run the script commandspecial DrawWholeMapView
to redraw the map and see the metatile changes.
- Usually used to conditionally set metatiles. If you set metatiles outside of an
-
ON_RESUME
- Run after the end of a map load, and any time the map "resumes".- This includes things like exiting a full-screen menu, like the bag, or finishing a battle.
- This also includes when entering the map for the first time after a load.
- This is often used by the game to add or update objects, or to restart "per step callbacks".
-
ON_RETURN_TO_FIELD
- Run afterON_RESUME
, but only upon returning to the field from a battle or menu, and not when the map first loads. -
ON_WARP_INTO_MAP_TABLE
- A script table, evaluated after the map's objects are loaded, before the map is drawn and the screen fades in. -
ON_FRAME_TABLE
- A script table, evaluated every frame while the player is able to walk around a map.- This is where you start your cutscenes upon entering a new map.
Most of the above events take one script label, a script which is run when the event takes place. The two script tables, however, are a little different. The following is an example of a script table:
MossdeepCity_StevensHouse_OnFrame:
map_script_2 VAR_TEMP_A, 0, EventScript_RunCutscene1
map_script_2 VAR_TEMP_A, 5, EventScript_RunCutscene2
map_script_2 VAR_TEMP_B, 8, EventScript_RunCutscene3
.2byte 0
When the game evaluates a script table, it checks the variable specified to the value specified, and if they match, it runs the script at the end of the same line and stops evaluating the table. If it gets through the whole table without any of the conditions matching, nothing more happens and the game continues normally.
Note: The script tables can only check variables in the vanilla game. You cannot check a flag this way.
IMPORTANT: All map script, with the exception of scripts in the ON_FRAME_TABLE
, must be able to run in a single frame. The map scripts run in a special separate script context for this purpose, and if your script calls on any commands that take multiple frames to evaluate (such as lock
, delay
, or any of the wait*
commands), your game will softlock on a black screen.
You'll find quickly once scripting that if you attempt to put a trigger to run a script down on the same tile the player starts on in a map, the script will not run. That's because triggers only run when the player commands their avatar to step on the trigger. If the player ends up on a trigger thanks to warp movement or applymovement
(see below), the trigger will not trigger.
If you wish to run a cutscene as soon as the map loads, then you'll need to put your script into the ON_FRAME_TABLE
. As soon as the player would gain control in a map, if the variable matches the value, the game will run the script instead, and you can do a cutscene here.
MossdeepCity_StevensHouse_MapScripts::
map_script MAP_SCRIPT_ON_FRAME_TABLE, MossdeepCity_StevensHouse_OnFrame
.byte 0
MossdeepCity_StevensHouse_OnFrame:
map_script_2 VAR_TEMP_A, 0, EventScript_RunCutscene1
.2byte 0
EventScript_RunCutscene1:
lock
@ Insert cutscene here
setvar VAR_TEMP_A, 1
release
end
Remember in your cutscene script to set the variable you check to run the script to something else before the script ends, or the cutscene will just start over again from the top, forever. Also note that VAR_TEMP_*
variables are temporary and are reset upon a map change. Which means in the example above, my Cutscene1 would run every time I walk into the map.
In the remaining sections, we're going to go over several common commands and what you can do with them. If you want a list of all commands, you can look in asm/macros/event.inc
, which defines all the commands that can be used in the scripts. All of the commands have an explanation of what they do above them.
Let's start with bufferleadmonspeciesname
, which displays the species of your player's first party member - just like it says on the tin!
bufferleadmonspeciesname [STR_VAR]
The argument is where you choose the string variable to assign the lead Pokémon's name to. There are three string vars: STR_VAR_1
, STR_VAR_2
, and STR_VAR_3
, and they can be referenced as placeholders. (Note: in older versions of pokeemerald, you had to reference the string vars by array index, ie STR_VAR_1 = 0, STR_VAR_2 = 1, etc.)
Script_LeadPartyMemberIsCute::
bufferleadmonspeciesname STR_VAR_1
msgbox Text_LeadPartyMemberIsCute, MSGBOX_NPC
end
Text_LeadPartyMemberIsCute:
.string "Aww, your {STR_VAR_1} is so cute!$"
Note: The bufferleadmonspeciesname
command has to come before the msgbox that uses it, otherwise the game will display whatever was in the string variable before. This is true for all buffer commands.
bufferspeciesname [STR_VAR], [SPECIES_*]
The bufferspeciesname
is similar to before, only you're adding a comma and the name of the species you want to display. Let's combine both bufferleadmonspeciesname
and bufferspeciesname
in a script:
Script_CombiningBufferCommands::
bufferleadmonspeciesname 0
bufferspeciesname 1, SPECIES_TOTODILE
msgbox Text_CombiningBufferCommands, MSGBOX_NPC
end
Text_CombiningBufferCommands:
.string "Your {STR_VAR_1} is cool, but I\n"
.string "bet my {STR_VAR_2} is stronger!$"
All we've done here is store the player's lead Pokémon in {STR_VAR_1}
and Totodile's name in {STR_VAR_2}
. Should you need them, the list of defines for Pokémon is in include\constants\species.h
.
There are many other commands which can buffer strings into string variables, all named pretty straightforwardly (but as always, you can look in asm/macros/event.inc
for more info):
bufferpartymonnick [STR_VAR], [PARTY SLOT]
bufferitemname [STR_VAR], [ITEM_ID]
bufferitemnameplural [STR_VAR], [ITEM_ID], [Quantity]
buffermovename [STR_VAR], [MOVE_ID]
buffernumberstring [STR_VAR], [Number]
bufferstring [STR_VAR], [Text Label]
buffertrainerclassname [STR_VAR], [TRAINER_ID]
buffertrainername [STR_VAR], [TRAINER_ID]
As the title suggests, we'll be looking at how to make conditional messages appear depending on player gender.
checkplayergender
compare VAR_RESULT, [Gender]
goto_if_eq [Script Label]
A more fleshed out example:
Script_GenderDependencyExample::
checkplayergender
compare VAR_RESULT, MALE
goto_if_eq Script_MaleExample
msgbox Text_FemaleExample, MSGBOX_NPC
end
Script_MaleExample::
msgbox Text_MaleExample, MSGBOX_NPC
end
Text_FemaleExample:
.string "You are playing as a girl.$"
Text_MaleExample:
.string "You are playing as a boy.$"
You could also have the opposite, of course:
Script_GenderDependencyExample::
checkplayergender
compare VAR_RESULT, FEMALE
goto_if_eq Script_FemaleExample
msgbox Text_MaleExample, MSGBOX_NPC
end
Script_FemaleExample::
msgbox Text_FemaleExample, MSGBOX_NPC
end
Text_MaleExample:
.string "You are playing as a boy.$"
Text_FemaleExample:
.string "You are playing as a girl.$"
Do you need to make a NPC or other overworld sprite appear or disappear? Let's look at the following commands:
addobject [Local ID]
removeobject [Local ID]
showobjectat [Local ID], [Map]
hideobjectat [Local ID], [Map]
Each of these commands takes a "Local ID", which is the id of the object in the map. It can be found in Porymap's events tab, here:
First, to make an object disappear, you can use removeobject
, which will despawn the object from the map and set its event flag (if it has one set). Note: If an object doesn't have an event flag set on it, it will reappear when the camera next moves (usually because the player moved).
To make an object invisible, you can use hideobjectat
, which will make the object invisible but still have collision and still able to be interacted with. You can use hideobjectat OBJ_EVENT_ID_PLAYER, 0
to hide the player (it doesn't matter what you pass as a map, so 0 is fine).
To show an object that is currently invisible, use showobjectat
to make it visible. To show an object that is currently not spawned (such as if the object has its event flag set or if it is off-screen), use addobject
to spawn it.
If you have an event flag on your object, you will want to set or clear the flag when you add or remove the object:
removeobject [Local ID]
setflag [Flag]
clearflag [Flag]
addobject [Local ID]
Before you think about the multitude of free items you can give your players, you should always make sure they can actually take the item first! There are two ways of doing this.
checkitemspace [Item], [Quantity=1]
compare VAR_RESULT, FALSE
goto_if_eq [Script Label]
This command checks if the player has enough room in their bag for the amount of the desired item. If there's no room in the bag, the last line redirects to a new script snippet. Let's say we wanted to check if the player has room in their bag for five Poké Balls.
checkitemspace ITEM_POKE_BALL, 5
compare VAR_RESULT, FALSE
goto_if_eq Script_NotEnoughSpaceInBag
Not using this prior to giving items can cause the player to "lose" them if they've got no space in their bag to take it. Now, onto actually giving the items!
giveitem [Item], [Quantity=1]
All you need is the above line, super simple. It even automatically generates a standard notification (with fanfare) that the player has obtained an item! Now, remember when I was saying that there are two ways of checking if the player has room in their bag for the item(s) you want to give them? You can actually kill two birds with one stone:
giveitem ITEM_POKE_BALL, 5
compare VAR_RESULT, FALSE
goto_if_eq Script_BagIsFull
We're trying to give the player five Poké Balls, but if they haven't got enough space for them, the script will jump to the label Script_BagIsFull
.
To remove an item:
removeitem [Item], [Quantity=1]
Checking whether or not the player has an item in their bag is done with the checkitem
command:
checkitem [Item], [Quantity=1]
compare VAR_RESULT, 1
goto_if_eq [Script Label]
Let's see a proper example script. It will check if the player has any Potions, and if they don't, it will give them one.
Script_ItemDemonstration::
lock
faceplayer
msgbox Text_ItemGiveaway, MSGBOX_DEFAULT
checkitem ITEM_POTION
compare VAR_RESULT, TRUE
goto_if_ge Script_HasPotionsAlready
msgbox Text_GivingPotion, MSGBOX_DEFAULT
giveitem ITEM_POTION
compare VAR_RESULT, 0
goto_if_eq Script_BagIsFull
release
end
Script_HasPotionsAlready::
msgbox Text_HasPotionsAlready, MSGBOX_DEFAULT
release
end
Script_BagIsFull::
msgbox Text_BagIsFull, MSGBOX_DEFAULT
release
end
Text_ItemGiveaway:
.string "Our POKéMART is running a\n"
.string "POTION giveaway today.$"
Text_GivingPotion:
.string "Here you go!$"
Text_HasPotionsAlready:
.string "You already have a POTION.\n"
.string "Don't be greedy!$"
Text_BagIsFull:
.string "Your BAG is full.$"
I used goto_if_ge
instead of goto_if_eq
when checking for the Potions because this time, we're checking not that the player has exactly one - we're checking that they have more than or exactly one before we go to our label Script_HasPotionsAlready
. The ge stands for greater than or equal to. If the player hasn't got enough room in their bag to take the Potion, the script will go to our label Script_BagIsFull
. Should the player have the bag space and no Potions, the script will carry on and give them the Potion.
The items list is in “include\constants\items.h”.
Note: There is another command additem
, which will attempt to add an item to the player's bag without notifying the player, and will fail silently if there's not enough room. This command is used in places like the New Game script, where messages cannot be shown.
Finally: All of these commands take "[Quantity=1]", meaning that you don't have to supply a number of the item to give, and it will default to one if you do not.
Wondering how to give your player a gift Pokémon or Egg? In order to do this effectively, we'll need to make use of two commands.
The first is getpartysize
:
getpartysize
compare VAR_RESULT, [Number]
goto_if_eq [Script Label]
The compare VAR_RESULT
line checks if there are the specified number of Pokémon in your player's party. For example, compare VAR_RESULT, 6
would check if there were six. If you've been reading the other sections of this tutorial, you'll have caught on that the goto_if_eq
line means that if the player has the specified number of Pokémon in their party, it'll redirect to a new script label. Why do we do this? If the player has a full party already (6), they won't be able to fit the "gift" Pokémon in their party; it'll just go straight to their PC storage. Either way, it's up to you how you use this - if you want to give a Pokémon while your player has six Pokémon in their party already, you could just have your new label contain a message that notifies the player that the gift Pokémon will be sent to their PC. If you don't want the gift Pokémon to be given unless the player has room for it in their party, you can display a message such as "Oh, you don't have room in your party for this Pokémon."
Anyway, onto the command for giving the Pokémon:
givemon [Pokémon], [Level], [Held Item=ITEM_NONE]
Open include\constants\species.h
for Pokémon species and include\constants\items.h
for a list of items. If you don't want the Pokémon you're giving to be holding an item, just omit the argument or use ITEM_NONE
. Here's an example of how to use both getpartysize
and givemon
:
Script_GivePokemonDemo::
lock
faceplayer
msgbox Text_TakeDratini, MSGBOX_DEFAULT
getpartysize
compare VAR_RESULT, 6
goto_if_eq Script_PlayerHasFullParty
givemon SPECIES_DRATINI, 5, ITEM_DRAGON_FANG
playfanfare MUS_OBTAIN_ITEM
msgbox Text_ReceivedDratini, MSGBOX_DEFAULT
waitfanfare
release
end
Script_PlayerHasFullParty::
msgbox Text_PlayerHasFullParty, MSGBOX_DEFAULT
release
end
Text_TakeDratini:
.string "Please, take this Dratini.$"
Text_ReceivedDratini:
.string "{PLAYER} received a Dratini!$"
Text_PlayerHasFullParty:
.string "Ah, your party is full.$"
With the getpartysize
command, we've checked if the player already has a full party, and if they do, the script will jump to the label Script_PlayerHasFullParty
and the Pokémon won't be given. In the case that the player's party is not full, it'll give a level 5 Dratini holding a Dragon Fang. If you know what you're doing, you'll notice that this script will keep giving infinite Dratini if the player's party isn't full due to the fact that a flag isn't set. I figured that if people have read the flags section, they should be able to figure out how to rectify that on their own ;)
To give an Pokémon Egg instead of a Pokémon:
giveegg [Pokémon]
Example:
Script_GiveEggDemo::
lock
faceplayer
msgbox Text_EggQuestion, MSGBOX_YESNO
compare VAR_RESULT, 0
goto_if_eq Script_PlayerAnsweredNo
getpartysize
compare VAR_RESULT, 6
goto_if_eq Script_PlayerHasFullParty
giveegg SPECIES_LARVITAR
playfanfare MUS_OBTAIN_ITEM
msgbox Text_ReceivedEgg, MSGBOX_DEFAULT
waitfanfare
release
end
Script_PlayerHasFullParty::
msgbox Text_PlayerHasFullParty, MSGBOX_DEFAULT
release
end
Script_PlayerAnsweredNo::
msgbox Text_PlayerAnsweredNo, MSGBOX_DEFAULT
release
end
Text_EggQuestion:
.string "Will you take this Egg?$"
Text_PlayerAnsweredNo:
.string "Oh, that's too bad.$"
Text_ReceivedEgg:
.string "{PLAYER} received the Egg!$"
Text_PlayerHasFullParty:
.string "Your party is full.\n"
.string "There's no room for this Egg.$"
This script asks the player whether or not they would like to accept the Egg. After that, if the player responds with "YES", it'll check if they have space in their party before giving it to them.
Adding an item to the player's PC is just as easy with a single line:
addpcitem [Item], [Quantity=1]
Useful in cases where a player's bag might be full, but you want them to receive an item anyway. The items list is in include\constants\items.h
. In order to check if a player has a particular item in their PC, we'd do:
checkpcitem [Item], [Quantity=1]
compare VAR_RESULT, TRUE
goto_if_[Condition] [Script Label]
A more fleshed-out demonstration:
checkpcitem ITEM_POTION, 1
compare VAR_RESULT, TRUE
goto_if_ge Script_HasPotionInPC
Should the player have one or more Potions in their PC's item storage, the script will go to the label Script_HasPotionInPC
.
To make the player / NPCs move in a script, we use the applymovement
command:
applymovement [Local ID], [Movements Label]
waitmovement [Local ID=0]
The applymovement
command starts an object moving along a path specified by a movement script with the given label. The waitmovement
line ensures that the movements are finished before the script continues. waitmovement
can take an object's id as a parameter, which will make it wait for that specific object to finish its movement, or it can take 0 (the default if it isn't supplied) to wait for the last object that applymovement
was used on.
Let's say we wanted the player to move three steps upwards along with the NPC they're talking to:
Script_MovementsDemonstration::
lock
faceplayer
msgbox Text_MovementsDemonstration, MSGBOX_DEFAULT
applymovement 1, Movement_Example
applymovement OBJ_EVENT_ID_PLAYER, Movement_Example
waitmovement
release
end
Text_MovementsDemonstration:
.string "Hey!\n"
.string "Let's walk three steps up.$"
Movement_Example:
walk_up
walk_up
walk_up
step_end
OBJ_EVENT_ID_PLAYER
is used as the player's local ID. I've used 1 for my NPC's ID just for example purposes.
Note: If you find that your NPC isn't following your movement, make sure you have a waitmovement
call before release
; release
cancels any movement, and if the script doesn't wait, the movement won't get a chance to happen before the script finishes!
Note: If an object has a movement script still running on it, a second applymovement
on it will not take. Make sure you waitmovement
before applying another movement to the same object!
A list of movement types can currently be seen in asm\macros\movement.inc
. Don't forget to add step_end
to indicate that your movements are finished!
The random
command can be used to generate a random number within the range you set, and then have the script go to various labels depending on the number. Let's say we wanted a range of 5:
random 5
switch VAR_RESULT
case 0, [Script Label 1]
case 1, [Script Label 2]
case 2, [Script Label 3]
case 3, [Script Label 4]
case 4, [Script Label 5]
The switch
/case
commands are a more concise way to do a series of compare
/goto_if_eq
statements.
Let's look at an example - a NPC who has three different things to say:
Script_RandomDemonstration::
lock
faceplayer
random 3
switch VAR_RESULT
case 0, Script_RandomOption1
case 1, Script_RandomOption2
case 2, Script_RandomOption3
end
Script_RandomOption1::
msgbox Text_RandomOption1, MSGBOX_DEFAULT
release
end
Script_RandomOption2::
msgbox Text_RandomOption2, MSGBOX_DEFAULT
release
end
Script_RandomOption3::
msgbox Text_RandomOption3, MSGBOX_DEFAULT
release
end
Text_RandomOption1:
.string "Option 1.$"
Text_RandomOption2:
.string "Option 2.$"
Text_RandomOption3:
.string "Option 3.$"
There are a whole bunch of things you could use this for! I like using it for my generic city/town population NPCs to make them a little bit more interesting, but you can use it for a lot more than that.
Creating your own shops is straightforward enough with the pokemart command.
pokemart [Mart Label]
All you have to do is choose what you'd like your shop to sell! The list of items can be seen in “include\constants\items.h”. Once you've decided on the items you want your mart to stock, just follow the example below. Let's say we wanted to make a herb shop, we'd have:
.align 2
Pokemart_HerbShop:
.2byte ITEM_ENERGY_POWDER
.2byte ITEM_ENERGY_ROOT
.2byte ITEM_HEAL_POWDER
.2byte ITEM_REVIVAL_HERB
.2byte ITEM_NONE
Make sure you remember to add .2byte ITEM_NONE
indicate the end of the list of goods. (Note: Vanilla marts have release
and end
at the bottom of the item list, but this is not necessary.) A full length example:
Script_PokemartExample::
lock
faceplayer
message Text_HerbShopGreeting
waitmessage
pokemart Pokemart_HerbShop
msgbox Text_HerbShopEnd, MSGBOX_DEFAULT
release
end
.align 2
Pokemart_HerbShop:
.2byte ITEM_ENERGY_POWDER
.2byte ITEM_ENERGY_ROOT
.2byte ITEM_HEAL_POWDER
.2byte ITEM_REVIVAL_HERB
.2byte ITEM_NONE
Text_HerbShopGreeting:
.string "Welcome to our Herb Shop.\n"
.string "How can I help you?$"
Text_HerbShopEnd:
.string "Please come again!$"
To manipulate weather in the overworld, use the following commands:
setweather [Weather Type]
doweather
We use setweather
to choose the weather we'd like and doweather
to activate it. For instance, here's the kind of script we'd need to start a storm:
Script_ExampleWeatherScript::
lock
faceplayer
msgbox Text_ExampleWeatherScriptPreRain, MSGBOX_DEFAULT
setweather WEATHER_RAIN_HEAVY
doweather
msgbox Text_ExampleWeatherScriptPostRain, MSGBOX_DEFAULT
release
end
Text_ExampleWeatherScriptPreRain:
.string "What a lovely day!$"
Text_ExampleWeatherScriptPostRain:
.string "Aww, I spoke too soon.$"
You can also use resetweather
(no argument) instead of setweather
to reset the weather to whatever is the default on the current map.
Note: setweather
is a separate command from doweather
, because doweather
should not be used in transition scripts (more on that later); just using setweather
is enough in the map loading scripts, and the normal map loading process with do the weather for you.
The weather list can be found in include\constants\weather.h
.
If you need a script that involves having the player or NPCs enter/exit a building, you'll need to know how to manipulate the door animations.
opendoor [X], [Y]
waitdooranim
The first line, opendoor
, is where we specify that we want the door to use its opening animation and its location on the map. This line should always be followed by waitdooranim
, which waits for the door's animation to complete. The X/Y coordinates refer to the position of your door tile in your map. You'll need to make sure this is a proper door tile (that is, is an MB_ANIMATED_DOOR
tile with the appropriate animation when interacted with normally).
To close the door again:
closedoor [X], [Y]
waitdooranim
There are two additional commands:
setdooropen [X], [Y]
setdoorclosed [X], [Y]
These do what you might expect, sets the door at the given coordinates to be open or closed without the animation.
You might not use it too often throughout your hack, but showing a Pokémon's sprite can add a little something extra to scripts where the Pokémon is being discussed.
showmonpic [Pokémon], [X], [Y]
So you need to specify the sprite you want to display and the X/Y coordinates of the screen where you want the box containing the sprite to be drawn. The Pokémon species list is in include\constants\species.h
if you need it. And to hide the box again:
hidemonpic
A full script example:
Script_DisplaySpriteDemo::
lock
faceplayer
showmonpic SPECIES_DRATINI, 10, 3
msgbox Text_DisplaySpriteDemo, MSGBOX_DEFAULT
hidemonpic
release
end
Text_DisplaySpriteDemo:
.string "Have you seen this Pokémon?$"
Coordinates of 10, 3
will display the box containing the specified sprite in the middle of the screen.
It's easy to give the overworld a "fading out" effect - useful for cutscenes and such - by using the fadescreen command which looks like this:
fadescreen [Fade Type]
Fade types include FADE_FROM_BLACK
, FADE_TO_BLACK
, FADE_FROM_WHITE
, and FADE_TO_WHITE
. Here's an example:
Script_FadescreenExample::
msgbox Text_FadescreenExample, MSGBOX_DEFAULT
fadescreen FADE_TO_BLACK
fadescreen FADE_FROM_BLACK
end
Text_FadescreenExample:
.string "Screen fading to black.$"
It's also possible to create a white version of the same effect:
Script_FadescreenExample::
msgbox Text_FadescreenExample, MSGBOX_DEFAULT
fadescreen FADE_TO_WHITE
fadescreen FADE_FROM_WHITE
end
Text_FadescreenExample:
.string "Screen fading to white.$"
Using Pokémon cries as a sound effect is another super easy thing to do, all you need is the playmoncry command:
playmoncry [Pokémon], 0
Followed by waitmoncry
, which doesn't have any arguments. Just as it looks, it simply means the script will continue after the cry has finished playing.
Script_CryDemo::
lock
faceplayer
playmoncry SPECIES_ARBOK, 0
msgbox Text_CryDemo, MSGBOX_DEFAULT
waitmoncry
release
end
Text_CryDemo:
.string "Arbok: Hisssss!$"
The gettime
command can be used to set the values of variables VAR_0x8000
, VAR_0x8001
, and VAR_0x8002
to the in-game hour, minute, and second respectively. To make sure the time is up to date, you should also call dotimebasedevents
before that. This is an example script for a NPC who gives different greetings depending on the hour:
Script_HourDemonstration::
lock
faceplayer
dotimebasedevents
gettime
compare VAR_0x8000, 4
goto_if_le Script_Night
compare VAR_0x8000, 12
goto_if_lt Script_Morning
compare VAR_0x8000, 18
goto_if_ge Script_Night
msgbox Text_Day, MSGBOX_DEFAULT
release
end
Script_Morning::
msgbox Text_Morning, MSGBOX_DEFAULT
release
end
Script_Night::
msgbox Text_Night, MSGBOX_DEFAULT
release
end
Text_Morning:
.string "Good morning.$"
Text_Day:
.string "Good day.$"
Text_Night:
.string "Good evening.$"
You could swap out the values to check for a particular minute or second too, if you wanted to run a very specific event that only occurs at a very specific time!
Sound effects can bring a lot of life to overworld gameplay. To play sounds, use the following:
playse [Label]
If you want to make the script wait for the sound to finish before continuing, you can add waitse
if you want to. For example, if you'd have liked to use a sound effect for an exclamation point emote on your player:
playse SE_PIN
applymovement OBJ_EVENT_ID_PLAYER, Movement_Exclamation
waitmovement
A fanfare is a short jingle, like the healing sound in Pokémon Centers. To use one in your script, use these commands:
playfanfare [Label]
waitfanfare
waitfanfare
means the script will wait for the jingle to finish playing before continuing. So, let's say we wanted to play the fanfare sound for obtaining a Gym Badge:
playfanfare MUS_OBTAIN_BADGE
waitfanfare
You can find a list of labels for sound effects and music in include\constants\songs.h
.
If you wanna do something more advanced with your message boxes, then it's time to get into some more advanced commands and things.
Firstly, the msgbox
command (using MSGBOX_DEFAULT
) is actually a convenience command that does the following:
message [Label]
waitmessage
waitbuttonpress
- The
message
command is what actually starts showing the message box to the player. - The
waitmessage
command pauses script execution until the message is finished printing.- Note: The message is not "finished printing" until it reaches a
$
. If the message is waiting for the user to press a button when it hits a\p
or\l
, it is still waiting for the message to "finish printing".
- Note: The message is not "finished printing" until it reaches a
- The
waitbuttonpress
command does exactly what it says: waits for the player to press A or B.- This is here because as soon as the message finishes printing,
waitmessage
ends, which means the player may not have time to read that last part of the message before the script continues if you don't wait for an additional button press.
- This is here because as soon as the message finishes printing,
Another command we can use is the closemessage
command (no arguments), which simply closes the message box without needing to use release
.
If you look at the speech Professor Birch gives at the start of the game, you'll notice the first advanced trick for messages: Using \p
followed by $
. This is the trick that Professor Birch's speech uses at the beginning of the game to do things while text is printing.
Because \p
displays a downward arrow as it's waiting for the player, and waitmessage
continues to wait even when the downward arrow is flashing. Using this, you can make it look like a message box "continues" on without any break by using \p$
at the end of the message. I'll show you what I mean below.
We've already talked a bit about placeholders in text: {PLAYER}
is a placeholder for the player's name, and {STR_VAR_1}
, {STR_VAR_2}
, and {STR_VAR_3}
are placeholders for whatever we want to buffer into the variables.
But there are many more placeholders we can use, and we can create our own placeholders as well (but that's beyond the scope of this tutorial).
What we're interested in for this section is the commands we can put inside placeholders:
-
{COLOR [COLOR]}
can change the color of our text. -
{HIGHLIGHT [COLOR]}
changes the color of the background of our text. -
{SHADOW [COLOR]}
changes the drop shadow of the text.- [COLOR]s are anything in the textbox palette, which includes
RED
,GREEN
,BLUE
,WHITE
, andLIGHT_RED
,LIGHT_BLUE
,LIGHT_GREEN
,LIGHT_GRAY
, andDARK_GRAY
.
- [COLOR]s are anything in the textbox palette, which includes
-
{PAUSE [TIME]}
pauses the text for [TIME] number of frames. So{PAUSE 60}
pauses the text for one second.- You can only put up to 255 in [TIME], but you can put multiple pauses one after the other.
-
{CLEAR_TO [NUM]}
makes the text printer print whitespace until it gets to a given x-offset. This is used most often in menus to print text to the right. -
{FONT [FONT]}
changes the font of the printed text- Fonts are one of
FONT_SMALL
,FONT_NORMAL
,FONT_SHORT
,FONT_NARROW
, orFONT_SMALL_NARROW
. - Note: Despite
{RESET_FONT}
being a valid placeholder command, it doesn't do anything in vanilla. However, we have a tutorial on how to implement it here.
- Fonts are one of
-
{PLAY_SE [SOUND]}
is equivalent to using theplayse
command mid-textbox. -
{WAIT_SE}
is equivalent to using thewaitse
command mid-textbox. -
{PLAY_BGM [MUSIC]}
is similar to using theplaybgm
-
{JPN}
switches to printing Japanese text, and{ENG}
switches back to English text.
See charmap.txt
for a list of everything you can put into in-game strings. Ones prepended with B_
are for in-battle only.
Now that we're using the message
and waitmessage
commands separately, nothing is stopping us from putting some things in-between them if we wish. Remember that message
starts printing the text, and applymovement
starts the object's movement script.
So, with all of the above, we can do something like the following:
EventScript_ScienceMan::
lock
faceplayer
message Text_CanUsePCToStoreItems1
waitmessage
message Text_CanUsePCToStoreItems2
applymovement VAR_LAST_TALKED, Movement_CanUsePCToStoreItems1
waitmessage
waitbuttonpress
waitmovement VAR_LAST_TALKED
closemessage
delay 8
applymovement VAR_LAST_TALKED, Movement_CanUsePCToStoreItems2
waitmovement VAR_LAST_TALKED
release
end
Text_CanUsePCToStoreItems1:
.string "Did you know?{PAUSE 30} You can use a PC!{PAUSE 30}\n"
.string "To store {COLOR RED}ITEMS?!{COLOR DARK_GRAY}\l"
.string "{SHADOW DARK_GRAY}Items!{SHADOW LIGHT_GRAY} "
.string "{HIGHLIGHT LIGHT_BLUE}ITEMS{HIGHLIGHT WHITE}, mind you!!\p"
.string "$"
Text_CanUsePCToStoreItems2:
.string "The power of science is staggering!\p"
.string "{CLEAR_TO 80}STAGGERING!!$"
Movement_CanUsePCToStoreItems1:
jump_in_place_up_down
delay_16
emote_exclamation_mark
delay_16
step_end
Movement_CanUsePCToStoreItems2:
face_down
delay_8
face_left
delay_8
face_up
delay_8
face_right
delay_8
face_player
step_end