Fundamental Scripting Guide: Difference between revisions

From The DarkMod Wiki
Jump to navigationJump to search
mNo edit summary
(Added Variables section.)
Line 193: Line 193:


Any mistakes stop TDM from starting up with a blue screen. You will need to press "copy" and paste the error message somewhere like Notepad or Word to read it.
Any mistakes stop TDM from starting up with a blue screen. You will need to press "copy" and paste the error message somewhere like Notepad or Word to read it.
== Variables ==
Variables are a key principle in scripting. They allow you to reuse the same script with different results depending on the input. Another use is for storing data, allowing it to be used by other scripts or script functions.
=== Data types ===
Each variable must be named and have a data type, telling the engine whether it's to be read as a text (string), number (float), vector etc. See below for a reference table:
{| class="wikitable"
|-
| String || A string of letters/numbers, in other words plain text. It must always have double quotation marks. Example: “Text message 123”
|-
| Float || A float is a single number, decimals are allowed. Example: 15.2
|-
| Vector || A vector consists of 3 numbers, often used for 3D movements (x z y) or colours (red green blue). It must always have single quotation marks. Example: '0 90 0'
|-
| Boolean || Can be true or false. Easily replaced by floats (0 or 1). Example: TRUE
|-
| Void || No type. Most commonly used for script functions that have no output, such as most basic scripts.
|-
| Entity || Indicates an entity, usually using the name as seen in DR. If it's a specific entity, instead of a variable, then a $ sign is required. Example: $player1
|-
| Subcategories of “entity” || These are required for some script functions to work, such as ai, light or tdm_elevator. For example, you can check whether an “ai” is alerted or knocked out, which isn't possible with regular entity script functions.
|}
=== Creating new variables ===
The first time the engine finds a variable in a script it needs to be given a data type. Afterwards you use just the name.
Example:
float attempts;
sys.println("Player has " + attempts + " left.");
You may also want to assign an initial value. Otherwise, floats will default to 0, strings to "", vectors to '0 0 0' and entities to $null_entity.
Example:
float attempts = 3;
Another option is to define a variable using the output of a script function.
Examples:
vector starting_origin = $func_static_267.getOrigin();
float can_see = $guard_westwing_1.canSee($player1);
float sound_duration = $entity1.startSound("snd_move", SND_CHANNEL_ANY, false);  //stores the sound duration of the sound being played
Where a variable is defined is important. If it's inside a script, the variable will only be useable inside that script. If it's defined outside of a script, all later scripts will be able to use it.
=== Example usage of variables: puzzle with max 3 attempts ===
Say you wanted to give a player 3 attempts to solve an optional puzzle, with a chance to reset his number of attempts:
float attempts; //how many attempts the player has made. Defined outside of a script so both scripts can access it.
float attempts_left = 3; //how many attempts the player has left, max 3
void attempt_failed() //called every time the player fails an attempt
{
attempts = attempts + 1; //increase attempts counter by 1
attempts_left = 3 – attempts; //calculate how many attempts left
if (attempts_left == 0) $puzzle.setFrobable(0); //if no more attempts, make the puzzle entity unfrobable
}
void reset_attempts() //calling this resets the player's attempts to 0
{
attempts = 0;
attempts_left = 3 – attempts;
$puzzle.setFrobable(1); //make sure the puzzle entity is frobable
}
* The default value for new floats is 0, therefore "float attempts;" is the same as "float attempts = 0;"
* There is some redundancy in this setup, i.e. it's not necessary to give attempts_left a value of 3 at the beginning because this gets calculated in the 2 scripts. But in my opinion it makes the script easier to follow, and it's good to make sure your variables are always up to date in case you later add another script that should work with them.

Revision as of 22:57, 14 December 2020

Author - Dragofer

This is a (work in progress) resource written primarily for mappers with no background in scripting or coding, such as myself, though others too may find practical guidance for working with TDM's scripting system. This is what I've picked up over the years through experimentation, looking at existing scripts and interacting with other forum members.

My aim is for the article to be both comprehensive and down to earth, using many examples and avoiding unnecessary technical terms. There will be some overlap with existing wiki articles, since this aims to have everything in one place out of one hand.

The concept will be to start with teaching some basic script literacy, then introduce scripting principles one after another. Towards the end, practical examples show ways of tying everything together to get more complex scripted effects.


Anatomy of a script

This section will define essential terms, then look at some basic scripts of slightly increasing complexity to demonstrate what a script is made up of.

Basic terms

