-
Notifications
You must be signed in to change notification settings - Fork 619
ICARUS Scripting
ICARUS is the scripting language used by the Jedi Knight series games for defining character, camera, level and object behaviour in the games, and for creating cinematic cutscenes.
The following is the official, comprehensive manual on the ICARUS scripting language and its general user interface front-end software, BehavEd.
BehavEd is available to download as part of the Jedi Academy SDK, released by Raven Software.
BehavEd is a drag and drop interface for building scripts. On the left you'll see the list of ICARUS commands available to you. On the right is a column of buttons used for BehavEd functions like loading and saving your scripts, quitting, copying and pasting commands, etc.
Actions
Add - Add selected command to the script window (same as double-clicking on the command)
Delete - Delete the selected line from the script
Edit - Edit the selected line (same as double-clicking on the line)
Clone - Copy the selected line and paste the copy on the next line
Copy - Copy the selected line to the buffer
Paste - Paste the line in the buffer to the line after the currently selected line
File
New - Start a new script
Open - Open an existing script
Append - Append an existing script to the end of the one you're editing.
Save - Save your script under it's current name
Save As... - Name and save your script
Application
Preferences - Brings up the list of directories BehavEd should look for certain files in.
About... - Some information about BehavEd
Exit - Quit BehavEd
Treeview Options
Show Types - Turns on the showing of the types of all values and strings and table entries.
%g floats - Shows all floats with several decimal places (default shows only decimal places used)
Compile! - Process your script so that it is ready to be run in the game by ICARUS.
The Feedback Window
All errors and messaged related to compiling a script will appear in the bottom white window. If a script compiled successfully you will see a smiley face and the text "OK". Otherwise, it will report the error.
ICARUS Command List
You can add a command to your script (displayed in the large central window) by either dragging a command from this list to that window or double clicking on it. If you simply highlight a command, information about it will appear just above the Feedback Window.
Key:
{} - Indicates a command that can be expanded to contain other commands.
e - A normal command
[] - A macro, essentially a group of commands.
Commands:
(For detailed descriptions, see each command's individual description later in this document)
flush if loop affect run wait action sound move
rotate use kill remove print rem declare free get
random set camera task do wait dowait walkTo runTo
Once you've added a new command to your script, you have to enter the right parameters. This is done by either double-clicking on the command or highlighting it and hitting the "edit" button. You will be presented with a series of edit boxes that you can alter the values of.
Some are simple edit boxes where you just type in the values you desire. These will usually have an indication below the box of what type of data is expected, string, float, vector, etc.
Some are drop-boxes where you must pick a value from a list of available ones. These drop-downs will occasionally change what the subsequent edit boxes expect as data. When you've chosen a new drop-down value, you must hit the "Re-Evaluate" button to ensure that the extra edit boxes are updated to the right number and type.
Next to edit boxes you will see a "Helper" button. By clicking on this, you can access the "get" command (see below). Only the get fields that match the type of the expected data will be available. If the expected data type is a float you also have the choice of using the "random" function (again, see below). Simply type in the minimum and maximum ranges for the float value and hit the random button. If you decide not to use any of the helper functions, hit "revert" to return to a normal edit-box.
When you are done, hit "ok" or "cancel" if you don't wish to apply the changes you made.
(flush, loop, wait, if, affect, run)
These are the commands that control the "flow" of the script. In other words, they affect how and when commands are executed.
rem - this command doesn't actually control the flow of the script, it is just a comment. You can put notes to yourself in here or a description of the action to make it easier for others to read the script, etc. It's generally a good practice to put a rem as the first line of your script so you can tell what the script does just by looking at the first line.
flush - This command will clear out any and all commands that have not yet executed on an entity. This does not clear any set values or other such variables you may have set on an entity, just the unprocessed commands.
For example, you have one script that is run right away:
set ( "SET_BEHAVIORSTATE", "BS_DEFAULT" )
set ( "SET_LOOK_FOR_ENEMIES", "false" )
set ( "SET_WALKING", "true" )
set ( "SET_NAVGOAL", "walk1" )
wait ( 3000 )
print ( "Hello!" )
But then, after the first two commands have been run, another script is run on the entity that does this:
flush()
set ( "SET_LOOK_FOR_ENEMIES", "true" )
set ( "SET_WALKING", "false" )
print( "I kill you!" )
The "flush" command will clear out any commands that haven't run yet in the first script. Assuming this second script ran before the wait and print commands of the first script were executed, the NPC would now ignore those altogether, go into a Hunt and Kill Behavior State and print "I kill you!". It would never wait the 3 seconds and print "Hello" - those commands have been "flushed".
Flushes are useful for clearing previous scripts that are running in loops. A flush will delete that loop.
loop - is a simple command that allows you to execute a sequence of commands repeatedly. So instead of:
wait ( 3000 )
print ( "Hello World!" )
wait ( 3000 )
print ( "Hello World!" )
wait ( 3000 )
print ( "Hello World!" )
wait ( 3000 )
print ( "Hello World!" )
You could simply use:
Loop ( 4 )
{
wait ( 3000 )
print ( "Hello World!" )
}
If you wish a loop to continue forever, put a "-1" in the loop number.
Loop ( -1 )
{
wait ( 3000 )
print ( "Hello World!" )
}
This will make these two commands repeat forever or until these commands are flushed.
wait - Is a simple command used to make a script wait a certain number of milliseconds before continuing. 1000 milliseconds equals one second.
For example:
print( "Hello..." )
wait( 1000 )
print( "World!")
Will make the script print "Hello...", wait a second, then print "World!".
if - This command will let you specify a condition that must be satisfied in order to execute a certain block of commands.
The simple format is:
if ( value1 = value2 )
{
print( "Hello World!" )
}
The only values you can check are variables and values you obtain using the "get" command.
For example:
if ( get(FLOAT, "health") > 0 )
{
print( "Hello World!" )
}
More on the variables and the "get" command later...
You may also put an "else" block of commands after the if command:
if ( get(FLOAT, "health") > 0 )
{
print( "Hello World!" )
}
else
{
print( "Hey, I'm dead!" )
}
NOTE: The "else" command is not yet available in the BehavEd editor...
affect - This command is used to send a block of commands to an entity other than the one running the current script. For example:
print( "Hello, Fred!")
affect ( "Fred", FLUSH )
{
wait( 1000 )
print( "Leave me alone!" )
}
In the above example, the entity running the script will print "Hello, Fred!", then tells fred to wait a second and respond with "Leave me alone!"
The name ("Fred") that you specify must be the script_targetname of the entity you wish to affect. For NPCs, their script_targetname will be the same as the NPC_targetname you specify for them in the map editor. For all other entities, you must specify a script_targetname if you intend to affect them at some point.
The second parameter in the "affect" command decides whether you want to totally wipe out what the entity was doing before or just insert the new command into what he was doing. The choices are:
FLUSH - Just like the "flush" command, will wipe out any script commands Fred might have been waiting to process in favor of the new ones in the "affect" block.
INSERT - Wherever Fred is in his script commands he is running, throw in these new commands. So if Fred is sitting at a table and alternating between eating and looking around, this new command will make him print "Leave me alone!" and allow him to continue eating and looking around.
It's important to remember that an affect simply dumps commands onto another entity. The script that has the affect in it will not wait for the commands inside the affect to finish.
For example:
print( "Hello, Fred!")
affect ( "Fred", FLUSH )
{
wait( 1000 )
print( "Leave me alone!" )
}
print( "Damn you to Hell, Fred!" )
This will not wait until Fred is done talking before printing "Damn you to Hell, Fred!". What will happen is something like this:
"Hello, Fred!"
"Damn you to Hell, Fred!"
Then, a second later, Fred will say:
"Leave me alone!"
This is because "affects" complete instantly for the script the affect is in. If you wanted the reply to Fred to wait a second after Fred spoke, this is what you would do.
print( "Hello, Fred!")
affect ( "Fred", FLUSH )
{
wait( 1000 )
print( "Leave me alone!" )
}
wait( 2000 )
print( "Damn you to Hell, Fred!" )
The reason you wait two seconds is because you and Fred are now running your scripts simultaneously. You have to wait for the one second before Fred speaks and wait another second before you speak.
This is a very important rule and is easy to forget. Always remember: affects are not executed in the script you are running, they are shoved onto another entity for them to execute!
run - this command is used to run another script from inside a current script.
Say you have a script like so:
print( "Hello World!" )
run( "test/response" )
wait( 1000 )
print( "Oh well, I give up." )
and a script named "test/response" like so:
wait( 1000 )
print( "I said Helloooo World!" )
What would happen when you ran the first script is this:
"Hello World!"
(wait a second)
"I said Helloooo World!"
(wait a second)
"Oh well, I give up."
So, basically, all a "run" command does is insert a bunch of commands from another script into your script where your "run" command is. This becomes useful when you have a block of commands you want to execute from several different scripts. If you take these commands and isolate them in one script, you can "run" them from any script you wish. This should save you some time in not having to enter that command block over and over. This also makes fixing errors and making changes in that block of commands much easier since you only have to make a correction/change in one script, not several.
(walkTo, runTo, etc)
Macros are simply blocks of commands, grouped for convenience. Macros have no special function. They can be useful for containing a large block of commands in a collapsible group so it doesn't clutter up your editing window.
(task, do, wait, dowait)
Tasks are a way of making your script wait for certain commands to finish before it continues. This is useful for all sorts of scenarios. If, for example, you want an NPC to walk to a button then use it, you must tell the script to wait for the NPC to get to the button before you tell them to use the button. If you want to wait for an NPC to finish an animation or a certain line of dialogue before changing your camera angle, you need to use a task.
The general format for creating a task is as such:
task ( "TASKNAME" )
{
commands...
}
The TASKNAME is anything you want, though you should not use the same TASKNAME for two different tasks.
To execute the commands in the task, use the command:
do ( "TASKNAME" )
This will start the commands in the task block you have identified by name.
When you want to pause script execution until the task is completed, use this command:
wait ( "TASKNAME" )
The script will not continue until all the commands in the task are completed. NOTE: If you use the "wait" without using the "do" command first, your script will NEVER CONTINUE! In other words, don't do that.
Often, you may find that you want to "do" a task then "wait" for it immediately. In this case, in order to save some time, you can use this command:
dowait ( "TASKNAME" )
This will start the task block's commands and then instantly pause script execution until that task is complete.
It is important to know that while there are some commands that take some time to complete (like getting to a navgoal or finishing a line of dialogue), most commands complete immediately. In this document, any command that does NOT complete immediately is marked by an asterisk (*). If you are going to make a task, you have to have at least one of these commands in it, otherwise your task will complete as soon as you start it.
One of the most important things about tasks to remember is this: A task is unique to the entity it is defined on. This means if you define a task on one entity and try to "do" the task on a different entity, you will get an error that says "cannot find task block TASKNAME".
For example:
task ( "Go to the door" )
{
commands...
}
affect ( "Fred", FLUSH )
{
dowait ( "Go to the door");
}
Is incorrect. "Fred" has no idea whatsoever what the task "Go to the door" is, because that task was defined on the entity that affected "Fred", not on "Fred" himself!
The correct way:
affect ( "Fred", FLUSH )
{
task ( "Go to the door" )
{
commands...
}
dowait ( "Go to the door");
}
This is a common error at first, but you will get used to it.
The final thing you should remember about tasks is you should define them only once. You may execute the task as many times as you want, but define it only once.
For example:
loop ( 50 )
{
task ( "Go to the door" )
{
commands...
}
dowait ( "Go to the door");
}
Is incorrect. This is going to define the "Go to the door" task 50 times! What you want to do is:
task ( "Go to the door" )
{
commands...
}
loop ( 50 )
{
dowait ( "Go to the door");
}
This way, you define the task only once, but execute it 50 times, as you intended.
sound( CHANNEL, filename )
The sound command is used to play a sound. There are several channels you can play a sound on. Playing a sound on a channel that is already in use will cut off the previous sound.
CHAN_AUTO Auto-picks an empty channel to play sound on
CHAN_LOCAL menu sounds, etc
CHAN_WEAPON plays on weapon sound channel
CHAN_VOICE Voice sounds cause mouth animation and attenuate slowly
CHAN_VOICE_ATTEN Causes mouth animation, use normal sound falloff (attenuates faster)
CHAN_ITEM plays on item sound channel
CHAN_BODY plays on body sound channel (footsteps, impact sounds, etc.)
CHAN_AMBIENT added for ambient sounds
CHAN_LOCAL_SOUND chat messages, etc
CHAN_ANNOUNCER announcer voices, etc, plays globally
CHAN_LESS_ATTEN attenuates similar to chan_voice, but uses CHAN_AUTO's empty channel auto-pick behavior
CHAN_MENU1, menu stuff, etc
CHAN_VOICE_GLOBAL Causes mouth animation and is broadcast, like announcer
To wait for a voice sound to finish before continuing a script:
task( "say hello" )
{
sound( CHAN_VOICE, "sound/voice/test/helloworld.wav" )
}
print( "Hello yourself! )
You can also wait for sounds played on CHAN_VOICE_ATTEN and CHAN_VOICE_GLOBAL channels. You cannot wait for sounds played on any of the other channels.
(move, rotate)
Think of each "move" command as the waypoint, a keyframe in your animation the brush will move between, example:
loop ( -1 )
{
move ( < 0.000 0.000 0.000 >, < 0.000 0.000 0.000 >, 2000.000 );
wait ( 5000.000 );
move ( < 0.000 0.000 100.000 >, < 0.000 0.000 0.000 >, 2000.000 );
wait ( 5000.000 );
}
This code will put the affected entity on a constant motion between (0,0,0) and (0,0,100), each move taking 2 seconds, with 3 second stops.
While the first set of coordinates refers to position on map, the second set of numbers refers to rotation that happens between the waypoints.
loop ( -1 )
{
move ( < 0.000 0.000 0.000 >, < 0.000 0.000 0.000 >, 2000.000 );
wait ( 5000.000 );
move ( < 0.000 0.000 0.000 >, < 0.000 180.000 0.000 >, 2000.000 );
wait ( 5000.000 );
}
This code will continously 'flip' the entity by 180 degrees on the Z axis.
You may also use the "get" and "tag" commands to get the coordinates you want, like so:
move ( tag("spot1", ORIGIN), tag("rotation1", ORIGIN), 1000 )
move ( get(VECTOR, "parm1"), get(VECTOR, "parm2"), 1000 )
More on the tag and get commands later...
The rotate turns the brush to a specific angle over time, like so:
rotate( <0 90 0>, 1000 )
Turns brush's yaw to 90 over 1 second.
Note that a duration of 0 will make the brush turn/move to its new angles/position instantly.
(enable, disable, move, pan, zoom, roll, follow, track, distance, fade, shake, path)
The "camera" commands are used for cinematics: scripted events in which the player's POV changes to a camera view, like a movie.
Enable - This turns on camera mode. It changes the player's view origin and angles to that of the camera.
Disable - This turns off the camera and returns the player's view origin and angles to that of Munro.
Move - This is used to move the camera to a specific spot or coordinate. This can be done one of three ways:
camera( MOVE, <0 0 0>, 0 )
moves the camera to the coordinates <0 0 0>, the origin of the world
camera( MOVE, tag( "cameraSpot1", ORIGIN ), 0 )
moves the camera to the spot in the map that the ref_tag named "cameraSpot1" is located.
camera( MOVE, get( VECTOR, "SET_ORIGIN" ), 0 )
moves the camera to the origin of the current ent (or whatever vector is stored in whatever set field or variable you "get").
The last parameter controls how long (in milliseconds) it takes for the camera to get to the specified position. A value of zero, as above, will move the camera there instantly. A value of 1000 will make the camera move from it's current location toward the new location at a speed that will get it to it's final position in 1000 milliseconds (one second).
Pan - This is used to change the camera's view angles. You can pan left, right, up, down and even tilt the camera right or left.
Camera ( PAN, destinationAngle, panDirection, duration )
The general format is:
Camera( PAN, <0 0 0>, <0 0 0>, 0 )
The vector's values are: <pitch yaw roll>
The destinationAngle is the final angle you intend to finish at. The panDirection is the direction you wish each axis to move in. Any value is valid, but it will only look to see if each axis' direction is positive or negative. A value of zero will make the camera find the shortest direction to pan in to reach the destinationAngle.
Again, like the MOVE command, you can set this in three different ways:
camera( PAN, <0 0 0>, <1 1 1>, 0 )
camera( PAN, tag( "cameraSpot1", ORIGIN ), <1 1 1>, 0 )
or
camera( PAN, get( VECTOR, "SET_ORIGIN" ), <1 1 1>, 0 )
A duration (last parameter) of 0 makes the camera immediately set its angles to those specified, a longer duration will make the camera turn ("pan") to the new angles.
Zoom - Changes the FOV or "focal length" of the camera. Rather like zooming in or out.
Camera( ZOOM, 80, 0 )
The second value is the FOV of the camera you wish to use. 80 is the normal game FOV, 1 is maximum zoom, and 120 is the widest angle you can have. The last parameter is the duration of the zoom - zero means instant and non-zero will make the camera's zoom change gradually over the specified amount of milliseconds.
Roll - This command will allow you to change just the roll of the camera without messing up the other angles. The reason for the existence of this command is that a ref_tag cannot be easily set up to store a roll value.
Camera( ROLL, 45, 0 )
A positive value tilts ("rolls") the camera to the right, a negative value tilts it to the left. The last value is the duration of the roll.
Follow - Allows the camera to automatically aim itself at a group of entities.
camera( FOLLOW, "cameraGroup", speed, initialLerp )
The "cameraGroup" is a string. Every entity with the same string in it's "cameraGroup" will be part of the "scene". The camera will find the center of every "actor" in the scene and stay pointed at the center of them. This is useful not just for following a moving actor but also for framing groups of actors. To set someone's cameraGroup, use the SET_CAMERA_GROUP command (see the Set Command Variables section).
The second parameter is the camera's turning speed. The higher the turning speed, the better the camera will keep up with the action. A value of zero will use the default camera turning speed (100).
"initialLerp" is a true/false (1/0) command that will tell the camera to PAN from it's current angle to the angle that frames the scene you've requested (rather than just snapping to the angle). This is where a slower or faster turning speed may come in handy - it allows you to control the speed of the initialLerp pan.
Track - Makes the camera move along a path.
Camera( TRACK, "trackName", speed, initialLerp )
The "trackName" should be the targetname of the first in a series of linked path_corners on your map. The camera will move from path_corner to path_corner until it reaches the end. You may target the last path_corner at whatever you want and the camera will fire it when it gets there.
The "speed" parameter determines how fast the camera moves on it's track.
"InitialLerp" will make the camera MOVE from its current position to the start of the track you specify. It will move at the speed you specify in this command.
When the camera gets to a path_corner, it will look at the next path_corner to see if the designer has set a "speed" value on it in the map editor. If so, it will use that speed instead of the speed set in this command. In this way you can make your camera move along its track at different speeds as it progresses.
Distance - Makes the camera keep a specific distance from it's cameraGroup's center.
Camera( DISTANCE, distance, initialLerp )
This command works in conjunction with the "FOLLOW" command, and usually the "TRACK" command. The "distance" you specify tells the camera to speed up or slow down to maintain a constant distance from the "cameraGroup" specified in the "FOLLOW" command. If the camera is on a track, it will stay on the track and try to keep this distance (overriding any speed value set by the TRACK command).
The "initialLerp" will make the camera speed up to get to the desired distance from the cameraGroup rather than snap to it.
Fade - Makes the camera change it's RGB color and opacity from one value to another.
Camera( FADE, startRGB, startOpacity, endRGB, endOpacity, duration )
Very simply, used to create a colored overlay that can fade in and out. To do a simple fade to black lasting 2 seconds:
Camera( FADE, <0 0 0>, 0, <0 0 0>, 1, 2000 )
You can also fade back from black, fade to/from white or whatever color you want, using the red green and blue vector.
Shake - Makes the camera shake for a certain amount of time.
Camera( SHAKE, intensity, duration )
"intensity" can be from 1 to 16.
"duration" is in milliseconds.
Path* - Makes camera follow a ROF path
Specify a ROF file for the camera to play. The camera will move along that ROF. This is a command you can wait for completion on.
Camera( PATH, "roff/bob.rof" )
Get( FLOAT/VECTOR/STRING, "" )
Anything you can set, you can get. Get is used to copy a value from one Set field/variable to another and in "if" statements.
set("SET_PARM1", "Fred")
if( get(STRING, "SET_PARM1") = "Fred" )
{
print("Hello World!")
}
Note that you must specify what type of info you want to "get" from the variable whose name you specify. This should match what you'll be storing the "get" in or comparing it to. Valid types are STRING, VECTOR and FLOAT.
In BehavEd, "Get" is accessed by hitting the "helper" button when you're editing a command - it will automatically pick the right type for you. For "if" statements in BehavEd, you still have to type the whole get expression manually.
(global/local)
All variables must be declared before being used. Provide the type of the new variable and the name.
declare( FLOAT, "counter" )
declare( VECTOR, "position" )
declare( STRING, "myName" )
You can then set the variable like so:
set( "myName", "Fred" )
Then you can get the variable any time you want, for instance, in an if statement:
If ( get(STRING, "myName") = "Fred" )
{
print( "Hello, World!" )
}
When you're done with the variable you can free it:
free( "myName" )
All variables are freed when the level ends, but you might want to free one yourself if you're running low on available variables (there are 32 of each type available at a time).
Additionally, you can get info from a variable in set commands, as below:
set( "SET_NAVGOAL", get(STRING, "globalNavGoal") )
This way you could have some other script set the value of the globalNavGoal (it would be the targetname of a navgoal), then someone else just has to run this generic script that would tell them to go to whatever the globalNavGoal is. They would set the globalNavGoal like so:
set( "globalNavGoal", "FredsHome" )
or, if that script is meant to be a generic script, they can use parms, like so:
set( "globalNavGoal", get(STRING, "SET_PARM1") )
This way you can have 50 ents use this script to set a navgoal to whatever their parm1 is.
Random( minNumber, maxNumber )
This is a valuable function for getting some randomness in your scripts. Simply enter this in a spot where a number would normally occur and it will pick a random number.
random( 0, 3.5 )
Will plug in a random number between 0 and 3.5.
The only way to access this in BehavEd is to hit the "helper" button next to float type edit boxes and use the "rnd" button and edit fields (as described in the first section).
tag( "targetname", ORIGIN/ANGLE )
In Q3Radiant, you can place objects called ref_tags. You can set an angle on them by pointing them at an info_null or simply picking an angle. These ref_tags can then be used in scripts as a nice way to get coordinates and angles without having to mess with actual numbers.
For example:
camera( MOVE, tag("cameraSpot1", ORIGIN), 0 )
This will move the camera to the origin of the tag with the targetname "cameraSpot1".
You can also get angles from a tag:
camera( PAN, tag("cameraSpot1", ANGLES), <1 1 1>, 0 )
The general benefit of using this system is that you never have to tweak actual coordinates if you want to just adjust the tag by eye or need to move whole parts of the map.
set( SET_TABLE, value )
The "set" command is probably the most useful single command. Generically, this function will simply change the value of whatever Set Variable you choose from the Table (in a drop-down list in BehavEd). However, the different effects of changing these variables are widely varied making this a powerful command.
Usage:
set( "SET_BEHAVIORSTATE", "BS_WANDER" )
This simple command, for example, will make an NPC randomly wander throughout the waypoint network on a level.
For the full list of Set Command Variables, see the next section.
A quick word about the SET_PARM variables:
There are eight of these - SET_PARM1 through SET_PARM8. These are meant to be generic value holders, changing their value will do nothing in itself. Think of them as storage. Every entity can store up to 8 values in these parms. They can be numbers, vectors or strings. The main purpose of these are that they can also be set on an entity in the map editor. This has the effect of allowing you to write one script that works slightly differently for a potentially infinite number of entities who run it.
A simple example:
On your map, you have an NPC, who you want to walk between three navgoals, called "nav1", "nav2", "nav3", etc. Here's the script you would normally write:
set( "SET_BEHAVIORSTATE", "BS_DEFAULT" )
set( "SET_WALKING", "true" )
Loop( -1 )
{
task( "walk to nav1" )
{
set( "SET_NAVGOAL", "nav1" )
}
task( "walk to nav2" )
{
set( "SET_NAVGOAL", "nav2" )
}
task( "walk to nav3" )
{
set( "SET_NAVGOAL", "nav3" )
}
}
Simple enough, your guy will now walk between the three navgoals forever.
Now you place another NPC who's supposed to walk between a totally different set of three navgoals, "point1", "point2" and "point3". Now, you could make a whole new script for this, but it might be a good idea to just use the same exact one. Here's where parms come in.
On the first NPC, walking to the "nav" set of navgoals, set these values in the map editor:
parm1 nav1
parm2 nav2
parm3 nav3
On the second NPC, walking to the "point" set of navgoals, set these values in the map editor:
parm1 point1
parm2 point2
parm3 point3
Then, both NPCs can run the same script, which looks like this:
set( "SET_BEHAVIORSTATE", "BS_DEFAULT" )
set( "SET_WALKING", "true" )
Loop( -1 )
{
task( "walk to nav1" )
{
set( "SET_NAVGOAL", get( STRING, "SET_PARM1" ) )
}
task( "walk to nav2" )
{
set( "SET_NAVGOAL", get( STRING, "SET_PARM2" ) )
}
task( "walk to nav3" )
{
set( "SET_NAVGOAL", get( STRING, "SET_PARM3" ) )
}
}
Each get command will get the corresponding navgoal name that was stored in the entity's parms in the map editor.
This allows you to re-use a script over and over. It might seem like more work at first, especially for just these two NPCs, but if you start to have fifty NPCs all doing the same thing, you'll soon find that keeping track of one script and never having to write another is very helpful. These scripts can be used for several NPCs on the same level and even NPCs on a totally different level.
One thing to keep in mind is that if you are going to make several NPCs use the same script, be very careful making changes to it, as it will affect every NPC using it. However, if you come to find that you wish to (or need to) make a change, you will only have to change one script instead of fifty!
This is where the majority of the game-specific commands reside. Every set field has a specific function in the game. Of immediate importance for setting up and using monsters is the concept of Behavior Sets and Behavior States:
Behavior Sets (spawnscript, usescript, awakescript, etc.) - This is a script an NPC will run when a certain condition is met (see below for a description of these).
Behavior States - This is a specific behavior - an AI routine that the NPC can be told to use. See the Behavior State Table below.
Note again that anything with an asterisk (*) will pause script execution until the command is complete.
Note that "targetname" and "NPC_targetname" are virtually synonymous.
Set Field Name Parm Type Description
SET_ANGERSCRIPT script file path Run the first time an NPC acquires an enemy.
SET_ATTACKSCRIPT script file path Script run when this entity attacks
SET_AWAKESCRIPT script file path Script run when entity is startled by an event.
SET_BLOCKEDSCRIPT script file path Script run when entity is blocked by another entity and unable to move.
SET_DEATHSCRIPT script file path Script run when you are killed. Note that if you have SET_UNDYING set to "true", this script will execute when you get down to 1 health.
SET_DELAYEDSCRIPT script file path Script to run after a certain amount of time has passed (see "SET_DELAYSCRIPTTIME")
SET_FFIRESCRIPT script file path Script to run when player has shot own team repeatedly
SET_FFDEATHSCRIPT script file path Script to run when player kills a teammate
SET_FLEESCRIPT script file path Script to run when entity is hit and has less than 1/3 of it's initial health
SET_LOSTENEMYSCRIPT script file path Script run when enemy cannot navigate to his target and gives up.
SET_MINDTRICKSCRIPT script file path Script to run when player uses jedi mind trick on the NPC.
SET_PAINSCRIPT script file path Script run when entity is hit, presuming "ignorepain" is not set to true.
SET_SPAWNSCRIPT script file path Script run when entity is spawned.
SET_STUCKSCRIPT script file path Script run when entity is blocked by world and cannot manuever around in any way.
SET_USESCRIPT script file path Script run when entity is 'used' by another entity. The field that must be set on an NPC for being used by another entity is "NPC_targetname". So if you wanted a trigger to use an NPC, set the triggers "target" key to "Joe" (for example) and the NPC's "NPC_targetname" key to "Joe". This can be applied to non-NPCs as well.
SET_VICTORYSCRIPT script file path Script run when entity kills someone.
Note that the script file path should strip off everything from Q:\quake\baseq3\real_scripts... and does not take a file extension. So if you wanted to use the script at
"Q:\quake\baseq3\real_scripts\test\getmad.scr"
The proper script file path would be:
"test\getmad"
Note that setting a script file path of "NULL" will simply clear the script slot altogether.
Note that these scripts execute every time the event happens, if you want it to run only once, you must clear that behavior set slot in the script that it runs for that event.
EXAMPLE: If you want someone to start unaware and then go into a "hunt and kill" behavior state the first time they're shot, do this:
Set ( "SET_BEHAVIORSTATE", "BS_DEFAULT" );
Set ( "SET_IGNOREALERTS", "true" );
Set ( "SET_LOOK_FOR_ENEMIES", "false" );
Set ( "SET_PAINSCRIPT", "test\getmad" );
And, the "test/getmad" script would look like this:
Set ( "SET_IGNOREALERTS", "false" );
Set ( "SET_LOOK_FOR_ENEMIES", "true" );
Set ( "SET_PAINSCRIPT", "NULL" );
Behavior Set - Related Script Set Fields:
SET_DELAYSCRIPTTIME int How long in seconds to wait before executing the "delayedscript".
Speeds, Origins & Angles:
SET_ORIGIN "x y z" Sets origin of current entity - in Q3Radiant map coords.
SET_COPYORIGIN targetname Copies an origin from entity with given targetname to current entity.
SET_TELEPORT_DEST* "x y z" Set origin here as soon as the area is clear
SET_ANGLES "pitch yaw roll" Sets angles of current entity
SET_DPITCH* int Pitch entity will try to turn to. Positive looks down, negative looks up, straight ahead is 0.
SET_DYAW* int Yaw entity will try to turn to. Values 0 - 360, same orientation as Radiant.
SET_LOCKYAW "off/auto/float" Locks the legs yaw angle of the entity. The head and torso will turn freely but only so far as their tolerance off the leg's angles will let them. That way it will move one direction and look another. "Off" turns off lock angle. "Auto" locks it the current yaw angle, a float value forces it to a specific yaw.
SET_XVELOCITY float Adds amount to current X velocity of current entity - X velocity is the entity's direction and speed along the X axis in Q3Radiant map coordinates, negative is "west", positive is "east". The value is generally how many map units per second they will travel.
SET_YVELOCITY float Adds amount to current Y velocity of current entity - Y velocity is the entity's direction and speed along the Y axis in Q3Radiant map coordinates, negative is "south", positive is "north". The value is generally how many map units per second they will travel.
SET_ZVELOCITY float Adds amount to current Z velocity of current entity - Z velocity is the entity's direction and speed along the Z axis in Q3Radiant map coordinates, negative is "up", positive is "down". The value is generally how many map units per second they will travel.
SET_Z_OFFSET float Vertical offset from original origin... offset/ent's speed * 1000ms is duration.
SET_RUNSPEED int Speed entity moves when running
SET_WALKSPEED int Speed entity moves when walking
SET_YAWSPEED int How quickly an entity can turn (degrees per second)
SET_FORWARDMOVE int Speed to move current entity forward (use negative value to move backward). This is a relative speed, not actual, valid ranges are -127 to 127. 127 = use runSpeed, 64 = use walkSpeed, 0 = don't force any movement.
SET_RIGHTMOVE int Speed to move current entity to the right (use negative value to move left). This is a relative speed, not actual, valid ranges are -127 to 127. 127 = use runSpeed, 64 = use walkSpeed, 0 = don't force any movement.
SET_NOAVOID "true/false" Current entity will not do checks to avoid world geometry and other entities.
Targets & Goals:
Note that most goals should usually be waypoint_navgoals, but you can specify any entity as a goal.
SET_CAPTUREGOAL targetname Entity for entity to head to when in bState BS_ADVANCEFIGHT.
SET_ENEMY targetname Set enemy of entity to entity with matching targetname.
SET_LOCKEDENEMY "true/false" Current entity won't give up attacking current enemy until dead.
SET_PAINTARGET targetname When hit, entity will use the entity with the specified targetname.
SET_TARGET targetname Set/change your target
SET_TARGET2 targetname Set/change your target2
SET_TARGETNAME targetname Change targetname of current entity.
SET_VIEWTARGET* targetname Set desiredYaw and desiredPitch for current entity based on position of the entity with the given targetname.
SET_NAVGOAL* targetname Sets entity's navGoal to the entity with the matching targetname - will not continue until entity has reached that navGoal.
SET_AFFIRMNEGTARG "self/name" Set up entity who is to be the receiver for positive/negative responses from player, "self" is the current entity, other wise give the targetname of the entity you want to be affected.
SET_ENEMYTEAM Team Table What team this ent should look for as enemies.
SET_PLAYERTEAM Team Table What team this ent should treat as allies.
SET_REMOVE_TARGET targetname Target that is fired when someone completes the BS_REMOVE behaviorState
SET_LOCATION* trigger msg What trigger_location you're in - Can only be gotten, not set!
Animation Data:
Note that SET_FACESMILE, SET_FACEHAPPY, SET_FACEGLAD, and the SET_FACESHOCKED commands only have an effect in an OpenJK build past March 2015. They will be simply ignored by the official retail release of the game.
SET_ANIM_UPPER* Anim Table Animation for upper half of current entity model.
SET_ANIM_LOWER* Anim Table Animation for the lower half of the entity model.
SET_ANIM_BOTH* Anim Table Animation for entity's upper and lower models.
SET_ANIM_HOLDTIME_LOWER* int Amount of time (milliseconds) the lower animation should be played. -1 = forever.
SET_ANIM_HOLDTIME_UPPER* int Amount of time (milliseconds) the upper animation should be played. -1 = forever.
SET_ANIM_HOLDTIME_BOTH* int Amount of time (milliseconds) the upper and lower animations should be played. -1 = forever.
SET_FACEAUX float Set face to Aux expression for number of seconds
SET_FACEBLINK float Set face to Blink expression for number of seconds
SET_FACEBLINKFROWN float Set face to Blinkfrown expression for number of seconds
SET_FACEFROWN float Set face to Frown expression for number of seconds
SET_FACESMILE float Set face to Smile expression for number of seconds
SET_FACEHAPPY float Set face to Happy expression for number of seconds
SET_FACEGLAD float Set face to Glad expression for number of seconds
SET_FACESHOCKED float Set face to Shocked/Surprised expression for number of seconds
SET_FACENORMAL float Set face to Normal expression for number of seconds.
SET_FACEEYESCLOSED float Set face to Eyes closed
SET_FACEEYESOPENED float Set face to Eyes open
SET_ADDRHANDBOLT_MODEL string .glm model to place on NPC r_hand tag/bolt
SET_REMOVERHANDBOLT_MODEL string .glm to remove from NPC right hand bolt
SET_ADDLHANDBOLT_MODEL string .glm model to place on NPC l_hand tag/bolt
SET_REMOVELHANDBOLT_MODEL string .glm to remove from NPC left hand bolt
SET_SCALE float Scale the entity model
Behavioral:
SET_BEHAVIORSTATE bState Table Sets the active behavior state of the entity. See Behavior State Table for a list of possible bStates.
SET_DEFAULTBSTATE bState Table Sets behavior state to run if behavior state is default/cleared somehow.
SET_TEMPBEHAVIOR bState Table Sets behavior state to use temporarily, once cleared, the entity will use it's normal behavior state.
SET_AGGRESSION int How likely NPC is to fire. Value 1(min) - 5(max), same as the NPC stat in NPCs.cfg.
SET_AIM int How accurate NPC's aim is. Value 1(min) - 5 (max), same as the NPC stat in NPCs.cfg.
SET_VIGILANCE float Set vigilance for entity, how often entity checks for enemy - 0 = never, 1 = every frame.
SET_IGNOREPAIN "true/false" Entity does not react to pain at all.
SET_IGNOREENEMIES "true/false" Pay no attention to enemies
SET_IGNOREALERTS,//## %t="BOOL_TYPES" # Do not get enemy set by allies in area(ambush)
SET_DONTSHOOT "true/false" No one will shoot at current entity
SET_DONTFIRE "true/false" Current entity won't fire weapon
SET_NOTARGET "true/false" No one will target current entity as an enemy.
SET_SHIELDS "true/false" NOT IMPLEMENTED
SET_ALT_FIRE "true/false" Force NPC to use altfire when shooting
SET_NO_RESPONSE "true/false" NPCs will do generic responses when this is on (usescripts override generic responses as well)
SET_NO_COMBAT_TALK "true/false" NPCs will not do their combat talking noises when this is on
SET_NO_ALERT_TALK "true/false" NPCs will not do their alert detection noises when this is on, but will still respond to alerts
SET_SHOT_SPACING int milliseconds between shots for an NPC - reset to defaults when changes weapon
SET_FOLLOWDIST float How far away to stay from leader in BS_FOLLOW_LEADER bState
SET_NO_GROUPS "true/false" This NPC cannot alert groups or be part of a group
SET_FIRE_WEAPON "true/false" Makes NPC will hold down the fire button, until this is set to false
SET_NO_MINDTRICK "true/false" Makes NPC immune to jedi mind-trick
SET_USE_CP_NEAREST "true/false" NPCs will use their closest combat points, not try and find ones next to the player, or flank player (good for NPCs who have no route to player - for example, when they're on the other side of a ravine or something).
SET_NO_ACROBATICS "true/false" Jedi won't jump, roll or cartwheel
SET_CROUCHED "true/false" Makes entity crouch.
SET_LEAN "left/right/none" NOT IMPLEMENTED
SET_RUNNING "true/false" Makes entity move at runSpeed.
SET_WALKING "true/false" Makes entity move at walkSpeed.
SET_CHASE_ENEMIES "true/false" NPC will chase after enemies
SET_LOOK_FOR_ENEMIES "true/false" NPC will be on the lookout for enemies
SET_FACE_MOVE_DIR "true/false" NOT IMPLEMENTED
SET_DONT_FLEE "true/false" NPC will not run from danger
SET_FORCED_MARCH "true/false" NPC will not move unless you aim at him
SET_LOOK_TARGET "targetname" The NPC will turn it's head (within its range) to look at whatever NPC/entity you specify. Note that this is automatically set when the NPC has an enemy.
Stats:
SET_SHOOTDIST float Max distance from which entity will shoot enemies.
SET_VISRANGE float Max distance from which entity will detect enemies.
SET_HFOV int Set horizontal field of view for entity.
SET_VFOV int Set vertical field of view for entity.
SET_EARSHOT float Max distance from which entity will hear sound events.
Misc NPC-Specific:
SET_ICARUS_FREEZE targetname Specify name of entity to freeze - !!!NOTE!!! since the ent is frozen, it cannot unfreeze itself, you must have some other entity unfreeze a frozen ent!!!
SET_ICARUS_UNFREEZE targetname Specify name of entity to unfreeze - !!!NOTE!!! since the ent is frozen, it cannot unfreeze itself, you must have some other entity unfreeze a frozen ent!!!
Player-Only:
SET_TREASONED "true/false" Player has turned on his own- scripts will stop, ally NPCs will turn on him and level changes won't work.
SET_PLAYER_LOCKED "true/false" Makes it so player cannot move or fire
SET_LOCK_PLAYER_WEAPONS "true/false" Makes it so player cannot switch weapons
SET_VIEWENTITY targetname Make the player look through this ent's eyes - also shunts player movement control to this ent.
Players and NPCs:
SET_INVISIBLE "true/false" Makes an NPC not solid and not visible
SET_INVINCIBLE "true/false" Completely unkillable
SET_FORCE_INVINCIBLE "true/false" Force Invincibility effect, also godmode
SET_SABERACTIVE "true/false" Turns saber on/off
SET_NO_KNOCKBACK "true/false" Stops this ent from taking knockback from weapons
SET_HEALTH int Sets the health of current entity
SET_FRICTION int Set friction for current entity, 0(min) - 6(max)
SET_GRAVITY float Set gravity for current entity, 0 = none, 0.8 is normal.
SET_WEAPON Weapon Table Sets weapon for entity. If "drop" entity throws out current weapon. NOTE: in JK2, an entity's default behavior (BS_DEFAULT) is controlled by the weapon they have equipped. If a Rodian has a disruptor, he acts like a sniper, if he has a lightsaber, he acts like a jedi, etc.
SET_UNDYING "true/false" Entity takes damage down to 1, but cannot be killed.
SET_SOLID* "true/false" Make current entity solid or not solid. If you're setting this to true on an entity, you can put this in a task and wait for it to finish (it will not turn solid until there is no other entity or architecture inside of it).
SET_WIDTH int width of player/NPC
SET_VAMPIRE "true/false" Draws only in mirrors/portals
SET_NO_IMPACT_DAMAGE "true/false" Stops this ent from taking impact damage
SET_MORELIGHT "true/false" NPC/Player will have a minimum lighting of 96
SET_DISMEMBERABLE "true/false" NPC/Player will not be dismemberable if you set this to false (default is true)
SET_NO_FORCE "true/false" NPC/Player will not be affected by force powers
SET_NO_FALLTODEATH "true/false" NPC/Player will not scream and tumble and fall to hit death over large drops.
SET_ITEM Item Table Give items
SET_FORCE_HEAL_LEVEL force level Change force heal level (NPCs don't use this)
SET_FORCE_JUMP_LEVEL force level Change force jump level
SET_FORCE_SPEED_LEVEL force level Change force speed level
SET_FORCE_PUSH_LEVEL force level Change force push level
SET_FORCE_PULL_LEVEL force level Change force pull level
SET_FORCE_MINDTRICK_LEVEL force level Change mind trick level (NPCs don't use this)
SET_FORCE_GRIP_LEVEL force level Change force grip level
SET_FORCE_LIGHTNING_LEVEL force level Change force lightning level
SET_SABER_THROW force level Change saber throw level
SET_SABER_DEFENSE force level Change saber defense level
SET_SABER_OFFENSE force level Change force offense level
All Entities:
SET_COUNT int Change an entity's count field
SET_WAIT float Change an entity's wait field
SET_PLAYER_USABLE "true/false" The entity can can be activateby the player's "use" button - this will also make the entity run its usescript when used by the player.
SET_EVENT event table NOT IMPLEMENTED
SET_PARM1-8 any value Used for storing and retrieving values that will be needed by the script in various situations. See the description of SET_PARMs in the Set Command overview above.
SET_LOOPSOUND filename Looping sound to play on entity.
SET_DMG_BY_HEAVY_WEAP_ONLY "true/false" When true, only a heavy weapon class missile/laser can damage this ent.
SET_SHIELDED "true/false" When true, ion_cannon is shielded from any kind of damage.
SET_INACTIVE "true/false" in lieu of using a target_activate or target_deactivate (prevents entity from being used when used by a script, player or trigger, etc.)
SET_FUNC_USABLE_VISIBLE "true/false" provides an alternate way of changing func_usable to be visible or not, DOES NOT AFFECT SOLID
Text Printing Control:
SET_SCROLLTEXT int ID of text string to print
SET_CAPTIONTEXTCOLOR string Color of caption text RED,WHITE,BLUE, YELLOW
SET_CENTERTEXTCOLOR string Color of center-printed text RED,WHITE,BLUE, YELLOW
SET_SCROLLTEXTCOLOR string Color of scrolling text RED,WHITE,BLUE, YELLOW
Non-NPC Animation:
These commands are for use on misc_model_breakables only.
SET_STARTFRAME int frame to start animation sequence on
SET_ANIMFRAME int frame to set animation sequence to
SET_ENDFRAME* int frame to end animation sequence on
SET_LOOP_ANIM "true/false" Loop the animation sequence
SET_DISABLE_SHADER_ANIM "true/false" Allows turning off an animating shader in a script
SET_SHADER_ANIM "true/false" Sets a shader with an image map to be under frame control
Other (do not affect entities):
SET_TIMESCALE int Slow down the rate of time for entire game - 1.0 is normal speed.
SET_PRECACHE filename The .pre file that lists all sounds that will be used in your scripts about to run. This is recommended as it will make the script run smoother since the sounds will not need to be loaded when they are played.
SET_CAMERA_GROUP string This is the value that the camera FOLLOW command will look at in order to determine who the camera should be pointing at. See the camera commands section for more information.
SET_CAMERA_GROUP_TAG string What tag on all clients to try to track.
SET_CAMERA_GROUP_Z_OFS float when following an ent with the camera, apply this z offset
SET_INTERFACE "true/false" Toggle player interface on/off
SET_CINEMATIC_SKIPSCRIPT script file path Script to run if someone skips the cinematic that is currently running.
SET_VIDEO_FADE_IN "true/false" Makes video playback fade in when it starts
SET_VIDEO_FADE_OUT "true/false" Makes video playback fade out when it ends
SET_ADJUST_AREA_PORTALS "true/false" Only set this on things you move with script commands that you *want* to open/close area portals. Default is off.
SET_SECRET_AREA_FOUND "true/false" Increment secret areas found counter
SET_END_SCREENDISSOLVE "true/false" End of game dissolve into star background and credits
SET_USE_SUBTITLES "true/false" When true NPC will always display subtitle regardless of subtitle setting
SET_CLEAN_DAMAGING_ENTS "true/false" Removes entities that could muck up cinematics (explosives, turrets, seekers)
SET_HUD "true/false" Turns on/off HUD
SET_MUSIC_STATE music states Set the state of the dynamic music
SET_OBJECTIVE_SHOW objectives Show objective on mission screen
SET_OBJECTIVE_HIDE objectives Hide objective from mission screen
SET_OBJECTIVE_SUCCEEDED objectives Mark objective as completed
SET_OBJECTIVE_FAILED objectives Mark objective as failed
SET_MISSIONSTATUSACTIVE objectives Turns on Mission Status Screen
SET_MISSION_STATUS_SCREEN objectives Display Mission Status screen before advancing to next level
SET_MISSIONFAILED "missionfailed" Mission failed screen activates
SET_MISSIONSTATUSTEXT "statustext" Text to appear in mission status screen
SET_TACTICAL_SHOW "tactical" Show tactical info on mission objectives screen
SET_TACTICAL_HIDE "tactical" Hide tactical info on mission objectives screen
SET_OBJECTIVE_CLEARALL Force all objectives to be hidden
SET_MENU_SCREEN menuscreens NOT IMPLEMENTED
SET_CLOSINGCREDITS Show closing credits
Used by SET_ANIM_LOWER, SET_ANIM_UPPER, SET_ANIM_BOTH.
Note that not every animation listed here is guaranteed to exist in every skeleton!
Also: the description of certain generic animations (like gesture, etc) are not guaranteed to be the same in each skeleton, they may in fact look quite different.
Read the included anims.h file in the "gamesource" directory where BehavEd is installed on your machine. You can view the file in a text editor for the names and descriptions of the animations. Also, those same comments appear in BehavEd when you select them from the animation list.
BS_DEFAULT The default behavior for this NPC. This is what you will use 90% of the time. Use the "behavioral" SET commands listed in the "NPC-Only Set Fields "section to modify their behavior. NOTE: in JK2, an entity's default behavior (BS_DEFAULT) is controlled by the weapon they have equipped. If a Rodian has a disruptor, he acts like a sniper, if he has a lightsaber, he acts like a jedi, etc.
BS_ADVANCE_FIGHT* NOT IMPLEMENTED - Head to captureGoal and fight along the way, continues when reached captureGoal.
BS_FOLLOW_LEADER Follow your leader and shoot any enemies you come across
BS_JUMP* Face navgoal and jump to it
BS_REMOVE Once the player is out of viewrange of the NPC, it will remove itself.
BS_SEARCH From your homewaypoint, repeatedly search a random branch and return.
BS_WANDER Wander through the waypoint network aimlessly.
BS_NOCLIP Moves through walls, etc.
BS_CINEMATIC Does nothing but face it's angles and move to a goal if it has one
BS_SLEEP NOT IMPLEMENTED
Drop drop current weapon
WP_NONE no weapon
WP_SABER
WP_BRYAR_PISTOL
WP_BLASTER
WP_DISRUPTOR
WP_BOWCASTER
WP_REPEATER
WP_DEMP2
WP_FLECHETTE
WP_ROCKET_LAUNCHER
WP_THERMAL
WP_TRIP_MINE
WP_DET_PACK
WP_STUN_BATON
WP_MELEE For humanoid NPCs and the player, this is the fists
WP_EMPLACED_GUN
WP_BOT_LASER Probe droid - Laser blast
WP_TURRET turret guns
WP_ATST_MAIN
WP_ATST_SIDE
WP_TIE_FIGHTER
WP_RAPID_FIRE_CONC
WP_BLASTER_PISTOL Blaster pistol for enemy NPCs (like Imperials)
TEAM_FREE No team
TEAM_PLAYER Player and his allies
TEAM_ENEMY All enemies
TEAM_NEUTRAL Most droids
INV_ELECTROBINOCULARS Binoculars
INV_BACTA_CANISTER Bacta Canister
INV_SEEKER Inquisitor
INV_LIGHTAMP_GOGGLES Lightamp Goggles
INV_SENTRY Portable Assault Sentry
DM_AUTO let the game determine the dynamic music as normal
DM_SILENCE stop the music
DM_EXPLORE force the exploration music to play
DM_ACTION force the action music to play
DM_BOSS force the boss battle music to play (if there is any)
DM_DEATH force the "player dead" music to play