ExplorerScript language specification

This document contains the language specification for ExplorerScript. ExplorerScript is a scripting language that can be converted into the SSB script files of Pokémon Mystery Dungeon Explorers of Sky. The language is more flexible though, it can in it’s core also be used for other games or purposes.

The lexer and parser specifications can be found in the explorerscript.antlr package as an Antlr 4 grammar.

EoS Compiler

These blocks are used in the document to document things specific to the decompiler / compiler that are contained in this repository, they are specific to Explorers of Sky.

SkyTemple

These blocks contain notes on how the components of the SkyTemple project implement certain things.

We recommend any tool that works with EoS to try to be comaptible with the SkyTemple implementation.

Routines

Every script file contains a collection of routines (and/or macros).

A routine contains a set of statements.

Coroutine

A coroutine is a named routine and is meant to be used as a common routine used by other scripts.

The behaviour for having coroutines and other routines in the same file is currently undefined.

coro NAME {
    /** Statements **/
}

SkyTemple

A script file that contains coroutines can not contain any other routines. It must contain all of the coroutines that are specified in the game’s list of coroutines. The only compiled script file that usually contains coroutines is SCRIPT/COMMON/unionall.ssb. Operations exist to call coroutines.

Generic Routine

A generic routine, that has an ID. If a script file contains routines their index must start at 0 and increment by one, there must not be a gap.

def 1234 {
    /** Statements **/
}

Targeted Routine

A routine for an actor (for actor), an object (for object) or a performer (for performer) and an ID.

As identifiers for the actors, objects and performers any constant or integer can be used.

def 1234 for actor ACTOR_PLAYER {
    /** Statements **/
}

Aliases

Instead of containing statements, a routine can contain the keyword alias previous;. It will then use the same operations as the routine before it (by index number). In case of coroutines the assembler decides what the routine before it is, based on an index of names.

def 0 {
    /** Statements **/
}

def 1 {
    alias previous;
    /** Will use statements from def 0 **/
}

Data Types

ExplorerScript defines different data types. These are described below.

Integer

A whole number. The size of the integer is not restricted by the language. Integers can be represented in different bases.

12      // base 10 (decimal)
-12     // negative base 10
0x12    // base 16 (hexadecimal)
0o7     // base 8 (octal)
0b110   // base 2 (binary)

SkyTemple

All integers are 15-bit signed integers.

Fixed Point Value

A non-integer number with decimal places. The size of the number is not restricted by the language. Currently all fixed point values need to be written in base 10.

The language does not restrict the compiler to use actual fixed point values, the compiler may also store them as floats, etc. Likewise the compiler does not guarantee any promises about the precision of the compiled values. The compiler may error if a value can not be represented.

.12
1.12
-.12
-1.12

SkyTemple

Valid value ranges are ~-64.0 - 63.996. Decimal values are rounded to the nearest 1/256 increment. This means the lowest values for decimal places is 0, the highest is ~.996 and precision for increments is ~.0039.

System Constants

A system constant is a stand-in for an integer. Their names are usually in uppercase.

SkyTemple

Variables that are used by the debugger are also actually system constants that represent a number! For example $SCENARIO_MAIN is actually just a constant containing the number of a variable. This is because the conditional and assigment statements don’t actually work with variables, they require the ID of variables instead.

Please see the note and example on “Conditional check” for “if-Blocks”.

For a list of common constant prefixes, see the document on the CLI interface.

(Constant) Strings

A simple string literal that has no translations available.

It may be a single line string, enclosed in single- or double quotes.

// Single quotes string:
'Hello World'
// Double quotes string:
"Hello World"

A single line string may contain \n to insert a newline in the resulting text.