Script events Script events are an action of some kind, i.e. waiting x seconds or triggering an entity. They are the basic building blocks of scripting.
Script function This is the technically correct term for a "script". Script functions often contain multiple script events, and some can also be used like script events in other script functions. Usually this guide will refer to them simply as "scripts".
Scriptobject This is a powerful set of scripts that are applied to single entities. This allows you to write code once, but let it apply to an unlimited number of entities without conflicts. An example is the tdm_light_holder scriptobject, which controls the skin, particle, lit state etc. of its candle.
Variables Variables are another basic building block and allow a script to store data for further processing and to have variable effects. Each variable must be named and given a data type.
Data types Every variable and every input/output of a script event needs to have a data type to inform the engine whether it should be read as a text, number, vector etc.

Script events

Example script event: wait()

This is a very basic script event which instructs the script to wait for 3s:

sys.wait(3);

Here's a closer look at its components:

  • sys.
    • Every script event must be called on an entity. For very generic events like "wait", sys is typically used, shorthand for system.
  • wait(3)
    • This is the actual script event. Every script event must be accompanied by input brackets, even if they're left empty. In this case, "3" is the input.
  • ;
    • A semicolon is needed to mark the end of a line of instructions. Forgetting these is a common mistake that stops the map from loading.


Example script event: setOrigin()

This is another simple script event. It's called on player1 and teleports (sets the origin of) him to the position '0 150 0'.

$player1.setOrigin('0 150 0');
  • $player1
    • Notice the $ sign: this means the script engine should look for a specific entity in the game with that name. The player is always called "player1".


Scripts

Example script: rudimentary script for sending a message to the console

Script events can't work on their own, they need to be part of a script:

void test_script()
{
	sys.println(“Test message.”);
}
  • void
    • This defines what type of result the script puts out to other scripts: "void" means nothing while i.e. "float" would mean a number. In the large majority of cases you'd use void.
  • test_script()
    • This is the name of the script: "test_script".
    • Like with script events, input brackets are mandatory for scripts, even if the script doesn't use them. For a script that should damage various entities by various amounts, you could define an entity input and a float (number) input.
    • Note that no semicolon is needed at the end of this top line.
  • {}
    • Curly brackets mark the beginning and end of the body of the script.
  • sys.println("Test message");
    • This is a script event which prints a text to the console and then leaves a line (ln), so that every text is on its own line.


Example script: simple cutscene
void simple_cutscene()
{
	sys.fadeOut('0 0 0', 2);		//fade to black (colour vector '0 0 0') in 2s
	sys.wait(2);				//wait 2s for the fadeOut
	sys.trigger($speaker_narrator);	        //trigger a speaker with a narrator's voice
	$camera.activate($player1);		//activate a camera to disable player controls

	sys.wait(10);				//wait narrator is speaking
	sys.fadeIn('0 0 0', 2);			//fade back in from a black screen in 2s
	$camera.activate($player1);		//return player controls
}

This scripts a basic cutscene: fade to black, activate a speaker, fade back in. At both ends a camera is toggled so that the player can't move while the screen is black.

  • //
    • This is a comment for the reader: anything behind a double slash gets ignored. This is a useful habit for helping others and your future self understand how your script works.
    • An alternative way to comment, useful for mutli-line comments or temporarily disabling a section of the script, is to use /* at the beginning and */ at the end. This way you don't need to mark every single line as a comment.
  • sys.fadeOut('0 0 0', 2);
    • This script event uses 2 different inputs:
      • '0 0 0' is a vector defining the colour to which the screen should fade. Every number represents the intensity of red, green or blue colour.
      • 2 is a float (number) defining how long the fadeout should take.
  • sys.trigger($speaker_narrator);
    • This is a script event to trigger a specific entity called "speaker_narrator", which would simply be a speaker in the map with a custom soundshader.
  • $camera.activate($player1);
    • This is an alternative way to trigger, aka activate, an entity. This is needed when you want one entity to be triggered by another entity, instead of "sys". In this case, player1 is the activator who activates the camera. Another example would be the player activating a teleporter entity.


More scripting basics

General basics

  • Scripting is case-sensitive.
  • The scripting engine reads from top to bottom. That makes the order in which scripts and variables are arranged quite important.
  • The $ sign instructs the engine to look for an entity ingame with that name. If there's no $ sign, the engine will instead look for a variable with that name in the scripts.


TDM Script Reference

The TDM Script Reference is an essential wiki resource for scripting, listing all available script events for the current version of TDM. All script events are listed twice: the first time they're sorted alphabetically, the second time they're sorted based on which kinds of entities they can be called on. By far not all script events have received manual descriptions, so sometimes you may need to experiment with them to properly figure them out.

Here is an example:

scriptEvent float canSee(entity ent);

    no description

    Spawnclasses responding to this event: idAI

Interpretation:

  • the script event canSee() is suitable for being called on AIs.
  • an entity must be entered into its input bracket.
  • the script event returns a float. This means you could for example store the result of this check in a float variable and use that elsewhere in your scripting.


Actually using the script event may take some experimentation, mainly to see which entity the event should be called on and which entity should go into the brackets. In the end it could look something like this:

$guard_westwing_1.canSee($player1);


