SCR Format (Shadowcaster)
Overview
Shadowcaster level scripts (*.SCR) control many special effects and level-flow modifications at runtime. Such effects include changing the level environment values (which sky is drawn if any, colored lighting, fog, and darkness), teleporting the player either to a different level (or within the same one), deleting and spawning static objects or items, locking or unlocking doors, and granting metaforms or experience points. These scripts are based on the concept of conditional lists -- that is, in order for an action to take place, a (series of) condition(s) must be satisfied.
There is no header. Script code begins immediately at the start of the file. A single script block consists of a list of conditions followed immediately by a list of function calls, and finally, a terminator. A script may not begin with a function call; this is explicitly treated as invalid data by the game. Conditions always have the following form:
int16_t condition_id;
uint8_t evaluation_type;
uint32_t value;
There is a list of 100 hard-coded conditions the game executable recognizes, many of which are exclusively script-controlled and are not modified directly by the game. In most circumstances, evaluation_type is set to 0x83 to mean "equal to"; other values are not yet fully known. This is followed by an immediate value to compare against. Scripts contain no explicit flow control and rely entirely on these condition evaluations.
For example, 0000 83 05000000 will be satisfied if condition #0 is equal to 5.
A single script block may contain many such condition formulations. The function list is only executed if all such conditions are satisfied. The condition list is finished specifying conditions upon encountering a condition with its highest bit set (0x8000). These are function calls. The game contains a list of 41 hard-coded functions that level scripts may call by ID. A function call follows this format:
int16_t function_id;
uint8_t numargs;
uint32_t args[...];
For example, 0F80 01 0D000000 -- function call 15, one argument (0x0D). This plays song ID 13 immediately upon satisfying its preceding condition list.
A function list is finished specifying calls when the game encounters a terminator byte outside of an argument list. A terminator can be 0xFF for single-fire scripts that are "deleted" after they are run, or 0x00 in some cases for repeatable effects. If no more condition/function lists proceed after this terminator byte, the script file is finished.
Creating a Condition List
Condition lists are the first elements in a script, and are the most straightforward to produce, as they always consist of 7-byte records stored one after the other without separation.
Condition lists can be thought of as assembling a single large "if" statement in C/C++-like languages. Once all conditions are satisfied, the functions are then executed.
List of Known Conditions
Qvars (0x00 - 0x3A)
Quest variables ("qvars" for short to distinguish them from other hardcoded conditions) are not written by the game executable and can freely be used by the script system. They are preserved between levels and even script heap wipes. These only have special meanings based on what you assign to them in a script yourself.
player_coarse_x (0x3C)
The X coordinate of the tile the player is currently standing in.
player_coarse_y (0x3D)
The Y coordinate of the tile the player is currently standing in.
levelid (0x3E)
The level number the player is currently exploring.
held_item (0x3F)
The ID within itemtype.nfo of the object the player is currently holding as their cursor.
zeropage (0x40)
Whatever value happens to be in the first 4 bytes in DOS low memory.
ceilingflag (0x43)
Whether the ceiling (5) is drawn on this level, or if sky is drawn instead (0).
player_mana (0x45)
How much mana the player currently has.
Evaluation Types
The only properly known evaluation type is 0x83 for "equal to".
The immediate operand value is compared to the value held by the specified condition. If the two are equal, the evaluation passes.
Creating a Function List
Function lists immediately follow the condition list once you are finished specifying conditions. Please note, however, that *you* are responsible for supplying the correct number of arguments; the game will not sanity check your data (it is possible for example to call SP_Teleport() with one argument when four are expected, leading to undefined behavior).
All arguments are always uint32_t.
List of Known Functions
SP_QvarAdd(which_qvar, operand)
Function call #0.
qvar.value += operand
SP_QvarSetAdd(which_qvar, base, operand)
Function call #1.
qvar.value = base + operand
SP_QvarSet(which_qvar, base)
Function call #2, #3.
qvar.value = base
SP_RemoveWall(x, y, ?)
Function call #4.
Removes a north/west wall within the tile at x,y. The "?" operand has unclear meaning at this time.
SP_Missile(tilex, tiley, direction, type)
Function call #5.
Spawn a missile at tilex,tiley moving along direction. Direction goes counter-clockwise starting at 0=east in 90-degree increments. In other words, sane values are 0-3.
SP_PlaySound(id)
Function call #6.
Play a sound by its internal ID. Some useful values are 1 = stone02c.voc, 3 = chain01b.voc, 5 = splat02b.voc.
SP_DeleteObj(tag)
Function call #7.
Find one (or many) OJT-format object(s) by their tag and delete all found objects from the game.
SP_MoveObj(tag, destx, desty, ?)
Function call #8.
Find one (or many) OJT-format object(s) by their tag and instantly move them to the center of the tile at destx,desty.
SP_ChangeTile(tilex, tiley, surface, textureid)
Function call #9.
Set the tile's geometry at tilex,tiley with textureid.
Surface can be: 0 = floor, 1 = ceiling, 2 = north_wall, 3 = west_wall. If a wall is specified as the surface type and textureid == 0, that wall and its collision are removed.
SP_Teleport(destx, desty, angle, level)
Function call #10.
Fade out the screen using colormap grading; move the player to destx, desty on levelid facing in direction angle.
angle ranges from 0-255, with 0 facing east and ascending values rotating counter-clockwise. North is 64, west is 128, and south is 192.
If levelid == 0, the player is teleported within the current level.
SP_SetColorMap(id)
Function call #11.
Immediately set the current rendering color shading table to internal-ID id.