Sphinx and the Cursed Mummy Wiki
Advertisement
Eurocom logo 2006 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 a REPEAT 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 a WHILE loop, that’s fine. However, if the user tries to terminate the WHILE loop within that REPEAT loop, the ENDWHILE 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 readability
  • DEBUG - debug output. Causes the program to output a number to the debug window
  • BREAK - Breaks the code out of any conditional sections or loops and continues from where that piece of code ends. Recommended that it's always used where switch/case is used
  • EXIT - Breaks completely out of the script and returns to main game engine
  • WAIT - Causes interpreter to wait for a specified number of frames
  • GETFRAMECOUNT - Gets number of AI frames elapsed. Usually since script started
  • SETFRAMECOUNT - Allows user to modify the number of frames elapsed

Variable specific and mathematical 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 variable
  • CONST - Create a labeled constant number
  • RAND - 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 an IF sequence.
  • ELSE - If the ‘IF’ fails, ELSE provides an ‘opposite’ condition
  • ELSEIF - Similar to ELSE except it can also offer a condition
  • ENDIF - Closes the IF statement
  • AND - Used in combination with IF or ELSEIF 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 variable
  • CASE - Used for every possible value that user wants to check specifically
  • DEFAULT - If no cases are met, then code will opt for default (optional)
  • ENDSWITCH - Ends the switch sequence

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 times
  • ENDREPEAT - Closes the repeat statement
  • WHILE - Allows a piece of code to continue repeating while a mathematical condition (same as with IF) is still being met
  • ENDWHILE - Closes the while statement
  • AND - Similarly with IF, a number of conditions can be chained together using AND

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 procedure
  • ENDPROC - Terminates a procedure and exits
  • LEAVEPROC - Exits a procedure from a mid point
  • CALLPROC - calls another procedure
  • INSTANCE - initialises an instance of another time sharing procedure
  • RETURN - sets a return value for the current procedure (used before endproc)
  • EXCLUSIVE - If used as a suffix after the defproc 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 modes
  • SUSPEND - suspends another script from running.
  • RESUME - resumes a suspended script
  • RESET - resets another script and re-runs it from the start

(See later section on procedures for rules and more information)

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 array
  • ENDARRAY - marks the end of an array
  • GETARRAY - gets an element from an array
  • SETARRAY - sets an element in an array

When arrays are defined, make sure you do not have a variable of the same name as the arrays as the compiler will then attempt to use the variable instead of the array!

Main Instruction Set - Full 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

Note: Not actually true in the final version of the compiler; set the second argument to 1 for the number to appear in hexadecimal, don't use HEX. [swy]
  • E.g.: DEBUG 1 (output a 1) or DEBUG 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

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, or Variable1 = Variable2
  • Similarly, mathematical calculations are performed in the same way
    • E.g.: Variable1 = 5 * Variable2, or Variable1 = Variable2+Variable3
      • See later section on expression Evaluation
  • Constant numbers can also be passed in hexadecimal using the C style 0x<hex num> notation.
    • E.g.: Variable = 0xff
  • 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 ++ or Variable--

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
  • LOCAL variable declaration can be combined with assigning a value
    • E.G. int TEST1 = 5 or int TEST1 = TEST2
  • GLOBAL variable declaration CANNOT be combined with assigning a value. It will ignore any “= value” placed after. Therefore you declare them with int and assign a value to them later on

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 or TEST1 = rand TEST2
  • Rand command can also be written in a different way. The first variable can be placed after the Rand 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
  • 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 corresponding ENDARRAY 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 the endarray. Each entry is an INT, 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.
Note: The debugger usually gets the appearance of arrays correct, however it may produce garbage sometimes if the start of the array is off the top of the screen.

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 corresponding ENDIF
  • Every ELSE or ELSEIF statement must have a corresponding IF 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 means if test 1 is NOT equal to 1

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 an ELSE is present after an IF, if the IF condition fails, it will perform code after the ELSE 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 the ELSE and will jump straight to the ENDIF
  • 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 and WHILE 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 IFs and ELSEIFs 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 one Case statement
  • A switch statement must have a corresponding Endswitch 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. ‘Breaks’ 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 with ENDWHILE.
  • The WHILE statement is always used in connection with an IF style comparison. The comparison can be between 2 variables or between a variable and a constant.

As such, a while loop would take a form similar to this, obviously with the comparison of your choice after the 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. In Exclusive mode, the game’s AI isn’t allowed to run along side the script. As such it would never get the chance to set the Special Event 1 flag, therefore the loop would never exit.

This covers both inserting this code into an EXCLUSIVE declared function, or in a region of code that runs in EXCLUSIVE 1 mode..

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 call Proc2, which in turn can call Proc3. 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 or 0. It can be used to switch the parser into or out of exclusive mode on a per group of instructions basis
  • 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 within IF, 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 a 0 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.
NOTE Please ensure all rumble that you insert into the game conforms to all the console manufacturers’ guidelines. Sony for example have a limit as to how long you can rumble a pad for..

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 the STOPMOVING 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
NOTE! This command used to have to be placed in a while loop.
  • The command automatically sets the AnimMode to TurnLeft or TurnRight, 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, or 1 radian. A full circle is 2*PI radians = 618) Note, due to storage constraints, if you pass a constant number, it is in the range -127 to 127. 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 or 0 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 to 127. 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 to 127

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

