A to Z Scripting: Scripting with...

From The DarkMod Wiki
Jump to navigationJump to search

This article has useful more specific information for scripting with various elements, such as AI, sounds and time.


Overall, TDM's timekeeping isn't too precise, meaning that you should try to avoid measuring time in smaller increments than 0.1s.

waitFrame() events

The game engine runs, in theory, at 60 frames (or tics) per second, independently of the visual framerate. In practice, it appears that the exact duration of sys.waitFrame() is not constant.

wait() events

When you run a wait() event, the engine performs a check every frame to see whether the total time elapsed has surpassed the time it was instructed to wait. This means that if you use wait() with very small durations, the engine will probably overshoot that duration due to a lack of precision. So i.e. a sys.wait(0.02) might only complete after 2 frames, which might be around 0.03-0.05s


The most effective way to keep track of time is to read the game clock with sys.getTime() and compare with previous readings. Example:

void move_mover()					//move a mover and keep track of how much time it needed
	float time_starting	= sys.getTime();	//read the game clock to see what time the mover started

	$func_mover_1.moveToPos('150 0 0');

	float time_elapsed = sys.getTime() - time_starting;	//read the game clock again and subtract the starting time to get time_elapsed

An alternative approach, incrementing a float variable in a looping script, has been found to be relatively inaccurate, i.e. a systematic error of 10%.


AI flags

AI flags are pre-made variables contained in the "ai" scriptobject. They contain useful information about an AI, such as how alerted it is, whether it has been knocked out, whether it can see an enemy and so on. The "player" scriptobject inherits from the "ai" scriptobject, so the flags are also available for getting the status of the player, a useful example would be AI_CROUCHING.

In order to access the AI flags of an entity, you first need to define the entity as an "ai" or "player". Examples:

ai guard = $guard_warehouse_1;
player player_self = $player1;			//try to avoid using "self" (unless you're writing a scriptobject), rather use i.e. "player_self"

Then you can use the flags like this:

if ((!guard.AI_KNOCKEDOUT) && (!guard.AI_DEAD)) guard.bark("snd_conversation");	//if the ai is neither KO nor dead, make him bark a line for a (monologue) conversation
if (player_self.AI_CROUCHING)								//check if the player is crouching

Here's a selection of useful AI flags. You can find an exhaustive listing of all AI_flags in the ai scriptobject, which is defined in tdm_base01.pk4 > script/tdm_ai.script > jump to "object ai".

boolean	AI_TALK;		//AI is talking
boolean	AI_DEAD;		//AI is dead
boolean	AI_KNOCKEDOUT;		//AI is knocked out
boolean	AI_ENEMY_VISIBLE;	//enemy is visible
boolean	AI_ENEMY_IN_FOV;	//enemy is in field of view
boolean	AI_CROUCH;		//AI is inspecting a corpse, or the player is crouched
boolean	AI_RUN;			//AI is running
float		AI_AlertIndex;		//goes from 0-5, 0 means completely idle and 5 is in combat. Derived from AlertLevel.
float		AI_AlertLevel;		//goes from 0-23, increases when the AI notices suspicious things and decays with time.


Script events for sounds are slightly atypical and counter-intuitive to use, which is why they have their own section.

Ways of starting a sound

The preferred method to start a sound on an entity is to call startSound() on it:

startSound(string sound, float channel, float netSync);

Here's the rundown of the 3 inputs:

  • "sound" is not the name of a soundshader, but of a spawnarg on the entity that begins with "snd_", i.e. "snd_extinguished"
  • "channel" is the channel on which the sound should be played, allowing you to play multiple sounds in different channels on the same entity. You can enter the channel either as a string or a float. See "Sound channels"
  • "netsync" should always be false. It might have to do with syncing sound in multiplayer.


$torch_1.startSound("snd_extinguish", SND_CHANNEL_ANY, false);

If a soundshader is stored in a spawnarg beginning with "snd_", it will automatically get cached for that entity when it spawns. This ensures that the sound is ready to play immediately. If it's not cached, you might hear a crackle before the sound plays if the engine can't load the sound quickly enough. This is called a cache miss.

Alternatively, you can use startSoundShader() to name a soundshader directly, without using a snd_ spawnarg. In that case, the sound should be cached in advance, as below:

$torch_1.cacheSoundShader("torch_extinguished");			//makes sure this soundshader won't pop when it's played for the first time
sys.waitFrame();							//give time for the cache to occur; probably even better would be to cache the sound at map start
$torch_1.startSoundShader("torch_extinguished", SND_CHANNEL_ANY);	//"torch_extinguished" would be the name of a soundshader in the TDM assets

Sound channels

You can play multiple sounds simultaneously on the same entity by playing them on different channels. Some channels may be reserved for important game sounds (i.e. ambient music, objective completions), so you might prefer using i.e. ...ANY, ...UNUSED or ...UNUSED_2

You may reference a channel either by its name or its ID number.

As a string As a float

Sound properties

When a sound is initiated via script, its properties (looping, min/max distances etc.) will be primarily controlled through settings in the soundshader.

Stopping sound

The script event for stopping a sound on an entity is stopSound(), with the channel as input. If you want all sounds on all channels to stop, use SND_CHANNEL_ANY as input.

AI barks

The correct term for a soundshader spoken by an AI is "bark", thus the script event bark() would be used to make an AI speak via script. Compared to i.e. startSound(), this has the advantage that lipsync is enabled and that the AI wouldn't randomly say something else at the same time. It does still work on dead or unconscious AIs, so you may want to check the AI_DEAD and AI_KNOCKEDOUT flags first (see "AI flags").

Inventory items

Inventory items are entities that have moved into the inventory. They retain their entity name whenever the player picks them up or drops them. An exception is stackable items: whenever a player picks up a stackable item, it only increases the "inv_count" of the entity the player already has in his inventory. Dropping the items produces identical copies of the entity, so stackable items lose any individual properties they might have had.

Note that you can refer to inventory items by either of their 2 names:

  1. The entity name, which only the mapper sees is unique in the map, i.e. atdm_key_simple_1
  2. The inventory item name, which is a string that's shown to the player and can be used by multiple entities, i.e. "Simple key"

Alternatively you can work with the item's category, i.e. "Keys" or "Moulds".

Calling scripts by using inv items on in-world entities

There are 3 ways in which a player might try to "use" an inventory item (i.e. a key) on an in-world entity (i.e. a door):

  1. Pressing the "use" key with the inventory item selected and the in-world entity frob-highlighted.
  2. Frobbing the in-world entity with the inventory item selected.
  3. For moveables: holding the inventory item in front of the in-world entity. This requires stim/response to work.

You should therefore cover all the possible situations when writing a script that should be called by presenting an inventory item to an in-world entity.

1. Pressing the "use" key with the inventory item selected and the in-world entity frob-highlighted.

The "use" key is typically bound to "enter" or "u". The most basic way of setting this up is with these spawnargs on the in-world entity:

"use_action_script" "name_of_script"
"used_by" "name_of_inventory_item"
2. Frobbing the in-world entity with the inventory item selected.

This is just plain frobbing while the player has the inventory item selected in his inventory, which is an alternative to "using". The in-world entity needs these spawnargs:

"frob_action_script" "name_of_script"
"frobable" "1"

The script will then need to check whether the player has the correct type of inventory item selected. It's convenient to get the "used_by" spawnarg for this:

void name_of_script(entity ent_inworld)
if ( $player1.getCurInvItemEntity() ==

Next / previous article