FANDOM


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.

This language would be increasingly expanded year after year until the company entered bankruptcy in 2012.

This wiki page is the exact same version that the original map scripters used for reference, and it was released in 2018 by THQ Nordic together with Euroland and the other data contained in the Authoring Tools DLC. It is publicly reproduced here with permission.

Version 1.0 | August 11, 2003 | Eurocom Developments Ltd | Revisions

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

IntroductionEdit

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 GuidelinesEdit

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 rulesEdit

While the above are recommendations, these below are rules. They will make more sense after you have read further and looked at the instruction set.

These rules are:

  • All instruction code must be contained within procedures (functions).
  • Variables can be declared within a function or outside of a procedure.
  • Variables declared within a procedure are termed ‘local’ and they can only be accessed by commands within that procedure. Local variable names can be reused between procedures and they will not interfere with each other as with C.
  • Variables declared outside of any procedures are termed ‘global’ and they can be accessed by commands within ANY procedure in that file. If a variable has been declared global, a variable of the same name cannot be declared later on again, even as a local in the same file.
  • Every procedure must have an end point.
  • Procedures CANNOT call themselves (recursive).

General TermsEdit

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 OverviewEdit

GeneralEdit

These commands are general commands that can be used in a variety of ways throughout the code, as such they fall into no specific category:

  • REM - Remark. Allows user to place a comment in the code. At compile time this line is removed from the build. It's simply there to allow messages to be added into the source code for readability
  • DEBUG - debug output. Causes the program to output a number to the debug window
  • BREAK - Breaks the code out of any conditional sections or loops and continues from where that piece of code ends. Recommended that it's always used where switch/case is used
  • EXIT - Breaks completely out of the script and returns to main game engine
  • WAIT - Causes interpreter to wait for a specified number of frames
  • GETFRAMECOUNT - Gets number of AI frames elapsed. Usually since script started
  • SETFRAMECOUNT - Allows user to modify the number of frames elapsed

Variable specific and mathematical InstructionsEdit

Variables are dealt with specifically and unconditionally using these instructions.

You can’t use a variable that’s not been created, it must be declared (INT) first.

  • INT - Create an integer variable
  • CONST - Create a labeled constant number
  • RAND - Allows a random value to be assigned to a variable

Conditional IFEdit

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 SwitchEdit

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 codeEdit

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 CodeEdit

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 CodeEdit

The script language has support for basic one dimensional static arrays. They are defined similar to procedures and store sequences of 32bit values, for example hash-codes

  • DEFARRAY - marks the start of an array
  • ENDARRAY - marks the end of an array
  • GETARRAY - gets an element from an array
  • SETARRAY - sets an element in an array

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

Main Instruction Set - Full SyntaxEdit

General InstructionsEdit

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> Edit

Provides nothing more than a way of placing useful messages in the source code. The compiler completely ignores this instruction and the comments that follow it. However it greatly increases the readability of the source code to use these commands.

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.

Eg: 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

Eg: DEBUG 1 - output a 1

Or DEBUG TEST1 HEX - output value of variable TEST1 in hexadecimal

DEBUGS <short string>Edit

Like the above, this instruction doesn’t have much effect, except to display debug messages. The string can be up to a whopping 7 characters long. When the interpreter finds one, it prints it in the C debugger’s output window, and adds a space after it. Several can therefore be chained together. To make it end a line send <CR> as a string. Additionally to get the script to output a ‘who I am’ info string, send <ID> as a string

Eg 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 INSTRUCTIONSEdit

BREAKEdit

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

EXITEdit

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 COMMANDSEdit

WAITEdit

Causes the interpreter to wait for a requested number of frames. It is assumed that the scripts are polled called once per game frame. Changing the execution speed of the script will not affect how long it takes for the wait state to end.

<variable> = GETFRAMECOUNT <scope>Edit

