This page is heavily based on an Eurocom document. It was imported from SphinxScriptingLanguage.doc .
|
Graeme Richardson created the Sphinx Scripting Language and Instruction Set Usage while working at Eurocom and helping to build EngineX back in 2001. He was in charge of developing the bytecode and syntax.
The 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
Version |
Primary Author(s) |
Description of Version |
Date Completed |
V 0.1 |
Graeme Richardson |
Initial Draft |
2001-10-24 |
V 0.2 |
XXXXX |
e.g. Revision after publisher feedback |
2001-11-15 |
Introduction[]
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.
General Guidelines[]
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 “TEST
”
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
REM
or//
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 rules[]
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).
General Terms[]
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
REPEAT
loop within aREPEAT
loop, 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
REPEAT
loop within aWHILE
loop, that’s fine. However, if the user tries to terminate theWHILE
loop within thatREPEAT
loop, theENDWHILE
command 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 Overview[]
General[]
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 readabilityDEBUG
- debug output. Causes the program to output a number to the debug windowBREAK
- 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 usedEXIT
- Breaks completely out of the script and returns to main game engineWAIT
- Causes interpreter to wait for a specified number of framesGETFRAMECOUNT
- Gets number of AI frames elapsed. Usually since script startedSETFRAMECOUNT
- Allows user to modify the number of frames elapsed
Variable specific and mathematical Instructions[]
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 variableCONST
- Create a labeled constant numberRAND
- Allows a random value to be assigned to a variable
Conditional IF
[]
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 anIF
sequence.ELSE
- If the ‘IF
’ fails,ELSE
provides an ‘opposite’ conditionELSEIF
- Similar toELSE
except it can also offer a conditionENDIF
- Closes theIF
statementAND
- Used in combination with IF orELSEIF
to chain multiple conditions together
Conditional Switch[]
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 variableCASE
- Used for every possible value that user wants to check specificallyDEFAULT
- If no cases are met, then code will opt for default (optional)ENDSWITCH
- Ends the switch sequence
Looping code[]
Sometimes its desirable to force a piece of code to repeat several times over. There are two methods of doing this, REPEAT
and 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 timesENDREPEAT
- Closes the repeat statementWHILE
- Allows a piece of code to continue repeating while a mathematical condition (same as with IF) is still being metENDWHILE
- Closes the while statementAND
- Similarly withIF
, a number of conditions can be chained together using AND
Procedure Code[]
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 procedureENDPROC
- Terminates a procedure and exitsLEAVEPROC
- Exits a procedure from a mid pointCALLPROC
- calls another procedureINSTANCE
- initialises an instance of another time sharing procedureRETURN
- sets a return value for the current procedure (used beforeendproc
)EXCLUSIVE
- If used as a suffix after thedefproc
and 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 modesSUSPEND
- suspends another script from running.RESUME
- resumes a suspended scriptRESET
- resets another script and re-runs it from the start
(See later section on procedures for rules and more information)
Array Code[]
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 arrayENDARRAY
- marks the end of an arrayGETARRAY
- gets an element from an arraySETARRAY
- 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 Syntax[]
General Instructions[]
As these instructions are tied to no specific area, they are detailed together here as general commands. REM
and 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>
[]
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.
The command REM
and ‘//
’ are fully interchangeable. Rem is more readable, but //
is easy to type and handy for quickly commenting out lines of code.
- E.g.:
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
- E.g.:
DEBUG 1
(output a 1) orDEBUG TEST1 HEX
(output value of variable TEST1 in hexadecimal)
DEBUGS <short string>
[]
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
E.g.:
DEBUGS <ID>
DEBUGS Hello
DEBUGS <CR>
Would print something like… (ID section in blue, explained below)
MO_QU01_Skeletal_Spider - 'Main', Instance 0 : Hello
…in the debugger output window
The 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.
TERMINATION INSTRUCTIONS[]
BREAK
[]
- 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
EXIT
[]
- 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.
TIMING COMMANDS[]
WAIT
[]
- 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>
[]
- 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. - E.g.
TestVariable = GETFRAMECOUNT local
- If
SETFRAMECOUNT <variable or num> <scope>
[]
- 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>
[]
- 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. It's up to the game programmers how and where they reset these variables
Variable and Integer Code[]
These provide the basic variable operators to enable calculations and later comparisons etc to be completed.
Assignments and mathematical calculations.[]
- These are discussed later on, however variables, once declared with an INT statement are assigned values using the ‘=’ sign like in any other language
- E.g.:
Variable = 5
, orVariable1 = Variable2
- E.g.:
- Similarly, mathematical calculations are performed in the same way
- E.g.:
Variable1 = 5 * Variable2
, orVariable1 = Variable2+Variable3
- See later section on expression Evaluation
- E.g.:
- Constant numbers can also be passed in hexadecimal using the C style
0x<hex num>
notation.- E.g.:
Variable = 0xff
- E.g.:
- In addition, if a variable needs to be incremented or decremented by 1, then the C style shorthand
++
and–
syntax can be used:- E.g.:
Variable ++
orVariable--
- E.g.:
Variable Declaration[]
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.
INT a
- declare an integer variable ‘
a
’. Once declared, the language can ‘see’ the variable- E.g.
int TEST1
- E.g.
- LOCAL variable declaration can be combined with assigning a value
- E.G.
int TEST1 = 5
orint TEST1 = TEST2
- E.G.
- 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
Constant Declaration[]
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, TRUE
(1
), FALSE
(0
) and NULL
(0
)
Random Numbers[]
If you need random values, then use the RAND
command:
a = RAND <value>
a = RAND b
- set the value of ‘
a
’ to a random number up to the size of either ‘b
’ or<value>
- e.g.
TEST1 = rand 5
orTEST1 = rand TEST2
- e.g.
Rand
command can also be written in a different way. The first variable can be placed after theRand
command and before the second variable/value:- Therefore ”
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
- Therefore ”
- Rand can Also be used as part of a variable declaration e.g :
int Variable = Rand 5
int Variable = Rand OtherVariable
Bit-wise commands[]
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>
[]
- returns 1 or 0 for the value of the specified bit
SetBit <variable> <bit number> <1 / 0>
[]
- sets the specified bit of the variable to either 1 or 0
Array Code[]
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 correspondingENDARRAY
command. - Ensure when declaring an array that you do not have a variable which has the same name as the array
Array Code Commands[]
DEFARRAY <array name>
[]
- This command starts a new array. Arrays cannot be defined within procedures, nor can they be defined within other arrays
ENDARRAY
[]
- Ends an array.
variable = GETARRAY <array> <index>
[]
- 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>
[]
- 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 Access[]
Arrays can also be accessed in a method like C/C++
i.e. 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 format[]
- Array data is stored between the
defarray
and theendarray
. Each entry is anINT,
ie 32bits - 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 usage[]
In addition to directly accessing arrays by name, you can assign a variable to an array,
E.g. MY_VARIABLE = ARRAY_NAME
.
Then replace the array name in the GETARRAY
or 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?
E.g.:
var=ARRAY1
setarray ARRAYOFARRAYS 0 var
(or ARRAYOFARRAYS[0]=var
)
var=ARRAY2
setarray ARRAYOFARRAYS 1 var
(or ARRAYOFARRAYS[1]=var
)
Points to note and safety precautions[]
- 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. i.e., if you change an array entry in one, they will all ‘see’ that change.
Conditional IF
Instructions[]
The scripting language has full conditional execution using the instructions IF
, ELSE
, ELSEIF
and ENDIF
Basic Rules:
- Every
IF
statement must have a correspondingENDIF
- Every
ELSE
orELSEIF
statement must have a correspondingIF
statement
Instructions:
IF <condition>
[]
- 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 –
if TEST1>1
- Or –
if TEST1<TEST2
*Or – if TEST2>=3
- The full range of conditions are
=
,>
,<
,>=
,<=
. - Additionally inserting ‘
!
’ into the condition reverses the outcome - Eg – if
TEST1!=1
- this meansif test 1
is NOT equal to1
ENDIF
[]
- tells the language that we’re not worried about the
IF
statement’s conditions any more - Effectively returns to normal flow of code
- see next page for an example
ELSE
[]
- This provides a full unconditional alternative to an
IF
statement’s outcome. If anELSE
is present after anIF
, if theIF
condition fails, it will perform code after theELSE
statement - It basically means ‘otherwise’. An analogy. “If you’re too warm, take your coat off. Otherwise leave it on
- If the
IF
statement’s conditions ARE met, it will not perform the code after theELSE
and will jump straight to theENDIF
- See next page for an example
ELSEIF <condition>
[]
- 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
ELSE
- See next page for an example
…..AND <condition>
[]
- Used in combination with
IF
,ELSEIF
andWHILE
to 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 ELSE
, ELSEIF
or ENDIF
. Each ELSE
or 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 IF
statements:[]
The 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 IF
statement:[]
if TEST1>1
rem Run Some code here if TEST1 is greater than 1
endif
This piece of code simply checks to see if TEST1
is 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 IF
statement fails, then we can use the ELSE
command
An example of an IF
statement with ELSE
:[]
if TEST1>1
rem Run Some code here if TEST1 is greater than 1
else
rem If TEST1 is not greater than 1, run some alternative code here
endif
Else
in this situation provides a 100% backup plan if the IF
condition 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 ELSE
except that it provides another condition and will only execute its piece of code firstly if all preceding IF
s and ELSEIF
s have failed and if its condition is met. Additionally, the user can still supply an ELSE
statement after it, or not as is required
An example of an IF
statement with ELSEIF
and ELSE
:[]
if TEST1>1
rem Run Some code here if TEST1 is greater than 1
elseif TEST1<1
rem Run Some code here if TEST1 is less than 1
else
rem if both the above fail, then Run some code here
endif
Additionally, if sequences can be placed within other if sequences (nesting). Each set acts completely as its own entirety. Up to 32 sets can be placed inside each other. Ifs can also be placed inside REPEAT
loops, WHILE
loops and within SWITCH
statements.
An Example of Chained conditions using AND
[]
if TEST1>1 and TEST1<10
rem do some code in here
endif
if TEST1=1 and TEST2 != 100 and TEST3<TEST2
rem do some code in here
endif
The AND
command simply allows the IF
to work on several conditions at once. If they’re all met, the code will execute
Conditional SWITCH
Instructions[]
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.
Basic Rules:
- a
Switch
statement must have at least oneCase
statement - A
switch
statement must have a correspondingEndswitch
statement - 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
Instructions:
SWITCH <variable>
[]
- 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!
- E.g.
switch TEST1
ENDSWITCH
[]
- This terminates a switch sequence
- Effectively returns to normal flow of code
CASE <constant>
[]
- 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
- E.g.
case 1
- See examples on next page
DEFAULT
[]
- Provides an ‘everything else’ case
- It's optional
- It effectively equates to ‘
ELSE
’ in the context of an ‘IF
’ statement - 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 CASE
, DEFAULT
or ENDSWITCH
. Each CASE
or 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 SWITCH
statements:[]
The 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 SWITCH
statement:[]
switch TEST1
case 1
rem Run Some code here if TEST1 = 1
break
case 2
rem Run Some code here if TEST1 = 2
endswitch
Here, the variable TEST1
is 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
The ‘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 endswitch
command.
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 DEFAULT
:[]
switch TEST1
case 1
rem Run Some code here if TEST1 = 1
break
case 2
rem Run Some code here if TEST1 = 2
break
default
rem Run Some code here if all above cases fail
endswitch
As before, the program will check for the =1
and =2
cases. However here, if these fail, instead of doing nothing, they will automatically revert to the default case. The rules change slightly when using default. ‘Break
s’ must be used on all other cases otherwise code will always end up calling the default case
.
Also, the default
case MUST be placed last
REPEAT
Loop Instructions[]
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.
REPEAT <value>
- 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
endrepeat
will be repeated by the number of times the value states - Has to have a corresponding
endrepeat
command - Value must be +ve and has a maximum size of 65536
ENDREPEAT
- This signifies the end of the
repeat
loop - Cannot be used without a corresponding
repeat
command
REPEAT
has a ‘scope range’ of 255. This means that the REPEAT
can only see 255 instructions ahead of itself to find the ENDREPEAT
Examples of REPEAT
loops[]
The REM
statements in these examples represent where you would add the code that you want to run within the loop
Example of REPEAT
with a constant value:[]
repeat 5
rem Do this piece of code 5 times
endrepeat
Often it's desirable to repeat a piece of code by a variable number of times. For example if a calculation had been made and you wanted to use the answer to run a piece of code “that” many times.
Example of REPEAT
with a variable value:[]
repeat TEST1
rem Do this piece of code TEST1 number of times
endrepeat
Additionally, repeat
loops 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 IF
, SWITCH
and WHILE
sequences.
WHILE
Loop Instructions[]
While is effectively almost like REPEAT
loop mixed with an IF
statement.
- 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 withENDWHILE
. - The
WHILE
statement is always used in connection with anIF
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 while
statement
WHILE VAR<10
…do some code in here
ENDWHILE
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
while VAR>1
…do some code in here but never change VAR
endwhile
because VAR
is always greater than 1
, (as it's set to 10
) the loop will never end.
However this:
VAR = 10
while VAR>1
…do some code in here
VAR=VAR-1
endwhile
will end because after a few cycles around the loop, VAR
eventually becomes not greater than 1
.
WHILE
has a ‘scope range’ of 255. This means that the WHILE
can only see 255 instructions ahead of itself to find the ENDWHILE
Several conditions can be chained together using AND
, same as with IF
statements
E.g.:
while VAR1=10 and VAR2 =0
…do some code in here
endwhile
Using WHILE
usefully in time-sharing mode in a game[]
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 VAR
.
Int var = 0
while var =0
var = getobjective HC_SPECIALEVENT1
endwhile
- 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 non-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. InExclusive
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..
Procedure Instructions[]
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.
DEFPROC
[]
- 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>
[]
- 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>
[]
- 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
CALLPROC
/ variable = CALLPROC
[]
- 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.
Proc1
can callProc2
, which in turn can callProc3
.Proc3
will 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
CALLPROC
command
INSTANCE
/ variable = INSTANCE
[]
- *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
EXCLUSIVE
mode procedures - 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
[]
- Returns
1
or 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
INSTANCE
command 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>
[]
- 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
1
or0
. 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 non-exclusive mode
- If a procedure running in non-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 - Exclusive
1
/0
state 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.
SUSPEND
[]
- 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.
RESUME
[]
- Resumes a suspended instance from the point where it was paused.
- Execution continues exactly as normal
- ALL can be substituted for proc name
RESET
[]
- 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.
KILL
[]
- Similar to Suspend except that it completely kills the procedure
- Nothing is preserved from the killed procedure
GOTO
Instructions[]
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.
Beware of 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
LABEL
command (see below) - No conditions need to be met,
GOTO
will always jump GOTO
can 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 - The
LABEL
instruction can only be used at the ‘top level’ scope within a procedure. By this it means that it cannot be used withinIF
,WHILE
,REPEAT
,SWITCH
sequences. This is to prevent stack problems occurring at run time. - A Label must be in the same procedure as its
GOTO
.
Example:
MyLabel:
Debugs Sphinx
Debugs Was
Debugs Here
Debugs <CR>
Goto MyLabel
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 Instructions[]
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 Instructions[]
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 >
[]
- Gets the state of an objective variable via a hashcode and stores it in a script variable
SETOBJECTIVE < hashcode > < var >
[]
- Sets the state of an objective variable via a hashcode from a script variable
<var> = GETOBJECTIVEBIT <hashcode> <bit number 0-31>
[]
- 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>
[]
- 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 Instructions[]
BEGINCONTROL
[]
- Tells the AI that the current object is currently under control of the script language and not under AI control
ENDCONTROL
[]
- Tells the AI that it is back in control of the current object
<var> = SETANIM < hashcode >
[]
- 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> = GETANIM
[]
- Returns the hash-code ID of the animation the NPC is currently performing to a variable
SETBLENDDEPTH <var or const (0-500) >
[]
- 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 :)
WAIT <numframes>
[]
- Tells the script language to wait for a designated number of frames
FREEZEGAME <1 / 0 >
[]
- Allows the coder to completely freeze everything except the camera. This includes all AI and animation. Only the camera will move. Passing a
1
freezes passing a0
unfreezes
LOCKCONTROLS < 1 / 0 >
[]
- 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 0
unlocks controls
FORCECONTROLLOCK
[]
- Locks the controls regardless of player anim-mode. Use
Lockcontrols 0
to unlock!
Controller Rumble commands[]
There are a set of special commands to control the rumble feature of joy-pads.
STARTRUMBLE <strength 0-255> <duration in frames>
[]
- This starts the control pad rumbling.
STOPRUMBLE
[]
- Stops a pad vibrating
STOPALLRUMBLE
[]
- Stops all pads rumbling
Sound and Music commands[]
StartMusic <MFX_ hash> volume
[]
- 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>
[]
- 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.
StopMusic
[]
- Stops any music track currently playing, without fading.
StartSound <SFX_hash> volume
[]
- Starts a sound effect playing
StopSound <SFX_Hash>
[]
- Stops a looping sound effect from playing
Miscellaneous instructions[]
CUTSEQUENCE <hashcode>
[]
- 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
REMOVEFROMMAP
[]
- 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>
[]
- 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>
[]
- Sends a predefined message type to another ref identifier
- Message types can be found by running the
HT_Admin
program - If the reference is 0, instead of 1-8, it sends the message to itself. [swy]
SETLINKVAR <Receive Variable> <Send Variable> <Ref identifier>
[]
- 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>
[]
- 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>
[]
- Returns the distance to another scripted trigger which has an ID that matches the ID number here.
<variable> = GETDISTANCETOLINK <ref identifier>
[]
- Returns the distance to another trigger which this one is linked to
EnableEntity <hashcode>
[]
- 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
DisableEntity <hashcode>
[]
- 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>
[]
- Gets the state of a joy pad button using an associated hashcode. Current codes are:
HT_KeyPress_Square
HT_KeyPress_Circle
HT_KeyPress_Cross
HT_KeyPress_Triangle
SCREENFADE <hashcode>
[]
- Performs a predefined screenfade as described by the hashcode. Current fades are:
HT_ScreenFade_FadeToBlack
HT_ScreenFade_FadeFromBlack
PLAYERTELEPORT <link>
[]
- Causes the player to teleport to a linked trigger. The player will assume the position and orientation of this trigger
PLAYERPLAYANIM <hashcode>
[]
- Makes the player character play an animation. Currently not working
variable = GETMAPHASH
[]
- Retrieves the numeric hash code of the currently loaded (EDB) map file.
variable = ISLINKACTIVE <link>
[]
- 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 Commands[]
Commands specifically related to moving the object or NPC around and positioning of related objects
GETPOSFROMLINK <link number>
[]
- Sets the position of the current trigger to that of a linked trigger
SETPOSOFLINK <link number>
[]
- Sets the position of a linked trigger to that of the current trigger
ACTIVATELINK <link number>
[]
- Activates a linked trigger by sending HT_Trig_Message_Activate to a 1-8 index. [swy]
<variable> = GETDISTANCETOLINK <ID NUMBER>
[]
- Returns the distance to another trigger that this one is linked to.
<variable> = GETDISTANCETOSPAWN
[]
- Returns the distance to the NPC’s original spawn point
RETURNTOSPAWNPOINT
[]
- Tells the NPC to head back to its original spawn point. The character will keep walking until told otherwise
FOLLOWPLAYER <optional 1>
[]
- 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
1
after the command causes the character to automatically walk/idle depending on its range from the player. If this is used, you must use theSTOPMOVING
command or give him another mobility command to stop it moving as the walk/idle is fully automatic.
RUNAWAYFROMPLAYER
[]
- Similar to above except the character runs in completely the opposite direction
STOPMOVING
[]
- 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
FOLLOWLINK <link number> <optional 1>
[]
- 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
TURNTOLINK <link number>
[]
- 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
AnimMode
toTurnLeft
orTurnRight
, so the character needs to have these animations present
variable = ISFACINGLINK <link number>
[]
- This command returns
1
if the NPC is looking in the direction of the linked NPC.
FLOCKCONTROL <0/1/2>
[]
- 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
1
tells them to follow this trigger. - Passing a
2
tells them to run away from this trigger.
DEACTIVATELINK <link number>
[]
- Deactivates a linked trigger by sending HT_Trig_Message_Suspend to a 1-8 index. [swy]
Miscellaneous Flag and value setting commands[]
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)
Value Commands[]
SetItemValue <Hash-Code> Value
[]
variable = GetItemValue <Hash-Code>
[]
Value Hashcodes[]
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
HT_Local_FireMummy
HT_Local_SmokeMummy
HT_Local_ElectricMummy
HT_Local_SmallMummy
HT_Local_PaperMummy
HT_Local_StoneMummy
HT_Local_InvisibleMummy
HT_Local_BlindMummy
Sets the health level of an item
HT_GameScript_Item_Health
Flag Commands[]
SetItemFlag <Hash-Code> 1/0
[]
variable = GetItemFlag <Hash-Code>
[]
Value Hashcodes[]
HT_GameScript_Item_Invincible
[]
- Sets the item to be invincible or not
HT_GameScript_Item_PathPingPong
[]
- Sets whether a character should ping pong on its path or if it should be cyclic
HT_Trig_Message_DisableDistance
[]
- Used on context triggers to disable them from within. Player character won’t ‘see’ them any more
HT_GameScript_Item_PlayerHasPickupable
[]
- Used with GetItemFlag to see if character is carrying a pickupable object
HT_GameScript_Item_NPCHeadTrack
[]
- 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
HT_Trig_DistanceActionFlag_DropPickupable
[]
- When used with SetItemFlag, the player will drop any pickup-able that he is carrying
HT_Trig_DistanceActionFlag_RespawnPickupable
[]
- When used with SetItemFlag, any pickup-able the player is carrying will respawn
Path/Node Mobility and Position Commands[]
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 NPCs[]
SETPATH <HT_HASHCODE>
[]
- Tells the NPC to start following a path as described by the hash code
variable = GETPATH
[]
- Returns the hashcode value of the current path the NPC is following
variable = GETPATHNODE
[]
- Returns the value of the current node the NPC is at on its current path
GOTOPATHNODE <variable>
[]
- Tells the NPC to head to a certain node on its current path
POSITIONATPATHNODE <variable>
[]
- Forcibly moves an NPC instantly to another place on a path
TURNTOPATHNODE <HT_HASHCODE> <variable>
[]
- 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>
[]
- Makes face a node on a supplied path immediately
Path Control for TR_PATH
triggers[]
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 items[]
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> amount
[]
- The amount variable represents either centimeters for movement or 100ths of a radian for rotation. (eg
100
would equal one metre, or1
radian. A full circle is2*PI radians = 618
) Note, due to storage constraints, if you pass a constant number, it is in the range-127
to127
. Anything larger, use a variable
HT_GameScript_Control_Rotate
[]
- Rotates the object around its Y axis relative to its current orientation
HT_GameScript_Control_SetAngle
[]
- Sets the Y axis rotation
HT_GameScript_Control_FwdBack
[]
- Moves the object forwards and backwards along its Y Axis orientation
HT_GameScript_Control_Strafe
[]
- Moves the object side to side perpendicular its Y Axis orientation
HT_GameScript_Control_MoveX
[]
- Moves the object along the map’s X axis
HT_GameScript_Control_MoveY
[]
- Moves the object along the map’s Y axis
HT_GameScript_Control_MoveZ
[]
- Moves the object along the map’s Z axis
HT_GameScript_Control_SetX
[]
- Sets the objects X position in world space
HT_GameScript_Control_SetY
[]
- Sets the objects Y position in world space
HT_GameScript_Control_SetZ
[]
- Sets the objects Z position in world space
Player Interaction Commands[]
<variable> = GETDISTANCETOPLAYER
[]
- Returns the distance the player is away from the NPC in centimeters to the variable.
LookAtPlayer
[]
- Character will turn its head to watch the player (not used yet)
TurnAndFacePlayer <HT_AnimMode_TurnLeft…..>
[]
- 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
HT_AnimMode_TurnLeft
/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.
TalkToPlayer <HT_AnimMode_Talk……>
[]
- 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> = IsFacingPlayer
[]
- Returns
1
or0
depending whether or not the character is facing the player character. The character has a leeway of 10 degrees
<variable> = GetTurnDirection
[]
- Returns
-1
if the character must turn left to face the player,+1
if it must turn right, or zero if it is already facing the player
IGNOREPLAYER <num>
[]
- 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.
INTERACT <num>
[]
- 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 Instructions[]
PRINTMESSAGE < hashcode >
[]
- Prints a line of text in a help box, using its hashcode as an ID
< var > = GETABUTTONPRESS
[]
- Returns which onscreen button on a help window was pressed (if any)
< var > = ISTHEREHTEXT
[]
- Returns whether there’s any text in a help window
< var > = ISSAID
[]
- Has everything been said and has user pressed button to close message. Returns 1 if this has happened, otherwise returns 0
CLEARWINDOW
[]
- Clears the help window
Inventory Instructions[]
INVENTORYADD <HT_HASHCODE> variable
or number
[]
- 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
to127
. If you use a variable it can be any size you want. - E.g.:
InventoryAdd HT_Item_Pickup_BronzeScarab 10
INVENTORYSET <HT_HASHCODE> variable or number
[]
- 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.
- E.g.:
InventorySet HT_Item_Pickup_BronzeScarab 50
INVENTORYMAXSET <HT_HASHCODE> variable or number
[]
- 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
0
-255
.
<var> = QUERYINVENTORY <HT_HASHCODE>
[]
- Finds the number of items of the type described by the hashcode in the player’s inventory
- E.g.:
Scarabs = QueryInventory HT_Item_Pickup_BronzeScarab
<var> = ITEMSELECTOR <HT_HASHCODE_INVENTORY FILTER>
[]
- 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,
0
if there are no items, or-1
if player cancels - E.g.:
Var = itemselector HT_Trig_InvFilter_QuestItems
<var> = QUERYINVTYPES <HT_HASHCODE>
[]
- Returns number of items of a certain type as specified with hashcode.
- No idea what its for, ask Lenny.
- Current applicable hash codes are:
HT_InvQuery_Jewels
HT_InvQuery_CapturedMonsters
- If you need any more, ask Lenny.
Health/Ankh Instructions[]
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
Health/Ankh Instructions[]
< var > = GETHEALTH
[]
- Gets current health of player in single health units
< var > = GETGOLDANKH
[]
- Get number of gold Ankhs owned by the player
SETMAXHEALTH
[]
- Sets health to maximum. All Gold Ankhs on the screen will be filled
ANKHADD <Hash-Code> <number>
[]
- 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
-127
to127
ANKHSET <Hash-Code> <number>
[]
- As above but it sets the exact number of ankhs instead of adding to it
- Range of Number is
0
-255
[]
These are the hash-codes that directly relate to the AnkhAdd
and AnkhSet
commands:
HT_Item_Pickup_BronzeAnkh
[]
- Bronze Ankh adds 3 health units to the health bar (fills one Gold Ankh)
HT_Item_Pickup_SilverAnkh
[]
- Silver Ankh adds 24 health units to the health bar (fills 8 Gold Ankhs)
HT_Item_Pickup_GoldAnkh
[]
- Gold Ankh adds one extra health container to the health bar
Scarab Instructions[]
These commands are used in addition to the InventoryAdd
, InventorySet
and QueryInventory
commands that perform the actual adding to purse and finding contents of purse functionality.
Instructions[]
SETMAXSCARAB < var >
[]
- Set maximum number of scarabs player can have (in Bronze scarabs)
< var > = ISWALLETFULL
[]
- Returns whether player’s wallet is full or not
< var > = GETWALLETSPACE
[]
- Returns how much room is left in player’s wallet
SCARABDISPLAY < 1 / 0 >
[]
- Sets whether the scarabs remain onscreen at all times (
1
) or revert to normal vanishing after a delay (0
)
Hash-Codes[]
These hash-codes can be used with the InventoryAdd
and InventorySet
commands to add and remove scarabs from the player’s purse
HT_Item_Pickup_BronzeScarab
[]
- Bronze Scarab = one unit of wealth
HT_Item_Pickup_SilverScarab
[]
- Silver Scarab = five units of wealth
HT_Item_Pickup_GoldScarab
[]
- Gold Scarab = 20 units of wealth
Camera Commands[]
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 instructions[]
These instructions switch the overall operation mode of the camera
CAMERAMODEPLAYER
[]
- Sets camera mode to normal follow the player mode.
CAMERAMODETOME
[]
- Camera looks at the trigger running the script
CAMERAMODEPATH <link number>
[]
- 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
CAMERAMODELINK < link number >
[]
- Similar to CameraToMe except the camera turns to look at a trigger linked to this one
CAMERAMODESCRIPTPATH <HT_Hashcode_Path>
[]
CAMERAMODETRACK <link number>
[]
- Sets camera to a tracking cam where it remains in one place and tracks the player. The link number references the camera to use
CAMERAMODEFIRST
[]
- Sets the camera to first person mode looking from the character’s eyes.
CAMERAMODETALK
[]
- 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 retrieval[]
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
CAMERASTORE
[]
- 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
CAMERARESTORE
[]
- Restores the camera to the stored version. Once camera has been restored, another camera can be stored
Camera Parameter Controls[]
When used in conjunction with the normal player camera (CameraModePlayer
) and the Look-At modes (CameraModeToMe
, 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>
[]
- Sets the distance of the camera to the player in cm. Normal distance is 350
CAMERAELEV <variable or constant>
[]
- 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>
[]
- Sets the height of the camera relative to the player’s eye level in cm. Normal distance is 120
CAMERATURN <variable or constant>
[]
- 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>
[]
- Locks the camera to its current position. It will still track whatever its looking at but won’t physically move.
Camera Tweak Controls[]
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>
[]
- 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 passing0
)
- The list will expand as the project continues. Current tweak mode hashcodes, with their trailing values types are:
HT_CameraMode_ClimbLow <1/0>
[]
- This tweaks the climb camera to make the camera look up at the player
HT_CameraMode_TinyRadius <1/0>
[]
- Makes the camera have a tiny radius allowing small gap access
HT_CameraMode_DynamicFOV <radius>
[]
- 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
HT_CameraMode_DynamicFocus <radius>
[]
- 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!!
HT_CameraMode_AutoStrafe <radius>
[]
- 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 >
[]
- As above, but camera will change elevation as well turning
HT_CameraMode_StrafeOnlyHeight < radius >
[]
- As above, but camera will change only elevation
HT_CameraMode_Reverse <1/0>
[]
- 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 commands[]
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>
[]
- Camera is forcibly moved to a node on the path
CAMERAGOTONODE <node number>
[]
- 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
CAMERALOOKATPLAYER
[]
- Camera will point at the player
CAMERALOOKATLINK
[]
- Camera will point at a trigger linked to the trigger running the script
CAMERALOOKATME
[]
- Camera will look at the trigger running the script
CAMERATURNSPEED <speed 0-1000>
[]
- 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>
[]
- Sets the speed at which the camera will move between nodes. Speed works as above.
HUD Control[]
The in-game HUD can be controlled using one command and a series of hash-codes.
The command is:
HUDCONTROL <Hash-Code> Value1 Value2
[]
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 0
-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 instructions[]
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]
SHOWPOPUPINV <Hash-Code>
- Cause popup inventory to appear, show the exact inventory item match, documented here for posterity.
HIDEPOPUPINV
- Hide it.
ADDTOPOPUPINV <Hash-Code>
- 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 Commands[]
These commands allow entries to be made and removed from the book
BookAddNote <hashcode> <variable>
[]
- 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!!
BookRemoveNote <hashcode>
[]
- Removes a quest note from the book
BookAddPicture <hashcode> <variable>
[]
- 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 Instructions[]
Overview[]
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 variable
[]
- 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-codes[]
HT_Gamescript_Macro_FortuneTeller
[]
- 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_01
toHT_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_AnkhPieceClue01
toHT_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. - Returning
HT_Text_Hel_CursedPalace_FortuneTeller_AnkhPiecesComplete
if all 20 are done. - Returning
HT_Text_Hel_CursedPalace_FortuneTeller_AnkhPiecesNoneValid
otherwise.
- Returning one text hashcode from
- It loops over the all objectives from
HT_GameScript_Macro_LockFirstPerson 0/1
[]
- Locks the
HT_GPad_Script_LockFirstPerson
contextual button/action in the D-Pad. Set it to off and on again.
HT_GameScript_Macro_MiniGameMusic <numeric-option>
[]
- 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:
MFX_MiniGame_Pairs
: 1MFX_MiniGame_Shoot
: 2MFX_MiniGame_Simon
: 3MFX_MiniGame_Walls
: 4
HT_GameScript_Macro_ResetToSphinx
[]
- 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>
[]
- Dynamically retrieve the buying or setting price from the
HT_SpreadSheet_ShopPrices
spreadsheet, retrieved from theHT_File_ShopPrices
EDB, hardcoded.
HT_GameScript_Macro_GetMonstName <monster-file-hashcode> <name-type-for-shop-or-museum-1/0>
[]
- 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_burble
it will returnHT_Text_Inv_BM09_Smiling_burble
(if the second parameter is set to 1) orHT_Text_Museum_Name_BM09_Smiling_burble
. If the monster hashcode is invalid it returnsHT_Text_Inv_Carrot
.
- For example, if we feed it
HT_GameScript_Macro_CameraModePlayer
[]
- 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:
CAMERATOME
,CAMERATOPLAYER
,CAMERATOLINK
,CAMERAMODETALK
.
HT_GameScript_Macro_IsHelpFading
[]
- 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.
HT_GameScript_Macro_AwardAchievement
[]
- Discarded, defined from somewhere but unused in the current PC version. Achievements are hardcoded to objectives and other conditions in the normal restored versions.
HT_GameScript_Macro_GetModValue <number-or-hashcode-for-key> [<default-value-to-return-if-key-does-not-exist>]
[]
- Read a persistent, mod-specific value from
Sphinx.ini
in the[ModValue_<current-mod-folder>]
section of 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] - The second parameter is optional, and can be used to set a preset/default/returned value when the key does not yet exist.
- All the parameters must be variables for them to work, constants are limited in size and will cause undefined results.
HT_GameScript_Macro_SetModValue <number-or-hashcode-for-key> <value>
[]
- Write a persistent, mod-specific value to
Sphinx.ini
in the[ModValue_<current-mod-folder>]
section of 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] - When the macro is unimplemented (old versions) it will return 0, when it succeeds it will return the user-provided key/first parameter instead.
- All the parameters must be variables for them to work, constants are limited in size and will cause undefined results.
HT_GameScript_Macro_GetGlobalValue <number-or-hashcode-for-key> [<default-value-to-return-if-key-does-not-exist>]
[]
- Same as
GetModValue
but game-wide, in the[GlobalValue]
section of Sphinx.ini. All mods share the same persistent storage indexes. [swy] - The second parameter is optional, and can be used to set a preset/default/returned value when the key does not yet exist.
- All the parameters must be variables for them to work, constants are limited in size and will cause undefined results.
HT_GameScript_Macro_SetGlobalValue <number-or-hashcode-for-key> <value>
[]
- Same as
SetModValue
but game-wide, in the[GlobalValue]
section of Sphinx.ini. All mods share the same persistent storage indexes. [swy] - When the macro is unimplemented (old versions) it will return 0, when it succeeds it will return the user-provided key/first parameter instead.
HT_GameScript_Macro_GetGameVersion
[]
- 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]
- All the parameters must be variables for them to work, constants are limited in size and will cause undefined results.
HT_GameScript_Macro_GetSaveSlot
[]
- 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] - All the parameters must be variables for them to work, constants are limited in size and will cause undefined results.
HT_GameScript_Macro_GetTimeDate <time-or-date-0/1>
[]
- 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
GETTIMER
for 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]
- As a bit of an extra, if someone only wants a single field the numbers 2/3/4/5/5/7 can be used as parameter to retrieve the year/month/day/hours/minutes/seconds instead. This is less efficient, but simpler to use.
- All the parameters must be variables for them to work, constants are limited in size and will cause undefined results.
HT_GameScript_Macro_GetAnimSkin [optional-return-type-0-or-1]
[]
- Get the current AnimSkin hashcode for the child item of this trigger, if any.
- The second parameter is optional, and if unset/0 the macro will return the skin hashcode, which is the normal value one would want.
- When the second parameter is not 0, the macro will return the skin's EDB hashcode instead. [swy]
- If there is any error, the macro will return -1, if unimplemented it will return 0.
- All the parameters must be variables for them to work, constants are limited in size and will cause undefined results.
HT_GameScript_Macro_SetAnimSkin <HT_AnimSkin_*> [
<HT_File_*>
]
[]
- 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 characters and more complex behaviors. The second parameter is optional and when empty/0 it will reuse the base/first item animator EDB file, the most common case, instead of using (or loading) an external one. [swy]
- If unimplemented, the macro will return 0, it will return -1 if the trigger does not have any (skin-animated) item, and -2 if the optionally-specified
HT_File_
is not present in the filelist, -3 if theHT_File_
could not be loaded. Otherwise, it will succeed returning the user-provided skin hashcode. - The default EDB is implicitly loaded, as it is already being used.
- All the parameters must be variables for them to work, constants are limited in size and will cause undefined results.
HT_GameScript_Macro_AddAnimator <HT_Script_*> [<HT_File_*>]
[]
- Appends a new animator to the curren't trigger's child item. The second parameter is optional and when empty/0 it will reuse the base/first item animator EDB file, instead of using an external one. An animator can be a normal script, a particle system (e.g.
HT_Script_RightHandFX
) or anything else. [swy] - If unimplemented, the macro will return 0, it will return -1 if the trigger does not have any item, and -2 if the optionally-specified
HT_File_
is not present in the filelist, -3 if theHT_File_
could not be loaded. Otherwise it will return the user-provided animator hashcode, when it succeeds. - The default EDB is implicitly loaded, as it is already being used.
- All the parameters must be variables for them to work, constants are limited in size and will cause undefined results.
HT_GameScript_Macro_RemoveAnimator <HT_Script_FX_*>
[]
- Removes one animator with the same hashcode, only if found in the list of active animators of the current trigger's item. [swy]
- If unimplemented, the macro will return 0, it will return -1 if the trigger does not have any item. Otherwise it will return the user-provided animator hashcode, when it succeeds.
- All the parameters must be variables for them to work, constants are limited in size (8 bits) and will cause undefined results.
Using Variables[]
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 INT
.
- For example:
int MyNewVariable
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 theendproc
). 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!
Internal Globals[]
Every script has a set of internal global variables. These are named ARG0
through to ARG5
, and RETURN0
to RETURN7
. Any script can use them, and the setlinkvar
instruction can allow another script to access them.
Additionally there are 2 other variables called MESSAGE
and CUTSEQ
. These are set when messages are received by the OnMessage
v-table function or a cut scene finishes.
Rules and tips:[]
- 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
Expression Evaluation[]
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 | * and / multiply and divide.
|
Level 2 | + and - add and subtract.
|
Level 3 | < and > bit-wise shift left or right.
|
Level 4 | & , | and ^ bit-wise AND , OR and EOR .
|
Examples[]
Example I[]
TEST1 = TEST2 * TEST3 + TEST4
This would break down to the following maths operations (in order)
temporary = TEST2 * TEST3
TEST1 = temporary + TEST4
Example II[]
TEST1 = TEST2 * TEST3 + TEST4 * -TEST5 + 8
Would break down to
temporary1 = TEST2 * TEST3
temporary2 = TEST4 * -TEST5
temporary1 = temporary1 + temporary2
TEST1 = temporary1 + 8
Rules[]
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.
Using Procedures[]
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:
- Externally
- Internally
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[]
When the 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-
EXCLUSIVE
function. - 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 Mode[]
As has been mentioned earlier, the language can run in one of 2 modes. Exclusive and non-exclusive.
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 non-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:- E.g.:
defproc MYPROC exclusive
- E.g.:
- 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.
Non-Exclusive Mode (or ‘time sharing’ mode)[]
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 ‘non-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
ARGx
andRETURNx
variables. 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.
Eg:
...
exclusive 1
ARG1=LOCAL1
callproc NextProc
endproc
defproc NextProc
int NEWLOCAL
NEWLOCAL= ARG1
exclusive 0
...
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 withexclusive 0
’s. - When one procedure calls another procedure, the exclusive state is stacked. When the second procedure ends, the
exclusive
state 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 1
state 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
ARG
variables. Switch toexclusive 1
first, copy to locals (for example) and switch back toexclusive 0
again
Using the V Table[]
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) “OnCreate
”, “OnDeath
” etc. Therefore to bind with OnCreate
for example would be as simple as:
defproc OnCreate
V-Table checking is not case sensitive so you can use a mix of caps and lower case if you desire.
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
’.
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 runningMain
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 non-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 functions[]
Main
[]
- Always called just after the trigger is created. Use it as the character’s main background processing procedure, character setup etc.
OnCreate
[]
- Called at the point where the trigger is created
OnSuspend
[]
- Called when the trigger is suspended (for example when player moves outside its suspend radius).
OnDestroy
[]
- Called when the trigger is destroyed and removed from the map. Similarly to OnSuspend, it needs to be written in Exclusive mode
OnHit
[]
- When the character is hit by the player or a weapon.
OnDeath
[]
- Called when the NPC is killed. Can be used to drop pickups for example
OnArriveAtPathNode
[]
- 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_PATH
triggers, but is only called when the entity reaches the last node.
OnMessage
[]
- 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
OnContext
[]
- Called when the player presses the ‘context’ button on their joy-pad in connection with this NPC. Use for conversations etc.
OnContextArea
[]
- 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
OnCutSequence
[]
- Called immediately after a cut-scene ends. This allows any character movement, removal etc to be processed following a cut scene.
OnCollide
[]
- 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
LINK
set up with the link number of the object it collided with
Using the Script Debugger[]
Overview[]
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.
Preview[]

