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’).