Scripting basics

From The DarkMod Wiki
Jump to navigationJump to search

Doom 3 scripts are text files containing script code in a proprietary syntax. By convention, they are given a .script extension. Game-wide script files are located in the script directory, while map-specific script files are located in the same directory as the corresponding map.

Syntax

Doom 3 scripts have a vaguely C++-like syntax, though they are nowhere near as powerful as C++. They have standard braces-and-semicolons syntax, and a notion of "classes". Class members are defined inside a class declaration and defined separately, rather like C++. Unlike C++, there are no separate header files; everything goes into one script file.

Doom 3 scripts have only a few data types:

  • float - A number.
  • boolean - can be false or true.
  • string - A string (sequence of characters).
  • vector - Three numbers in one variable; useful for representing locations, velocities, etc.
  • entity - A reference to an entity (a pointer, in C++ parlance).

Note the complete lack of a dedicated integer type (use float instead), or any ability to define complex data structures! If data structures are needed, they are typically created in the SDK and scriptevents (see below) are provided to allow scripts to manipulate them.

For a more detailed specification of Doom 3 script syntax, see Modwiki's SCRIPT file reference.

The float data type

All numeric values are stored in float data types, which is the only numeric type. Neither int, double nor short, unsigned or other numeric keywords known from C exist in D3 Scripting. Still, most of the operators are applicable, like ++, +, -, +=, *=, etc.

The boolean data type

The data type boolean can only hold two values, namely true or false (TRUE or FALSE work as well). Behind the scenes, this is just another float variable.

Note: you can also use float as argument of conditional expressions, like this:

float myFloat = 1; // this will evaluate to TRUE when used in conditional expr.
if (myFloat) {
  // do something, 
}

The string data type

Strings hold sequences of characters, like the keys or values of entity spawnargs. Their use is quite intuitive, the + operator can be used to concatenate them:

string myStr = $player1.getKey("name");   // Retrieve the value of the "name" spawnarg of the player entity
myStr = "The name of player 1 is " + myStr + "\n";
sys.println(myStr);   // Print the text to the console

The vector data type

The data type vector implements a three-component vector. As arrays and their native operator [] is not implemented in D3 scripting, the elements of vectors are accessed like this:

vector myVec;    // declare a new vector
myVec_x = 20;
myVec_y = 0;
myVec_z = 0;
// myVec now holds the 3D vector <20,0,0>

Alternatively, you can assign a string to a vector variable, like this (the string gets converted correctly, but be sure to use single quotes):

myVec = '0 10 0';

As an example, the getOrigin() methods return a vector.

The entity data type

References to other entities are stored in the entity data type. Entities can be looked up by name, like this:

entity myEnt;     // declare myEnt to be of type entity
myEnt = $player1; // Let myEnt refer to the player entity, which by convention has the name "player1".

The entity data type can be compared to the pointers (that's also how they are stored internally) - there also exists a NULL entity, named $null_entity.

The sys entity

Script events always have to be called on an entity, like this:

$player1.kill();      // kill the player

However, there are a lot of scriptEvents which are unrelated to a specific entity, like cos(), sin(), spawn() and such. These "generic" events are invoked on the sys entity, like this:

entity myLight = sys.spawn("light_moving");   // spawn a new light entity
myLight.setOrigin($player1.getOrigin());      // move it to the point where the player is currently residing

TODO: More detail.

Discovery (or, why is Doom 3 ignoring my script file?)

Doom 3 will automatically find and load some kinds of files, such as .def files. However, it does not do the same for script files; if you merely create a .script file and place it in the script directory, Doom 3 will blithely ignore it.

There are two ways to make Doom 3 use a script:

  • Place it in a map script. Map scripts are loaded when the corresponding map is loaded. They must be given the same name and placed in the same location as the corresponding map file, but with a .script extension instead of a .map extension. For example, the map script for maps/mydir/mymap.map must be called maps/mydir/mymap.script. Map scripts are used to implement map-specific behaviours; as such, mappers will often use them but mod coders typically won't.
  • There is only one script that Doom 3 loads for all maps. It's called script/tdm_main.script, and is loaded before the map-specific script. Since this script would be extremely long if it contained all the general-purpose script code in the entire mod, we don't put any script code in tdm_main.script directly. Instead, we use #include directives to pull in scripts with different names. For example, tdm_events.script is #included from tdm_main.script.

Most of the TDM-specific scripts, including tdm_ai_base.script, are #included from tdm_main.script. We could also put further #include directives into tdm_ai_base.script, and so on. As long as a script gets #included at least once, it will get loaded in all maps. Note that you should be careful not to #include the same script twice, or Doom 3 will complain about multiple definitions - one common way to avoid accidental double-inclusion is to place so-called "inclusion guards" in your script file (C/C++ programmers will be familiar with this kind of preprocessor trickery):

#ifndef __TDM_MOVERS__
#define __TDM_MOVERS__

// your code here will get included exactly once

#endif //__TDM_MOVERS__

Script invocation

TODO: Describe the various ways in which functions in script files can get called.

Note the doom_main function in doom_main.script; presumably this is called on level load???

Interfacing with the SDK

No language can operate usefully in a vacuum - at some point it needs to go out and do stuff.

TODO: Describe the various ways in which the SDK can interface with Doom 3 scripts; scriptevents, scriptvars (instances of idScriptVar), etc.

Vanilla Doom 3 script reference: http://www.modwiki.net/wiki/Script_events_%28Doom_3%29

Also need refs for Dark Mod's scriptevents.

Things that don't exist in D3 scripting

This is a non-comprehensive list of things that don't exist in D3 scripting, but may be expected or known from other languages.

  • structs
  • pointers (including dereference operators ->, *)
  • integers (use floats instead)
  • arrays (including the array operator[])
  • exceptions
  • "crashes" (you can call scriptEvents on any entity, even if that entity doesn't support it, the script won't crash).
  • char data types, which implies that const char* is also missing (use string instead)
  • private/public/protected
  • multiple inheritance