Skip to content

The Basics of Scripting

tustin2121 edited this page Apr 10, 2023 · 8 revisions

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:

Foundation

Labelling

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.

image

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.

PoryScript

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.

Simple Text Scripts

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.
  • 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 running applymovements (see below), and closes the message box.
    • If you used lockall instead, you'll have to use releaseall.
    • If a script ends and the player can move around while a message box is on-screen, you probably forgot to release or releaseall.
  • 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 does lock, faceplayer, and release for you. Used most often with NPCs.
  • MSGBOX_SIGN - This type of msgbox does lock and release 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: the yesnobox 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 constants NO and YES, or FALSE and TRUE 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.

Flags

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.

Variables

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

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 command special DrawWholeMapView to redraw the map and see the metatile changes.
  • 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 after ON_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.

Cutscenes on Map Load

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.

Basic Commands

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.

Buffers

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]

Player Gender Check

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.$"

Hide/Show Map Objects

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:

image

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]

Give, Take, & Check For Items

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.

Giving Pokémon

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.

Give & Check For PC Items

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.

Movements

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!

Random

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.

Pokémarts

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!$"

Weather

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.

Door Commands

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.

Display Pokémon Sprite

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.

Screen Fading Effects

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.$"

Play Pokémon Cry

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!$"

Time-based Scripts

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 & Fanfares

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.

Advanced Messages

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".
  • 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.

Another command we can use is the closemessage command (no arguments), which simply closes the message box without needing to use release.

Message box "continuation"

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.

Placeholders

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, and LIGHT_RED, LIGHT_BLUE, LIGHT_GREEN, LIGHT_GRAY, and DARK_GRAY.
  • {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
  • {PLAY_SE [SOUND]} is equivalent to using the playse command mid-textbox.
  • {WAIT_SE} is equivalent to using the waitse command mid-textbox.
  • {PLAY_BGM [MUSIC]} is similar to using the playbgm
  • {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.

Separating the message from the wait

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
Clone this wiki locally