Clock-Triggered Game Events

From The DarkMod Wiki
Jump to: navigation, search

written by Fidcal


SUMMARY: A script that keeps time and rotates clock hands, and optionally can trigger any entities at any selected time plus a script that strikes the hour.

If you just want a simple working clock that tells time and nothing else then see Prefab Comments and select one of the clocks that does not need a script. There is also a grandfather clock under models\furniture.

This script provides both an optional working clock method and an easy means for mappers to trigger events in-game at selected times. By default it triggers a sound that strikes the hours but it can also be used to trigger any entity at any chosen time. For example it can fail a time objective "Escape from the prison before 2:30am." at exactly that time. Another example is used in Edridge Hall where a guard goes off duty at 2:00am and the new guard does not take up position for a minute or so - thus making it easier for the player to get over the wall.

The script is set up to operate two func_mover clock hands on a clock face and these are provided in prefabs (see Prefab Comments. It is important to make sure the entity names remain the same as in the script or alternatively, change them in the script. A visible clock is theoretically not needed however - though it is hard to see how the player would make sense of a time clue or objective without reference to one.

The clock script

This is the script that keeps time and rotates the clock hands. See next section for how to set it up...

 void runClock(entity bigHand, entity smallHand)
 	float hours = 11;
 	float mins = 59;
 	float secs = 58;
 	float strike = 0;
 	while (1)
 		bigHand.rotateOnce('0 0 0.1017');  //1 min in 59 secs as no move when hour incremented
 		secs = secs + 1;
 		//clone any number of these.....
 		if (mins == 99 && hours == 99) // set hours & mins to trigger any entity
 			$yourEntity.activate($player1);  //replace 'yourEntity' with any entity to be triggered
 		if (secs == 59)
 			smallHand.rotateOnce('0 0 0.5');
 			secs = 0;
 			mins = mins + 1;
 			if (mins == 60)
 				mins = 0;
 				hours = hours + 1;

 				if (hours == 12)
 					hours = 0;

How to Set Up the Script & Clock

The clock script is listed in the previous section. This is how you set it up...

  • Copy the runClock script from above.
  • If you already have a script file for your map then paste the runClock script into it or....
  • If you haven't already got a script file then paste it into your plain text editor and save it in the same folder as your map with the same name but suffix .script. Example :
  • Edit the lines float hours, float mins, float secs values to the time you want the clock to show at the start.
  • Resave the file.
  • Import one of the clock prefabs (see Prefab Comments or make your own.
  • Firstly, position and turn the entire clock prefab to face the way you want it in-game. (Dark Radiant's 'rotate models independently' button should be UNset.
  • Rotate the clock hands around the clockface to the same time as you set in the script above.
  • The clock is now ready to keep and show time in your Dark Mod mission (without special events or striking the hours yet.)

Starting the Clock at Mission Start

  • To start the clock running at the start of the mission you must also have in your script file this main function (add it if not there already) and insert on a line between its curly brackets as shown and it must be placed BELOW the main runClock script function:
void main ()
	runClock($clock_hand_big, $clock_hand_small);
  • Check that the name of the clock hands entities in your map matches the names in the above script as they may change on importing into the editor. They should by default be clock_hand_big and clock_hand_small. If for some reason you change the entity names then you must also change the entries in the script above to match.
  • The main function will run automatically at mission start and the clock will begin working.
  • If you have any other function calls in 'main' you should consider and test placement; I put one below the clock line and it failed to work yet above the clock line both worked synchronistically. Another option is to start the clock or any other function during the mission (even if immediately) see next section.

NOTE: (by grayman) The reason for the failure is that, as described, the runClock() routine will run endlessly, never returning to the main() routine so it can run further things.

The correct way to do this is to change the following line in the main() routine:

	thread runClock($clock_hand_big, $clock_hand_small);

Now runClock will execute in its own thread, and control will return to the main() routine for it to do further work.

Starting the Clock during the Mission

You may want to start the clock within the mission. You might, for example, want the clock and a particular time of day to to be visible only when the player reaches a certain place in the mission. Or, you might have conflict with some other script and want to call them separately and not from the 'main' function. To do this:

  • Proceed exactly as in Starting the Clock at Mission Start but put the line runClock($clock_hand_big, $clock_hand_small); into a function called startClock instead of main so it looks thus...
void startClock ()
	runClock($clock_hand_big, $clock_hand_small);
  • Create a target_tdm_callscriptfunction entity in your mission. It can be anywhere except the void but it makes sense to keep it near the clock.
  • Give it the key value pair: call startClock
  • Set up some trigger of your own, eg, a trigger_once entity and target the above entity where and when you want the clock to start, for example, if the player goes out a door into a courtyard and sees the clock for the first time then you could get the door to trigger the clock to start.

Triggering Events on Time

You can trigger any entity at any selected time. Advanced users can also call scripted events of course but it is recommended that you do this indirectly using the method described in Striking the Hours below otherwise it may slow down the clock.

Striking the Hours

This uses a target_tdm_callscriptfunction entity by default named ClockStrike and this is provided with the scripted clocks prefabs. Its name must be the same as in the runClock script. This calls a script (see below.) This entity I have also given the key value pair snd_effect churchbell02. Use a different sound shader if you want but bear in mind that radius and volume are controlled in the shader file. Using a speaker did not work here because of problems with starting and stopping it rapidly and reliably. The method I use is stable and permits the 'bell' to be struck again while it is still ringing yet the final strike fades naturally. The runClock script passes the hours to strike using a cvar called game_clock_hours.

You should copy and paste the script from below into your script file anywhere between other functions though it is normal to put 'main' at the bottom. All that matters is that if function A calls function B then function B must be ABOVE A. This is why runClock must be above 'main'.

 void clockChime()
 	float hours = sys.strToFloat(sys.getcvar("game_clock_hours"));
 	float strike = 1;
 	while(strike <= hours)
 		$ClockStrike.startSound("snd_effect", SND_CHANNEL_BODY, false);
 		strike = strike + 1;

Triggering Entities on Time

To trigger an entity refer to these lines in the runClock script...

if (mins == 99 && hours == 99)
  • Change the mins and hours values to the time you want the trigger.
  • Change yourEntity to the name of the entity you want to trigger (keep the $ prefix so eg, $myEnt)
  • The whole script function can be copied and pasted below itself to have any number of timed triggers. It is possible with a lot that this might slow the clock slightly.

Timed Objectives and Mission Fail 'Out of Time'

If you want to set up a timed objective, eg, "Escape the dungeon before your execution at 3:00am" or "Survive within the crypt until 3:00am" then:

  • Set up the objective as a custom objective (see Objectives Editor.)
  • Create a target_tdm_setobjectivestate.
  • Give it a name, eg, TimeObjective
  • Give it the property value pair: obj_idN <n> (replace <n> with the objective number)
  • Give it the property value pair: obj_state <n> (replace <n> with 1 to complete or 3 to fail)
  • Create an if conditional script as described above in Triggering Entities on Time with the following line included...
  • Change the mins and hours values to the time you want the mission to end.