Alternatively, if you want to store the result as a variable (that we name "guard_sees_player") for later use:

float guard_sees_player = $guard_westwing_1.canSee($player1);


Troubleshooting

  • Problem: after loading the map, the screen shows only a close-up of a random texture, can't move
    • This happens if you just started TDM, had a scripting error, fixed the error and tried to reload your map.
    • Fix: load into another map or restart TDM, then try to load the map again.


Setting up the .script files

Map scripts

The easiest way to setup scripts is as a map script: create a new .txt file in your maps folder with the same name as your map and change the .txt extension to .script. Any scripts in this file will only be available in that map.

If you're setting up a .script file for the first time, double click the file and set Notepad or Wordpad as the standard program for opening .script files.

The next step is to add a main() script at the very bottom of your map script:

void main()
{
	sys.waitFrame();
}

The main() script is automatically called at map start. This makes it convenient for testing scripts and initialising scripts that should run from the beginning of the mission.

It's good practice to let the main() script begin with sys.waitFrame();. This is because all entities are spawned in the first frame of the mission, so waiting a single frame makes sure they're ready for script events.


General scripts

It's possible to let your scripts be available in all missions. This requires an additional step and has the downside that you have to restart TDM (rather than just the map) before changes take effect. Therefore it's recommended to develop & test your script as a map script first.

The .script file can have any name and must be placed within a new "script" folder (without an s) instead of the "maps" folder.

You will need to tell the game to include that .script file. To do this, create a new file called tdm_custom_scripts.txt, change the extension to .script and type in the following to include a .script file called "my_custom_script":

#include script/my_custom_script.script

Any mistakes stop TDM from starting up with a blue screen. You will need to press "copy" and paste the error message somewhere like Notepad or Word to read it.


Variables

Variables are a key principle in scripting. They allow you to reuse the same script with different results depending on the input. Another use is for storing data, allowing it to be used by other scripts or script functions.


Data types

Each variable must be named and have a data type, telling the engine whether it's to be read as a text (string), number (float), vector etc. See below for a reference table:

String A string of letters/numbers, in other words plain text. It must always have double quotation marks. Example: “Text message 123”
Float A float is a single number, decimals are allowed. Example: 15.2
Vector A vector consists of 3 numbers, often used for 3D movements (x z y) or colours (red green blue). It must always have single quotation marks. Example: '0 90 0'
Boolean Can be true or false. Easily replaced by floats (0 or 1). Example: TRUE
Void No type. Most commonly used for script functions that have no output, such as most basic scripts.
Entity Indicates an entity, usually using the name as seen in DR. If it's a specific entity, instead of a variable, then a $ sign is required. Example: $player1
Subcategories of “entity” These are required for some script functions to work, such as ai, light or tdm_elevator. For example, you can check whether an “ai” is alerted or knocked out, which isn't possible with regular entity script functions.


Creating new variables

The first time the engine finds a variable in a script it needs to be given a data type. Afterwards you use just the name. Example:

float attempts;

sys.println("Player has " + attempts + " left.");

You may also want to assign an initial value. Otherwise, floats will default to 0, strings to "", vectors to '0 0 0' and entities to $null_entity. Example:

float attempts = 3;

Another option is to define a variable using the output of a script function. Examples:

vector starting_origin = $func_static_267.getOrigin();
float can_see = $guard_westwing_1.canSee($player1);
float sound_duration = $entity1.startSound("snd_move", SND_CHANNEL_ANY, false);  //stores the sound duration of the sound being played

Where a variable is defined is important. If it's inside a script, the variable will only be useable inside that script. If it's defined outside of a script, all later scripts will be able to use it.


Example usage of variables: puzzle with max 3 attempts

Say you wanted to give a player 3 attempts to solve an optional puzzle, with a chance to reset his number of attempts:

float attempts;				//how many attempts the player has made. Defined outside of a script so both scripts can access it.
float attempts_left = 3;			//how many attempts the player has left, max 3

void attempt_failed()				//called every time the player fails an attempt
{
	attempts	= attempts + 1;		//increase attempts counter by 1
	attempts_left	= 3 – attempts;		//calculate how many attempts left

	if (attempts_left == 0) $puzzle.setFrobable(0);	//if no more attempts, make the puzzle entity unfrobable
}

void reset_attempts()				//calling this resets the player's attempts to 0
{
	attempts = 0;
	attempts_left = 3 – attempts;

	$puzzle.setFrobable(1);			//make sure the puzzle entity is frobable
}
  • The default value for new floats is 0, therefore "float attempts;" is the same as "float attempts = 0;"
  • There is some redundancy in this setup, i.e. it's not necessary to give attempts_left a value of 3 at the beginning because this gets calculated in the 2 scripts. But in my opinion it makes the script easier to follow, and it's good to make sure your variables are always up to date in case you later add another script that should work with them.