The PC version of the game in 2002, paused in stepping mode, running the script debugger.
- 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.

The Watcher can be opened via Ctrl + W.
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
HT_
. - Some lines of code may appear different to how you typed them in Euroland. For example ‘
variable++
’ will appear as ‘variable=variable+1
’. - Compound maths expressions will be split up over several lines into multiple instructions.
- All blank lines,
REM
statements and labels in your listing will have gone.
Error Codes[]
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 Script | Interpreter Cannot find this Script to run |
---|---|---|
2 | Invalid Instruction | Interpreter does not recognize this instruction |
3 | Invalid Exclusive State | Interpreter is attempting to enter an invalid state using Exclusive command
|
4 | Recursion | Function is attempting to call itself (possibly indirectly) |
5 | Instance Overflow | No room to start another instance on this object |
6 | If Stack Overflow
|
Too many nested If s
|
7 | Repeat Stack Overflow
|
Too many nested Repeats
|
8 | Proc Stack Overflow
|
Too many nested procedure calls |
9 | Unknown Failure | General internal failure, tell programming team |
10 | Exclusive Cycles Exceeded | Used in debug mode to attempt to prevent endless loop lockups in exclusive mode |
11 | Wrong Version Number | Interpreter is not same version number as script compiler. Grab again |
12 | Function Blocked | Block mode is on and you’ve attempted to run same procedure twice concurrently |
13 | Invalid Repeat Value
|
Trying to repeat with a value less than 1
|
14 | Divide By Zero | Trying to divide a number by zero. It's mathematically not possible |
15 | Invalid Trigger Link | 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 Animation | Interpreter has tried to run an animation that doesn’t exist |
Sample Script Listings[]
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
Hello World[]
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.
defproc Main
debugs HELLO
debugs WORLD!
debugs <CR>
endproc
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.
The 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.
Using Repeat
loops[]
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.
defproc Main
int MyVariable = 0
repeat 10
debug MyVariable
MyVariable = MyVariable + 1
endrepeat
debugs Hello
debugs World!
debugs <CR>
endproc
This code also demonstrates the use of a variable. In this case, the variable is called ‘MyVariable
’
- 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 ondebugs
means ‘string’.. Thedebug
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.
Using IF
statements[]
Using 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 ‘MyVariable
’ to 5
and prints a different message depending on the outcome.
defproc Main
int MyVariable = 0
repeat 10
if MyVariable < 5
debugs Less
debugs than
elseif MyVariable >5
debugs More
debugs than
else
debugs equal
endif
debugs 5
debugs <CR>
MyVariable = MyVariable + 1
endrepeat
endproc
The program uses IF
, ELSEIF
and 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 else
or ELSEIF
is encountered, it will jump to the ENDIF
.
An 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 procedures[]
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 calling[]
Defproc Main
Debugs In
Debugs Main
Callproc Test
Debugs Finish
Debugs <CR>
Endproc
Defproc Test
Debugs In
Debugs Test
Endproc
This simply runs a bit of Main
, then calls Test
, runs that, then finishes running Main
. 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 value[]
Defproc Main
Int Var=0
Debug Var
Var = Callproc Test
Debug Var
Debugs <cr>
Endproc
Defproc Test
Int TestVar = 39
Endproc TestVar
This example sets the value of Var
to zero in Main
, then requests a new value for it from Test
. Test
returns the value of the variable TestVar
on exit and Main
sets its own variable to that
Using SWITCH
statements[]
The following example uses a switch statement. What happens here is that it gets a random value between 0
and 2
in MyVariable
and prints it to the debugger window. It gets the random number using the Rand
command.
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.
defproc Main
int MyVariable
repeat 20
MyVariable = rand 2
debug MyVariable
switch MyVariable
case 0
debugs ZERO
break
case 1
debugs ONE
break
case 2
debugs TWO
endswitch
debugs <CR>
debugs <CR>
endrepeat
endproc
Using GOTO
[]
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 50
.
defproc Main
int MyVariable = 0
jumpback:
debug MyVariable
debugs Adding
debugs One
debugs <CR>
MyVariable=MyVariable+1
if MyVariable<50
goto jumpback
endif
debugs <CR>
debug MyVariable
debugs Big
debugs Enough
debugs <CR>
endproc
Suspending, Resuming and Resetting[]
The script language allows procedures to halt other procedures in their tracks, or reset them using the 3 commands suspend
, resume
, 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.
defproc Main
debugs Reset
debugs <CR>
mainloop:
wait 60
debugs Hello!
debugs <CR>
goto mainloop
endproc
defproc OnContext
suspend Main
wait 600
resume Main
endproc
- 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. IfOnContext
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 withreset
, theMain
procedure would completely restart and it would print ‘Reset’ first, before printing ‘hello!’
Example of Using Arrays in code[]
//this example reads elements of the array in one at a time and outputs them to the debug window
defproc Main
int test
int count=0
debugs <ID>
repeat 10
//read an element from the array
test = GetArray TESTARRAY count
debug test HEX
count++
endrepeat
debugs <CR>
endproc
//here’s the array!
//notice that there are 2 array elements per line. In this case, hash-codes
defarray TESTARRAY
HT_AnimMode_Move HT_AnimMode_Idle
HT_AnimMode_Move2 HT_AnimMode_Idle2
HT_AnimMode_Move3 HT_AnimMode_Idle3
HT_AnimMode_Move4 HT_AnimMode_Idle4
HT_AnimMode_Move5 HT_AnimMode_Idle5
endarray
Sphinx Gameplay Script Examples[]
Simple ‘inventoryadd
’ example[]
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 theWait
command, it pauses for 60 frames, (one second on a 60hz refresh display).
- The
2
after theInventoryAdd
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.
defproc MAIN
repeat 50
inventoryadd HT_Item_Pickup_BronzeScarab 2
wait 60
endrepeat
endproc
Making an NPC react to a player coming into/leaving range[]
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.
- Notice how
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
defproc MAIN
int Distance
int AlertMode = 0
int Frames = 10
begincontrol
MainLoop:
wait 30
Distance = GetDistanceToPlayer
if Distance < 500
if AlertMode = 0
Frames = setanim HT_ALERT_ANIMATION
wait Frames
AlertMode = 1
endif
elseif Distance > 800
if AlertMode = 1
Frames = setanim HT_IDLE_ANIMATION
wait Frames
AlertMode = 0
endif
endif
goto MainLoop
endproc
Using Paths and nodes with a script to control an NPC[]
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
, and OnArriveAtPathNode
.
OnCreate
is similar to Main in that it is always called when an NPC is created. It is called afterMain
however. Main would generally be used to set up stuff, whereasOnCreate
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 anIF
statement to work out which path it is currently on. Once it has worked this out, it then uses aswitch
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 Wait
command.
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 GotoPathNode
, GetPathNode
, SetPath
and GetPath
commands
DefProc OnCreate
BeginControl
SetAnim HT_AnimMode_Move
GotoPathNode 2
EndProc
DefProc OnArriveAtPathNode
int Node
int Path
int Frames
Path = GetPath
Node = GetPathNode
if Path = HT_Path_BoulderPath1
switch Node
case 2
Frames = SetAnim HT_AnimMode_Talk
Wait Frames
SetAnim HT_AnimMode_Move
SetPath HT_Path_BoulderPath2
GotoPathNode 3
break
case 4
SetAnim HT_AnimMode_Idle
break
endswitch
else
switch Node
case 3
Frames = SetAnim HT_AnimMode_Talk2
Wait Frames
SetAnim HT_AnimMode_Move
GotoPathNode 7
break
case 7
Frames = SetAnim HT_AnimMode_Idle
Wait Frames
SetAnim HT_AnimMode_Move
SetPath HT_Path_BoulderPath1
GotoPathNode 4
break
endswitch
endif
EndProc
Delaying animation sequences[]
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
defproc Main
int Pause
Pause = rand 120
wait Pause
begincontrol
setanim HT_HashcodeForPrayingAnimation
endproc
Sending a message to another NPC[]
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 – Simple[]
Messaging code for Spider3
, the sender[]
This piece of code causes Spider3
to send messages (in this case the number 1
, then 2
) to the trigger specified on Reference Identifier #2 every 60 frames.
defproc Main
mainloop:
wait 60
debugs Sending
debugs message
debugs <CR>
sendmessage 2 1
sendmessage 2 2
goto mainloop
endproc
Messaging code for Spider2
, the recipient[]
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.
defproc '''OnMessage'''
debugs Got
debugs message
switch MESSAGE
case 1
debugs one
break
case 2
debugs two
endswitch
debugs <CR>
endproc
EXAMPLE 2 – Laughing Guards[]
- “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 sender[]
defproc Main
int Frames
mainloop:
Frames = SetAnim HT_SHOOT_DART_AT_GEB_PICTURE
wait Frames
rem Extra code here to wait for Dart to Hit Geb Picture.
sendmessage 1 99
sendmessage 2 99
Frames = SetAnim HT_IDLE_ANIMATION
wait Frames
goto mainloop
endproc
The code is fairly straightforward, the guard throws a dart. When the Dart misses the picture, the code sends a message (99) to the other 2 guards, who are linked via Euroland as Reference identifier 1 and 2.
Messaging code for other Guards, the recipients[]
defproc OnMessage
int Frames
switch MESSAGE
case 99
Frames = SetAnim HT_LAUGH_AT_OTHER_SOLDIER
wait Frames
Frames = SetAnim HT_IDLE_ANIMATION
break
default
endswitch
endproc
Again, the OnMessage
v-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 OnMessage
function 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’ behavior[]
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.
int Distance
int Type
defproc Main
rem pick a random start node for monks
setpath HT_PathForMonksToWalkAround
Type = rand 5
PositionAtPathNode Type
Rem pick a random behavior type for monks
Type = rand 2
mainloop:
wait 60
Distance = getdistancetoplayer
debugs I
debugs AM
debugs TYPE
switch Type
case 0
callproc MonkType1
break
case 1
callproc MonkType2
break
default
callproc MonkType3
endswitch
debugs <CR>
goto mainloop
endproc
defproc MonkType1
rem Do MonkType 1 behavior in here
debugs ONE
endproc
defproc MonkType2
rem Do MonkType 2 behavior in here
debugs TWO
endproc
defproc MonkType3
rem Do MonkType 3 behavior in here
debugs THREE
endproc
Making an NPC ignore the player[]
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
defproc Main
mainloop:
debugs AWARE
ignoreplayer 0
wait 120
debugs IGNORE
ignoreplayer 1
wait 120
goto mainloop
endproc
Triggering and Reacting to Cut Sequences[]
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:
defproc Main
int distance
int mode = 0
mainloop:
wait 30
distance = getdistancetoplayer
if mode=0
if distance<200
mode=1
CutSequence HT_KING_CUTSCENE
endif
endif
goto mainloop
endproc
- 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.
defproc OnCutSequence
exclusive 1
switch CUTSEQ
case HT_KING_CUTSCENE
rem Move Character to a different location
break
case HT_ANOTHER_CUTSCENE
rem Move Character to a another different location
endswitch
exclusive 0
endproc
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 item[]
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.
int HasAnkh
defproc Main
int distance
HasAnkh=0
mainloop:
wait 30
distance = GetDistanceToPlayer
if distance<500
HasAnkh = QueryInventory HT_Item_Ability_OxygenAnkh
if HasAnkh=0
// We don’t have the item, prevent the player passing
else
// We Have The Item let player through
endif
endif
goto mainloop
endproc