Gets the number of frames elapsed. Usually since script was started and stores the result in the variable. If the word ‘local’ or ‘1’ is added after the command, it reads the number of frames elapsed since the current instance was started (‘local’ is only useful in Time Sharing mode, it's ignored in exclusive)

If Local is omitted or the procedure is running in exclusive mode, it will refer to the object’s global counter. This is usually the number of frames the object has ‘been alive’ for.

E.g. TestVariable = GETFRAMECOUNT local

SETFRAMECOUNT <variable or num> <scope>Edit

Sets the frame counter to a numerical value or the value of a variable passed in. Again, scope allows it to refer to the current instance’s own counter or the object’s global one if you place the word LOCAL after the variable

<variable> = GETTIMER <game scope>Edit

Allows access to the timer variables. By placing the word ‘local’ after the command it returns the level timer. Leaving local out returns the total game timer.

Its up to the game programmers how and where they reset these variables

Variable and Integer CodeEdit

These provide the basic variable operators to enable calculations and later comparisons etc to be completed.

Assignments and mathematical calculations.Edit

These are discussed later on, however variables, once declared with an INT statement are assigned values using the ‘=’ sign like in any other language

Eg: Variable = 5, or Variable1 = Variable2

Similarly, mathematical calculations are performed in the same way

Eg: 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.

Eg: 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:

Eg Variable ++ or Variable--

Variable DeclarationEdit

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 DeclarationEdit

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 NumbersEdit

If you need random values, then use the RAND command:

a = RAND <value>Edit

a = RAND bEdit

  • set the value of ‘a’ to a random number up to the size of either ‘b’ or <value>
  • eg 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 eg :
    • int Variable = Rand 5
    • int Variable = Rand OtherVariable

Bit-wise commandsEdit

It is possible to access the 32 bit variables on a per bit basis using the following 2 commands

variable = GetBit <variable> <bit number 0-31>Edit

  • returns 1 or 0 for the value of the specified bit

SetBit <variable> <bit number> <1 / 0>Edit

  • sets the specified bit of the variable to either 1 or 0

Array CodeEdit

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 CommandsEdit

DEFARRAY <array name>Edit

  • This command starts a new array. Arrays cannot be defined within procedures, nor can they be defined within other arrays

ENDARRAYEdit

  • Ends an array.

variable = GETARRAY <array> <index> Edit

  • Gets an element from an array. The index (for those non programmers amongst you) is effectively the number of the item you wish to retrieve from the array, minus one. So if you want the 10th element, you’d pass 9, 6th element, you’d pass 5, etc etc
  • If you try and read outside of an array’s range it will report an error in debug mode, or do nothing in non-debug.

SETARRAY <array> <index> <value>Edit

  • Sets an array element to a value, same rules apply as above regarding index and accessing outside of range
  • The value can either be a variable or a constant. Due to storage constraints, a constant can be maximum 255, if you need bigger, use a variable.
  • Arrays are store

C Style Array AccessEdit

Arrays can also be accessed in a method like C/C++

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 formatEdit

  • 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 useageEdit

In addition to directly accessing arrays by name, you can assign a variable to an array,

Eg 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.:

  1. var=ARRAY1
  2. setarray ARRAYOFARRAYS 0 var

(or ARRAYOFARRAYS[0]=var)


  1. var=ARRAY2
  2. setarray ARRAYOFARRAYS 1 var

(or ARRAYOFARRAYS[1]=var)


Points to note and safety precautionsEdit

The scripts’ arrays have range checking to make sure you don’t read or write outside of their limits. To allow this to work reliably, any variables you use to ‘point’ at the arrays as detailed in the section above, must always be the value they were set to when you assigned them to an array.

If you adjust that value, the interpreter will simply report an error next time you try and access the array. It's much safer this way. If you want to access different data in the same array block, use the index, don’t change the pointer.

The arrays are stored in the script code space along with the actual code. If you have 4 triggers all accessing the same script, they will also be sharing any arrays that they have between them. Ie, if you change an array entry in one, they will all ‘see’ that change.

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 InstructionsEdit

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>Edit

  • 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

ENDIFEdit

  • 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

ELSEEdit

  • 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>Edit

  • 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>Edit

  • 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:Edit

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:Edit

  1. if TEST1>1
  2.     rem Run Some code here if TEST1 is greater than 1
  3. 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:Edit

  1. if TEST1>1
  2.     rem Run Some code here if TEST1 is greater than 1
  3. else
  4.     rem If TEST1 is not greater than 1, run some alternative code here
  5. 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:Edit

  1. if TEST1>1
  2.     rem Run Some code here if TEST1 is greater than 1
  3. elseif TEST1<1
  4.     rem Run Some code here if TEST1 is less than 1
  5. else
  6.     rem if both the above fail, then Run some code here
  7. 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 ANDEdit

  1. if TEST1>1 and TEST1<10
  2.     rem do some code in here
  3. endif
  4.  
  5. if TEST1=1 and TEST2 != 100 and TEST3<TEST2
  6.     rem do some code in here
  7. 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 InstructionsEdit

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>Edit

  • 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

ENDSWITCHEdit

  • This terminates a switch sequence
  • Effectively returns to normal flow of code

CASE <constant>Edit

  • 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

DEFAULTEdit

  • 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:Edit

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:Edit

  1. switch TEST1
  2.  case 1
  3.     rem Run Some code here if TEST1 = 1
  4.     break
  5.  case 2		
  6.     rem Run Some code here if TEST1 = 2
  7. 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:Edit

  1. switch TEST1
  2.   case 1
  3.     rem Run Some code here if TEST1 = 1
  4.     break
  5.   case 2
  6.     rem Run Some code here if TEST1 = 2
  7.     break
  8.   default
  9.     rem Run Some code here if all above cases fail
  10. 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 InstructionsEdit

Repeat instructions allow a piece of code to be repeated several times. They are the simplest form of loop provided by the scripting language. Repeats can act on either a variable or a constant value to govern how many times they will operate.

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 loopsEdit

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:Edit

  1. repeat 5
  2.     rem Do this piece of code 5 times
  3. 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:Edit

  1. repeat TEST1
  2.     rem Do this piece of code TEST1 number of times
  3. 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 InstructionsEdit

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

  1. WHILE VAR<10
  2. do some code in here
  3. 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’

  1. VAR = 10
  2. while VAR>1
  3. do some code in here but never change VAR
  4. endwhile

because VAR is always greater than 1, (as it's set to 10) the loop will never end.

However this:

  1. VAR = 10
  2. while VAR>1
  3. do some code in here
  4.     VAR=VAR-1
  5. 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

Eg:

  1. while VAR1=10 and VAR2 =0
  2. do some code in here
  3. endwhile

Using WHILE usefully in time-sharing mode in a gameEdit

The fact that a WHILE loop can be left ‘hanging’ and waiting for a condition to be met can be very useful in time-sharing mode. This example uses a Sphinx specific instruction ‘GetObjective’ which returns the value of a game objective variable from the game’s AI. The loop will remain running until the GetObjective function places a non zero value in the variable VAR.

  1. Int var = 0
  2. while var =0
  3.     var = getobjective HC_SPECIALEVENT1
  4. 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 none-zero value meaning perhaps a task has been completed.

As soon as the objective is achieved, the loop will exit and more script code below can be processed. Effectively this sort of coding would work very efficiently for mission and level control duties.

NEVER use this kind of method in EXCLUSIVE mode. As it will cause a lockup. In Exclusive mode, the game’s AI isn’t allowed to run along side the script. As such it would never get the chance to set the Special Event 1 flag, therefore the loop would never exit.

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

Procedure InstructionsEdit

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 Edit

  • This denotes the start of a new procedure
  • All script files must contain at least one of them
  • A new procedure cannot be started within another procedure
  • The proc name is used by other procedures to call it and also references the procedure for calling from outside the script engine

ENDPROC <optional variable or number>Edit

  • This denotes the end of a procedure and causes program to cleanly exit that procedure
  • Exclusive, loop and conditional states from previous calling procedure are restored
  • Every procedure must have one
  • It can optionally return a variable, constant or hash-code. If not required, just leave it out

LEAVEPROC <variable or number>Edit

  • Optionally allows a subroutine to exit cleanly at any point.
  • Exclusive, loop and conditional states from previous calling procedure are restored
  • It can optionally return a variable, constant or hash-code. If not required, just leave it out

CALLPROC Edit

variable = CALLPROC Edit

  • This command can be used to call another procedure
  • The procedure being called must be defined within this file
  • A procedure cannot call itself (recursion) – compiler will report this as an error
  • Procedure calls can be nested however, e.g. 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 Edit

variable = INSTANCE Edit

  • Similar* to the above but with these differences
  • This command can create a new instance of another time sharing procedure that will run independently of this one.
  • Will not work with 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 Edit

  • 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>Edit

  • Can be used in 2 ways
    • if used after the function name on a defproc, it declares that the whole procedure will be run in exclusive mode when called from outside the script language. No term is required after the keyword
    • if used within a function it must be followed by a 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 none exclusive mode
  • If a procedure running in none exclusive mode calls a procedure with exclusive declared in its definition, it will ignore the exclusive setting on the defproc, however it will respond to inline exclusive switches
  • 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 Edit

  • 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 Edit

  • Resumes a suspended instance from the point where it was paused.
  • Execution continues exactly as normal
  • ALL can be substituted for proc name

RESET Edit

  • 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 Edit

  • Similar to Suspend except that it completely kills the procedure
  • Nothing is preserved from the killed procedure

GOTO InstructionsEdit

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:

  1. MyLabel:
  2. Debugs Sphinx
  3. Debugs Was
  4. Debugs Here
  5. Debugs <CR>
  6. 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 InstructionsEdit

In addition to the general instruction set, the language currently implements instructions specific to Sphinx. At a later stage these can be replaced with instructions specific to any other game, as long as they follow the set of guidelines concerning the creation of new instructions (see the Interpreter Usage doc)

The Sphinx Specific instructions are mapped directly onto .cpp interface functions in the game’s AI and HUD. Most instructions perform simple set/get/adjust functions, some reference game text, and some reference Hash-Codes.

Objective Variable InstructionsEdit

The Objective variables are a series of variables included in the game that are identified by hash-codes. The advantage of them is that they are completely global to all currently running scripts and to the actual game code. Also they are saved in save-games. As such they are used to preserve game progress information.

They come in 2 pairs. The first set allows the storing and reading of complete 32 bit values into the variables, the second allows each of the variables to be used as 32 on/off flags.

Each set can read variables used by the other set, which could be handy if combined with the bit-wise logic maths instructions that the language supports

< var > = GETOBJECTIVE < hashcode >Edit

  • Gets the state of an objective variable via a hashcode and stores it in a script variable

SETOBJECTIVE < hashcode > < var >Edit

  • Sets the state of an objective variable via a hashcode from a script variable

<var> = GETOBJECTIVEBIT <hashcode> <bit number 0-31>Edit

  • Gets the value of one bit of an objective variable, as above and stores it in script variable
  • Allows an objective variable to store 32 separate and independent on/off states as opposed to just one number. Handy if you know what you’re doing!!!

SETOBJECTIVEBIT <hashcode> <bit number 0-31> <1 / 0>Edit

  • Sets the value of one bit of an objective variable. Any non-zero number as the last parameter will result in the bit being set. The bit number can either be a number 0-31 or a variable containing that number, the last parameter must be a number

General AI InstructionsEdit

BEGINCONTROLEdit

  • Tells the AI that the current object is currently under control of the script language and not under AI control

ENDCONTROLEdit

  • Tells the AI that it is back in control of the current object

<var> = SETANIM < hashcode >Edit

  • Sets an animation for the script’s owning character. Will optionally return the number of frames the animation will play for in the variable preceding it

<var> = GETANIMEdit

  • Returns the hash-code ID of the animation the NPC is currently performing to a variable

<code>SETBLENDDEPTH <var or const (0-500) >Edit

  • Sets the level of animation blending between animations of the same type, eg from a walk to a job to a run.
  • There are up to 5 levels for each animation mode, therefore 0-99 represents blending between for example Move to Move2, 100-199 = Move2 to Move3 etc etc. Ask Richard :)

WAIT <numframes>Edit

  • Tells the script language to wait for a designated number of frames

FREEZEGAME <1 / 0 >Edit

  • Allows coder to completely freeze everything except the camera. This includes all AI and animation. Only the camera will move. Passing a 1 freezes passing a 0 unfreezes

LOCKCONTROLS < 1 / 0 >Edit

  • Freezes all joy-pad input for the main character. Does it nicely in terms of player’s anim mode etc, so there’s no jarring etc. LockControls 0 unlocks controls

FORCECONTROLLOCKEdit

  • Locks the controls regardless of player anim-mode. Use Lockcontrols 0 to unlock!

Controller Rumble commands Edit

There are a set of special commands to control the rumble feature of joy-pads.

STARTRUMBLE <strength 0-255> <duration in frames>Edit

  • This starts the control pad rumbling.
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 Edit

  • Stops a pad vibrating

STOPALLRUMBLEEdit

  • Stops all pads rumbling

Sound and Music commandsEdit

StartMusic <MFX_ hash> volumeEdit

  • Starts a piece of music playing, taking into account any music fader set up.
  • Volume range 0 to 127

MusicFade <fade in> <fade out> <cross fade>Edit

  • Sets up a fade for the next piece of music to start with
  • Fade in and out values range between 0 and 255. 0 being an instant start and 255 being 10 seconds. Weird range, yes, but its to keep it inline with the triggers
  • Cross Fade 1/0. Controls whether the 2 pieces of music cross fade between each other.

StartSound <SFX_hash> volumeEdit

  • Starts a sound effect playing

StopSound <SFX_Hash>Edit

  • Stops a looping sound effect from playing

Miscellaneous instructionsEdit

CUTSEQUENCE <hashcode>Edit

  • 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

REMOVEFROMMAPEdit

  • This instruction halts all script processing on this trigger, it then signals to the engine that this character, its trigger and all its data is to be removed from the map and deleted asap.

SENDMESSAGE <ref identifier> <message>Edit

  • Sends a message to another trigger that this one is linked to. (Set via the reference identifiers on the trigger’s properties in Euroland)
  • LEGACY COMMAND. Only retained for compatibility. Use the command below where possible

SENDHASHCODE <HT_HashcodeMessage> <Ref identifier>Edit

  • Sends a predefined message type to another ref identifier
  • Message types can be found by running the HT_Admin program

SETLINKVAR <Receive Variable> <Send Variable> <Ref identifier>Edit

  • Sets one of the global internal variables to the value in the send variable. (See later section on variables for the internal globals)

SETID <ID number>Edit

  • Allows the language to set an ID number for this object. The ID is used in connection with other instructions in the language

<variable> = GETDISTANCETOID <ID NUMBER>Edit

  • Returns the distance to another scripted trigger which has an ID that matches the ID number here.

<variable> = GETDISTANCETOLINK <ref identifier>Edit

  • Returns the distance to another trigger which this one is linked to

EnableEntity <hashcode>Edit

  • 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>Edit

  • Disables the rendering of a particular entity which is part of this character. Could be used to hide items the character is carrying or remove body parts

variable = GETPLAYERBUTTON <hashcode>Edit

  • Gets the state of a joy pad button using an associated hashcode. Current codes are:
    • HT_KeyPress_Square
    • HT_KeyPress_Circle
    • HT_KeyPress_Cross
    • HT_KeyPress_Triangle

SCREENFADE <hashcode>Edit

  • Performs a predefined screenfade as described by the hashcode. Current fades are:
    • HT_ScreenFade_FadeToBlack
    • HT_ScreenFade_FadeFromBlack

PLAYERTELEPORT <link>Edit

  • Causes the player to teleport to a linked trigger. The player will assume the position and orientation of this trigger

PLAYERPLAYANIM <hashcode>Edit

  • Makes the player character play an animation. Currently not working

Misc Mobility and Position CommandsEdit

Commands specifically related to moving the object or NPC around and positioning of related objects

GETPOSFROMLINK <link number>Edit

  • Sets the position of the current trigger to that of a linked trigger

SETPOSOFLINK <link number>Edit

  • Sets the position of a linked trigger to that of the current trigger

ACTIVATELINK <link number>Edit

  • Activates a linked trigger

<variable> = GETDISTANCETOLINK <ID NUMBER>Edit

  • Returns the distance to another trigger that this one is linked to.

<variable> = GETDISTANCETOSPAWNEdit

  • Returns the distance to the NPC’s original spawn point

RETURNTOSPAWNPOINTEdit

  • Tells the NPC to head back to its original spawn point. The character will keep walking until told otherwise

FOLLOWPLAYER <optional 1>Edit

  • Sets the character’s destination as being the player character. If the character is in a move state, it will follow the player indefinitely until told to go somewhere else.
  • If desired, passing an optional 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.

RUNAWAYFROMPLAYEREdit

  • Similar to above except the character runs in completely the opposite direction

STOPMOVINGEdit

  • 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>Edit

  • 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>Edit

  • 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>Edit

  • This command returns 1 if the NPC is looking in the direction of the linked NPC.

FLOCKCONTROL <0/1/2>Edit

  • 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.

Miscellaneous Flag and value setting commandsEdit

These commands are controlled via hash-codes and are used to set and get the values of various in-game variables and flags. The commands are general purpose. The values can be passed in as constants (max 255) or as a variable (any size)

Value CommandsEdit

SetItemValue <Hash-Code> ValueEdit

variable = GetItemValue <Hash-Code>Edit

Value HashcodesEdit

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 CommandsEdit

SetItemFlag <Hash-Code> 1/0Edit

variable = GetItemFlag <Hash-Code>Edit

Value HashcodesEdit

HT_GameScript_Item_InvincibleEdit

  • Sets the item to be invincible or not

HT_GameScript_Item_PathPingPongEdit

  • Sets whether a character should ping pong on its path or if it should be cyclic

HT_Trig_Message_DisableDistanceEdit

  • Used on context triggers to disable them from within. Player character won’t ‘see’ them any more

HT_GameScript_Item_PlayerHasPickupableEdit

  • Used with GetItemFlag to see if character is carrying a pickupable object

HT_GameScript_Item_NPCHeadTrackEdit

  • 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_DropPickupableEdit

  • When used with SetItemFlag, the player will drop any pickup-able that he is carrying

HT_Trig_DistanceActionFlag_RespawnPickupableEdit

  • When used with SetItemFlag, any pickup-able the player is carrying will respawn

Path/Node Mobility and Position CommandsEdit

These commands are used to control the movement of characters via paths. The commands will fail for triggers that are not creatures as stationary or non ‘alive’ objects cannot follow paths. The exception to this rule is a TR_PATH trigger which allows the use of the SETPATH command, see below.

Path Controls for NPCsEdit

SETPATH <HT_HASHCODE>Edit

  • Tells the NPC to start following a path as described by the hash code

variable = GETPATHEdit

  • Returns the hashcode value of the current path the NPC is following

variable = GETPATHNODEEdit

  • Returns the value of the current node the NPC is at on its current path

GOTOPATHNODE <variable>Edit

  • Tells the NPC to head to a certain node on its current path

POSITIONATPATHNODE <variable>Edit

  • Forcibly moves an NPC instantly to another place on a path

TURNTOPATHNODE <HT_HASHCODE> <variable>Edit

  • Makes NPC turn and face a node on a supplied path. Command will block script processing on that instance until character is facing the node

FACEPATHNODE <HT_HASHCODE> <variable>Edit

  • Makes face a node on a supplied path immediately

Path Control for TR_PATH triggersEdit

SETPATH <HT_HASHCODE> <speed 0-255cm>

  • Similar to above except the user can pass an additional number after the hashcode which states the speed in cm/sec that the entity attached to the trigger can move at. If no number is passed, then the entity assumes the speed will remain unchanged.

Direct Movement and rotation of itemsEdit

It's possible to directly move objects around without paths etc. This would be of more use for mini-games etc, but you might find a use for it

There is only one command and a series of hash-codes. Include the hashcode for the type of movement that you require.

MOVEITEM <HT_Hashcode> amountEdit

  • The amount variable represents either centimeters for movement or 100ths of a radian for rotation. (eg 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_RotateEdit

  • Rotates the object around its Y axis relative to its current orientation

HT_GameScript_Control_SetAngleEdit

  • Sets the Y axis rotation

HT_GameScript_Control_FwdBackEdit

  • Moves the object forwards and backwards along its Y Axis orientation

HT_GameScript_Control_StrafeEdit

  • Moves the object side to side perpendicular its Y Axis orientation

HT_GameScript_Control_MoveXEdit

  • Moves the object along the map’s X axis

HT_GameScript_Control_MoveYEdit

  • Moves the object along the map’s Y axis

HT_GameScript_Control_MoveZEdit

  • Moves the object along the map’s Z axis

HT_GameScript_Control_SetXEdit

  • Sets the objects X position in world space

HT_GameScript_Control_SetYEdit

  • Sets the objects Y position in world space

HT_GameScript_Control_SetZEdit

  • Sets the objects Z position in world space

Player Interaction CommandsEdit

<variable> = GETDISTANCETOPLAYEREdit

  • Returns the distance the player is away from the NPC in centimeters to the variable.

LookAtPlayerEdit

  • Character will turn its head to watch the player (not used yet)

TurnAndFacePlayer <HT_AnimMode_TurnLeft…..>Edit

  • 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……>Edit

  • While a message box is on the screen for the character talking, the character will run a talk animation while the text is being typed, and an idle when its waiting. The command will exit when the message box closes.
  • The hashcode is optional. If passed, it will use that hashcode to perform the talking animation, if not passed it will use the character’s first talk animation
  • Additionally, the idle it uses will be the one running prior to the talk to player command being issued. If an animation other than idle was being played then the command will use the character’s first idle animation.

<variable> = IsFacingPlayerEdit

  • Returns 1 or 0 depending whether or not the character is facing the player character. The character has a leeway of 10 degrees

<variable> = GetTurnDirectionEdit

  • 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>Edit

  • 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>Edit

  • This command tells all other NPCs that an NPC is currently interacting with the player. This in turn prevents them from attempting to interact with the player themselves.
  • It works by blocking all context sensitive interaction for all other NPCs’ scripts. In general it shouldn’t be needed much because an NPC’s OnContext procedure automatically switches the mode on when it starts and off when it exits. This command is provided incase you wish to change the mode under any other circumstances. If you switch it on, remember to switch it off again!!!

Help Interface InstructionsEdit

PRINTMESSAGE < hashcode >Edit

  • Prints a line of text in a help box, using its hashcode as an ID

< var > = GETABUTTONPRESSEdit

  • Returns which onscreen button on a help window was pressed (if any)

< var > = ISTHEREHTEXTEdit

  • Returns whether there’s any text in a help window

< var > = ISSAIDEdit

  • Has everything been said and has user pressed button to close message. Returns 1 if this has happened, otherwise returns 0

CLEARWINDOWEdit

  • Clears the help window

Inventory InstructionsEdit

INVENTORYADD <HT_HASHCODE> variable or numberEdit

  • 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 numberEdit

  • Similar to the above except it sets the inventory to the value as opposed to adding the value onto what’s already in the inventory.
  • Range of constant number if used is 0-255.
  • E.g.: InventorySet HT_Item_Pickup_BronzeScarab 50

INVENTORYMAXSET <HT_HASHCODE> variable or numberEdit

  • Sets max number of a certain type of object that can be held in inventory. Doesn’t actually change the amount of that object in the inventory
  • Range of constant number if used is 0-255.

<var> = QUERYINVENTORY <HT_HASHCODE>Edit

  • 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>Edit

  • Brings up the item selector rotator to allow player to select an item to give to say an NPC
  • Returns either the hashcode of the item, 0 if there are no items, or -1 if player cancels
  • E.g.: Var = itemselector HT_Trig_InvFilter_QuestItems

<var> = QUERYINVTYPES <HT_HASHCODE>Edit

  • Returns number of items of a certain type as specified with hashcode.
  • No idea what its for, ask Lenny.
  • Current applicable hash codes are:
    • HT_InvQuery_Jewels
    • HT_InvQuery_CapturedMonsters
  • If you need any more, ask Lenny.

Health/Ankh InstructionsEdit

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 InstructionsEdit

< var > = GETHEALTHEdit

  • Gets current health of player in single health units

< var > = GETGOLDANKHEdit

  • Get number of gold Ankhs owned by the player

SETMAXHEALTHEdit

  • Sets health to maximum. All Gold Ankhs on the screen will be filled

ANKHADD <Hash-Code> <number>Edit

  • Adds a number of ankhs of type described by the hash-code to the player’s health bar
  • If the user adds a Gold Ankh, this will increase the number of Gold Ankh health containers
  • If the user adds a Bronze or Silver ankh, it will add actual health to the player and fill up the gold ankhs on screen
  • Range of number is -127 to 127

ANKHSET <Hash-Code> <number>Edit

  • As above but it sets the exact number of ankhs instead of adding to it
  • Range of Number is 0-255

Health/Ankh related HashcodesEdit

These are the hash-codes that directly relate to the AnkhAdd and AnkhSet commands:

HT_Item_Pickup_BronzeAnkhEdit

  • Bronze Ankh adds 3 health units to the health bar (fills one Gold Ankh)

HT_Item_Pickup_SilverAnkhEdit

  • Silver Ankh adds 24 health units to the health bar (fills 8 Gold Ankhs)

HT_Item_Pickup_GoldAnkhEdit

  • Gold Ankh adds one extra health container to the health bar

Scarab InstructionsEdit

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.

InstructionsEdit

SETMAXSCARAB < var >Edit

  • Set maximum number of scarabs player can have (in Bronze scarabs)

< var > = ISWALLETFULLEdit

  • Returns whether player’s wallet is full or not

< var > = GETWALLETSPACEEdit

  • Returns how much room is left in player’s wallet

SCARABDISPLAY < 1 / 0 >Edit

  • Sets whether the scarabs remain onscreen at all times (1) or revert to normal vanishing after a delay (0)

Hash-CodesEdit

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_BronzeScarabEdit

  • Bronze Scarab = one unit of wealth

HT_Item_Pickup_SilverScarabEdit

  • Silver Scarab = five units of wealth

HT_Item_Pickup_GoldScarabEdit

  • Gold Scarab = 20 units of wealth

Camera CommandsEdit

The scripting language can be used to control the camera mode and certain parameters. It can also be used to set up minor cutscenes.

Camera mode change instructionsEdit

These instructions switch the overall operation mode of the camera

CAMERAMODEPLAYEREdit

  • Sets camera mode to normal follow the player mode.

CAMERAMODETOMEEdit

  • Camera looks at the trigger running the script

CAMERAMODEPATH <link number>Edit

  • 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 >Edit

  • Similar to CameraToMe except the camera turns to look at a trigger linked to this one

CAMERAMODESCRIPTPATH <HT_Hashcode_Path>Edit

See section below on using this mode

CAMERAMODETRACK <link number>Edit

  • Sets camera to a tracking cam where it remains in one place and tracks the player. The link number references the camera to use

CAMERAMODEFIRSTEdit

  • Sets the camera to first person mode looking from the character’s eyes.

CAMERAMODETALKEdit

  • Conversation camera. Sits behind the player or NPC (whichever is closer) and slightly to one side. Useful for when characters talk to the player

Previous Camera storage and retrievalEdit

These 2 instructions are used to take a ‘snapshot’ of the camera in its current state so it can be restored to its correct state after a script influenced event

CAMERASTOREEdit

  • 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

CAMERARESTOREEdit

  • Restores the camera to the stored version. Once camera has been restored, another camera can be stored

Camera Parameter ControlsEdit

When used in conjunction with the normal player camera (CameraModePlayer) and the Look-At modes (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>Edit

  • Sets the distance of the camera to the player in cm. Normal distance is 350

CAMERAELEV <variable or constant>Edit

  • Sets the elevation angle of the camera in 1/100ths of a radian. A value of zero is directly above the player and a value of 314 (~PI*100) is directly below

CAMERAHEIGHT <variable or constant>Edit

  • Sets the height of the camera relative to the player’s eye level in cm. Normal distance is 120

CAMERATURN <variable or constant>Edit

  • Turns the camera in 1/100ths of a radian. A positive value turns clockwise, a negative value turns anticlockwise. 314 (~PI*100) will turn through PI radians or 180 degrees.

CAMERAFREEZE <1 / 0>Edit

  • Locks the camera to its current position. It will still track whatever its looking at but won’t physically move.

Camera Tweak ControlsEdit

Camera tweaks are certain events that occur fairly rarely in the game but require one of the game’s cameras to act in a slightly different way

CAMERATWEAK <HT_HashCode> <value>Edit

  • Used to switch on or off various camera modes that are used in rare situations. The hashcode describes the mode that you wish to enable (by passing 1) or disable (by passing 0)
  • The list will expand as the project continues. Current tweak mode hashcodes, with their trailing values types are:

HT_CameraMode_ClimbLow <1/0>Edit

  • This tweaks the climb camera to make the camera look up at the player

HT_CameraMode_TinyRadius <1/0>Edit

  • Makes the camera have a tiny radius allowing small gap access

HT_CameraMode_DynamicFOV <radius>Edit

  • 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>Edit

  • 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>Edit

  • Camera turns through Y axis to keep the NPC in shot. Very effective and should be limited to ONE character at a time in a one-on-one situation.

HT_CameraMode_AutoStrafeWithHeight < radius >Edit

  • As above, but camera will change elevation as well turning

HT_CameraMode_StrafeOnlyHeight < radius >Edit

  • As above, but camera will change only elevation

HT_CameraMode_Reverse <1/0>Edit

  • Causes normal player camera to act in reverse and try to look at the player from the front instead of the back

Camera Scene Script Path commandsEdit

The language allows the script coder to create simple cut-scenes by controlling where the camera is positioned and what its looking at, be it the player or any other trigger that is linked to the trigger that is controlling the camera. The positioning of the camera is done via a path.

The mode is invoked with the CameraModeScriptPath command with the hashcode of the path you wish to sit the camera on. The camera can then freely be moved between nodes and told what to look at

CAMERAPOSITIONATNODE <node number>Edit

  • Camera is forcibly moved to a node on the path

CAMERAGOTONODE <node number>Edit

  • Camera will drift towards the node number at the speed you specify (see below). It will remain looking at what it was looking at before

CAMERALOOKATPLAYEREdit

  • Camera will point at the player

CAMERALOOKATLINKEdit

  • Camera will point at a trigger linked to the trigger running the script

CAMERALOOKATMEEdit

  • Camera will look at the trigger running the script

CAMERATURNSPEED <speed 0-1000>Edit

  • Sets the speed at which the camera will turn to look at a new object. 0 is completely motionless and 1000 is instantaneous

CAMERAMOVESPEED <speed 0-1000>Edit

  • Sets the speed at which the camera will move between nodes. Speed works as above.

HUD ControlEdit

The in-game HUD can be controlled using one command and a series of hash-codes.

The command is:

HUDCONTROL <Hash-Code> Value1 Value2Edit

The hashcode in this case is effectively a control code. The 2 values following it are optional. The actual hash-codes will appear as time goes on. Some will take the extra values and some will not. The main use for this will be to activate/de-activate different special modes, such as for mini-games etc

The values can either be variables or constants. Constants can only be in the range of 0-255 due to storage constraints. Any larger values can be passed via a variable.

Hashcodes will be added here as they are needed.

Book Of Sphinx CommandsEdit

These commands allow entries to be made and removed from the book

BookAddNote <hashcode> <variable>Edit

  • Adds a new quest note to the book. The hashcode is the hash of the title and the variable must contain the hashcode of the description text
  • Might seem weird to use the variable instead of another hashcode but the instructions aren’t large enough to contain the data for 2 hashcodes!!

BookRemoveNote <hashcode>Edit

  • Removes a quest note from the book

BookAddPicture <hashcode> <variable>Edit

  • Adds a quest picture to the book to chart progress through the game
  • Hashcode is that of the picture. The variable is the hashcode of the text description of the picture.

Script Macro InstructionsEdit

OverviewEdit

These instructions are used to replace tasks that are impossible or overly difficult in the scripting language.

The format is always the same:

Variable = Macro HT_Gamescript_Macro_NameOfMacro variable variableEdit

  • The variables at the end currently must be sent even if not required. Just pass in any old variable.
  • The result is always passed back in the prefixing variable.

Current Macro Instruction hash-codesEdit

HT_Gamescript_Macro_FortuneTellerEdit

  • 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

Using VariablesEdit

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 GlobalsEdit

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:Edit

  • Global variable access is slightly quicker at run time but they can also be overwritten by other procedures in the current script. They also use less memory in time sharing mode than locals
  • Local variables are untouchable by any other procedure but use more memory in time sharing mode than global ones.
  • Max 15 characters long in name
  • Be sensible. Don’t declare variables for the sake of it. Try to reuse an existing one if its appropriate and safe to do so.
  • If at all possible, use global, but be careful!
  • You can declare a maximum of 225 variables in one file. While its possible, its really not recommended as they will use memory up. 225 variables, plus all the internal

Expression EvaluationEdit

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:

  1. TEST1 = TEST2 * TEST3 + TEST4

This would break down to the following maths operations (in order)

  1. temporary = TEST2 * TEST3
  2. TEST1 = temporary + TEST4


  1. TEST1 = TEST2 * TEST3 + TEST4 * -TEST5 + 8

Would break down to

  1. temporary1 = TEST2 * TEST3
  2. temporary2 = TEST4 * -TEST5
  3. temporary1 = temporary1 + temporary2
  4. TEST1 = temporary1 + 8

General rules of thumb are:

  • It doesn’t understand brackets/braces etc. It will throw an error if it finds them
  • Keep expressions to a maximum of 7 mathematical operations
  • You can’t use maths calculations as part of a comparison. Pre-calculate what you want to compare first
  • Where possible, try and keep them as simple and as few and far between as possible as the more complex they are, the more work has to be done by the parser.
  • If you’re going to use the same answer over and over again, work it out once and store it in a variable, then use that.

Using ProceduresEdit

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 CommandEdit

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 ModeEdit

As has been mentioned earlier, the language can run in one of 2 modes. Exclusive and none exclusive.

ExclusiveEdit

In this mode, a series of instructions can be carried out with the full focus of the parser. It will not be interrupted until either the code completes or the code reverts to none-exclusive mode.

It offers 2 benefits. Firstly the code can guarantee that it’s global variables will never change unless it changes them, and the code will run very much faster whilst in exclusive mode.

A procedure can be declared as exclusive so when invoked from outside the script language it will run until completion. Placing the word ‘exclusive’ after the procedure definition does this:

Eg: defproc MYPROC exclusive

When called in this mode from outside the script language, the code runs in the ‘global instance’ and will ignore any further requests to change its exclusive state in the script code itself

If a function is not declared exclusive in its defproc, it can still make use of exclusive mode by using the exclusive command within the code, followed by a 1 or a 0 to turn it on or off. Once activated, with an exclusive 1 as above it will run until the function either ends or an exclusive 0 is encountered in the code.

If a procedure that has been called from a previous procedure changes the exclusive state and forgets to change it back, on exit the previous procedure will have its own exclusive state restored.

As above, when activated it has full control over the global variables and can guarantee that they will not change unless it changes them.

The disadvantage of using exclusive mode is that it 100% locks out any other kind of processing until the script is completed. This can cause a problem with badly written code if the script ends up in an ‘endless loop’. Nb In error checking mode it will attempt to spot endless loops.

None Exclusive Mode (or ‘time sharing’ mode)Edit

This method is considered as the default method of execution. A script running in this mode will run concurrently with any other scripts that are using this mode. As such, it’s a form of multi tasking. For critical sections it can enter the above mode for as long as needed. It is also possible to have several copies of the same function running at the same time.

It works by processing a few instructions from each procedure in turn; as such they all run together, yet as far as they’re concerned they don’t know that.

Points to remember are:

  • A ‘none exclusive’ procedure’s local variables are still private to it and cannot be modified by another concurrent task (this also applies to several instances of the same procedure running, including any other procedures they call, they won’t overwrite each others’ locals)
  • However, they share the same global variables. Any other procedure in the same script file can modify global variables, including the 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:

  1.     ...
  2.     exclusive 1
  3.     ARG1=LOCAL1
  4.     callproc NextProc
  5. endproc
  6.  
  7. defproc NextProc
  8.     int NEWLOCAL
  9.     NEWLOCAL= ARG1
  10.     exclusive 0
  11.     ...

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 TableEdit

Each project will have a V Table whether it uses it or not. The V Table is set up in the compiler as to which functions it will add to the list.

The V Table itself is simply a list of procedures that are called in situations that occur regularly in most script objects. This doesn’t mean they’ll all call the exact same piece of code. What it means is that most scripted objects are likely to require a piece of script code that is called under very commonly occurring situations such as when they are created or when they are destroyed.

Using the V-Table allows the script interpreter to take a speedy shortcut at runtime to access them, and it allows much greater standardization and portability. If a scripted object doesn’t need a procedure that’s listed in the v-table, then simply don’t supply one. If the engine tries to call a v-table procedure that doesn’t exist then it simply ignores it.

The compiler contains a list of the functions. To use them, you’ll need to know what that project’s list of v-table ‘bound’ functions is. To use them, all you have to do is name your procedure with the appropriate name and the compiler will automatically bind them to the V-Table for that object.

Common v-table procedure names would be (as cited above) “OnCreate”, “OnDeath” etc. Therefore to bind with OnCreate for example would be as simple as:

  1. 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 none-exclusive one. It doesn’t have to be, but in the context of its usage and the fact it always gets called, make sure that if you do decided to make it run in exclusive mode that you don’t make it too intensive!!

Sphinx’s current V-table functionsEdit

MainEdit

  • Always called just after the trigger is created. Use it as the character’s main background processing procedure, character setup etc.

OnCreateEdit

  • Called at the point where the trigger is created

OnSuspendEdit

  • 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.

OnDestroyEdit

  • Called when the trigger is destroyed and removed from the map. Similarly to OnSuspend, it needs to be written in Exclusive mode

OnHitEdit

  • When the character is hit by the player or a weapon.

OnDeathEdit

  • Called when the NPC is killed. Can be used to drop pickups for example

OnArriveAtPathNodeEdit

  • 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.

OnMessageEdit

  • 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

OnContextEdit

  • Called when the player presses the ‘context’ button on their joy-pad in connection with this NPC. Use for conversations etc.

OnContextAreaEdit

  • 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

OnCutSequenceEdit

  • Called immediately after a cut-scene ends. This allows any character movement, removal etc to be processed following a cut scene.

OnCollideEdit

  • 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 DebuggerEdit

OverviewEdit

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>
ScriptDebugger

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

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 CodesEdit

These are the error codes that the script interpreter may generate. Some will also be generated by the debugger and displayed at the bottom of the debug window if it starts up due to an error or encounters an error while its active

1 - Unknown ScriptEdit

  • Interpreter Cannot find this Script to run

2 - Invalid InstructionEdit

  • Interpreter does not recognize this instruction

3 - Invalid Exclusive StateEdit

  • Interpreter is attempting to enter an invalid state using Exclusive command

4 - RecursionEdit

  • Function is attempting to call itself (possibly indirectly)

5 - Instance OverflowEdit

  • No room to start another instance on this object

6 - If Stack OverflowEdit

  • Too many nested Ifs

7 - Repeat Stack OverflowEdit

  • Too many nested Repeats

8 - Proc Stack OverflowEdit

  • Too many nested procedure calls

9 - Unknown FailureEdit

  • General internal failure, tell programming team

10 - Exclusive Cycles ExceededEdit

  • Used in debug mode to attempt to prevent endless loop lockups in exclusive mode

11 - Wrong Version NumberEdit

  • Interpreter is not same version number as script compiler. Grab again

12 - Function BlockedEdit

  • Block mode is on and you’ve attempted to run same procedure twice concurrently

13 - Invalid Repeat ValueEdit

  • Trying to repeat with a value less than 1

14 - Divide By ZeroEdit

  • Trying to divide a number by zero. It's mathematically not possible

15 – Invalid Trigger LinkEdit

  • Interpreter has attempted to send a message to another trigger via a link or do some other link related processing on a link that was not set up in Euroland.

16 – Invalid AnimationEdit

  • Interpreter has tried to run an animation that doesn’t exist


Sample Script ListingsEdit

The following section contains several example listings, with explanation to demonstrate how to use the language effectively. Important script words particular to each example are highlighted in blue

Hello WorldEdit

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.

  1. defproc Main
  2.     debugs HELLO
  3.     debugs WORLD!
  4.     debugs <CR>
  5. 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 loopsEdit

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.

  1. defproc Main
  2.     int MyVariable = 0
  3.  
  4.     repeat 10
  5.         debug MyVariable
  6.         MyVariable = MyVariable + 1
  7.     endrepeat
  8.  
  9.     debugs Hello
  10.     debugs World!
  11.     debugs <CR>
  12. 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 statementsEdit

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.

  1. defproc Main
  2.     int MyVariable = 0
  3.     repeat 10
  4.         if MyVariable < 5
  5.             debugs Less
  6.             debugs than
  7.         elseif MyVariable >5
  8.             debugs More
  9.             debugs than
  10.         else
  11.             debugs equal
  12.         endif
  13.  
  14.         debugs 5
  15.         debugs <CR>
  16.         MyVariable = MyVariable + 1
  17.     endrepeat
  18. 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 proceduresEdit

Multiple Procedures are handy as they allow us to sub-divide functional areas of code into different sections. The most obvious example of this in the scripting language is the v-table procedures which are called from the engine. However, its often desirable to take this a stage further and use ‘sub-routines’. This allows us to keep the code much tidier, readable and section specific areas off to one side.

Procedures are called using the ‘CALLPROC’ command. When called, the program jumps to the new procedure, runs that, then drops back to where it was before it was called. Each procedure has its own local variables, which is another handy aspect.

Simple Procedure callingEdit

  1. Defproc Main
  2.     Debugs In
  3.     Debugs Main
  4.  
  5.     Callproc Test
  6.  
  7.     Debugs Finish
  8.     Debugs <CR>
  9. Endproc
  10.  
  11. Defproc Test
  12.     Debugs In
  13.     Debugs Test
  14. 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 valueEdit

  1. Defproc Main
  2.     Int Var=0
  3.     Debug Var
  4.     Var = Callproc Test
  5.     Debug Var
  6.     Debugs <cr>
  7. Endproc
  8.  
  9. Defproc Test
  10.     Int TestVar = 39
  11. 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 statementsEdit

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.

  1. defproc Main
  2.     int MyVariable
  3.     repeat 20
  4.         MyVariable = rand 2
  5.         debug MyVariable
  6.  
  7.         switch MyVariable
  8.           case 0
  9.             debugs ZERO
  10.             break
  11.           case 1
  12.             debugs ONE
  13.             break
  14.           case 2
  15.             debugs TWO
  16.         endswitch
  17.  
  18.         debugs <CR>
  19.         debugs <CR>
  20.     endrepeat
  21. endproc

Using GOTOEdit

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.

  1. defproc Main
  2.     int MyVariable = 0
  3.  
  4. jumpback:
  5.     debug MyVariable
  6.     debugs Adding
  7.     debugs One
  8.     debugs <CR>
  9.  
  10.     MyVariable=MyVariable+1
  11.     if MyVariable<50
  12.         goto jumpback
  13.     endif
  14.  
  15.     debugs <CR>
  16.     debug MyVariable
  17.     debugs Big
  18.     debugs Enough
  19.     debugs <CR>
  20. endproc

Suspending, Resuming and ResettingEdit

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.

  1. defproc Main
  2.     debugs Reset
  3.     debugs <CR>
  4. mainloop:
  5.     wait 60
  6.     debugs Hello!
  7.     debugs <CR>
  8.     goto mainloop
  9. endproc
  10.  
  11. defproc OnContext
  12.     suspend Main
  13.     wait 600
  14.     resume Main
  15. 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 codeEdit

  1. //this example reads elements of the array in one at a time and outputs them to the debug window
  2.  
  3. defproc Main
  4.     int test
  5.     int count=0
  6.     debugs <ID>
  7.  
  8.     repeat 10
  9.         //read an element from the array
  10.         test = GetArray TESTARRAY count
  11.         debug test HEX
  12.         count++
  13.     endrepeat
  14.  
  15.     debugs <CR>
  16. endproc
  17.  
  18. //here’s the array!
  19. //notice that there are 2 array elements per line. In this case, hash-codes
  20.  
  21. defarray TESTARRAY
  22.     HT_AnimMode_Move  HT_AnimMode_Idle
  23.     HT_AnimMode_Move2 HT_AnimMode_Idle2
  24.     HT_AnimMode_Move3 HT_AnimMode_Idle3
  25.     HT_AnimMode_Move4 HT_AnimMode_Idle4
  26.     HT_AnimMode_Move5 HT_AnimMode_Idle5
  27. endarray

Sphinx Gameplay Script ExamplesEdit

Simple ‘inventoryadd’ exampleEdit

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.

  1. defproc MAIN
  2.     repeat 50
  3.         inventoryadd HT_Item_Pickup_BronzeScarab 2
  4.         wait 60
  5.     endrepeat
  6. endproc

Making an NPC react to a player coming into/leaving rangeEdit

For example: “– The guards will adopt a firm posture when the player approaches (running a specific animation). They will stay in that position (Firm idle) until the player leaves a certain range, and returning to their normal position.

This piece of code endlessly checks the player’s distance every 30 frames. If the player comes within 500cm of the NPC, it switches to an alert state and sets a variable (AlertMode) to remember this. The player has to move away by 800cm to get the NPC to return to an idle state, again it sets the variable.

The reason for the variable is so that it knows that its, for example, already alert or not-alert and therefore doesn’t need to re-trigger any animation.

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

  1. defproc MAIN
  2.     int Distance
  3.     int AlertMode = 0
  4.     int Frames = 10
  5.  
  6.     begincontrol
  7.  
  8. MainLoop:
  9.     wait 30
  10.     Distance = GetDistanceToPlayer
  11.     if Distance < 500
  12.         if AlertMode = 0
  13.             Frames = setanim HT_ALERT_ANIMATION
  14.             wait Frames
  15.             AlertMode = 1
  16.         endif
  17.     elseif Distance > 800
  18.         if AlertMode = 1
  19.             Frames = setanim HT_IDLE_ANIMATION
  20.             wait Frames
  21.             AlertMode = 0
  22.         endif
  23.     endif
  24.     goto MainLoop
  25. endproc

Using Paths and nodes with a script to control an NPCEdit

This example demonstrates how we can use sphinx specific instructions to control the movement of an NPC. The NPC moves along 2 paths set up in Euroland, this script controls where the NPC moves on these paths.

It uses a further 2 V-table functions. OnCreate, 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

  1. DefProc OnCreate
  2.     BeginControl
  3.     SetAnim HT_AnimMode_Move
  4.     GotoPathNode 2
  5. EndProc
  6.  
  7. DefProc OnArriveAtPathNode
  8.     int Node
  9.     int Path
  10.     int Frames
  11.     Path = GetPath
  12.     Node = GetPathNode
  13.  
  14. ''cont on next page……''
  15.  
  16.     if Path = HT_Path_BoulderPath1
  17.         switch Node
  18.           case 2
  19.             Frames = SetAnim HT_AnimMode_Talk
  20.             Wait Frames
  21.             SetAnim HT_AnimMode_Move
  22.             SetPath HT_Path_BoulderPath2
  23.             GotoPathNode 3
  24.             break
  25.  
  26.           case 4
  27.             SetAnim HT_AnimMode_Idle
  28.             break
  29.         endswitch
  30.     else
  31.         switch Node
  32.           case 3
  33.             Frames = SetAnim HT_AnimMode_Talk2
  34.             Wait Frames
  35.             SetAnim HT_AnimMode_Move
  36.             GotoPathNode 7
  37.             break
  38.           case 7
  39.             Frames = SetAnim HT_AnimMode_Idle
  40.             Wait Frames
  41.             SetAnim HT_AnimMode_Move
  42.             SetPath HT_Path_BoulderPath1
  43.             GotoPathNode 4
  44.             break
  45.         endswitch
  46.     endif
  47. EndProc

Delaying animation sequencesEdit

Sometimes it may be desirable to delay animations starting for a set of characters, for example if there are many characters together all running the same animation it may be good to have them running out of sequence with each other, otherwise they may look a bit robotic if they all moved together.

For example:

Praying - running one animation all the time. There will be the case that there will be two monks together running the same animation at the same time what is not going to look very nice, it would be nice if we can delay the animation a few frames.

We can do this simply by running a random pause before we trigger the animation to start running

  1. defproc Main
  2.     int Pause
  3.     Pause = rand 120
  4.     wait Pause
  5.  
  6.     begincontrol
  7.     setanim HT_HashcodeForPrayingAnimation
  8. endproc

Sending a message to another NPCEdit

This feature enables things like ‘I’m under attack’, ‘I’m dead’, ‘I just did this action’ etc messages to be sent from one NPC to another. It uses the same system as the linked trigger messages set up in Euroland. Therefore the scripts will respond to messaging set up in Euroland and the standard messaging will respond to messages sent by scripts.

The first thing that needs to be done to set up a messaging connection is to link the NPCs’ triggers in Euroland. The link is one way, so if you needed to message 2 ways you’d need to create another link back. To set up a link to another trigger, select the trigger that you wish to send the message from and click on properties, then select the References tab (see below)

EuroLandSetTriggerLinksForScript


Pick a Reference identifier, on the above one I’ve used Reference Identifier #2, then select from the drop down list the trigger you wish to link to, I’m linking FROM ‘Spider3’ TO ‘spider2’. You can specify up to of these 8 links. And, obviously any trigger you wish to link to needs to have an identifier name given to it, or you won’t be able to select it.

Once the link is set up, you can use the scripting language SendMessage command to send values via that reference identifier link to the other script. The other script receives all messages in its ‘OnMessage’ v-table procedure. Code example follows

EXAMPLE 1 – SimpleEdit

Messaging code for Spider3, the senderEdit

This piece of code causes Spider3 to send messages (in this case the number 1, then 2) to the trigger specified on Reference Identifier #2 every 60 frames.

  1. defproc Main
  2.  
  3. mainloop:
  4.     wait 60
  5.  
  6.     debugs Sending
  7.     debugs message
  8.     debugs <CR>
  9.     sendmessage 2 1
  10.     sendmessage 2 2
  11.     goto mainloop
  12. endproc

continued on next page…

Messaging code for Spider2, the recipientEdit

This piece of code will allow Spider2 to respond to any messages sent to it. The code doesn’t know who send the message to it, so if you’re using a lot of messaging, use meaningful numbers for different events caused my different senders, unless you wish it to respond in the same way no matter who the sender was. All that happens here is that it print ‘GOT MESSAGE <num>’ to the debug window.

The value of the message received is stored in the global variable MESSAGE which is already declared.

  1. defproc '''OnMessage'''
  2.     debugs Got
  3.     debugs message
  4.  
  5.     switch MESSAGE
  6.       case 1
  7.         debugs one
  8.         break
  9.       case 2
  10.         debugs two
  11.     endswitch
  12.     debugs <CR>
  13. endproc

EXAMPLE 2 – Laughing GuardsEdit

This soldier will be shooting darts at the Geb picture at regular intervals. He will always miss and get annoyed with himself. Every time he shoots and misses the other two soldiers will laugh at his efforts.

Messaging code for main Guard, the senderEdit

  1. defproc Main
  2.     int Frames
  3. mainloop:
  4.     Frames = SetAnim HT_SHOOT_DART_AT_GEB_PICTURE
  5.     wait Frames
  6.     rem Extra code here to wait for Dart to Hit Geb Picture.
  7.     sendmessage 1 99
  8.     sendmessage 2 99
  9.     Frames = SetAnim HT_IDLE_ANIMATION
  10.     wait Frames
  11.     goto mainloop
  12. 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 recipientsEdit

  1. defproc OnMessage
  2.     int Frames
  3.     switch MESSAGE
  4.       case 99
  5.         Frames = SetAnim HT_LAUGH_AT_OTHER_SOLDIER
  6.         wait Frames
  7.         Frames = SetAnim HT_IDLE_ANIMATION
  8.         break
  9.       default
  10.     endswitch
  11. 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’ behaviorEdit

If we had an area within the game which had a large collection of NPC’s it may be desirable to allocate them with subtly different behavior to give them more personality. For example:

  • The monks will have three different behaviors:
    • Walking around a path with 5 nodes. If the player approaches a monk (2 meters) he will face him and Talk with the player , then he will carry on walking on the path.
    • Other monks won’t talk when the player approaches, they will carry on walking
    • And other monks will face the player but they will not talk, they will only run a waiting animation.

To do this we need to assign a random activity to the monks when they are created.

This example is incomplete in terms of the monk’s behavior, however it demonstrates how they can be randomly given behavior and a starting position when they are created.

The example uses extra procedures which are called from Main depending which ‘personality’ the monk has. The reason for this is that it makes the code MUCH more readable in the long run!! All processing that would be used by every monk is processed as usual in main, then the specific processing would be done in each monk’s own behavior procedure.

This example is also using GLOBAL variables. This allows each of the 3 behavioral procedures to read common information such as distance to player and alert status.

  1. int Distance
  2. int Type
  3.  
  4. defproc Main
  5.     rem pick a random start node for monks
  6.  
  7.     setpath HT_PathForMonksToWalkAround
  8.     Type = rand 5
  9.     PositionAtPathNode Type
  10.  
  11. Continued on next page….
  12.  
  13.     Rem pick a random behavior type for monks
  14.     Type = rand 2
  15.  
  16. mainloop:
  17.     wait 60
  18.     Distance = getdistancetoplayer
  19.  
  20.     debugs I
  21.     debugs AM
  22.     debugs TYPE
  23.  
  24.     switch Type
  25.       case 0
  26.         callproc MonkType1
  27.         break
  28.  
  29.       case 1
  30.         callproc MonkType2
  31.         break
  32.  
  33.       default
  34.         callproc MonkType3
  35.     endswitch
  36.     debugs <CR>
  37.     goto mainloop
  38. endproc
  39.  
  40. Continued on next page….
  41.  
  42. defproc MonkType1
  43.     rem Do MonkType 1 behavior in here
  44.     debugs ONE
  45. endproc
  46.  
  47. defproc MonkType2
  48.     rem Do MonkType 2 behavior in here
  49.     debugs TWO
  50. endproc
  51.  
  52. defproc MonkType3
  53.     rem Do MonkType 3 behavior in here
  54.     debugs THREE
  55. endproc

Making an NPC ignore the playerEdit

Certain situations require the NPC to completely ignore any attempt by the player to interact with it. This is done by using the IGNORE command. Passing a 1 will switch the NPC into an ignoring state and 0 will make it take notice of the player again.

This example makes the NPC alternatively aware of the player for 2 seconds and then ignore him again for 2 seconds. Although not demonstrated by this example, the ignore command does not

  1. defproc Main
  2. mainloop:
  3.     debugs AWARE
  4.     ignoreplayer 0
  5.     wait 120
  6.     debugs IGNORE
  7.     ignoreplayer 1
  8.     wait 120
  9.     goto mainloop
  10. endproc

Triggering and Reacting to Cut SequencesEdit

It’s possible to activate cut sequences from within the script language. When they are activated all script and AI processing will be suspended until the cut sequence ends.

Once the cut sequence has ended, its often required that NPCs will be moved to different locations, be doing different actions or removed from the map completely, depending upon the piece of story told in the cut sequence.

To allow for this, Sphinx has a V-Table procedure called OnCutSequence which is automatically called immediately for each NPC after a cut sequence ends. If a character requires no action to be taken for any cut sequences, simply don’t write this procedure!!

The following code example is based on this scenario:

Palace - The player approaches the Akarian King and a cut-scene takes over. When the cut-scene finishes, Ishka has vanished from the room

This requires 2 pieces of script code. One piece for the King so that he will activate his cut sequence as the player approaches, and another for Ishka who will have to move once the cut sequence has completed. The king’s code sets a variable to say whether or not it has already triggered its cut sequence.

Here is the King’s code:

  1. defproc Main
  2.  
  3.     int distance
  4.     int mode = 0
  5.  
  6. mainloop:
  7.     wait 30
  8.     distance = getdistancetoplayer
  9.     if mode=0
  10.         if distance<200
  11.             mode=1
  12.             CutSequence HT_KING_CUTSCENE
  13.         endif
  14.     endif
  15.     goto mainloop
  16. 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.

  1. defproc OnCutSequence
  2.     exclusive 1
  3.  
  4.     switch CUTSEQ
  5.       case HT_KING_CUTSCENE
  6.         rem Move Character to a different location
  7.         break
  8.  
  9.       case HT_ANOTHER_CUTSCENE
  10.         rem Move Character to a another different location
  11.     endswitch
  12.  
  13.     exclusive 0
  14. 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 itemEdit

Depending upon whether or not the player has a certain object an NPC may react differently to the player character. For Example:

Temple – A guard near the door to the temple will speak to the player and tell him that he can’t go through the airlock without an Oxygen ankh. If the player has an oxygen ankh he will let the player pass.

We use the QueryInventory command to see if the player has an item or a number of items in their possession. The command returns the number of a particular item the player has in their inventory.

  1. int HasAnkh
  2.  
  3. defproc Main
  4.     int distance
  5.     HasAnkh=0
  6.  
  7. mainloop:
  8.     wait 30
  9.     distance = GetDistanceToPlayer
  10.     if distance<500
  11.         HasAnkh = QueryInventory HT_Item_Ability_OxygenAnkh
  12.         if HasAnkh=0
  13.             // We don’t have the item, prevent the player passing
  14.         else
  15.             // We Have The Item let player through
  16.         endif
  17.     endif
  18.  
  19.     goto mainloop
  20. endproc
Community content is available under CC-BY-SA unless otherwise noted.