A to Z Scripting: Scripting with...: Difference between revisions

From The DarkMod Wiki
Jump to navigationJump to search
No edit summary
No edit summary
Line 1: Line 1:
== Scripting with: ==
This article has useful more specific information for scripting with various elements, such as AI, sounds and time.
=== Time ===
==== wait() events ====
The game engine runs by default at 60 frames (or tics) per second, which is independent of the number of visual frames rendered per second. The most accurate expression for the duration of a frame is therefore 1/60 of a second. This is roughly 0.0167 seconds. The most reliable way to wait for only one frame is waitFrame().


When you run a wait() event, the engine performs a check every frame (every ~0.0167s) 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. sys.wait(0.02) would only complete after 2 frames, or 0.0333s.
== 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


==== Timekeeping ====
==== Timekeeping ====
There are a couple options for keeping track of time:
The most effective way to keep track of time is to read the game clock with sys.getTime() and compare with previous readings. Example:
* sys.getTime() will get the current game time. Example:
  void move_mover() //move a mover and keep track of how much time it needed
  void move_mover() //move a mover and keep track of how much time it needed
  {
  {
Line 21: Line 23:
  }
  }


 
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%.
* in a looping script, increment a float variable representing time elapsed with every cycle of the script. Prefer waitFrame() and fractions over wait() and small values due to the aforementioned advantage in precision. Example:
void time_keeping()
{
float time_elapsed = 0; //make sure time_elapsed is always set to 0 when this script starts
sys.waitFrame(); //wait the first frame now
while(1) //insert a condition of your liking here, i.e. whether a mover isMoving()
{
time_elapsed = time_elapsed + 1/60; //increase time_elapsed immediately, then wait. This way around keeps the timer a little more up to date.
sys.waitFrame();
}
}




=== AIs ===
=== AIs ===
==== AI flags ====
==== 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. Some of these flags are also used by the "player" scriptobject", such as AI_CROUCHING.
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:
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;
  ai guard = $guard_warehouse_1;
  player player_self = $player1; //recommended not to use only "self" as the name, since "self" is used in various other ways as well
  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:
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 ((!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
  if(player_self.AI_CROUCHING) //check if the player is crouching




Line 60: Line 49:
  boolean AI_CROUCH; //AI is inspecting a corpse, or the player is crouched
  boolean AI_CROUCH; //AI is inspecting a corpse, or the player is crouched
  boolean AI_RUN; //AI is running
  boolean AI_RUN; //AI is running
  float AI_AlertLevel; //0-5, where 0 is completely idle and 5 is combat
  float AI_AlertIndex; //goes from 0-5, 0 means completely idle and 5 is in combat
  float AI_AlertIndex; //corresponds to AlertLevel, as far as I'm aware
  float AI_AlertLevel; //goes from 0-23, increases when the player
 
Difference between AlertIndex and AlertLevel:
* AlertIndex is an index of AlertLevel.





Revision as of 17:48, 23 December 2020

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

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

Timekeeping

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.speed(50);
	$func_mover_1.moveToPos('150 0 0');

	sys.waitFor($func_mover_1);
	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%.


AIs

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
float		AI_AlertLevel;		//goes from 0-23, increases when the player

Difference between AlertIndex and AlertLevel:

  • AlertIndex is an index of AlertLevel.


Sounds

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

First of all, there are two similar-looking script events for starting a sound on an entity:

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

startSound() is the preferred method. This is because it can only play soundshaders that are defined on the entity in spawnargs beginning with snd_. What this spawnarg does is that it precaches (preloads) the sound at map start. If a sound is not precached, it might pop when it's played for the first time, as a result of not loading fast enough (a "cache miss").

The downside of startSound() is that the soundshaders must be defined in advance as spawnargs. You can use startSoundShader instead, but should use an earlier script event to precache the soundshader on that entity.

Another difference is that startSound() requires you to input a "netSync" float variable. I believe this has to do with multiplayer, making sure the sound is synchronised for all players. In any case, it's always set to false.


Thus, here are 2 correct ways to start a sound on an entity, shown as examples:

$torch_1.startSound("snd_extinguish", SND_CHANNEL_ANY, false);		//snd_extinguish is a spawnarg on the torch containing the name of the soundshader, it's not the name of the soundshader itself

//the spawnarg is set in DR or in the entity def of the torch

$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 are reserved for important game sounds (i.e. ambient music, objective completions) and shouldn't be used for scripted sounds.

Instead of typing out the name in full, you may instead use the channel's ID number (i.e. 9 instead of SND_CHANNEL_UNUSED) in sound script events.

Channels suited for scripted sounds:

As a string As a float
SND_CHANNEL_ANY 0
SND_CHANNEL_UNUSED 9
SND_CHANNEL_UNUSED_2 11


Other channels:

SND_CHANNEL_VOICE
SND_CHANNEL_VOICE2
SND_CHANNEL_BODY
SND_CHANNEL_BODY2
SND_CHANNEL_BODY3
SND_CHANNEL_WEAPON
SND_CHANNEL_ITEM
SND_CHANNEL_HEART
SND_CHANNEL_DEMONIC
SND_CHANNEL_AMBIENT
SND_CHANNEL_DAMAGE


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 for stopSound().


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").


Next / previous article