# Schedule scripting language The Schedule scripting language is a high level language that was made to help reading and modifying the schedule scripts used by various actors. It should be noted that the language discussed on this document was invented and made up by the community. There's no presence of higher level abstraction on the game ROM, neither it was used by the original development team at Nintendo. - [Schedule scripting language](#schedule-scripting-language) - [Features of the schedule system](#features-of-the-schedule-system) - [Short and long commands](#short-and-long-commands) - [How a schedule script looks like](#how-a-schedule-script-looks-like) - [Syntax](#syntax) - [Compiling](#compiling) - [Commands](#commands) - [Generics and non-generics](#generics-and-non-generics) - [Command arguments](#command-arguments) - [Conditional checks](#conditional-checks) - [`if_week_event_reg`](#if_week_event_reg) - [`if_week_event_reg` arguments](#if_week_event_reg-arguments) - [`if_week_event_reg` example](#if_week_event_reg-example) - [`if_week_event_reg` non-generics](#if_week_event_reg-non-generics) - [`if_time_range`](#if_time_range) - [`if_time_range` arguments](#if_time_range-arguments) - [`if_time_range` example](#if_time_range-example) - [`if_time_range` non-generics](#if_time_range-non-generics) - [`if_misc`](#if_misc) - [`if_misc` arguments](#if_misc-arguments) - [`if_misc` example](#if_misc-example) - [`if_misc` non-generics](#if_misc-non-generics) - [`if_scene`](#if_scene) - [`if_scene` arguments](#if_scene-arguments) - [`if_scene` example](#if_scene-example) - [`if_scene` non-generics](#if_scene-non-generics) - [`if_day`](#if_day) - [`if_day` arguments](#if_day-arguments) - [`if_day` example](#if_day-example) - [`if_day` non-generics](#if_day-non-generics) - [`if_before_time`](#if_before_time) - [`if_before_time` arguments](#if_before_time-arguments) - [`if_before_time` example](#if_before_time-example) - [`if_before_time` non-generics](#if_before_time-non-generics) - [`if_before_time` notes](#if_before_time-notes) - [`if_since_time`](#if_since_time) - [`if_since_time` arguments](#if_since_time-arguments) - [`if_since_time` example](#if_since_time-example) - [`if_since_time` non-generics](#if_since_time-non-generics) - [`if_since_time` notes](#if_since_time-notes) - [Unconditional branches](#unconditional-branches) - [`branch`](#branch) - [`branch` arguments](#branch-arguments) - [`branch` example](#branch-example) - [`branch` non-generics](#branch-non-generics) - [`else`](#else) - [`else` example](#else-example) - [Return commands](#return-commands) - [`return_s`](#return_s) - [`return_s` arguments](#return_s-arguments) - [`return_s` example](#return_s-example) - [`return_l`](#return_l) - [`return_l` arguments](#return_l-arguments) - [`return_l` example](#return_l-example) - [`return_none`](#return_none) - [`return_none` arguments](#return_none-arguments) - [`return_none` example](#return_none-example) - [`return_empty`](#return_empty) - [`return_empty` arguments](#return_empty-arguments) - [`return_empty` example](#return_empty-example) - [`return_time`](#return_time) - [`return_time` arguments](#return_time-arguments) - [`return_time` example](#return_time-example) - [Other commands](#other-commands) - [`nop`](#nop) - [`nop` arguments](#nop-arguments) - [`nop` example](#nop-example) - [Operators](#operators) - [`not`](#not) - [`not` example](#not-example) - [Labels](#labels) - [Formal grammar](#formal-grammar) - [Tokens](#tokens) ## Features of the schedule system The Schedule system is a very simple scripting language, it is composed of a series of commands that can check the state of the game and return a value depending on said state. This system can't declare variables, hold its own state or modify the game's state. A schedule script can check the state of the following: - Current in-game [day](#if_day). - If a [WeekEventReg flag](#if_week_event_reg) is set or not. - Current time is in a specified [time range](#if_time_range). - Current time is [before](#if_before_time)/[after](#if_since_time) a specified time. - Some other [miscellaneous](#if_misc) checks (see the `ScheduleCheckMisc` enum). All of those checks act as conditional branches into some other command. The schedule system also supports unconditional branches. The schedule script is run until a return command is executed. There are various return commands that allow different things: - [Return none](#return_none): The schedule finished without returning a value. - [Return empty](#return_empty): The schedule finished without changing the previous value. - [Return s](#return_s): The script returns a value that's 1 byte long. - [Return l](#return_l): The script returns a value that's 2 bytes long (Note this is bugged and will be truncated to 1 byte). - [Return time](#return_time): Returns a time range and a 1 byte value. Running an unknown command or branching to the middle of a command is undefined behaviour. A low level schedule script can be compared to a multi byte assembly language, on which each command occupies 1 byte plus a variable number of arguments. ### Short and long commands Due to the commands themselves using a variable amount of bytes it is possible to do some small optimizations, like if a branch distance (which is the amount of bytes the interpreter should skip if a check evaluates to true) can fit on a signed byte then a **short** (noted with the `_S` suffix) command is used, otherwise a **long** version (noted with the `_L` suffix) of the command is used instead. A long command uses two bytes to store the branch distance, meaning the command itself will be one byte longer. There are no commands that use more than 2 bytes to store the branch distance. The short and long distinction also exists for the returned value, in case the user wants to return a value that wouldn't fit on a single unsigned byte and requires an unsigned short (two bytes) instead. Please note that the vanilla built-in system interpreter has a bug on which the upper byte of a long returned value will be discarded, so please ensure your returned values always fit on the 0-255 range. ## How a schedule script looks like An extracted schedule script is just an array of raw data. A macroified version looks like this: ```c static ScheduleScript D_80BD3DB0[] = { /* 0x00 */ SCHEDULE_CMD_CHECK_NOT_IN_SCENE_S(SCENE_YADOYA, 0x21 - 0x04), /* 0x04 */ SCHEDULE_CMD_CHECK_NOT_IN_DAY_S(3, 0x0B - 0x08), /* 0x08 */ SCHEDULE_CMD_RET_VAL_L(1), /* 0x0B */ SCHEDULE_CMD_CHECK_NOT_IN_DAY_S(2, 0x20 - 0x0F), /* 0x0F */ SCHEDULE_CMD_CHECK_TIME_RANGE_S(21, 0, 23, 0, 0x1D - 0x15), /* 0x15 */ SCHEDULE_CMD_CHECK_WEEK_EVENT_REG_S(WEEKEVENTREG_HAD_MIDNIGHT_MEETING, 0x1C - 0x19), /* 0x19 */ SCHEDULE_CMD_RET_VAL_L(1), /* 0x1C */ SCHEDULE_CMD_RET_NONE(), /* 0x1D */ SCHEDULE_CMD_RET_VAL_L(3), /* 0x20 */ SCHEDULE_CMD_RET_NONE(), /* 0x21 */ SCHEDULE_CMD_CHECK_NOT_IN_SCENE_S(SCENE_OMOYA, 0x37 - 0x25), /* 0x25 */ SCHEDULE_CMD_CHECK_NOT_IN_DAY_S(3, 0x36 - 0x29), /* 0x29 */ SCHEDULE_CMD_CHECK_TIME_RANGE_S(18, 0, 6, 0, 0x30 - 0x2F), /* 0x2F */ SCHEDULE_CMD_RET_NONE(), /* 0x30 */ SCHEDULE_CMD_RET_TIME(18, 0, 6, 0, 2), /* 0x36 */ SCHEDULE_CMD_RET_NONE(), /* 0x37 */ SCHEDULE_CMD_RET_NONE(), }; ``` Having these scripts as arrays like this has two major flaws: - The control flow is not clear at a first glance (this is especially problematic for larger scripts). - The branch distances are hardcoded into each command, making the script itself hard to modify. As a solution, the high level Schedule script language uses a C-like syntax to better represent the control flow. The above script can be written as the following: ```c D_80BD3DB0 { if_scene (SCENE_YADOYA) { if_day (3) { return_l (1) } else if_day (2) { if_time_range (21, 0, 23, 0) { return_l (3) } else if_week_event_reg (WEEKEVENTREG_HAD_MIDNIGHT_MEETING) { return_none } else { return_l (1) } } else { return_none } } else if_scene (SCENE_OMOYA) { if_day (3) { if_time_range (18, 0, 6, 0) { return_time (18, 0, 6, 0, 2) } else { return_none } } else { return_none } } else { return_none } } ``` ## Syntax The syntax is simple, it consists on the name of the schedule script followed by a succession of commands. Some commands require arguments (by using parentheses) and some (conditional checks) can have bodies with subcommands and an optional `else` with its corresponding body with subcommands. Like in C, both whitespace and newlines are not part of the language and they get ignored during compilation. At the same time, this language accepts both C and C++ styles of comments, known as block comments (`/**/`) and line comments (`//`). Each top-level schedule script consists on its name, followed by a list of commands delimited by braces (`{` and `}`). A schedule script must always have at least one command on its body. A command's body is delimited by braces (`{` and `}`). A body must follow either a [conditional check](#conditional-checks) command or an `else`, meaning top-level bodies are not allowed. At the same time a conditional check must be followed by a body. An `else` must be followed by either a body or another conditional check command. Even if this language's syntax is inspired by C, there are a few key differences: - Commands are not separated by semicolons (`;`). Instead whitespace is used as separation. - Instead of having a single `if` conditional of which its parameters should evaluate to non-zero, this language uses [conditional checks](#conditional-checks). These are commands that receive parameters and have a body with parameters that would be executed if the command itself evaluated to true. This commands may be followed by an `else`, which has its own body. - There's no concept of functions, variables, loops, scopes, etc. - Each schedule script could be seen as a function itself, but said script can't "call" another scripts. ## Compiling Compiling a high level schedule script should produce an array for each script in the file. Each array should contain a series of macros that can be `#include`d by a C preprocessor, like in the following example: File: `build/src/overlays/actors/ovl_En_Ah/scheduleScripts.schl.inc` ```c static ScheduleScript D_80BD3DB0[] = { /* 0x00 */ SCHEDULE_CMD_CHECK_NOT_IN_SCENE_S(SCENE_YADOYA, 0x21 - 0x04), /* 0x04 */ SCHEDULE_CMD_CHECK_NOT_IN_DAY_S(3, 0x0B - 0x08), /* 0x08 */ SCHEDULE_CMD_RET_VAL_L(1), /* 0x0B */ SCHEDULE_CMD_CHECK_NOT_IN_DAY_S(2, 0x20 - 0x0F), /* 0x0F */ SCHEDULE_CMD_CHECK_TIME_RANGE_S(21, 0, 23, 0, 0x1D - 0x15), /* 0x15 */ SCHEDULE_CMD_CHECK_WEEK_EVENT_REG_S(WEEKEVENTREG_HAD_MIDNIGHT_MEETING, 0x1C - 0x19), /* 0x19 */ SCHEDULE_CMD_RET_VAL_L(1), /* 0x1C */ SCHEDULE_CMD_RET_NONE(), /* 0x1D */ SCHEDULE_CMD_RET_VAL_L(3), /* 0x20 */ SCHEDULE_CMD_RET_NONE(), /* 0x21 */ SCHEDULE_CMD_CHECK_NOT_IN_SCENE_S(SCENE_OMOYA, 0x37 - 0x25), /* 0x25 */ SCHEDULE_CMD_CHECK_NOT_IN_DAY_S(3, 0x36 - 0x29), /* 0x29 */ SCHEDULE_CMD_CHECK_TIME_RANGE_S(18, 0, 6, 0, 0x30 - 0x2F), /* 0x2F */ SCHEDULE_CMD_RET_NONE(), /* 0x30 */ SCHEDULE_CMD_RET_TIME(18, 0, 6, 0, 2), /* 0x36 */ SCHEDULE_CMD_RET_NONE(), /* 0x37 */ SCHEDULE_CMD_RET_NONE(), }; ``` In the actor's C code: ```c #include "src/overlays/actors/ovl_En_Ah/scheduleScripts.schl.inc" ``` ## Commands Commands are the fundamental (and almost only) building block of this language. A schedule script file must always contain at least one script, and a schedule script must always have at least one command. It's undefined behaviour if the script's control flow doesn't always lead to a return command. To see how the command arguments work, please see the [corresponding section](#command-arguments). Commands can be categorized in 4 major types: [Conditional checks](#conditional-checks), [unconditional branches](#unconditional-branches), [return commands](#return-commands) and [other commands](#other-commands). ### Generics and non-generics Schedule scripts use both [short and long commands](#short-and-long-commands) for most of the [conditional checks](#conditional-checks) and [unconditional branches](#unconditional-branches) commands, making it optimal space-wise, but terrible to worry about when actually writing a schedule script from the user's point of view. Due to this, this high level language allows for generic versions of those commands (a.k.a. suffix-less versions) that allow not having to worry about how many commands the body of a check has (and the byte length of them). The rest of this section will mostly refer to the generic versions of the commands. The non-generic versions of each command are available to be used too, but their use is not recommended. ### Command arguments Some commands require arguments. Arguments are used by checking commands to check something specific of the state of the game (what's the current day? what's the current scene? etc.) and take a decision based on it (branch to another command). Arguments are enclosed in parentheses (`(` and `)`) and passed verbatim to the generated output, allowing a compiler for this language to not need to recognize any identifier used in arguments, allowing to use any custom identifier (specially useful for schedule result enums). Note this behaviour may change in a future version of the language. ### Conditional checks Conditional checks commands are commands that take a decision based on the state of the game, allowing it to decide which set of subcommands to execute. All conditional checks require an argument and a body of subcommands. A conditional check can optionally be followed by an `else`. If the condition of a command is not satisfied and that command has an `else`, then control jumps to the body of that `else`. If there's no `else` and the condition isn't satisfied then control just falls through into the next command. #### `if_week_event_reg` Checks if the passed WeekEventReg flag is set, and execute the body of the command if it is set. ##### `if_week_event_reg` arguments - Argument 0: A WeekEventReg flag. `WEEKEVENTREG_` macros are preferred. ##### `if_week_event_reg` example ```c if_week_event_reg (WEEKEVENTREG_61_02) { return_s (31) } return_s (30) ``` ##### `if_week_event_reg` non-generics `if_week_event_reg_s` and `if_week_event_reg_l` #### `if_time_range` Checks if the current time is between the passed time range. ##### `if_time_range` arguments - Argument 0: Hour component of the start time - Argument 1: Minute component of the start time - Argument 2: Hour component of the end time - Argument 3: Minute component of the end time ##### `if_time_range` example ```c // Checks if the current time is between 18:00 ~ 6:00 if_time_range (18, 0, 6, 0) { return_time (18, 0, 6, 0, 2) } else { return_none } ``` ##### `if_time_range` non-generics `if_time_range_s` and `if_time_range_l` #### `if_misc` Checks if the passed miscellaneous argument is true. ##### `if_misc` arguments - Argument 0: A value of the `ScheduleCheckMisc` enum. ##### `if_misc` example ```c if_misc (SCHEDULE_CHECK_MISC_MASK_ROMANI) { return_s (33) } else { return_s (34) } ``` ##### `if_misc` non-generics `if_misc_s`. Note there's no long version of this command. It is advised to minimize the amount of commands used on a `ìf_misc` body and `else`'s body. #### `if_scene` Checks if the current scene matches the one passed as argument. ##### `if_scene` arguments - Argument 0: A value of the `SceneId` enum. ##### `if_scene` example ```c if_scene (SCENE_SECOM) { return_s (7) } ``` ##### `if_scene` non-generics `if_scene_s` and `if_scene_l` #### `if_day` Checks if the current day matches the passed argument. ##### `if_day` arguments - Argument 0: The day to check. For example, passing `1` will check for day 1. ##### `if_day` example ```c if_day (3) { return_l (1) } ``` ##### `if_day` non-generics `if_day_s` and `if_day_l` #### `if_before_time` Checks if the current time is before the time passed as argument. ##### `if_before_time` arguments - Argument 0: The hour component of the time. - Argument 1: The minute component of the time. ##### `if_before_time` example ```c if_before_time (8, 0) { return_s (19) } else { return_none } ``` ##### `if_before_time` non-generics `if_before_time_s` and `if_before_time_l` ##### `if_before_time` notes This command performs the opposite check of `if_since_time`. #### `if_since_time` Checks if the current time is after or equal to the time passed as argument. ##### `if_since_time` arguments - Argument 0: The hour component of the time. - Argument 1: The minute component of the time. ##### `if_since_time` example ```c if_since_time (13, 0) { return_s (9) } else { return_none } ``` ##### `if_since_time` non-generics `if_since_time_s` and `if_since_time_l` ##### `if_since_time` notes This command performs the contrary check to `if_before_time`. ### Unconditional branches Unconditional branches are basically the equivalent of C's `goto`s. They require a [label](#labels) to know where to branch to. #### `branch` Unconditionally transfer control to the passed label. ##### `branch` arguments - Argument 0: The label to branch to. ##### `branch` example ```c if_day (3) { if_since_time (10, 0) { label_0x8: return_none } else { branch (label_0xF) } } else { if_time_range (10, 0, 20, 0) { branch (label_0x8) } else { label_0xF: return_s (21) } } ``` ##### `branch` non-generics `branch_s` and `branch_l` ### `else` An `else` signals which commands should be executed in case a [conditional check](#conditional-checks) does not evaluates to true. The `else` signals this by either having those instructions inside its own body (marked by braces) or by not having those braces but being immediately followed by another conditional check command, similar to C's `else if`. #### `else` example The following two scripts are equivalent: ```c if_time_range (9, 50, 18, 1) { return_time (9, 50, 18, 1, 1) } else { if_time_range (18, 0, 6, 0) { return_time (18, 0, 6, 0, 2) } else { return_l (0) } } ``` ```c if_time_range (9, 50, 18, 1) { return_time (9, 50, 18, 1, 1) } else if_time_range (18, 0, 6, 0) { return_time (18, 0, 6, 0, 2) } else { return_l (0) } ``` ### Return commands A return command signals the end of the script and halts its execution. A return command allows to optionally return a value and/or a time range to the calling actor so it can change behavior accordingly. A schedule script with a control flow that leads to no return command is undefined behaviour. #### `return_s` Allows to return a short (one byte) value. ##### `return_s` arguments - Argument 0: The value to return. It must fit in a `u8`. ##### `return_s` example ```c if_time_range (8, 0, 12, 0) { return_s (EN_NB_SCH_1) } ``` #### `return_l` Allows to return a long (two bytes) value. Please note that the vanilla interpreter for the schedule scripts has a bug on which the upper byte of a long returned value will be discarded (will be truncated to a `u8`), so please ensure your returned values always fit in the 0-255 range. ##### `return_l` arguments - Argument 0: The value to return. It must fit in a `u16`. ##### `return_l` example ```c if_scene (SCENE_TOWN) { return_l (1) } else { return_none } ``` #### `return_none` The schedule finished without returning a value. The internal `hasResult` member of the `ScheduleOutput` struct will be set to `false`. ##### `return_none` arguments No arguments. ##### `return_none` example ```c if_week_event_reg (WEEKEVENTREG_HAD_MIDNIGHT_MEETING) { return_none } ``` #### `return_empty` The schedule finished without changing the previous value. The internal `hasResult` member of the `ScheduleOutput` struct will be set to `true`. ##### `return_empty` arguments No arguments. ##### `return_empty` example ```c if_time_range (15, 50, 16, 15) { return_empty } ``` #### `return_time` Returns a time range and a 1 byte value. ##### `return_time` arguments - Argument 0: Hour component of the start time - Argument 1: Minute component of the start time - Argument 2: Hour component of the end time - Argument 3: Minute component of the end time - Argument 4: A value to return. It must fit in a `u8` ##### `return_time` example ```c if_time_range (0, 0, 6, 0) { return_time (0, 0, 6, 0, TOILET_HAND_SCH_AVAILABLE) } else { return_none } ``` ### Other commands #### `nop` No operation. Doesn't perform an action or a check. ##### `nop` arguments - Argument 0: Unknown meaning. - Argument 1: Unknown meaning. - Argument 2: Unknown meaning. ##### `nop` example ```c nop (0, 1, 2) ``` ### Operators Operators can be applied to some commands. Currently only one operator is allowed on the language, the [`not`](#not) operator. #### `not` A `not` is a special kind of command that doesn't get compiled into the actual low level script, instead it changes the meaning of the check that's right next to it by inverting the logic of the check. In other words, the subcommands of a conditional check command will be executed if the check of said command evaluates to false instead of true. A `not` must always be followed by a [conditional check command](#conditional-checks). ##### `not` example The following two scripts are equivalent: ```c if_week_event_reg (WEEKEVENTREG_51_08) { if_since_time (22, 0) { return_s (12) } else { return_none } } else { return_s (12) } ``` ```c not if_week_event_reg (WEEKEVENTREG_51_08) { return_s (12) } else { if_since_time (22, 0) { return_s (12) } else { return_none } } ``` ### Labels A label is a special kind of command that doesn't get compiled into the actual low level script, instead it is used as a marker to be used for [`branch`es](#branch). A label is defined as an alphanumeric identifier (a to z, digits and underscores) followed by a colon (`:`). The colon is not considered part of the label's name. A label must always be followed by another command that isn't another label. ## Formal grammar This section presents the formal grammar for the Schedule scripting language. ```yac : ; : | ; : IDENTIFIER '{' '}' ; : | ; : | IDENTIFIER ':' ; : | | | ; : | ; : ; : RETURN_S | RETURN_L | RETURN_NONE | RETURN_EMPTY | RETURN_TIME ; : NOP ; : NOT | ; : | | | | | | ; : ELSE | ELSE ; : IF_WEEK_EVENT_REG | IF_WEEK_EVENT_REG_S | IF_WEEK_EVENT_REG_L ; : IF_TIME_RANGE | IF_TIME_RANGE_S | IF_TIME_RANGE_L ; : IF_MISC | IF_MISC_S ; : IF_SCENE | IF_SCENE_S | IF_SCENE_L ; : IF_DAY | IF_DAY_S | IF_DAY_L ; : IF_BEFORE_TIME | IF_BEFORE_TIME_S | IF_BEFORE_TIME_L ; : IF_SINCE_TIME | IF_SINCE_TIME_S | IF_SINCE_TIME_L ; : BRANCH | BRANCH_S | BRANCH_L ; : '{' '}' | '{' '}' ; : '(' ')' | '(' ')' ; : | ',' ; : NO_PARENTHESIS | ; ``` ### Tokens The presented grammar expects a few tokens. First column is the corresponding token and right is a regular expression to match said token. ```regex IDENTIFIER [a-zA-Z0-9_]+ IF_WEEK_EVENT_REG if_week_event_reg IF_WEEK_EVENT_REG_S if_week_event_reg_s IF_WEEK_EVENT_REG_L if_week_event_reg_l IF_TIME_RANGE if_time_range IF_TIME_RANGE_S if_time_range_s IF_TIME_RANGE_L if_time_range_l IF_MISC if_misc IF_MISC_S if_misc_s IF_SCENE if_scene IF_SCENE_S if_scene_s IF_SCENE_L if_scene_l IF_DAY if_day IF_DAY_S if_day_s IF_DAY_L if_day_l IF_BEFORE_TIME if_before_time IF_BEFORE_TIME_S if_before_time_s IF_BEFORE_TIME_L if_before_time_l IF_SINCE_TIME if_since_time IF_SINCE_TIME_S if_since_time_s IF_SINCE_TIME_L if_since_time_l BRANCH branch BRANCH_S branch_s BRANCH_L branch_l RETURN_S return_s RETURN_L return_l RETURN_NONE return_none RETURN_EMPTY return_empty RETURN_TIME return_time NOP nop ELSE else NOT not NO_PARENTHESIS [^\(\)]+ ```