Health/Ankh related Hashcodes[]

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>[]

See section below on using this mode

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 passing 0)
  • 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>[]

Deprecated in the final version; the Book of Sphinx does not support it. Does nothing.
  • 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:
    1. It loops over the all objectives from HT_Objective_Glo_AnkhPiece_01 to HT_Objective_Glo_AnkhPiece_20. Checking if each objective is set to 1 or 2 and accumulating them:
      1. One means that said 'glyph' can be found at that point in the story (one can physically access that Gold Ankh Piece).
      2. Two means done/found.
    2. 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 to HT_Text_Hel_CursedPalace_FortuneTeller_AnkhPieceClue20, randomly, from the generated list of the ones that can be found (set to one), but only if at least one of them is available.
      • Returning HT_Text_Hel_CursedPalace_FortuneTeller_AnkhPiecesComplete if all 20 are done.
      • Returning HT_Text_Hel_CursedPalace_FortuneTeller_AnkhPiecesNoneValid otherwise.

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: 1
    • MFX_MiniGame_Shoot: 2
    • MFX_MiniGame_Simon: 3
    • MFX_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 the HT_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 return HT_Text_Inv_BM09_Smiling_burble (if the second parameter is set to 1) or HT_Text_Museum_Name_BM09_Smiling_burble. If the monster hashcode is invalid it returns HT_Text_Inv_Carrot.

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 the HT_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 the HT_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 the endproc). A variable cannot be declared twice within a procedure, however the name can be used again in a different procedure as it can only be used by commands within that procedure.
  • Variable names can be a maximum of 15 characters long. Keep them sensible!! They can be composed of any character you wish, however once a space is found in a variable name, it will assume that any characters after that are not part of the name!

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
  • 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 and RETURNx 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 with exclusive 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 to exclusive 1 first, copy to locals (for example) and switch back to exclusive 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.

At the game end of things, this will automatically have created a shortcut to this function that the programmers will already be aware of. If you need the full list of supported v-table functions for your project, ask one of the game-play programmers. V-table procedures, when run from external, ie the game will not throw a script error if they are not present.

The other aspect of this is that if you do not intend your procedure to be included in the v-table, then make sure you don’t call it by a name that is in the table!!

One special case in the V-table is Main’.

  • Main should always be listed in the v-table list in the source files, even if its not required. When a script object is initialized, it will always attempt to start running Main as soon as the script object is initialized in-game, before it even attempts to run anything else. However, if main isn’t defined as a procedure, then never mind, it won’t run anything!
  • Main is basically there as a main procedure to initialize data that could be used for the character’s mission control code etc.
  • In terms of writing the main function. Generally this procedure should be a 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).
*IMPORTANT* This procedure needs to be written in exclusive mode as the scripts are suspended and removed from memory immediately afterwards. If its not written as exclusive the procedure will only part process before it is stopped.

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.
*IMPORTANT* Due to the nature of when this procedure is called in the AI, if the user wishes to use it to send a character to a different node, it may be advisable to write this procedure in EXCLUSIVE mode. Reason being, if you’re doing this, the script needs to finish executing such things before the AI gets its hands on the data again. Otherwise the AI will act on data that may be incorrect.
  • 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.
NB: If the script code is running in a non-debug mode, the debugger will not have any variable name information and will list them as “var<num>
Note from the future: In the Sphinx re-release the Back mouse key was switched to Scroll Lock, and the Forward one to the Pause key. For these keys (and single-step to work) you need AllowGamePause=1 in the [Debug] section of Sphinx.ini. Albeit KeyboardOptions=1 is also recommended. All these menus only appear when the game is launched in -dev mode, of course.

Preview[]

ScriptDebugger

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.

ScriptDebuggingWatcherOptions

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 Ifs
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 on debugs means ‘string’.. The debug command without the S is used to print either numbers or variables.
  • This piece of code has also been ‘indented’ using tabs. By tabbing, the code becomes instantly more readable as, for example, its immediately readable which code runs within the repeat loop, and which code does not.

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. If OnContext is called, it will suspend main for 600 frames and then resume it from where it left off, printing ‘Hello!’.
  • If you replaced the resume command with reset, the Main 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

//heres 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 the Wait command, it pauses for 60 frames, (one second on a 60hz refresh display).
  • The 2 after the InventoryAdd command could be replaced by any other number, or a variable. If no number is supplied it assumes you mean to just add one item.
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 after Main however. Main would generally be used to set up stuff, whereas OnCreate would be used to set things in motion.
  • OnArriveAtPathNode is called automatically every time the NPC reaches a node on a path. When called, it uses an IF statement to work out which path it is currently on. Once it has worked this out, it then uses a switch statement to perform tasks depending which node along the path the NPC is at.

Other things of interest here are the SetAnim command and the 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.

  1. 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)
    EuroLandSetTriggerLinksForScript
  2. 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.
  3. 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:

  1. 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.
  2. Other monks won’t talk when the player approaches, they will carry on walking
  3. 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 dont have the item, prevent the player passing
        else
            // We Have The Item let player through
        endif
    endif

    goto mainloop
endproc


Full table of contents[]

Advertisement