| This page is heavily based on an Eurocom document.|
It was imported from
This language would be increasingly expanded year after year until the company entered bankruptcy in 2012.
This wiki page is the exact same version that the original map scripters used for reference, and it was released in 2018 by THQ Nordic together with Euroland and the other data contained in the Authoring Tools DLC. It is publicly reproduced here with permission.
Version 1.0 | August 11, 2003 | Eurocom Developments Ltd | Revisions
Description of Version
e.g. Revision after publisher feedback
The scripting language has been designed for maximum simplicity yet still offering flexibility. The format has been kept very similar to BASIC in style. As such, it's fairly easy to read and understand, yet has a good level of functionality.
Scripts can be composed of several functions which are grouped together currently by keeping them in the same text file.
General programming protocols are adhered to such as local and global variables, nested loops, conditions, calculations etc.
The language offers 2 modes of execution. Exclusive mode and time slice mode. Exclusive mode allows a piece of code to run without interruption until it’s complete, whereas time-slice mode allows the code to run in the background without halting any other processes. Explained in detail later
The language is split into 2 sections. The Main instruction set and the Sphinx specific subset. The main set deals with general program flow, maths, variables and standard decision making. The Sphinx subset are higher level commands, wholly integrated with the language that control specific events and features in Sphinx.
The language is NOT case sensitive for keywords or commands, but IS case sensitive for variable, label and function names. As such, it will recognise the word “
repeat” the same as the word “
REPEAT” for a command. However a variable called “
test” is different to another called “
It is recommended that certain protocols should perhaps be followed to increase the readability of the code.
These recommendations are:
- Keep all commands in lower case.
- Keep Variables in either upper case or capitalise the first letter.
- Use C style indentation where appropriate.
- Use plenty
//statements – they make the code more readable and don’t affect run time performance as they’re stripped by the compiler.
- Use as few actual instructions as possible.
Language Assumptions and rulesEdit
While the above are recommendations, these below are rules. They will make more sense after you have read further and looked at the instruction set.
These rules are:
- All instruction code must be contained within procedures (functions).
- Variables can be declared within a function or outside of a procedure.
- Variables declared within a procedure are termed ‘
local’ and they can only be accessed by commands within that procedure. Local variable names can be reused between procedures and they will not interfere with each other as with C.
- Variables declared outside of any procedures are termed ‘global’ and they can be accessed by commands within ANY procedure in that file. If a variable has been declared global, a variable of the same name cannot be declared later on again, even as a local in the same file.
- Every procedure must have an end point.
- Procedures CANNOT call themselves (recursive).
Below are several terms that will apply later on in this document. They will make more sense after you have read further.
- Constant - a number that doesn’t change and is fixed as opposed to…
- Variable – a number that can change if required. Variables are represented by a name and can be manipulated as required and used in place of constants where appropriate.
- Nesting/Stacking – The language always performs the most recent, or ‘inner most’ set of instructions. If, for example you have a
REPEATloop within a
REPEATloop, it will complete the inner most loop, then continue with the outer most. (see examples later on).
- Scope (in terms of a variable) – Scope in this context says basically what variables the code can see/use. Global variables are always ‘in scope’, local variables are ‘in scope’ within their own procedure, however, they are ‘out of scope’ in terms of another procedure.
- Scope (in terms of an instruction) – Scope in this context is related to the nesting/stacking. If, for example the user places a
REPEATloop within a
WHILEloop, that’s fine. However, if the user tries to terminate the
WHILEloop within that
ENDWHILEcommand becomes out of scope. An error will be reported at compile time (see examples later on).
- Precedence (mathematical) – Certain mathematical operators will be enumerated before others if they’re bunched together. For example, a multiply will be worked out before an add.
- V-Table – an accelerator table which binds commonly named/used procedures to a set of defines making it easier and faster to access them from in-game C++ code. This in general can be project specific but good examples are: an ‘On Create’ procedure and an ‘On Destroy’ procedure.
- Scope Range – how far ahead another instruction can ‘see’ to something that’s related to it. Applies to certain instruction sequences.
Main Instruction OverviewEdit
These commands are general commands that can be used in a variety of ways throughout the code, as such they fall into no specific category:
REM- Remark. Allows user to place a comment in the code. At compile time this line is removed from the build. It's simply there to allow messages to be added into the source code for readability
DEBUG- debug output. Causes the program to output a number to the debug window
BREAK- Breaks the code out of any conditional sections or loops and continues from where that piece of code ends. Recommended that it's always used where switch/case is used
EXIT- Breaks completely out of the script and returns to main game engine
WAIT- Causes interpreter to wait for a specified number of frames
GETFRAMECOUNT- Gets number of AI frames elapsed. Usually since script started
SETFRAMECOUNT- Allows user to modify the number of frames elapsed
Variable specific and mathematical InstructionsEdit
Variables are dealt with specifically and unconditionally using these instructions.
You can’t use a variable that’s not been created, it must be declared (INT) first.
INT- Create an integer variable
CONST- Create a labeled constant number
RAND- Allows a random value to be assigned to a variable
These allow the flow of the program to branch based on certain mathematical conditions being achieved or not. Conditions are the usual
< and combinations thereof:
IF- The first command in an
ELSE- If the ‘
ELSEprovides an ‘opposite’ condition
ELSEIF- Similar to
ELSEexcept it can also offer a condition
ENDIF- Closes the
AND- Used in combination with IF or
ELSEIFto chain multiple conditions together
This is similar to an
IF statement in that it allows code to branch conditionally. It only has one effective condition,
=, and allows code to branch depending what exactly a variable is set to:
SWITCH- The first command in a switch sequence. This specifies the variable
CASE- Used for every possible value that user wants to check specifically
DEFAULT- If no cases are met, then code will opt for default (optional)
ENDSWITCH- Ends the switch sequence
Sometimes its desirable to force a piece of code to repeat several times over. There are two methods of doing this,
WHILE. Repeat is the simpler of the 2 and can even be used without variables.
REPEAT- Forces a piece of code to simply repeat X number of times
ENDREPEAT- Closes the repeat statement
WHILE- Allows a piece of code to continue repeating while a mathematical condition (same as with IF) is still being met
ENDWHILE- Closes the while statement
AND- Similarly with
IF, a number of conditions can be chained together using AND
Procedures are the term used for the separate functions within a script. Another term would be routines and sub-routines.
They each have a unique name and a defined start and end point. A procedure can have its own local variables. A procedure is able to call another one.
DEFPROC- Starts and names a procedure
ENDPROC- Terminates a procedure and exits
LEAVEPROC- Exits a procedure from a mid point
CALLPROC- calls another procedure
INSTANCE- initialises an instance of another time sharing procedure
RETURN- sets a return value for the current procedure (used before
EXCLUSIVE- If used as a suffix after the
defprocand procedure name, this term states that the code will run in exclusive mode. Alternatively it can be used as an in-code command to switch modes
SUSPEND- suspends another script from running.
RESUME- resumes a suspended script
RESET- resets another script and re-runs it from the start
(See later section on procedures for rules and more information)
The script language has support for basic one dimensional static arrays. They are defined similar to procedures and store sequences of 32bit values, for example hash-codes
DEFARRAY- marks the start of an array
ENDARRAY- marks the end of an array
GETARRAY- gets an element from an array
SETARRAY- sets an element in an array
When arrays are defined, make sure you do not have a variable of the same name as the arrays as the compiler will then attempt to use the variable instead of the array!
Main Instruction Set - Full SyntaxEdit
As these instructions are tied to no specific area, they are detailed together here as general commands.
DEBUG are fairly non-functional as such and have no controlling effect in the outcome of the final code. This does not apply to the other commands listed!!
REM <comment> or
// <comment> Edit
Provides nothing more than a way of placing useful messages in the source code. The compiler completely ignores this instruction and the comments that follow it. However it greatly increases the readability of the source code to use these commands.
REM and ‘
//’ are fully interchangeable. Rem is more readable, but
// is easy to type and handy for quickly commenting out lines of code.
REM ***This Procedure calculates what Frank is going to say next***
DEBUG <num> <HEX>
Again this instruction has little outcome with the final code, although it does get compiled. When the interpreter comes to a debug command, it will output the number or variable following the command to the C’s debugger window. Placing the word
HEX at the end will output the number in hexadecimal
DEBUG 1 - output a 1
DEBUG TEST1 HEX - output value of variable TEST1 in hexadecimal
DEBUGS <short string>Edit
Like the above, this instruction doesn’t have much effect, except to display debug messages. The string can be up to a whopping 7 characters long. When the interpreter finds one, it prints it in the C debugger’s output window, and adds a space after it. Several can therefore be chained together. To make it end a line send
<CR> as a string. Additionally to get the script to output a ‘who I am’ info string, send
<ID> as a string
Would print something like… (ID section in blue, explained below)
MO_QU01_Skeletal_Spider - 'Main', Instance 0 : Hello
…in the debugger output window
ID section is composed of 3 parts. The first part is the name of the trigger type, as seen in Euroland, the second part, in quotes is the name of the procedure that is running, and the third part is the instance number.
This instruction stops the language mid repeat, while, if or switch and jumps to that instruction’s end point. Instructions on using it are dealt with in the sections for the above commands later on when they’ve been introduced. In practice, it's only really useful for ‘switch’ and ‘if’ statements
This terminates scrip execution and drops right out of the language, back to the main game engine, or whatever called the script. Can be useful, but it's fairly extreme.
Causes the interpreter to wait for a requested number of frames. It is assumed that the scripts are polled called once per game frame. Changing the execution speed of the script will not affect how long it takes for the wait state to end.
<variable> = GETFRAMECOUNT <scope>Edit
Gets the number of frames elapsed. Usually since script was started and stores the result in the variable. If the word ‘
local’ or ‘
1’ is added after the command, it reads the number of frames elapsed since the current instance was started (‘
local’ is only useful in Time Sharing mode, it's ignored in exclusive)
If Local is omitted or the procedure is running in exclusive mode, it will refer to the object’s global counter. This is usually the number of frames the object has ‘been alive’ for.
TestVariable = GETFRAMECOUNT local
SETFRAMECOUNT <variable or num> <scope>Edit
Sets the frame counter to a numerical value or the value of a variable passed in. Again, scope allows it to refer to the current instance’s own counter or the object’s global one if you place the word LOCAL after the variable
<variable> = GETTIMER <game scope>Edit
Allows access to the timer variables. By placing the word ‘
local’ after the command it returns the level timer. Leaving local out returns the total game timer.
Its up to the game programmers how and where they reset these variables
Variable and Integer CodeEdit
These provide the basic variable operators to enable calculations and later comparisons etc to be completed.
Assignments and mathematical calculations.Edit
These are discussed later on, however variables, once declared with an INT statement are assigned values using the ‘=’ sign like in any other language
Variable = 5, or
Variable1 = Variable2
Similarly, mathematical calculations are performed in the same way
Variable1 = 5 * Variable2, or
Variable1 = Variable2+Variable3
See later section on expression Evaluation
Constant numbers can also be passed in hexadecimal using the C style
0x<hex num> notation.
Variable = 0xff
In addition, if a variable needs to be incremented or decremented by 1, then the C style shorthand
– syntax can be used:
Variable ++ or
All variables must be declared to the compiler before use and are declared using
INT. This stands for ‘integer’. All variables currently must be 32bit integer values.
- declare an integer variable ‘
a’. Once declared, the language can ‘see’ the variable
- LOCAL variable declaration can be combined with assigning a value
int TEST1 = 5or
int TEST1 = TEST2
- GLOBAL variable declaration CANNOT be combined with assigning a value. It will ignore any “
= value” placed after. Therefore you declare them with int and assign a value to them later on
Defined Constants are handy as they can be used to represent numbers in a more meaningful and re-readable way. They can be declared in the global variable area using the keyword
CONST. Once a
const is set, it cannot be modified
CONST MyConst = 5
There are predefined constants in the language,
If you need random values, then use the
a = RAND <value>Edit
a = RAND bEdit
- set the value of ‘
a’ to a random number up to the size of either ‘
TEST1 = rand 5
TEST1 = rand TEST2
Randcommand can also be written in a different way. The first variable can be placed after the
Randcommand and before the second variable/value:
a=Rand b” can be written “
Rand a b”. This method is not advised and is only included for compatibility purposes with old versions of the compiler
- Rand can Also be used as part of a variable declaration eg :
int Variable = Rand 5
int Variable = Rand OtherVariable
It is possible to access the 32 bit variables on a per bit basis using the following 2 commands
variable = GetBit <variable> <bit number 0-31>Edit
- returns 1 or 0 for the value of the specified bit
SetBit <variable> <bit number> <1 / 0>Edit
- sets the specified bit of the variable to either 1 or 0
Arrays in the script language are simple one dimensional affairs, however they are useful for storing and later retrieving data sequences, such as button presses for mini games, conversation sequences etc etc. Arrays should be placed after the code procedures.
Arrays are defined using the command
DEFARRAY and ended with a corresponding
Ensure when declaring an array that you do not have a variable which has the same name as the array
Array Code CommandsEdit
DEFARRAY <array name>Edit
- This command starts a new array. Arrays cannot be defined within procedures, nor can they be defined within other arrays
- Ends an array.
variable = GETARRAY <array> <index> Edit
- Gets an element from an array. The index (for those non programmers amongst you) is effectively the number of the item you wish to retrieve from the array, minus one. So if you want the 10th element, you’d pass 9, 6th element, you’d pass 5, etc etc
- If you try and read outside of an array’s range it will report an error in debug mode, or do nothing in non-debug.
SETARRAY <array> <index> <value>Edit
- Sets an array element to a value, same rules apply as above regarding index and accessing outside of range
- The value can either be a variable or a constant. Due to storage constraints, a constant can be maximum 255, if you need bigger, use a variable.
- Arrays are store
C Style Array AccessEdit
Arrays can also be accessed in a method like C/C++
variable = MYARRAY[index] and
MYARRAY [index] = variable etc
Points to note here, this is a direct replacement for the above get and set instructions. As such it should be used in the same way. Therefore
MYARRAY [index1] = MYOTHERARRAY [index] is not possible, you’d have to get the array element from the second array into a variable, then copy it into the first array. 2 stages.
Array Code data formatEdit
- Array data is stored between the
endarray. Each entry is an
- Each line of array data should contain 2 entries. If there’s only one piece of data on a line, it will insert a zero as the second entry. The reason for this is that each code line is 64 bits and each piece of array data is 32bits. Therefore to maximize storage efficiency it fits 2 pieces of data into one line
- Array data can consist of numbers, hash-codes or constants. No variables can be included in the data
- See example of an array in examples section later in document
More Advanced array useageEdit
In addition to directly accessing arrays by name, you can assign a variable to an array,
MY_VARIABLE = ARRAY_NAME.
Then replace the array name in the
SETARRAY with the variable name.
To any C programmers reading this, it's like a pointer to the array. You know what I mean
An example of how this would be useful would be if you had an NPC that had several sets of conversation depending on its state that were all stored in arrays of hash codes
Instead of having to duplicate the
GETARRAY command you would just have a variable which was set to equal the array of text that you wanted the NPC to use
Furthermore, you could set up another array and in that store the ‘pointers’ to the other arrays making it even easier to switch between them at a later stage. Got that?
setarray ARRAYOFARRAYS 0 var
setarray ARRAYOFARRAYS 1 var
Points to note and safety precautionsEdit
The scripts’ arrays have range checking to make sure you don’t read or write outside of their limits. To allow this to work reliably, any variables you use to ‘point’ at the arrays as detailed in the section above, must always be the value they were set to when you assigned them to an array.
If you adjust that value, the interpreter will simply report an error next time you try and access the array. It's much safer this way. If you want to access different data in the same array block, use the index, don’t change the pointer.
The arrays are stored in the script code space along with the actual code. If you have 4 triggers all accessing the same script, they will also be sharing any arrays that they have between them. Ie, if you change an array entry in one, they will all ‘see’ that change.
The scripting language has full conditional execution using the instructions
IFstatement must have a corresponding
ELSEIFstatement must have a corresponding
- This starts off every piece of conditional code by comparing at least one variable with another or a constant value. If the conditions are met, it will cause the next piece of code to be executed. Otherwise it will look for an alternative.
- Eg –
- Or –
- The full range of conditions are
- Additionally inserting ‘
!’ into the condition reverses the outcome
- Eg – if
TEST1!=1- this means
if test 1is NOT equal to
- tells the language that we’re not worried about the
IFstatement’s conditions any more
- Effectively returns to normal flow of code
- see next page for an example
- This provides a full unconditional alternative to an
IFstatement’s outcome. If an
ELSEis present after an
IF, if the
IFcondition fails, it will perform code after the
- It basically means ‘otherwise’. An analogy. “If you’re too warm, take your coat off. Otherwise leave it on
- If the
IFstatement’s conditions ARE met, it will not perform the code after the
ELSEand will jump straight to the
- See next page for an example
- Similar to
ELSE, except you can state another condition to be met
- It basically means ‘otherwise if’. “If you’re too warm, take your coat off. Otherwise if you’re too cold, get a scarf. Otherwise stay as you are’
- Apart from that it’s the same as
- See next page for an example
- Used in combination with
WHILEto chain multiple conditions together
- Cannot be just used by itself!!
IF has a ‘scope range’ of 255. This means that the
IF can only see 255 instructions ahead of itself to find the next
ELSEIF can also independently see 255 instruction lines ahead if itself, so in real terms this is highly unlikely to cause any problems!!
Example of conditional
REM statements in these examples represent where you would add the code that you want to run when conditions are met.
An example of a simple
rem Run Some code here if TEST1 is greater than 1
TEST1is greater than
1. If this is true then it would execute the code where the rem statement is. Otherwise it would do nothing. If we wanted it to run some alternative code if the
IFstatement fails, then we can use the
An example of an
IF statement with
rem Run Some code here if TEST1 is greater than 1
rem If TEST1 is not greater than 1, run some alternative code here
Elsein this situation provides a 100% backup plan if the
IFcondition fails. Effectively it means ‘if nothing else has worked, then do this’. However, we may need a more advanced test, one where we need to check for several conditions. To do this, we can use
ELSEIF. This is similar to
ELSEexcept that it provides another condition and will only execute its piece of code firstly if all preceding
ELSEIFs have failed and if its condition is met. Additionally, the user can still supply an
ELSEstatement after it, or not as is required
An example of an
IF statement with
rem Run Some code here if TEST1 is greater than 1
rem Run Some code here if TEST1 is less than 1
rem if both the above fail, then Run some code here
WHILEloops and within
An Example of Chained conditions using
if TEST1>1 and TEST1<10
rem do some code in here
if TEST1=1 and TEST2 != 100 and TEST3<TEST2
rem do some code in here
ANDcommand simply allows the
IFto work on several conditions at once. If they’re all met, the code will execute
Switch statements provide another method of causing code to branch depending on a variable’s conditions being met. A switch only has one condition as such, equals. Following a switch statement, the user provides a set of ‘cases’ that the variable can be equal to.
Switchstatement must have at least one
switchstatement must have a corresponding
- It's advisable to use a ‘
break’ statement after each case to speed up execution, except on the last case, where it's not required
- This starts off a switch sequence and specifies the variable it's looking at
- Can only act on a variable, there’s little point in using a constant!
- This terminates a switch sequence
- Effectively returns to normal flow of code
- These follow a switch statement and provide the situations on which it can act
- There must be at least one of these per switch
- Per case, it's advisable to use a break statement before the next case
- Case statements only act on constant numbers
- See examples on next page
- Provides an ‘everything else’ case
- It's optional
- It effectively equates to ‘
ELSE’ in the context of an ‘
- Use last in a switch sequence after all other cases have been dealt with
- See examples on next page
SWITCH has a ‘scope range’ of 255. This means that the
SWITCH can only see 255 instructions ahead of itself to find the next
DEFAULT can also independently see 255 instruction lines ahead if itself, so in real terms this is highly unlikely to cause any problems!!
Example of conditional
REM statements in these examples represent where you would add the code that you want to run when conditions are met
An example of a simple
rem Run Some code here if TEST1 = 1
rem Run Some code here if TEST1 = 2
TEST1is tested for 2 cases. If it’s equal to 1 or 2. If either of those cases are true, it will execute appropriate code (where the rem statements are now). If neither case is true, then the program will do nothing. This sequence can be extended of course to include other cases. Cases do not have to be in numerical order, it just looks tidier that way
break’ statement on the first case makes sure that when the case’s code is completed, the program jumps right out of the switch sequence and down to the
While ‘break’ is not 100% required in a situation like this, it's recommended as otherwise all other cases will be tested along the way until it reaches
endswitch. Clearly this would use more processing time.
Clearly it's desirable sometimes to have a case that covers all other bases. In this situation, we would introduce the ‘
default’ command. Effectively this is a special case that is caused if all other cases fail to be true.
An example of a simple
SWITCH statement with
rem Run Some code here if TEST1 = 1
rem Run Some code here if TEST1 = 2
rem Run Some code here if all above cases fail
=2cases. However here, if these fail, instead of doing nothing, they will automatically revert to the default case. The rules change slightly when using default. ‘
Breaks’ must be used on all other cases otherwise code will always end up calling the default
default case MUST be placed last
REPEAT Loop InstructionsEdit
Repeat instructions allow a piece of code to be repeated several times. They are the simplest form of loop provided by the scripting language. Repeats can act on either a variable or a constant value to govern how many times they will operate.
- This signifies the start of a repeat loop
- The value can either be a constant or a variable
- The code contained between this statement and the
endrepeatwill be repeated by the number of times the value states
- Has to have a corresponding
- Value must be +ve and has a maximum size of 65536
- This signifies the end of the
- Cannot be used without a corresponding
REPEAT has a ‘scope range’ of 255. This means that the
REPEAT can only see 255 instructions ahead of itself to find the
REM statements in these examples represent where you would add the code that you want to run within the loop
REPEAT with a constant value:Edit
rem Do this piece of code 5 times
REPEAT with a variable value:Edit
rem Do this piece of code TEST1 number of times
repeatloops can be placed inside each other (nested). The inner most loop will be triggered and run to completion once for every cycle of the outer loop.
Repeats can also be placed within
WHILE Loop InstructionsEdit
While is effectively almost like
REPEAT loop mixed with an
Basically it will repeat a series of instructions if a condition continues to be met, as such it’s a fairly powerful instruction, it's also the only instruction in the language that could be considered ‘dangerous’
The first instruction in the loop is
WHILE and the loop is terminated with
WHILE statement is always used in connection with an
IF style comparison. The comparison can be between 2 variables or between a variable and a constant.
As such, a
while loop would take a form similar to this, obviously with the comparison of your choice after the
…do some code in here
While loops can be dangerous if used incorrectly, especially if used in
EXCLUSIVE mode. The danger is that the script can get stuck within the while loop indefinitely if the condition is always met. You must always ensure that at some point the loop will exit. If the procedure is running in exclusive mode then this could result in a total lockup. The script interpreter if running with
ERROR_CHECK activated will attempt to spot these so called ‘endless loops’ and report an error.
Here is an example of an ‘endless loop’
VAR = 10
…do some code in here but never change VAR
VAR is always greater than
1, (as it's set to
10) the loop will never end.
VAR = 10
…do some code in here
will end because after a few cycles around the loop,
VAR eventually becomes not greater than
WHILE has a ‘scope range’ of 255. This means that the
WHILE can only see 255 instructions ahead of itself to find the
Several conditions can be chained together using
AND, same as with
while VAR1=10 and VAR2 =0
…do some code in here
WHILE usefully in time-sharing mode in a gameEdit
The fact that a
WHILE loop can be left ‘hanging’ and waiting for a condition to be met can be very useful in time-sharing mode. This example uses a Sphinx specific instruction ‘
GetObjective’ which returns the value of a game objective variable from the game’s AI. The loop will remain running until the
GetObjective function places a non zero value in the variable
Int var = 0
while var =0
var = getobjective HC_SPECIALEVENT1
Because it's being used in a time-sharing function, it won’t hang the game. The game would run happily in the foreground and this loop would cycle in the background. At some point, the game’s AI will set the Objective ‘Special Event 1’ to a none-zero value meaning perhaps a task has been completed.
As soon as the objective is achieved, the loop will exit and more script code below can be processed. Effectively this sort of coding would work very efficiently for mission and level control duties.
NEVER use this kind of method in
EXCLUSIVE mode. As it will cause a lockup. In
Exclusive mode, the game’s AI isn’t allowed to run along side the script. As such it would never get the chance to set the Special Event 1 flag, therefore the loop would never exit.
This covers both inserting this code into an
EXCLUSIVE declared function, or in a region of code that runs in
EXCLUSIVE 1 mode..
All code must be contained within procedures. The only lines that may be included outside of a procedure are variable declarations. Variables declared outside of a procedure are termed as ‘global variables’ and can be accessed by all procedures within the file. Variables declared within a procedure are termed ‘local variables’ and can only be accessed by instructions in that function.
There is no scope range on procedure calling.
- This denotes the start of a new procedure
- All script files must contain at least one of them
- A new procedure cannot be started within another procedure
- The proc name is used by other procedures to call it and also references the procedure for calling from outside the script engine
ENDPROC <optional variable or number>Edit
- This denotes the end of a procedure and causes program to cleanly exit that procedure
- Exclusive, loop and conditional states from previous calling procedure are restored
- Every procedure must have one
- It can optionally return a variable, constant or hash-code. If not required, just leave it out
LEAVEPROC <variable or number>Edit
- Optionally allows a subroutine to exit cleanly at any point.
- Exclusive, loop and conditional states from previous calling procedure are restored
- It can optionally return a variable, constant or hash-code. If not required, just leave it out
variable = CALLPROC Edit
- This command can be used to call another procedure
- The procedure being called must be defined within this file
- A procedure cannot call itself (recursion) – compiler will report this as an error
- Procedure calls can be nested however, e.g.
Proc2, which in turn can call
Proc3will complete first, followed by 2, then 1.
- If you are expecting a value to be returned from the procedure, it will be placed in the optional variable before the
variable = INSTANCE Edit
- Similar* to the above but with these differences
- This command can create a new instance of another time sharing procedure that will run independently of this one.
- Will not work with
- The new procedure will only execute a few instructions when instanced until next polled
- The instance number is returned in the variable, if the variable is supplied.
variable = ISRUNNING Edit
1or zero if a named instance is running
- The command only sees base level instances. These are procedures called from the main game, or by the
INSTANCEcommand in a piece of script code
- It cannot see procedures that have been
CALLPROC’d from another procedure. It will only ‘see’ the base level instance that did the call proc
EXCLUSIVE < ? term>Edit
- Can be used in 2 ways
- if used after the function name on a
defproc, it declares that the whole procedure will be run in exclusive mode when called from outside the script language. No term is required after the keyword
- if used within a function it must be followed by a
0. It can be used to switch the parser into or out of exclusive mode on a per group of instructions basis
- if used after the function name on a
- If a procedure with exclusive declared in its definition is called from outside of the script language, it will ignore any requests to drop to none exclusive mode
- If a procedure running in none exclusive mode calls a procedure with exclusive declared in its definition, it will ignore the exclusive setting on the
defproc, however it will respond to inline exclusive switches
0state is ‘stacked’ when another procedure is called. When the called procedure ends, the state will revert to the state that it was before it was called. This is for safety reasons to prevent badly written rogue procedures un-balancing the state.
- Suspends another instance. Effectively pauses it.
- Everything is preserved, including its variables
- The command also blocks any attempts to run an instance of this script
- You can substitute ALL for the proc name to suspend every other instance except for the one that invokes the suspend command. (you can suspend it using its proc name)
- Be careful. Don’t end up in a situation where every instance is suspended and locked out.
- Resumes a suspended instance from the point where it was paused.
- Execution continues exactly as normal
- ALL can be substituted for proc name
- Causes an instance to be reset and to run again immediately right from the start
- All variables are reset
- All can be substituted for proc name.
- Similar to Suspend except that it completely kills the procedure
- Nothing is preserved from the killed procedure
GOTO itself is an unconditional, unstructured jump instruction which simply tells the program execution to jump to another point. This can be very desirable, however its usage is restricted by the compiler due to the problems it could cause otherwise at runtime.
GOTO’s ability to cause endless loops. These are situations where a
GOTO will repeatedly jump back to a
LABEL with no way out. Sometimes these can be desirable (such as getting a piece of AI to loop indefinitely), but they should never be invoked in an
EXCLUSIVE mode procedure or region of code as the game will lock up!!!!
GOTO <label name>
- Tells the program to jump to another point which is signified by the
LABELcommand (see below)
- No conditions need to be met,
GOTOwill always jump
GOTOcan only be used to jump to another point within the same procedure
LABEL <label name> OR
<label name>: ← note colon
- Tells the compiler that there is a label called
<label name>at this point in the code
- Can either be in the form
LABEL <name>, or just the
<name>followed by a colon, with NO SPACE between the name and the colon
LABELinstruction can only be used at the ‘top level’ scope within a procedure. By this it means that it cannot be used within
SWITCHsequences. This is to prevent stack problems occurring at run time.
- A Label must be in the same procedure as its
This would simply display the words ‘Sphinx Was Here’ over and over again to the debugger’s output window. The typical ‘novice’ BASIC application :)
This example is an ‘endless loop’. There is no way the interpreter can escape from it. Its therefore quite a bad example, but for old times sake, it had to be written.
Sphinx Specific InstructionsEdit
In addition to the general instruction set, the language currently implements instructions specific to Sphinx. At a later stage these can be replaced with instructions specific to any other game, as long as they follow the set of guidelines concerning the creation of new instructions (see the Interpreter Usage doc)
The Sphinx Specific instructions are mapped directly onto
.cpp interface functions in the game’s AI and HUD. Most instructions perform simple set/get/adjust functions, some reference game text, and some reference Hash-Codes.
Objective Variable InstructionsEdit
The Objective variables are a series of variables included in the game that are identified by hash-codes. The advantage of them is that they are completely global to all currently running scripts and to the actual game code. Also they are saved in save-games. As such they are used to preserve game progress information.
They come in 2 pairs. The first set allows the storing and reading of complete 32 bit values into the variables, the second allows each of the variables to be used as 32 on/off flags.
Each set can read variables used by the other set, which could be handy if combined with the bit-wise logic maths instructions that the language supports
< var > = GETOBJECTIVE < hashcode >Edit
- Gets the state of an objective variable via a hashcode and stores it in a script variable
SETOBJECTIVE < hashcode > < var >Edit
- Sets the state of an objective variable via a hashcode from a script variable
<var> = GETOBJECTIVEBIT <hashcode> <bit number 0-31>Edit
- Gets the value of one bit of an objective variable, as above and stores it in script variable
- Allows an objective variable to store 32 separate and independent on/off states as opposed to just one number. Handy if you know what you’re doing!!!
SETOBJECTIVEBIT <hashcode> <bit number 0-31> <1 / 0>Edit
- Sets the value of one bit of an objective variable. Any non-zero number as the last parameter will result in the bit being set. The bit number can either be a number 0-31 or a variable containing that number, the last parameter must be a number
General AI InstructionsEdit
- Tells the AI that the current object is currently under control of the script language and not under AI control
- Tells the AI that it is back in control of the current object
<var> = SETANIM < hashcode >Edit
- Sets an animation for the script’s owning character. Will optionally return the number of frames the animation will play for in the variable preceding it
<var> = GETANIMEdit
- Returns the hash-code ID of the animation the NPC is currently performing to a variable
<code>SETBLENDDEPTH <var or const (0-500) >Edit
- Sets the level of animation blending between animations of the same type, eg from a walk to a job to a run.
- There are up to 5 levels for each animation mode, therefore 0-99 represents blending between for example Move to Move2, 100-199 = Move2 to Move3 etc etc. Ask Richard :)
- Tells the script language to wait for a designated number of frames
FREEZEGAME <1 / 0 >Edit
- Allows coder to completely freeze everything except the camera. This includes all AI and animation. Only the camera will move. Passing a
1freezes passing a
LOCKCONTROLS < 1 / 0 >Edit
- Freezes all joy-pad input for the main character. Does it nicely in terms of player’s anim mode etc, so there’s no jarring etc.
LockControls 0unlocks controls
- Locks the controls regardless of player anim-mode. Use
Lockcontrols 0to unlock!
Controller Rumble commands Edit
There are a set of special commands to control the rumble feature of joy-pads.
STARTRUMBLE <strength 0-255> <duration in frames>Edit
- This starts the control pad rumbling.
- Stops a pad vibrating
- Stops all pads rumbling
Sound and Music commandsEdit
StartMusic <MFX_ hash> volumeEdit
- Starts a piece of music playing, taking into account any music fader set up.
- Volume range 0 to 127
MusicFade <fade in> <fade out> <cross fade>Edit
- Sets up a fade for the next piece of music to start with
- Fade in and out values range between 0 and 255. 0 being an instant start and 255 being 10 seconds. Weird range, yes, but its to keep it inline with the triggers
- Cross Fade 1/0. Controls whether the 2 pieces of music cross fade between each other.
- Stops any music track currently playing, without fading.
StartSound <SFX_hash> volumeEdit
- Starts a sound effect playing
- Stops a looping sound effect from playing
- Runs a cut-sequence! Script execution is halted as it plays and will resume from the point where it paused when the cut sequence ends
- This instruction halts all script processing on this trigger, it then signals to the engine that this character, its trigger and all its data is to be removed from the map and deleted asap.
SENDMESSAGE <ref identifier> <message>Edit
- Sends a message to another trigger that this one is linked to. (Set via the reference identifiers on the trigger’s properties in Euroland)
- LEGACY COMMAND. Only retained for compatibility. Use the command below where possible
SENDHASHCODE <HT_HashcodeMessage> <Ref identifier>Edit
- Sends a predefined message type to another ref identifier
- Message types can be found by running the
SETLINKVAR <Receive Variable> <Send Variable> <Ref identifier>Edit
- Sets one of the global internal variables to the value in the send variable. (See later section on variables for the internal globals)
SETID <ID number>Edit
- Allows the language to set an ID number for this object. The ID is used in connection with other instructions in the language
<variable> = GETDISTANCETOID <ID NUMBER>Edit
- Returns the distance to another scripted trigger which has an ID that matches the ID number here.
<variable> = GETDISTANCETOLINK <ref identifier>Edit
- Returns the distance to another trigger which this one is linked to
- Enables the rendering of a particular entity which is part of this character. For example it can be used to switch on body parts or items that the character may be carrying
- Disables the rendering of a particular entity which is part of this character. Could be used to hide items the character is carrying or remove body parts
variable = GETPLAYERBUTTON <hashcode>Edit
- Gets the state of a joy pad button using an associated hashcode. Current codes are:
- Performs a predefined screenfade as described by the hashcode. Current fades are:
- Causes the player to teleport to a linked trigger. The player will assume the position and orientation of this trigger
- Makes the player character play an animation. Currently not working
variable = GETMAPHASHEdit
- Retrieves the numeric hash code of the currently loaded (EDB) map file.
- Checks if the item for the linked trigger currently exists as an active "entity" (i.e. not faded out) and returns a boolean expression if so (0/1). [swy]
Misc Mobility and Position CommandsEdit
Commands specifically related to moving the object or NPC around and positioning of related objects
- Sets the position of the current trigger to that of a linked trigger
- Sets the position of a linked trigger to that of the current trigger
- Activates a linked trigger
<variable> = GETDISTANCETOLINK <ID NUMBER>Edit
- Returns the distance to another trigger that this one is linked to.
<variable> = GETDISTANCETOSPAWNEdit
- Returns the distance to the NPC’s original spawn point
- Tells the NPC to head back to its original spawn point. The character will keep walking until told otherwise
FOLLOWPLAYER <optional 1>Edit
- Sets the character’s destination as being the player character. If the character is in a move state, it will follow the player indefinitely until told to go somewhere else.
- If desired, passing an optional
1after the command causes the character to automatically walk/idle depending on its range from the player. If this is used, you must use the
STOPMOVINGcommand or give him another mobility command to stop it moving as the walk/idle is fully automatic.
- Similar to above except the character runs in completely the opposite direction
- Tells the NPC to stay where it is, stop moving around and sets the character to its primary IDLE animation. This will override any movement currently in progress
- Character will follow another linked NPC around. It will do this until its told otherwise.
- Similar to Follow player, passing an optional 1 on the end will place the character in an automatic walk/idle state depending on its distance from the linked object. Again, to stop this you must use a STOPMOVING command or tell him to go somewhere else
- NPC will turn to look at another linked trigger. The command blocks all processing on this instance until the NPC is facing what you told it to face
- The command automatically sets the
TurnRight, so the character needs to have these animations present
- This command returns
1if the NPC is looking in the direction of the linked NPC.
- Very simple grouping/scattering control which affects all NPCs that this trigger links to in its reference identifier list.. so be careful when you use it. It's not proper flocking yet, but its similar enough for now until it gets written
- Passing a zero tells all linked triggers to stop moving
- Passing a
1tells them to follow this trigger.
- Passing a
2tells them to run away from this trigger.
- Deactivates a linked trigger. [swy]
Miscellaneous Flag and value setting commandsEdit
These commands are controlled via hash-codes and are used to set and get the values of various in-game variables and flags. The commands are general purpose. The values can be passed in as constants (max 255) or as a variable (any size)
SetItemValue <Hash-Code> ValueEdit
variable = GetItemValue <Hash-Code>Edit
These first ones allow the trigger to switch the mummy into a certain ability mode. The value is ‘how long’ for in frames, 999 being infinite
Sets the health level of an item
SetItemFlag <Hash-Code> 1/0Edit
variable = GetItemFlag <Hash-Code>Edit
- Sets the item to be invincible or not
- Sets whether a character should ping pong on its path or if it should be cyclic
- Used on context triggers to disable them from within. Player character won’t ‘see’ them any more
- Used with GetItemFlag to see if character is carrying a pickupable object
- Used with SetItemFlag to switch on or off head-tracking on the NPC, if on, and if the NPC has the ability, it will turn its head to look at the player
- When used with SetItemFlag, the player will drop any pickup-able that he is carrying
- When used with SetItemFlag, any pickup-able the player is carrying will respawn
Path/Node Mobility and Position CommandsEdit
These commands are used to control the movement of characters via paths. The commands will fail for triggers that are not creatures as stationary or non ‘alive’ objects cannot follow paths. The exception to this rule is a
TR_PATH trigger which allows the use of the
SETPATH command, see below.
Path Controls for NPCsEdit
- Tells the NPC to start following a path as described by the hash code
variable = GETPATHEdit
- Returns the hashcode value of the current path the NPC is following
variable = GETPATHNODEEdit
- Returns the value of the current node the NPC is at on its current path
- Tells the NPC to head to a certain node on its current path
- Forcibly moves an NPC instantly to another place on a path
TURNTOPATHNODE <HT_HASHCODE> <variable>Edit
- Makes NPC turn and face a node on a supplied path. Command will block script processing on that instance until character is facing the node
FACEPATHNODE <HT_HASHCODE> <variable>Edit
- Makes face a node on a supplied path immediately
Path Control for
SETPATH <HT_HASHCODE> <speed 0-255cm>
- Similar to above except the user can pass an additional number after the hashcode which states the speed in cm/sec that the entity attached to the trigger can move at. If no number is passed, then the entity assumes the speed will remain unchanged.
Direct Movement and rotation of itemsEdit
It's possible to directly move objects around without paths etc. This would be of more use for mini-games etc, but you might find a use for it
There is only one command and a series of hash-codes. Include the hashcode for the type of movement that you require.
MOVEITEM <HT_Hashcode> amountEdit
- The amount variable represents either centimeters for movement or 100ths of a radian for rotation. (eg
100would equal one metre, or
1radian. A full circle is
2*PI radians = 618) Note, due to storage constraints, if you pass a constant number, it is in the range
127. Anything larger, use a variable
- Rotates the object around its Y axis relative to its current orientation
- Sets the Y axis rotation
- Moves the object forwards and backwards along its Y Axis orientation
- Moves the object side to side perpendicular its Y Axis orientation
- Moves the object along the map’s X axis
- Moves the object along the map’s Y axis
- Moves the object along the map’s Z axis
- Sets the objects X position in world space
- Sets the objects Y position in world space
- Sets the objects Z position in world space
Player Interaction CommandsEdit
<variable> = GETDISTANCETOPLAYEREdit
- Returns the distance the player is away from the NPC in centimeters to the variable.
- Character will turn its head to watch the player (not used yet)
- Character will stop what its doing and turn around and face the player. This command requires the character to have Turn Left and Right on the spot animations. This instruction will block all subsequent commands until the NPC has turned around to the player’s direction.
- If no hashcode is passed, the command will assume the default
Right. Alternatively the user can pass in the hashcode of a turn left or right animation and it will use that pair of animations to turn the character. Only one hashcode needs to be passed, the language will assume it has to use the corresponding one in the other direction.
- While a message box is on the screen for the character talking, the character will run a talk animation while the text is being typed, and an idle when its waiting. The command will exit when the message box closes.
- The hashcode is optional. If passed, it will use that hashcode to perform the talking animation, if not passed it will use the character’s first talk animation
- Additionally, the idle it uses will be the one running prior to the talk to player command being issued. If an animation other than idle was being played then the command will use the character’s first idle animation.
<variable> = IsFacingPlayerEdit
0depending whether or not the character is facing the player character. The character has a leeway of 10 degrees
<variable> = GetTurnDirectionEdit
-1if the character must turn left to face the player,
+1if it must turn right, or zero if it is already facing the player
- Passing 1 or 0 to this tells the NPC whether to enter context sensitive situations, such as talking, with the player character or to ignore him. If you pass a 1 the NPC will ignore the player, passing zero it will respond to the player. An example would be where an NPC is busy doing something and ‘doesn’t want to be disturbed’
- The command also sets the passive state of the AI. If the character is a monster and he’s been set to ignore the player, he will cease to attack.
- This command tells all other NPCs that an NPC is currently interacting with the player. This in turn prevents them from attempting to interact with the player themselves.
- It works by blocking all context sensitive interaction for all other NPCs’ scripts. In general it shouldn’t be needed much because an NPC’s OnContext procedure automatically switches the mode on when it starts and off when it exits. This command is provided incase you wish to change the mode under any other circumstances. If you switch it on, remember to switch it off again!!!
Help Interface InstructionsEdit
PRINTMESSAGE < hashcode >Edit
- Prints a line of text in a help box, using its hashcode as an ID
< var > = GETABUTTONPRESSEdit
- Returns which onscreen button on a help window was pressed (if any)
< var > = ISTHEREHTEXTEdit
- Returns whether there’s any text in a help window
< var > = ISSAIDEdit
- Has everything been said and has user pressed button to close message. Returns 1 if this has happened, otherwise returns 0
- Clears the help window
INVENTORYADD <HT_HASHCODE> variable or
- Adds an item who’s type is described by the hash code to the player’s inventory. The variable may be omitted, whereby the instruction will assume you meant to add 1.
- By passing in negative values you can remove items from the inventory. Items covered by this include darts, scarabs and quest items
- The value can be passed as either a variable or a number. Due to instruction storage constraints, if you choose to pass a constant number it can only be in the range
127. If you use a variable it can be any size you want.
InventoryAdd HT_Item_Pickup_BronzeScarab 10
INVENTORYSET <HT_HASHCODE> variable or numberEdit
- Similar to the above except it sets the inventory to the value as opposed to adding the value onto what’s already in the inventory.
- Range of constant number if used is 0-255.
InventorySet HT_Item_Pickup_BronzeScarab 50
INVENTORYMAXSET <HT_HASHCODE> variable or numberEdit
- Sets max number of a certain type of object that can be held in inventory. Doesn’t actually change the amount of that object in the inventory
- Range of constant number if used is
<var> = QUERYINVENTORY <HT_HASHCODE>Edit
- Finds the number of items of the type described by the hashcode in the player’s inventory
Scarabs = QueryInventory HT_Item_Pickup_BronzeScarab
<var> = ITEMSELECTOR <HT_HASHCODE_INVENTORY FILTER>Edit
- Brings up the item selector rotator to allow player to select an item to give to say an NPC
- Returns either the hashcode of the item,
0if there are no items, or
-1if player cancels
Var = itemselector HT_Trig_InvFilter_QuestItems
<var> = QUERYINVTYPES <HT_HASHCODE>Edit
- Returns number of items of a certain type as specified with hashcode.
- No idea what its for, ask Lenny.
- Current applicable hash codes are:
- If you need any more, ask Lenny.
The player’s health level can be modified using the following set of commands and hash-codes. The health bar is made up of Gold Ankhs. The number of Gold Ankhs represent the amount of health a player is able to carry, each Ankh representing 3 health units. Collecting a Gold Ankh increases this. Additionally the script language can modify the number.
The actual player health itself is represented by how many of these Gold Ankhs are filled. They are filled by collecting silver and bronze Ankhs
< var > = GETHEALTHEdit
- Gets current health of player in single health units
< var > = GETGOLDANKHEdit
- Get number of gold Ankhs owned by the player
- Sets health to maximum. All Gold Ankhs on the screen will be filled
ANKHADD <Hash-Code> <number>Edit
- Adds a number of ankhs of type described by the hash-code to the player’s health bar
- If the user adds a Gold Ankh, this will increase the number of Gold Ankh health containers
- If the user adds a Bronze or Silver ankh, it will add actual health to the player and fill up the gold ankhs on screen
- Range of number is
ANKHSET <Hash-Code> <number>Edit
- As above but it sets the exact number of ankhs instead of adding to it
- Range of Number is
These are the hash-codes that directly relate to the
- Bronze Ankh adds 3 health units to the health bar (fills one Gold Ankh)
- Silver Ankh adds 24 health units to the health bar (fills 8 Gold Ankhs)
- Gold Ankh adds one extra health container to the health bar
These commands are used in addition to the
QueryInventory commands that perform the actual adding to purse and finding contents of purse functionality.
SETMAXSCARAB < var >Edit
- Set maximum number of scarabs player can have (in Bronze scarabs)
< var > = ISWALLETFULLEdit
- Returns whether player’s wallet is full or not
< var > = GETWALLETSPACEEdit
- Returns how much room is left in player’s wallet
SCARABDISPLAY < 1 / 0 >Edit
- Sets whether the scarabs remain onscreen at all times (
1) or revert to normal vanishing after a delay (
These hash-codes can be used with the
InventorySet commands to add and remove scarabs from the player’s purse
- Bronze Scarab = one unit of wealth
- Silver Scarab = five units of wealth
- Gold Scarab = 20 units of wealth
The scripting language can be used to control the camera mode and certain parameters. It can also be used to set up minor cutscenes.
Camera mode change instructionsEdit
These instructions switch the overall operation mode of the camera
- Sets camera mode to normal follow the player mode.
- Camera looks at the trigger running the script
- Switches the camera to a path camera. This will run back and forth along its path always looking at the player and trying to keep as close as it can. The path camera must be linked to this trigger using a reference identifier
- Similar to CameraToMe except the camera turns to look at a trigger linked to this one
- Sets camera to a tracking cam where it remains in one place and tracks the player. The link number references the camera to use
- Sets the camera to first person mode looking from the character’s eyes.
- Conversation camera. Sits behind the player or NPC (whichever is closer) and slightly to one side. Useful for when characters talk to the player
Previous Camera storage and retrievalEdit
These 2 instructions are used to take a ‘snapshot’ of the camera in its current state so it can be restored to its correct state after a script influenced event
- Stores the type of camera the player is using and associated data. Once a camera is stored, no more can be stored until the camera has been restored
- Restores the camera to the stored version. Once camera has been restored, another camera can be stored
Camera Parameter ControlsEdit
When used in conjunction with the normal player camera (
CameraModePlayer) and the Look-At modes (
CameraModeToLink), these instructions can be used to change the angle and distance the camera sits from the item its looking at.
CAMERADISTANCE <variable or constant>Edit
- Sets the distance of the camera to the player in cm. Normal distance is 350
CAMERAELEV <variable or constant>Edit
- Sets the elevation angle of the camera in 1/100ths of a radian. A value of zero is directly above the player and a value of 314 (~PI*100) is directly below
CAMERAHEIGHT <variable or constant>Edit
- Sets the height of the camera relative to the player’s eye level in cm. Normal distance is 120
CAMERATURN <variable or constant>Edit
- Turns the camera in 1/100ths of a radian. A positive value turns clockwise, a negative value turns anticlockwise. 314 (~PI*100) will turn through PI radians or 180 degrees.
CAMERAFREEZE <1 / 0>Edit
- Locks the camera to its current position. It will still track whatever its looking at but won’t physically move.
Camera Tweak ControlsEdit
Camera tweaks are certain events that occur fairly rarely in the game but require one of the game’s cameras to act in a slightly different way
CAMERATWEAK <HT_HashCode> <value>Edit
- Used to switch on or off various camera modes that are used in rare situations. The hashcode describes the mode that you wish to enable (by passing
1) or disable (by passing
- The list will expand as the project continues. Current tweak mode hashcodes, with their trailing values types are:
- This tweaks the climb camera to make the camera look up at the player
- Makes the camera have a tiny radius allowing small gap access
- Controls dynamic Field of View on an NPC. Causes field of view to shorted when near an NPC. Its default state is ON with a radius of 30m. Any more and it interferes, and less causes ‘vertigo’ effects. The correct effect is that it makes NPCs seem closer when you approach them, but allows open areas to look more ‘vast’ when you don’t
- Allows the camera to make more effort to keep this NPC in shot with the player. It doesn’t fully track, it just allows the camera’s focus to ‘tend towards’ a position where it can keep the NPC in shot for longer. A radius of 15 metres is good. Anything larger can get annoying. Should only be used on fighting characters, not on talking characters.
- Dynamic Focus doesn’t work well with Dynamic FOV!!
- Camera turns through Y axis to keep the NPC in shot. Very effective and should be limited to ONE character at a time in a one-on-one situation.
HT_CameraMode_AutoStrafeWithHeight < radius >Edit
- As above, but camera will change elevation as well turning
HT_CameraMode_StrafeOnlyHeight < radius >Edit
- As above, but camera will change only elevation
- Causes normal player camera to act in reverse and try to look at the player from the front instead of the back
Camera Scene Script Path commandsEdit
The language allows the script coder to create simple cut-scenes by controlling where the camera is positioned and what its looking at, be it the player or any other trigger that is linked to the trigger that is controlling the camera. The positioning of the camera is done via a path.
The mode is invoked with the
CameraModeScriptPath command with the hashcode of the path you wish to sit the camera on. The camera can then freely be moved between nodes and told what to look at
CAMERAPOSITIONATNODE <node number>Edit
- Camera is forcibly moved to a node on the path
CAMERAGOTONODE <node number>Edit
- Camera will drift towards the node number at the speed you specify (see below). It will remain looking at what it was looking at before
- Camera will point at the player
- Camera will point at a trigger linked to the trigger running the script
- Camera will look at the trigger running the script
CAMERATURNSPEED <speed 0-1000>Edit
- Sets the speed at which the camera will turn to look at a new object. 0 is completely motionless and 1000 is instantaneous
CAMERAMOVESPEED <speed 0-1000>Edit
- Sets the speed at which the camera will move between nodes. Speed works as above.
The in-game HUD can be controlled using one command and a series of hash-codes.
The command is:
HUDCONTROL <Hash-Code> Value1 Value2Edit
The hashcode in this case is effectively a control code. The 2 values following it are optional. The actual hash-codes will appear as time goes on. Some will take the extra values and some will not. The main use for this will be to activate/de-activate different special modes, such as for mini-games etc
The values can either be variables or constants. Constants can only be in the range of
255 due to storage constraints. Any larger values can be passed via a variable.
Hashcodes will be added here as they are needed.
Disabled Popup Inventory instructionsEdit
These don't work in the final Sphinx version, they are commented out because the HUD element wasn't implemented, probably an early version of the item rotator. To pick and list items. [swy]
- Cause popup inventory to appear, show the exact inventory item match, documented here for posterity.
- Hide it.
- Add an inventory object to it.
variable = QUERYPOPUPINV
- Return the current state of the popup inventory, if shown or hidden, as a boolean.
Book Of Sphinx CommandsEdit
These commands allow entries to be made and removed from the book
BookAddNote <hashcode> <variable>Edit
- Adds a new quest note to the book. The hashcode is the hash of the title and the variable must contain the hashcode of the description text
- Might seem weird to use the variable instead of another hashcode but the instructions aren’t large enough to contain the data for 2 hashcodes!!
- Removes a quest note from the book
BookAddPicture <hashcode> <variable>Edit
- Adds a quest picture to the book to chart progress through the game
- Hashcode is that of the picture. The variable is the hashcode of the text description of the picture.
Script Macro InstructionsEdit
These instructions are used to replace tasks that are impossible or overly difficult in the scripting language.
The format is always the same:
Variable = Macro HT_Gamescript_Macro_NameOfMacro variable variableEdit
- The variables at the end currently must be sent even if not required. Just pass in any old variable.
- The result is always passed back in the prefixing variable.
Current Macro Instruction hash-codesEdit
- The fortune teller in the Heliopolis Cursed palace. This macro decides what she’s going to say based on the state of several objective variables. This is an unused 'smart' mechanic in the final game that can be mirrored via scripting via arrays, it works roughly like this:
- It loops over the all objectives from
HT_Objective_Glo_AnkhPiece_20. Checking if each objective is set to 1 or 2 and accumulating them:
- One means that said 'glyph' can be found at that point in the story (one can physically access that Gold Ankh Piece).
- Two means done/found.
- It transparently gives the gamescript the 'best' response to show onscreen depending on context:
- Returning one text hashcode from
HT_Text_Hel_CursedPalace_FortuneTeller_AnkhPieceClue20, randomly, from the generated list of the ones that can be found (set to one), but only if at least one of them is available.
HT_Text_Hel_CursedPalace_FortuneTeller_AnkhPiecesCompleteif all 20 are done.
- Returning one text hashcode from
- It loops over the all objectives from
- Locks the
HT_GPad_Script_LockFirstPersoncontextual button/action in the D-Pad. Set it to off and on again.
- Starts the music playback in a special minigame mode, with slightly different rules and behavior. Set the numeric parameter to choose which one of them:
- Discarded, no traces remain; probably used to revert from the winged lion mode.
HT_GameScript_Macro_GetShopItem <item-inventory-hashcode> <buy-or-sell-1/0>Edit
- Dynamically retrieve the buying or setting price from the
HT_SpreadSheet_ShopPricesspreadsheet, retrieved from the
HT_GameScript_Macro_GetMonstName <monster-file-hashcode> <name-type-for-shop-or-museum-1/0>Edit
- Takes in a monster hash and provides the hash to the name of that monster. The table is hardcoded.
- For example, if we feed it
HT_File_BM09_Smiling_burbleit will return
HT_Text_Inv_BM09_Smiling_burble(if the second parameter is set to 1) or
HT_Text_Museum_Name_BM09_Smiling_burble. If the monster hashcode is invalid it returns
- For example, if we feed it
- Rotate the camera angle with somewhat complex trigonometry, then tween during 80 frames from the current position, then set the camera to normal by enforcing it and refresh the context buttons. Interacts with the following operations that also get or set the return angle:
- If the help window is active; either by being visible and fading or teletyping (having moving text, not being empty) in dialogs or hints it returns 1, otherwise 0. Don't ask me why, kind of unclear, search for examples in the gamescript codebase.
- Discarded, defined from somewhere but unused in the current PC version. Achievements are hardcoded to objectives and other conditions in the normal restored versions.
- Read a persistent, mod-specific value from
Sphinx.ini, using the first parameter as key. It can be used as an alternative way of storing objectives. Returns the value itself or -1 if the key doesn't exist. New in the PC version from 2020 onwards. [swy]
- Write a persistent, mod-specific value to
Sphinx.ini, using the first parameter as key. It can be used as an alternative way of storing objectives. New in the PC version from 2020 onwards. [swy]
- Same as
GetModValuebut game-wide, all mods share the same persistent storage indexes. [swy]
HT_GameScript_Macro_SetGlobalValue <number-or-hashcode> <value>Edit
- Same as
SetModValuebut game-wide, all mods share the same persistent storage indexes. [swy]
- Returns the current game version as a date in the YYYYMMDD format (e.g. 20200206). When unimplemented in any previous versions it returns 0, just like any other unimplemented macros. It can be used to easily check for extended functionality and operations depending on when they were introduced and have a fallback path for older game versions. [swy]
- Returns the current save slot index for the active game, the first slot starts at 0, the second is 1 and the third one is 2. If no save slot is currently selected it returns -1. It can be used as a way of having multiple per-save namespaces with the
Set/GetModValue-family of functions. [swy]
- If set to 0, returns the current local computer time as a single number in the HHMMSS format. e.g. 235912 for 11:59:12 PM. It can go backwards or forwards due to drift, timezones, or daylight savings. So don't use it as monotonic clock or for doing arithmetic operations (use
GETTIMERfor that), useful if you want to map game actions with real-time actions (i.e. night time). [swy]
- If set to 1, returns the local computer date as a single number in the YYYYMMSS format, e.g. 20200206 for February 6th, 2020. Same caveats apply. [swy]
- Get the current AnimSkin for the child item of this trigger, if any. [swy]
- Set the active AnimSkin for the child item of this trigger, if any. With this macro you can give custom armors to any of the main charaters and more complex behaviors. The first parameter can be -1 to reuse the base/first item animator EDB file, instead of using an external one. [swy]
HT_GameScript_Macro_AddAnimator <HT_File_*> <HT_Script_*>Edit
- Appends a new animator to to the curren't trigger's child item. The first parameter can be -1 to reuse the base/first item animator EDB file, instead of using an external one. [swy]
- Removes one animator with the same hashcode, only if found in the list of active animators of the current trigger's item. [swy]
Variables allow you to do many things in the language that you otherwise wouldn’t be able to do. For those unfamiliar with variables, they are effectively, in simple terms a ‘name’ that represents a number.
That name can then be used where you’d normally use a number. As the word ‘variable’ suggests, they can vary and be changed. When they are changed, every other bit of code that subsequently uses them will also be subjected to that change.
Variables must be declared in a piece of script code before they can be used. This effectively tells the compiler that it needs to subsequently look out for that name and that it then knows that the name is a variable.
To declare a variable we use the keyword
Variables fall into 2 groups. Global variables and Local variables.
A local variable can only be accessed by instructions in the procedure its declared in (see procedures), whereas a global can be accessed anywhere.
Global variables must be declared first in a script file and must ALL be before the first procedure. If you try and shove them in after that, the compiler will report an error. Once declared, it can be accessed from anywhere within that script file. But that name is now taken, it cannot be declared again in that file (even as a local)
The name can however be used in other files.
Locals are declared within procedures (i.e. after the
defproc and before the
endproc). A variable cannot be declared twice within a procedure, however the name can be used again in a different procedure as it can only be used by commands within that procedure.
Variable names can be a maximum of 15 characters long. Keep them sensible!! They can be composed of any character you wish, however once a space is found in a variable name, it will assume that any characters after that are not part of the name!
Every script has a set of internal global variables. These are named
ARG0 through to
Any script can use them, and the setlinkvar instruction can allow another script to access them.
Additionally there are 2 other variables called
CUTSEQ. These are set when messages are received by the
OnMessage v-table function or a cut scene finishes.
Rules and tips:Edit
- Global variable access is slightly quicker at run time but they can also be overwritten by other procedures in the current script. They also use less memory in time sharing mode than locals
- Local variables are untouchable by any other procedure but use more memory in time sharing mode than global ones.
- Max 15 characters long in name
- Be sensible. Don’t declare variables for the sake of it. Try to reuse an existing one if its appropriate and safe to do so.
- If at all possible, use global, but be careful!
- You can declare a maximum of 225 variables in one file. While its possible, its really not recommended as they will use memory up. 225 variables, plus all the internal
The language allows for simple mathematical evaluation to be completed. It follows the standard mathematical lines on operator precedence, as in which maths operations to perform before others. The compiler breaks down maths operations with more than one operation on a line into several single maths operations.
It also tries to simplify the expression where constants are concerned by attempting to pre-evaluate where possible to save calculation time at run-time
The evaluator doesn’t understand the use of brackets, so maths expressions must be carefully constructed to get the correct result.
For example it will perform a multiply or divide before a plus or a minus. Operations with the same level of precedence will be performed on a left to right basis throughout a maths expression.
The complete list of maths operations, in order of precedence (level 1 is first), is:
- Level 1 -
/multiply and divide.
- Level 2 -
-add and subtract.
- Level 3 -
>bit-wise shift left or right.
- Level 4 -
TEST1 = TEST2 * TEST3 + TEST4
This would break down to the following maths operations (in order)
temporary = TEST2 * TEST3
TEST1 = temporary + TEST4
TEST1 = TEST2 * TEST3 + TEST4 * -TEST5 + 8
Would break down to
temporary1 = TEST2 * TEST3
temporary2 = TEST4 * -TEST5
temporary1 = temporary1 + temporary2
TEST1 = temporary1 + 8
General rules of thumb are:
- It doesn’t understand brackets/braces etc. It will throw an error if it finds them
- Keep expressions to a maximum of 7 mathematical operations
- You can’t use maths calculations as part of a comparison. Pre-calculate what you want to compare first
- Where possible, try and keep them as simple and as few and far between as possible as the more complex they are, the more work has to be done by the parser.
- If you’re going to use the same answer over and over again, work it out once and store it in a variable, then use that.
Procedures are a vital part of the language. First and foremost, no code can be written outside of the scope of an procedure. Secondly procedures provide the ability to write reasonably structured code. Thirdly they provide a convenient way of subdividing code into defined areas each with their own set of variables.
Procedures can be called in two ways:
From an external (outside the script language) place, a script can be run with the
RunScript(number or v-table name); C++ function. This is the starting point of any usage of scripts within a game.
From an internal (a piece of script code in the current object), a script can be run with the
callproc <name> command or instanced with the
instance <name> command (see below)
When procedures are defined, it must be noted that the name of a procedure can be no more than 13 characters long. It can be composed of any SENSIBLE characters you like (sensible = readable and practical!!!), however spaces will terminate the name and it will ignore anything thereafter.
INSTANCE command is used inside script code, it activates a new instance of a time-sharing procedure. This will not start up immediately but will next time the time sharing procedures are polled by the interpreter. Effectively it calls the external
RunScript c++ function.
The new procedure will only execute 8 instructions when instanced and will continue next time it is polled like any other time sharing procedure. This allows it to initially read in and keep any global variables that it may need. It is then throttled back to normal script execution speed.
The instance number for the procedure can be returned to a variable. The reason its returned is so that if needs be you can pass back this value to the c++ code as it might be handy later on.
The command can only instance time-sharing procedures, the compiler will throw an error if you try and use the command on a defined-as-
The command if used incorrectly could be highly dangerous! It should be used with great caution as it could accidentally cause triggering of unexpected events, especially if called multiple times on the same procedure when Blocking mode is deactivated!!!
Its intended main usage is that it could be called by an object’s main set-up procedure to initialise and instance a series of other background tasks. This would mean that a script could transparently activate its background threads without the C++ programmer needing to know anything about them except for to call the object’s initialisation function.
This is by no means the only situation that the command can be used, its fairly versatile.
If in any doubt about using it, firstly work out whether it would be more appropriate to use a
callproc, do you really need to trigger another instance? If still in doubt, come and ask me!
Using Exclusive ModeEdit
As has been mentioned earlier, the language can run in one of 2 modes. Exclusive and none exclusive.
In this mode, a series of instructions can be carried out with the full focus of the parser. It will not be interrupted until either the code completes or the code reverts to none-exclusive mode.
It offers 2 benefits. Firstly the code can guarantee that it’s global variables will never change unless it changes them, and the code will run very much faster whilst in exclusive mode.
A procedure can be declared as exclusive so when invoked from outside the script language it will run until completion. Placing the word ‘
exclusive’ after the procedure definition does this:
defproc MYPROC exclusive
When called in this mode from outside the script language, the code runs in the ‘global instance’ and will ignore any further requests to change its exclusive state in the script code itself
If a function is not declared exclusive in its
defproc, it can still make use of exclusive mode by using the exclusive command within the code, followed by a
1 or a
0 to turn it on or off. Once activated, with an
exclusive 1 as above it will run until the function either ends or an
exclusive 0 is encountered in the code.
If a procedure that has been called from a previous procedure changes the
exclusive state and forgets to change it back, on exit the previous procedure will have its own exclusive state restored.
As above, when activated it has full control over the global variables and can guarantee that they will not change unless it changes them.
The disadvantage of using exclusive mode is that it 100% locks out any other kind of processing until the script is completed. This can cause a problem with badly written code if the script ends up in an ‘endless loop’. Nb In error checking mode it will attempt to spot endless loops.
None Exclusive Mode (or ‘time sharing’ mode)Edit
This method is considered as the default method of execution. A script running in this mode will run concurrently with any other scripts that are using this mode. As such, it’s a form of multi tasking. For critical sections it can enter the above mode for as long as needed. It is also possible to have several copies of the same function running at the same time.
It works by processing a few instructions from each procedure in turn; as such they all run together, yet as far as they’re concerned they don’t know that.
Points to remember are:
- A ‘none exclusive’ procedure’s local variables are still private to it and cannot be modified by another concurrent task (this also applies to several instances of the same procedure running, including any other procedures they call, they won’t overwrite each others’ locals)
- However, they share the same global variables. Any other procedure in the same script file can modify global variables, including the
RETURNxvariables. This means that data a function was relying on can become damaged. As a result, it’s best to copy important data to local variables, or if using globals to pass data between functions, activate/deactivate exclusive mode accordingly.
- For example, to pass data via global variables to another procedure, activate exclusive mode, set the variables, call the proc, read the variables and reset exclusive mode, or use global variables that you know won’t be modified by another procedure.
On the other hand, it offers several benefits. The code can be run wholly in the background. The fact that global variables can change during this can be advantageous. For example a script could simply wait in a loop, checking a variable until it changes.
General rules and tips are:
- Try and keep it simple.
- Make sure you match up
exclusive 1’s with
- When one procedure calls another procedure, the exclusive state is stacked. When the second procedure ends, the
exclusivestate of the first procedure will be restored. This is mainly for safety reasons to prevent dodgy procedures un-balancing the exclusive state.
- Try and avoid using globals that will clash with other processes
- Don’t stay in an
exclusive 1state too long as it defeats the object of time sharing
- When completely dropping out of the language, it’s safe to switch into exclusive mode before exit, and not have to switch back. This makes sure you can write to return variables reliably. When a function exits, no more functions are polled until the polling function is called again next time around.
- Similarly, when called from external, to safely copy
ARGvariables. Switch to
exclusive 1first, copy to locals (for example) and switch back to
Using the V TableEdit
Each project will have a V Table whether it uses it or not. The V Table is set up in the compiler as to which functions it will add to the list.
The V Table itself is simply a list of procedures that are called in situations that occur regularly in most script objects. This doesn’t mean they’ll all call the exact same piece of code. What it means is that most scripted objects are likely to require a piece of script code that is called under very commonly occurring situations such as when they are created or when they are destroyed.
Using the V-Table allows the script interpreter to take a speedy shortcut at runtime to access them, and it allows much greater standardization and portability. If a scripted object doesn’t need a procedure that’s listed in the v-table, then simply don’t supply one. If the engine tries to call a v-table procedure that doesn’t exist then it simply ignores it.
The compiler contains a list of the functions. To use them, you’ll need to know what that project’s list of v-table ‘bound’ functions is. To use them, all you have to do is name your procedure with the appropriate name and the compiler will automatically bind them to the V-Table for that object.
Common v-table procedure names would be (as cited above) “
OnDeath” etc. Therefore to bind with
OnCreate for example would be as simple as:
V-Table checking is not case sensitive so you can use a mix of caps and lower case if you desire.
At the game end of things, this will automatically have created a shortcut to this function that the programmers will already be aware of. If you need the full list of supported v-table functions for your project, ask one of the game-play programmers. V-table procedures, when run from external, ie the game will not throw a script error if they are not present.
The other aspect of this is that if you do not intend your procedure to be included in the v-table, then make sure you don’t call it by a name that is in the table!!
One special case in the V-table is ‘
Main should always be listed in the v-table list in the source files, even if its not required. When a script object is initialized, it will always attempt to start running
Main as soon as the script object is initialized in-game, before it even attempts to run anything else. However, if main isn’t defined as a procedure, then never mind, it won’t run anything!
Main is basically there as a main procedure to initialize data that could be used for the character’s mission control code etc.
In terms of writing the
main function. Generally this procedure should be a none-exclusive one. It doesn’t have to be, but in the context of its usage and the fact it always gets called, make sure that if you do decided to make it run in exclusive mode that you don’t make it too intensive!!
Sphinx’s current V-table functionsEdit
- Always called just after the trigger is created. Use it as the character’s main background processing procedure, character setup etc.
- Called at the point where the trigger is created
- Called when the trigger is suspended (for example when player moves outside its suspend radius).
- Called when the trigger is destroyed and removed from the map. Similarly to OnSuspend, it needs to be written in Exclusive mode
- When the character is hit by the player or a weapon.
- Called when the NPC is killed. Can be used to drop pickups for example
- Called when the NPC arrives at a new node along its current path. Can be used to make character change paths or perhaps play an animation.
- This procedure is also supported for
TR_PATHtriggers, but is only called when the entity reaches the last node.
- Called when the NPC receives a message that has been sent to it either from a trigger or from another script with the SendMessage command
- Called when the player presses the ‘context’ button on their joy-pad in connection with this NPC. Use for conversations etc.
- Called when the player is within the trigger’s context sensitive radius. Similar to above except that it doesn’t wait for the context button to be pressed
- Called immediately after a cut-scene ends. This allows any character movement, removal etc to be processed following a cut scene.
- Called after a collision occurs with a linked trigger’s item. Both triggers must be set to allow solid collision. When called, the function will have the variable
LINKset up with the link number of the object it collided with
Using the Script DebuggerEdit
The script language features a very simple debugging tool that enables the user to step through the code an instruction at a time.
The debugger is activated by inserting the command
<DEBUG> into a script listing. This is effectively a breakpoint. When the interpreter encounters this command, it will pause the game (same as if the user had pressed the BACK/FWD buttons on the mouse) and bring up the debugger window with the current section of code visible.
The user can then press the mouse FWD button to single step through the code a line at a time. The game will also move forward a frame at a time, and BACK button to select a slow motion mode of execution. The debugger follows the code visually on screen, and also displays 2 lists of variables on the right hand side. The first group are global variables and the second group are variables local to the procedure the script interpreter is running.
The current line of execution is highlighted in yellow and above the listing, the debugger displays the trigger type (as seen in Euroland), and the name of the instance that is running. Below that it displays the name of the current procedure the instance is running.
To resume normal flow, either step through the code until the instance completes, or press FWD/BACK on the mouse again. The FWD button single steps instructions and the BACK button runs game in slow-motion mode
To completely disable the debugger, open the ‘Watcher’ menu and uncheck the tick box marked ‘active’ next to ‘Script Debug’. To re-enable it, check the box again.
In addition to this check box, there are 4 further options on the watcher menu.
The first ‘Auto On Error’ sets whether the debugger should automatically activate when an error occurs in a piece of script code. On a code error, the debugger will pop up and the currently selected line will be generally be the line after the error, although the code may have run on slightly. At the bottom of the window, the debugger will display the error code (see list of error codes later in this chapter)
The other 2 checkboxes were added to make the display as useable as possible. Because of the limits of screen resolution, not all of the information will fit onscreen easily at once, and the code lines can cover the variables.
To make the variables appear in-front, check the ‘Vars On Top’ check box. Conversely, if you wish to see the code in-front of the variables, uncheck the box.
The Wide View option is mainly for PC users. Since the resolution of the screen is greater, the debugger can make some use of the extra space. If you wish it to do so, check the ‘Wide View’ checkbox. If you don’t, uncheck it. On PS2, its wise to uncheck it so the information fits onscreen better.
Other points to bear in mind about the debugger:
- It has no access to the ‘real’ hash-code names, therefore it prints out the hex number that corresponds to the hash-code, prefixed with
- Some lines of code may appear different to how you typed them in Euroland. For example ‘
variable++’ will appear as ‘
- Compound maths expressions will be split up over several lines into multiple instructions.
- All blank lines,
REMstatements and labels in your listing will have gone.
These are the error codes that the script interpreter may generate. Some will also be generated by the debugger and displayed at the bottom of the debug window if it starts up due to an error or encounters an error while its active
1 - Unknown ScriptEdit
- Interpreter Cannot find this Script to run
2 - Invalid InstructionEdit
- Interpreter does not recognize this instruction
3 - Invalid Exclusive StateEdit
- Interpreter is attempting to enter an invalid state using
4 - RecursionEdit
- Function is attempting to call itself (possibly indirectly)
5 - Instance OverflowEdit
- No room to start another instance on this object
If Stack OverflowEdit
- Too many nested
Repeat Stack OverflowEdit
- Too many nested
Proc Stack OverflowEdit
- Too many nested procedure calls
9 - Unknown FailureEdit
- General internal failure, tell programming team
10 - Exclusive Cycles ExceededEdit
- Used in debug mode to attempt to prevent endless loop lockups in exclusive mode
11 - Wrong Version NumberEdit
- Interpreter is not same version number as script compiler. Grab again
12 - Function BlockedEdit
- Block mode is on and you’ve attempted to run same procedure twice concurrently
13 - Invalid
- Trying to
repeatwith a value less than
14 - Divide By ZeroEdit
- Trying to divide a number by zero. It's mathematically not possible
15 – Invalid Trigger LinkEdit
- Interpreter has attempted to send a message to another trigger via a link or do some other link related processing on a link that was not set up in Euroland.
16 – Invalid AnimationEdit
- Interpreter has tried to run an animation that doesn’t exist
Sample Script ListingsEdit
The following section contains several example listings, with explanation to demonstrate how to use the language effectively. Important script words particular to each example are highlighted in blue
This is a sphinx scripting version of Hello World! All it serves to demonstrate is how to create a program that will firstly work, and secondly automatically run. It also demonstrates the use of the ‘
debugs’ command to print out text to the debugger window.
The program, as you can see only has one procedure. Because the procedure is called ‘
Main’ it will automatically run as soon as the script object is initialized in game.
debugs commands can be used to output a word of up to 7 letters to the debugger window. They will appear on the same line until a ‘
<CR>’ is found.
This is similar to the above example, except that it uses a repeat loop to cycle through a piece of code ten times, printing a number to the debugger output using the ‘
debug’ command. It then prints Hello World again. Because the number printing is within the loop, it will happen many times, whereas the ‘Hello World’ is outside of a loop, so it will only happen once.
int MyVariable = 0
MyVariable = MyVariable + 1
This code also demonstrates the use of a variable. In this case, the variable is called ‘
The variable is set to zero at the start and is increased every time the code goes around the loop. The
debug command is used to print this number out to the debugger output window.
Also, note the difference between the ‘
debug’ and the ‘
debugs’ command. The S on
debugs means ‘string’.. The
debug command without the S is used to print either numbers or variables.
This piece of code has also been ‘indented’ using tabs. By tabbing, the code becomes instantly more readable as, for example, its immediately readable which code runs within the
repeat loop, and which code does not.
IF allows the program flow to change depending on certain conditions being met. The following piece of code again uses a
repeat loop. Within the loop it compares the value of the variable ‘
5 and prints a different message depending on the outcome.
int MyVariable = 0
if MyVariable < 5
elseif MyVariable >5
MyVariable = MyVariable + 1
The program uses
ELSE. Should the first comparison, designated by the
IF fail, it will proceed to the next. Because the next comparison is an
ELSEIF, again it has to do a comparison. The third however is a simple
ELSE. This basically means ‘every other possibility not already dealt with’. When a condition is met, the language will run only the instructions after it. If another
ELSEIF is encountered, it will jump to the
IF sequence can be placed within another
IF sequence. It is required that the sequences are terminated in reverse order. I.e., the last one to be started must be finished first.
Calling other proceduresEdit
Multiple Procedures are handy as they allow us to sub-divide functional areas of code into different sections. The most obvious example of this in the scripting language is the v-table procedures which are called from the engine. However, its often desirable to take this a stage further and use ‘sub-routines’. This allows us to keep the code much tidier, readable and section specific areas off to one side.
Procedures are called using the ‘
CALLPROC’ command. When called, the program jumps to the new procedure, runs that, then drops back to where it was before it was called. Each procedure has its own local variables, which is another handy aspect.
Simple Procedure callingEdit
Another facility of procedures is the ability to return values back to a variable in the procedure that called them. The standard script syntax is used to do this. They can return variables, constants and hash-codes.
An example of an application for this would be a procedure that looked at all the objective variables and returned a hash-code saying which conversation to start with
Example of Procedures that return a valueEdit
Var = Callproc Test
Int TestVar = 39
Varto zero in
Main, then requests a new value for it from
Testreturns the value of the variable
TestVaron exit and
Mainsets its own variable to that
Using SWITCH statementsEdit
The following example uses a switch statement.
What happens here is that it gets a random value between
MyVariable and prints it to the debugger window.
It gets the random number using the
It then performs a
switch action on the variable to print out the corresponding word. Its similar to an
IF but simpler to use and read.
MyVariable = rand 2
This example resets a variable to zero. It then adds one. If the variable is less than
50, it uses the
GOTO command to jump back to the label ‘
jumpback:’ near the top of the listing. It will only stop jumping back when the variable equals
int MyVariable = 0
Suspending, Resuming and ResettingEdit
The script language allows procedures to halt other procedures in their tracks, or reset them using the 3 commands
reset. These can be useful in situations where you would like a character to stop running a script that was controlling his movement and animation when a certain situation occurred, such as talking to the player.
This example uses the Sphinx specific v-table ‘
OnContext’ procedure which is called when the player attempts to perform a context sensitive situation such as talking to the NPC.
This is a very simple example. Normally
Main would start up and print ‘Reset’ then it would keep printing ‘Hello!’ to the debugger window every 60 frames. If
OnContext is called, it will suspend main for 600 frames and then resume it from where it left off, printing ‘Hello!’.
If you replaced the
resume command with
Main procedure would completely restart and it would print ‘Reset’ first, before printing ‘hello!’
Example of Using Arrays in codeEdit
//this example reads elements of the array in one at a time and outputs them to the debug window
//read an element from the array
test = GetArray TESTARRAY count
debug test HEX
//here’s the array!
//notice that there are 2 array elements per line. In this case, hash-codes
Sphinx Gameplay Script ExamplesEdit
The following is a simple example of using the Sphinx Inventoryadd command.
This code would start up as soon as an NPC was created. It would then repeat 50 times.
On each pass through the loop, it would add 2 items of type ‘
HT_Item_Pickup_BronzeScarab’ to the player’s inventory. Then, using the
Wait command, it pauses for 60 frames, (one second on a 60hz refresh display).
The 2 after the
InventoryAdd command could be replaced by any other number, or a variable. If no number is supplied it assumes you mean to just add one item.
inventoryadd HT_Item_Pickup_BronzeScarab 2
Making an NPC react to a player coming into/leaving rangeEdit
For example: “– The guards will adopt a firm posture when the player approaches (running a specific animation). They will stay in that position (Firm idle) until the player leaves a certain range, and returning to their normal position.”
This piece of code endlessly checks the player’s distance every 30 frames. If the player comes within 500cm of the NPC, it switches to an alert state and sets a variable (
AlertMode) to remember this. The player has to move away by 800cm to get the NPC to return to an idle state, again it sets the variable.
The reason for the variable is so that it knows that its, for example, already alert or not-alert and therefore doesn’t need to re-trigger any animation.
setanim returns the number of frames the animation has and stores it in a variable (Frames). The variable is then used to pause (
wait) the script until the animation is complete.
It also uses
goto to keep endlessly running
int AlertMode = 0
int Frames = 10
Distance = GetDistanceToPlayer
if Distance < 500
if AlertMode = 0
Frames = setanim HT_ALERT_ANIMATION
AlertMode = 1
elseif Distance > 800
if AlertMode = 1
Frames = setanim HT_IDLE_ANIMATION
AlertMode = 0
Using Paths and nodes with a script to control an NPCEdit
This example demonstrates how we can use sphinx specific instructions to control the movement of an NPC. The NPC moves along 2 paths set up in Euroland, this script controls where the NPC moves on these paths.
It uses a further 2 V-table functions.
OnCreate is similar to Main in that it is always called when an NPC is created. It is called after
Main however. Main would generally be used to set up stuff, whereas
OnCreate would be used to set things in motion.
OnArriveAtPathNode is called automatically every time the NPC reaches a node on a path. When called, it uses an
IF statement to work out which path it is currently on. Once it has worked this out, it then uses a
switch statement to perform tasks depending which node along the path the NPC is at.
Other things of interest here are the
SetAnim command and the
SetAnim tells the NPC to start playing an animation, as described by the hash-code after it. SetAnim also returns (if you wish it to) the number of frames long the animation is. Then, the
Wait command then can be used to pause the script for the number of frames the animation lasts for.
Additionally the program uses the
Path = GetPath
Node = GetPathNode
''cont on next page……''
if Path = HT_Path_BoulderPath1
Frames = SetAnim HT_AnimMode_Talk
Frames = SetAnim HT_AnimMode_Talk2
Frames = SetAnim HT_AnimMode_Idle
Delaying animation sequencesEdit
Sometimes it may be desirable to delay animations starting for a set of characters, for example if there are many characters together all running the same animation it may be good to have them running out of sequence with each other, otherwise they may look a bit robotic if they all moved together. For example:
- “Praying - running one animation all the time. There will be the case that there will be two monks together running the same animation at the same time what is not going to look very nice, it would be nice if we can delay the animation a few frames.”
We can do this simply by running a random pause before we trigger the animation to start running
Pause = rand 120
Sending a message to another NPCEdit
This feature enables things like ‘I’m under attack’, ‘I’m dead’, ‘I just did this action’ etc messages to be sent from one NPC to another. It uses the same system as the linked trigger messages set up in Euroland. Therefore the scripts will respond to messaging set up in Euroland and the standard messaging will respond to messages sent by scripts.
The first thing that needs to be done to set up a messaging connection is to link the NPCs’ triggers in Euroland. The link is one way, so if you needed to message 2 ways you’d need to create another link back. To set up a link to another trigger, select the trigger that you wish to send the message from and click on properties, then select the References tab (see below)
Pick a Reference identifier, on the above one I’ve used Reference Identifier #2, then select from the drop down list the trigger you wish to link to, I’m linking FROM ‘
Spider3’ TO ‘
spider2’. You can specify up to of these 8 links. And, obviously any trigger you wish to link to needs to have an identifier name given to it, or you won’t be able to select it.
Once the link is set up, you can use the scripting language
SendMessage command to send values via that reference identifier link to the other script. The other script receives all messages in its ‘
OnMessage’ v-table procedure. Code example follows
EXAMPLE 1 – SimpleEdit
Messaging code for
Spider3, the senderEdit
This piece of code causes
Spider3 to send messages (in this case the number
2) to the trigger specified on Reference Identifier #2 every 60 frames.
sendmessage 2 1
sendmessage 2 2
continued on next page…
Messaging code for
Spider2, the recipientEdit
This piece of code will allow
Spider2 to respond to any messages sent to it. The code doesn’t know who send the message to it, so if you’re using a lot of messaging, use meaningful numbers for different events caused my different senders, unless you wish it to respond in the same way no matter who the sender was. All that happens here is that it print ‘GOT MESSAGE <num>’ to the debug window.
The value of the message received is stored in the global variable
MESSAGE which is already declared.
EXAMPLE 2 – Laughing GuardsEdit
- “This soldier will be shooting darts at the Geb picture at regular intervals. He will always miss and get annoyed with himself. Every time he shoots and misses the other two soldiers will laugh at his efforts.”
Messaging code for main Guard, the senderEdit
Frames = SetAnim HT_SHOOT_DART_AT_GEB_PICTURE
rem Extra code here to wait for Dart to Hit Geb Picture.
sendmessage 1 99
sendmessage 2 99
Frames = SetAnim HT_IDLE_ANIMATION
Messaging code for other Guards, the recipientsEdit
Frames = SetAnim HT_LAUGH_AT_OTHER_SOLDIER
Frames = SetAnim HT_IDLE_ANIMATION
OnMessagev-table function is used. If the message that has been sent to the guards is a one, the code knows the first guard has thrown his dart and missed. The first guard could pass
1, could pass
73, could pass
137, ie anything. As long as the
OnMessagefunction in the recipient knows what value to look for.
Once the correct message comes through (in this case,
99) the guard will play a laugh animation, pause then return to idle.
Introducing Variety to a group of Characters’ behaviorEdit
If we had an area within the game which had a large collection of NPC’s it may be desirable to allocate them with subtly different behavior to give them more personality. For example:
- The monks will have three different behaviors:
- Walking around a path with 5 nodes. If the player approaches a monk (2 meters) he will face him and Talk with the player , then he will carry on walking on the path.
- Other monks won’t talk when the player approaches, they will carry on walking
- And other monks will face the player but they will not talk, they will only run a waiting animation.
To do this we need to assign a random activity to the monks when they are created.
This example is incomplete in terms of the monk’s behavior, however it demonstrates how they can be randomly given behavior and a starting position when they are created.
The example uses extra procedures which are called from Main depending which ‘personality’ the monk has. The reason for this is that it makes the code MUCH more readable in the long run!! All processing that would be used by every monk is processed as usual in main, then the specific processing would be done in each monk’s own behavior procedure.
This example is also using GLOBAL variables. This allows each of the 3 behavioral procedures to read common information such as distance to player and alert status.
rem pick a random start node for monks
Type = rand 5
Continued on next page….
Rem pick a random behavior type for monks
Type = rand 2
Distance = getdistancetoplayer
Continued on next page….
rem Do MonkType 1 behavior in here
rem Do MonkType 2 behavior in here
rem Do MonkType 3 behavior in here
Making an NPC ignore the playerEdit
Certain situations require the NPC to completely ignore any attempt by the player to interact with it. This is done by using the
IGNORE command. Passing a 1 will switch the NPC into an ignoring state and 0 will make it take notice of the player again.
This example makes the NPC alternatively aware of the player for 2 seconds and then ignore him again for 2 seconds. Although not demonstrated by this example, the ignore command does not
Triggering and Reacting to Cut SequencesEdit
It’s possible to activate cut sequences from within the script language. When they are activated all script and AI processing will be suspended until the cut sequence ends.
Once the cut sequence has ended, its often required that NPCs will be moved to different locations, be doing different actions or removed from the map completely, depending upon the piece of story told in the cut sequence.
To allow for this, Sphinx has a V-Table procedure called
OnCutSequence which is automatically called immediately for each NPC after a cut sequence ends. If a character requires no action to be taken for any cut sequences, simply don’t write this procedure!!
The following code example is based on this scenario:
- “Palace - The player approaches the Akarian King and a cut-scene takes over. When the cut-scene finishes, Ishka has vanished from the room”
This requires 2 pieces of script code. One piece for the King so that he will activate his cut sequence as the player approaches, and another for Ishka who will have to move once the cut sequence has completed. The king’s code sets a variable to say whether or not it has already triggered its cut sequence.
Here is the King’s code:
int mode = 0
distance = getdistancetoplayer
The King’s code will run a cut sequence called ‘
HT_KING_CUTSCENE’, it also sets a variable to change the King’s mode. So next time the player is within 2 metres it won’t re-run the cut sequence.
Next is the code for Ishka who must vanish after the cut sequence finishes. To do this we supply an ‘
OnCutSequence’ v-table function which will automatically be called once the sequence finishes. The variable ‘
CUTSEQ’ is an internal global variable that will hold the hashcode of the cutscene that just played.
This code here would allow Ishka to react to more than one cut scene that is relevant to him, but not react to any that are not. It does this by using a switch command and uses the cuts scene’s hash-codes as the cases.
Within each case you would write the code to move Ishka around and change his behavior.
rem Move Character to a different location
rem Move Character to a another different location
Note the use of exclusive
1 and exclusive
0 around the
switch sequence. While not compulsory, it may be a good idea to use exclusive mode here. The reason for this is so the code is run instantly as opposed to running slowly over a few frames. This means the character will be moved immediately, otherwise we might see him jump or vanish due to the screen appearing before the script had moved him.
Allowing a character to see if you have an itemEdit
Depending upon whether or not the player has a certain object an NPC may react differently to the player character. For Example:
- “Temple – A guard near the door to the temple will speak to the player and tell him that he can’t go through the airlock without an Oxygen ankh. If the player has an oxygen ankh he will let the player pass.”
We use the
QueryInventory command to see if the player has an item or a number of items in their possession. The command returns the number of a particular item the player has in their inventory.
distance = GetDistanceToPlayer
HasAnkh = QueryInventory HT_Item_Ability_OxygenAnkh
// We don’t have the item, prevent the player passing
// We Have The Item let player through