A string literal may also be a multi line string, which is indicated by three quote characters (''' or """).

"""
This is a multiline string.
It can span multiple lines.
"""

New lines in a multi line string are kept and indentations (whitespace characters before the first non-whitespace character in a line) are stripped in a way that makes writing these strings convenient:

  • The indentation in the first line of the string literal (after the starting three quote characters) is preserved.

  • The indentation in the last line of the string literal is fully removed if it only consists of whitespace characters.

  • For all other lines, the least indentation among all of these lines is calculated and then the lines are dedented by that amount. If the last line does not fully consist of whitespace characters, this also applies to it.

If the first or last line would be empty after applying the rules above, they are removed from the resulting string.

Given this single line string literal…:

"First Line\nSecond Line\n  Some indentation in the third line\nFourth Line"

… all of these multi line string literals would be equivalent to it:

'''First Line
  Second Line
    Some indentation in the third line
  Fourth Line
              '''

"""
  First Line
  Second Line
    Some indentation in the third line
  Fourth Line"""

\n in multiline strings are kept as is, they are not converted to new lines.

Language Strings

A collection of strings for different languages. It must have at least one string defined. A language string’s value can be a single line or a multi line string literal, see above.

{
    languageA='String for lang A',
    languageB="String for lang B",
    languageC="""
      String for lang C
      on multiple lines
    """
}

SkyTemple

What languages are required depends on the ROM. Either only one of the languages supported must be specified, or all of them. If only one is specified, it is also used for the other languages.

Position Marks

Position marks mark a place on the map. They have a name (string literal) and two position attributes for X and Y position. In addition these attributes can have the suffix ‘.5’ to place the position marks between two coordinates.

Position<'Name', 20, 20.5>

SkyTemple

In SSB files Position marks are actually 4 parameters:

  • X Offset: 2 or 4 if the x position ends on ‘.5’, otherwise 0.

  • Y Offset: 2 or 4 if the x position ends on ‘.5’, otherwise 0.

  • X Position: The x position, with the decimal place stripped.

  • Y Position: The y position, with the decimal place stripped.

The coordinates are in tiles.

Statements and Blocks

Labels

A label marks a statement that can be jumped to. Using the jump control statement you can make the execution jump to these statements. A statement can be marked with multiple labels.

@hello_label;
operation();

@another_label;
@and_another_one;
another_operation();

SkyTemple

§label; can be used instead of @label; for backwards compatibility. You should use @label; in new scripts.

Control statements

Control statements have special meaning to the control flow of a script.

Warning

The specfication of end, hold and return are based on the EoS SSB operations of the same name. The descriptions are speculations based on observed behaviour.

end

Ends the script execution. Get’s compiled as an instruction.

EoS Compiler

Compiles to an End operation.

hold

Ends the current script execution and holds it, waiting for something external to cause a new instruction. Get’s compiled as an instruction.

EoS Compiler

Compiles to a Hold operation.

return

If used in a macro ends the macro and resumes script execution after the macro call.

If not used in a macro either returns from a coroutine or act’s the same as end.

EoS Compiler

If used in a macro, a Jump operation is generated to the end of a macro call. Otherwise compiles to a Return operation.

break

Can only be used in case-Blocks of switches.

Ends the case-Block and jumps to the end of the switch-Block.

continue

Can only be used in forever-Blocks, for-Blocks and while-Blocks.

Ends the current iteration of the loop and jumps back to the beginning. In the case of while- and for-Loops the condition is checked again, for for-loops the increment operation is run before.

break_loop

Can only be used in forever-Blocks, for-Blocks and while-Blocks.

Exits the current loop and jumps to the end of the loop block.

jump

Jumps to a label.

// This will execute operation() in an endless loop.
@hello_label;
operation();
jump @hello_label;

with-Blocks

Runs a statement in the context of an actor, an object or a performer. Only simple statements are allowed, no blocks or labels. The keywords actor, object or performer can be used to specify the type. As identifiers for their IDs integers and constants can be used. with blocks can contain multiple operations for convenience, but unlike code in targeted routines, operations in the block are not executed concurrently.

with (actor ACTOR_HELLO_WORLD) {
    operation1();
    operation2();
}

if-Blocks

A conditional block. One or more conditions are checked and if they are true, a block of code is executed.

An if-Block an also be extended with elseif statements to specify alternative conditions and blocks and/or a single else block, that will be executed when neither the if conditions nor any of the elseif conditions apply.

Conditions specifications for if and elseif can contain a list of conditions as described below. These can be combined with ||. If any of the conditions apply, the block is executed.

There is no syntax to specify that multiple conditions must apply (“and”). Use nested if-Blocks for this.

If conditions can be negated as a whole, by using the keyword not.

if (CONDTION || CONDITION) {
    /** Statements **/
} elseif not (CONDTION) {
    /** Statements **/
} else {
    /** Statements **/
}

if-Conditions

Conditional check

A simple conditional check, that checks if the first field of a variable (identified by an integer or constant) matches a value as specfied by the operator.

The value to compare against can be the value of a variable. In this case value(X) must be used, where X is the id of the variable to compare against.

if (VAR_A < 3) {}
if (4 < 3) {}  // !!This checks if the variable with ID 4 is < 3!!
if ($A == 3 || $A > value($B)) {}
if ($VAR1 >= value(3)) {} // !! This checks if $VAR1 >= the variable with ID 3 !!

SkyTemple

As noted earlier, SkyTemple uses the $ prefix for variable constants. We recommend only using this prefix. The rest of the documentation will use this prefix for everything related to variables.

EoS Compiler

If value(X) is used, this is compiled as a BranchVariable operation. Otherwise, if the operator is not ==, this is compiled as a BranchValue operation. Otherwise it is compiled as a Branch operation.

Bit check

Checks if a bit in a bitfield variable is set. If the compiler supports it, not can be used to check if the field is not set instead.

if (VAR_A[3]) {}
if (not VAR_A[3]) {}
if not (not VAR_A[3]) {} // Same as if (VAR_A[3]) {}

EoS Compiler

If the variable is the game variable PERFORMANCE_PROGRESS_LIST (see CLI docs), this gets compiled as a BranchPerformance

Else it gets compiled as BranchBit.

The not keyword is only allowed for PERFORMANCE_PROGRESS_LIST!

Scenario check

Checks the two fields of a scenario variable against a conditional operator. Both values compared against must match the conditional operator. As conditional operators only ==,<,>,<=,>= can be used.

if (scn($SCENARIO_MAIN) > [30, 2]) {
    /** This only gets executed when
        field 0 of the variable is > 30 and
        field 1 of the variable is > 2 **/
}

EoS Compiler

Depending on the operator, this gets compiled to BranchScenarioNow, BranchScenarioNowAfter, BranchScenarioNowBefore, BranchScenarioAfter, or BranchScenarioBefore.

Special conditions

Some special keywords can be checked against. All of these can be negated using the keyword not:

  • debug: Branches if the debug mode is enabled.

  • edit: Unknown, not implemented?

  • variation: Branches if the ROM is a demo ROM.

EoS Compiler

These get compiled to BranchDebug, BranchEdit and BranchVariation respectively.

SkyTemple

In the SkyTemple Script Engine Debugger, the debug mode can be enabled with the checkbox “Enable Debugging Mode”.

Operations as conditions

Any operations supported by the compiler may also be used as if-Conditions.

EoS Compiler

The compiler supports no operations as if-Conditions.

switch-Blocks

(This works like switches in most programming languages).

Switch blocks have a switch-Header, which is a condition that a set of case-Blocks check against.

Each case-Block has a case-Header with it’s condition to check against and a set of instructions. If the end of a case-Block’s body is reached, it will continue executing the next case body will be executed. The execution of a case-Block can be stopped with the break statement.

A switch can have a default case, that gets executed when no other case matches.

switch (SWITCH_HEADER) {
    CASE_HEADER:
        statement1;
        statement2;
        // Will continue with next block, beause no break.
    CASE_HEADER:  // Case headers can be combined like this.
    CASE_HEADER:
        statement1;
        statement2;
        break; // Will not continue with next block.
    CASE_HEADER:
    default:
        statement1;
        statement2;
}

switch-Headers

Variable

The cases of this switch check against the value of a variable identified by the integer or constant provided.

switch($VAR) { /* ... */ }

EoS Compiler

Get’s compiled as a Switch operation.

Scenario

The cases check the first field of a scenario variable. This is effectively the same as the normal variable switch header.

switch(scn($VAR)) { /* ... */ }

EoS Compiler

Get’s compiled as a SwitchScenario operation.

Random

The cases check against a randomly generated number. The value in parenthesis is the upper bound of that number, the lower bound is 0.

switch(random(1234)) { /* ... */ }

EoS Compiler

Get’s compiled as a SwitchRandom operation.

Dungeon Mode

The cases check against the dungeon mode of a dungeon identified by the integer or constant provided.

switch(dungeon_mode(DUNGEON_ABC)) { /* ... */ }

EoS Compiler

Get’s compiled as a SwitchDungeonMode operation.

Sector

The cases check against whether or not a sector (or layer) of the active scene is activated.

switch(sector()) { /* ... */ }

EoS Compiler

Get’s compiled as a SwitchSector operation.

Operations as switch headers

Operations that are supported by the compiler can also be used as headers for switches.

EoS Compiler

The compiler supports all operations as switch headers, although please note, that it doesn’t make sense to run all operations as such.

case-Headers

Value

Check that the value provided by the switch header matches a value.

case 9:

EoS Compiler

This gets compiled as a Case operation.

Check against operator

Check that the value provided by the switch header matches a conditional operator and a value. value(X) can be used, to check against the value of variable, specified by the integer or constant X, instead.

case > 9:
case == 12:
case FALSE 3:
case < value($VAR_TEST):

EoS Compiler

If value(X) is used, this gets compiled as a CaseVariable operation. Else it gets compiled as a CaseValue operation.

Message Switches

Message switches check against a value to see what message to display.

The begin with either message_SwitchTalk or message_SwitchMonologue.

As case-Header only “Value” headers are allowed. Instead of statements the bodies of case-Blocks contain a single string.

message_SwitchTalk ($PARTNER_TALK_KIND) {
    case 1:
        " Oh, wow! What a pretty sight!"
    case 2:
        {
            english=" Oh, wow! What a pretty sight!"
        }
    default:
        {
            english=" Wow! What a beautiful sight!"
        }
}

EoS Compiler

The switch statements get compiled as either message_SwitchTalk or message_SwitchMonologue. The cases get compiled as CaseText and the default case gets compiled as DefaultText.

forever-Loops

The statements in this block will loop forever, unless broken out of using break_loop.

forever {
    operation1();
    operation2();
    // Will repeat at beginning again.
}

for-Loops

(Works like for-Loops in other programming languages).

Executes code until a condition is no longer met. Has an initializing statement and an incrementing statement that is run every iteration of the loop.

The for loop has the following syntax:

for (initial; condition; increment;) {
  /* Statements */
}

“initial” is executed (one time) before the execution of the code block. Any simple statement is allowed.

“condition” defines the condition for executing the code block. Any if-Header is allowed (no “||”).

“increment” is executed (every time) after the code block has been executed, including when continue is used. Any simple statement is allowed.

while-Loops

(Works like while-Loops in other programming languages).

Loops until a condition is no longer true. Any if-Header condition is allowed (no “||”).

while (condition;) {
  /* Statements */
}

EoS Compiler

The switch statements get compiled as either message_SwitchTalk or message_SwitchMonologue. The cases get compiled as CaseText and the default case gets compiled as DefaultText.

Assignments

Statements to assign values to variables (identified by integers or constants).

Warning

Assignments can NOT be used to assign values to macro variables. Game variables and macro variables are fundamentally different, please also see the section on macro variables.

If you use a macro variable as the variable in an assignment, the variable with the ID of the macro variables current content will be set. In the following example $SCENARIO_MAIN will be set to 3.

macro example(%var) {
    %var = 3; // $SCENARIO_MAIN is now 3.
}

def 0 {
    ~example($SCENARIO_MAIN);
}

Simple

Assign a integer value or the value of another variable (with value(X)) to a variable. How the assignment is done depends on the assignment operator used.

$VAR = 3;
$VAR = value(3);
$VAR += 3;

EoS Compiler

If value(X) is used, this gets compiled as flag_CalcVariable. Else if, an assigment operator other than = is used, this gets compiled as flag_CalcValue. Else, this gets compiled as flag_Set.

Bit-Set

Set a bit field in a bitfield variable.

Only 0 and 1 are allowed as values.

$VAR[3] = 1;

EoS Compiler

If the variable is the game variable PERFORMANCE_PROGRESS_LIST (see CLI docs), this gets compiled as a flag_SetPerformance

Else it gets compiled as flag_CalcBit.

Scenario

Set’s the two fields of a scenario variable

$VAR = scn[1, 2];

EoS Compiler

This gets compiled as flag_SetScenario.

Clear

Clears a variable (TODO: What exactly happens is not clear).

clear $VAR;

EoS Compiler

This gets compiled as flag_Clear.

Reset

Resets a scenario variable or the dungeon result (TODO: What exactly happens is not clear).

reset $VAR;
reset dungeon_result;

EoS Compiler

This gets compiled as flag_Reset or flag_ResetDungeonResult.

Initial

Set’s a variable to it’s initial value (TODO: What exactly happens is not clear).

init $VAR;

EoS Compiler

This gets compiled as flag_Initial.

Adventure Log

Set’s a value of the adventure log. Only = is allowed.

adventure_log = 3;

EoS Compiler

This gets compiled as flag_SetAdventureLog.

Dungeon Mode

Set’s the dungeon mode of a dungeon with it’s ID specfied by an integer or constant.

dungeon_mode(3) = 3;
dungeon_mode(4) = DMODE_OPEN;

EoS Compiler

This gets compiled as flag_SetDungeonMode.

Operations

An operation can be any valid identifier and a list of arguments. The arguments can have any data type.

The compiler usually compiles these directly as the specified operations, it may raise errors for unknown operations.

MyOperation(3, "String", Position<'Name', 20, 20.5>);

Optionally, you may also pass a context (actor, object or performer) to run the operation in.

MyOperation<actor ACTOR_HELLO_WORLD>(3, "String", Position<'Name', 20, 20.5>);

SkyTemple

A list of operations supported by Explorers of Sky can be found on this Wiki page. Feel free to extend the list. Some of the opcodes are reserved for ExplorerScript specific syntax and should not be used (eg. the “Branch”, “Switch”, “Case” and “flag” operations).

Macro call

A macro call calls a macro. See the documentation on macros (below) for more information. Macro calls start with ~.

~MyMacro(3, "String", Position<'Name', 20, 20.5>);

Conditional operators

Operator

Description

FALSE

The condition always fails

TRUE

The condition always succeeds.

==

Both must be equal.

>

The value of the left side must be greater than the right side’s value.

<

The value of the left side must be smaller than the right side’s value.

>=

The value of the left side must be greater or equal to the right side’s value.

<=

The value of the left side must be smaller or equal to the right side’s value.

!=

Both must not be equal.

&

Bitwise AND.

^

Bitwise XOR.

&<<

True if the bit of the left side (as specified by the right side) is set.

Assigment operators

Operator

Description

=

Set’s the left side to the right side’s value.

-=

Changes the left side’s value to have the right side’s value subtracted.

+=

Changes the left side’s value to have the right side’s value added.

*=

Changes the left side’s value to be multiplied by the right side’s value.

/=

Changes the left side’s value to be divided by the right side’s value. Warning: No floats exist!

User Constants

In addition to system constants, you may also specify your own (user) constants in your source code.

These constants can take on the value of any data type (including system constants).

If they are defined outside of routines, they are scoped to the entire file. If the file is the main script, it is also scoped to all of the included files (see Imports / Includes). If defined inside of a function or macro, they are visible only within the function or macro they are defined in. User constants defined in functions or macros take precedence over user constants in the global scope. All visible user constants take precedence over system constants.

const MY_CONSTANT = 12;

def 0 {
    const MY_INNER_CONSTANT = "Hello!";

    foobar(MY_CONSTANT); // = foobar(12);
    foobar(MY_INNER_CONSTANT); // = foobar("Hello!");

    // Constants can be used before they are defined.
    foobar(COPIED_CONSTANT); // = foobar(12);
    const COPIED_CONSTANT = MY_CONSTANT;

    foobar(ANOTHER_CONSTANT); // = foobar(A_SYSTEM_CONSTANT);
}

def 1 {
    const MY_INNER_CONSTANT = "Goodbye!";

    foobar(MY_INNER_CONSTANT); // = foobar("Goodbye!");

    // COPIED_CONSTANT is not a user constant in this scope, so a system constant
    // of that name is used instead.
    // If there is no system constant COPIED_CONSTANT, compilation will fail.
    foobar(COPIED_CONSTANT); // = foobar(COPIED_CONSTANT);
}

const ANOTHER_CONSTANT = A_SYSTEM_CONSTANT;

Imports / Includes

An ExplorerScript source file can imports other ExplorerScript files. These files are merged together. Imports are always at the top of source files, before any routine or macro.

Imports can be specified using:

  • Relative paths to the source file (starting with ‘./’ or ‘../’)

  • Absolute paths (starting with ‘/’).

  • Paths relative to the include paths of the compiler (all other paths).

The path separator is always /, even under Windows.

import "/xyz/abc.exps"; // Absolute
import "./abc.exps"; // Relative
import "../abc.exps"; // Relative
import "abc.exps"; // In include path
import "xyz/abc.exps"; // In include path

SkyTemple

SkyTemple Script Engine Debugger does not allow any imported files to contain routines, they must only contain macros.

The include path for SkyTemple projects is the “Macros” directory inside the project’s directory.

Macros

Macros are small functions that can be specified by the user.

They have a name and can take a list of arguments. When called these arguments get filled with the values of the parameters specified in the call and turn into macro variables. Macro variables have the prefix %.

When compiled, the entire content of a macro is copied to where the call was in the source code, the actual compiled code does not contain a function call. All references to the macro variables are replaced with the values of the call.

Macros can call other macros but can not cause any form of recursion.

Using imports you can share macros across multiple source files.

macro another_example(%anotherVariable) {
    another_print(%anotherVariable);
}

macro example(%variable1, %variable2) {
    print(%variable1, %variable2);
    ~another_example(%variable1);
}

def 0 {
    ~example($SCENARIO_MAIN, 3);
    ~example(ANOTHER_CONSTANT, "A string");
    ~another_example("Another string");
}

This example is equivalent to:

def 0 {
    print($SCENARIO_MAIN, 3);
    another_print($SCENARIO_MAIN);
    print(ANOTHER_CONSTANT, "A string");
    another_print(ANOTHER_CONSTANT);
    another_print("Another string");
}

SkyTemple

Game variables and macro variables are not the same, they are not interchangeable. Game variables are actually constants for integers that refer to the variables and macro variables can have game variable constants as values!

See the warning at the “Assignment” section for more info on this behaviour.

Note

Macro variables can also use the prefix $ for backwards-compatibility with old ExplorerScript releases. This prefix should not be used anymore, it is deprecated and support for it may be removed in future versions.

Macro variables with the same name refer to the same variable regardless of prefix ($foo or %foo would both refer to the variable named ‘foo’).