John Cletheroe's
Trainz Hintz - TRS2004 Scenario Creation Tutorial


TRS2004 Scenario Creation Tutorial - Handler Functions For Asynchronous AI Train Operations

Introduction

Handler functions provide one means in a scenario of making an AI train perform a series of operations at the same time as the user is controlling their train. Another method is to use GameScript/TrainzScript's schedule and timetable functions.

This tutorial shows how to make an AI train automatically shuttle back and forth on a parallel track, while the user is given a series of instructions to perform.

Track Layout

In Surveyor, create a new layout. Construct two tracks parallel to each other but with no connections between them. The lefthand track will be for the user's train and the righthand track for the AI train.

Create a branch off the lefthand track, in the direction away from the righthand track.

In what follows, please feel free to adapt the details and names as you wish provided you are consistent.

On the lefthand (usertrain) track, create a trackmark called "Trackmark 1" and three triggers called "Trigger 1", "Trigger 2" and "Trigger 3". Each of the triggers should be on a different branch of the junction (LEFT, RIGHT and BACK).

On the righthand (AI train) track, create a trackmark called "Trackmark 2" and two triggers called "Trigger 4" and "Trigger 5".

Make sure each of the trackmarks and triggers is some way from the end of the track, and some way from its neighbours.

Save the layout and Export Scene Data to create a new scenario.

config.txt

In the scenario's config.txt file, add entries in the kuid-table for two locomotives, for example AN830 and QR2100. You can some wagons for the locomotives to pull if you wish but that isn't essential.

gs File

Add the usual statements to create the consist specs for two trains, for example playertrain with the AN830 and aitrain with the QR2100.

Add the usual statements to place playertrain on Trackmark 1 and aitrain on Trackmark 2.

Add statements to give the user an endless series of objectives, for example:

	World.SetGameTimeRate( World.TIME_RATE_1X );
	World.SetGameTime( 0.875 );	// 9am
	World.SetWeather( World.WEATHER_TYPE_CLEAR, World.WEATHER_CHANGEABILITY_NONE );
	World.SetCamera( playertrain, World.CAMERA_EXTERNAL );
	World.SetCameraAngle( 350, -15, 75 );

	playertrain.SetAutopilotMode( Train.CONTROL_MANUAL );

	int mode = 0;
	int previousmode = 0;
	string destination;

	while (1 == 1)
		{
		while (mode == previousmode)
			{
			mode = Math.Rand(1, 4);			// 1, 2 or 3
			}
		previousmode = mode;
		if (mode == 1)
			{
			destination = "Trigger 1";
			}
		if (mode == 2)
			{
			destination = "Trigger 2";
			}
		if (mode == 3)
			{
			destination = "Trigger 3";
			}
		Interface.SetObjective("Waiting for you to enter " + destination + ".", "");
		Navigate.OnTrigger(me, playertrain, destination, Navigate.TRIGGER_ENTER);
		Interface.SetObjective("You have entered " + destination + ".\nWait for new instructions.","");
		Sleep(5);
		}
Check that the scenario works so far. At this stage we are not doing anything with the AI train. The user should be told to drive to one of the triggers on the lefthand track chosen at random, then to a different one, and so on forever. As this section of the program is not the main focus of this tutorial, and it is fairly straightforward, it will not be explained in detail.

When all is ok, make the following amendments to the scenario's gs file:

Immediately above the "main thread" comments, insert these statements (which will be explained later):

	void Trigger4Handler(Message msg)
		{
		if (msg.minor == "Enter")
			{
			(cast<Train> msg.src).SoundHorn();
			(cast<Train> msg.src).SetDCCThrottle(0.5);
			Interface.Print("The AI train has entered Trigger 4 - now going forwards.\n\n\n\n");
			}
		}

	void Trigger5Handler(Message msg)
		{
		if (msg.minor == "Enter")
			{
			(cast<Train> msg.src).SoundHorn();
			(cast<Train> msg.src).SetDCCThrottle(-0.5);
			Interface.Print("The AI train has entered Trigger 5 - now going backwards.\n\n\n\n");
			}
		}
In the main section of the program, after the "string destination;" statement, insert the following:

	AddHandler(cast<Trigger> Router.GetGameObject("Trigger 4"), "Object", null, "Trigger4Handler");
	AddHandler(cast<Trigger> Router.GetGameObject("Trigger 5"), "Object", null, "Trigger5Handler");

	Sleep(5);

	aitrain.SoundHorn();
	aitrain.SetDCCThrottle(0.5);
	Interface.Print("AI train starting to move - going forwards.");
	Interface.SetMessageWindowVisible(true);
The two handler functions, called Trigger4Handler and Trigger5Handler, will cause the AI train to sound its horn and change direction. This example assumes that Trigger 5 is forward of Trigger 4.

In the handler functions, the cast function is required because the functions have to know which train to operate on, and they derive this information from the internal inter-object messages. The inter-object message system is a subject in its own right and therefore a full description of it is well beyond the scope of this tutorial. It is extremely useful for performing some very advanced tricks. In brief, every time an event occurs, objects send internal messages to and from each other. Each message has four properties: its source (src), its destination (dst), its major part and its minor part. The handler functions check that the minor part of the message is "Enter" so as to only react when the AI train enters the trigger and not when it stops in the trigger or when it leaves the trigger.

In the main part of the program, the AddHandler functions set things up so the handler functions "lie in wait" and will be obeyed whenever an event occurs on their specified trigger. The cast statements here are required to convert the triggers' names into the trigger objects themselves.

Do not insert any unnecessary spaces in AddHandler functions, as in my experience doing so causes the function to be ignored (with no error message).

The statements after the AddHandler functions should be fairly obvious. Interface.Print displays a message in the radio text box.

When the scenario is run after making these changes, the AI train should automatically run back and forth between the two triggers on the righthand track, with its horn sounded and radio text box messages displayed every time it reverses direction. While this is going on, the user can operate their train in accordance with the instructions displayed in the scenario objective panel. Even though the main program might be waiting for an extended period of time for the user train to enter the next trigger, the AI train will still correctly reverse at each end of its track.

In a real scenario, AI trains can be made to perform more interesting operations than merely running back and forth.

Handler functions can also be used for other purposes.

Be careful to only include commands in handler functions which will be completed very rapidly. This is because while the handler function is being obeyed, the main program is not. It might be possible to use threads to get round this problem but I have not yet investigated that subject.


Index
Overall Site Home Page
About this personal web site JohnCletheroe

EMail me

Most recently modified 1-Aug-09