TrainzScript Message Reference

From TrainzOnline
Revision as of 02:53, 31 August 2026 by Pcas1986 (talk | contribs) (Replaced example with one that works without obsolete functions.)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigationJump to search

What are Messages and how can they be used?

Messages:

  • Are internal communications between objects within Trainz.
  • Are completely hidden from the normal user, who won't be aware of their existence.
  • Provide notification of events within the Trainz script environment.
  • Provide a means of opening direct communications between objects.
  • Provide a means of sending instructions from one script to another.
  • Provide a means of timing scripted actions.

Messages are managed by the Router, a sort of internal post office, and can be exchanged between any objects which descend from GameObject, this includes driver commands, rules and libraries as well as almost all mesh based scriptable objects.

Scripts can:

  • Listen for messages and use them to take appropriate actions in response to events and instructions.
  • Use the message to extract information about the object sending or receiving the message.
  • Define and issue their own messages.
  • Listen in to messages intended for other objects.
  • Send messages to themselves, to arrive after a specified delay, as a 'reminder' to do something at a future time.

Messages are small data objects containing five pieces of information:

  • msg.major: A string identifying the message type.
  • msg.minor: A string containing the message subtype or additional information.
  • msg.src: A reference to the object that sent the message.
  • msg.dst: The intended recipient. May be null if the message was broadcast (legacy behavior, no longer supported).
  • msg.paramSoup: A Soup object containing extra data about the message (may be null).

How to listen for Messages

There are three techniques you can use:

  • AddHandler() This is the simplest listening device and is most useful when you know that you will always be interested in this type of message. AddHandler() is usually set up in the Init() method of a script. Hint: omit any optional spaces in statements that include an AddHandler function, as including them causes the function to be ignored without notification in TRS2004 and perhaps other versions as well.
  • A wait() loop. This needs to run in a separate thread and allows for a temporary arrangement where you want to know about messages for a shorter period or under a specified set of conditions.
  • Sniff(). This allows you to 'eavesdrop' on messages being sent to another object.

When and how to send Messages

You might consider issuing messages of your own under the following circumstances:

  • When an event has taken place in your script and other objects might need to know about it.
  • When you want to make your object available to other scripts to allow them to call your methods directly.
  • When you need to issue instructions to another object to carry out a particular task and you know that the target object will be able to understand your message.
  • When you want your own script to take an action at some time in the future, or at regular intervals.

There are three routines you can use to issue messages:

  • SendMessage() dispatches the message immediately. Do not use SendMessage() within a message handler and use PostMessage() instead. A message handler is a static thread and using SendMessage() within a static thread will raise an exception.
  • PostMessage() dispatches the message via the Router with a specified delay (which can be zero). The message will be executed on the next Router update after the delay has elapsed.
  • Router.PostMessage() allows messages to be dispatched even if the object you are scripting doesn't have its own messaging capabilities.

If the message destination is entered as me (meaning the current object) the message will be sent to the object which owns the script. If the destination is null the message will be broadcast and therefore available to any object in the session. Message broadcasting is obsolete as of TRS19 however, and may result in script exceptions or silent failures (depending on the local players compatibility setting).

If you have a reference to the message target (or if you know its name or id) you should consider the following:

  • If you know which methods to use it might be more appropriate to call the other script directly.
  • You should always avoid broadcasting messages if it is possible to send them directly to the intended recipient.

Never Assume Anything

The messaging system contains many traps for the unwary. If you are working with a junction for instance you will receive an Object,Enter message whenever a train enters your object's trigger zone. It is not safe however to assume that a subsequent Object,Leave means that the train has crossed the junction and left on the other side. Any of the following would also produce Object,Leave:

  • The train might have stopped and reversed away from your object.
  • An intervening junction might have been toggled so that the train is no longer visible to your object.
  • The train might have coupled to another (and therefore ceased to exist) before reaching your object.

It is usually necessary to treat a message as an indication that the event you are interested in could have happened and to use other tests to confirm.


Example

Listening for messages and using message handler methods

include "World.gs"
include "gstracksearch.gs"
// include this in your config.txt
// script-include-table
// {
//  0                                     <kuid2:388413:439002:5>
// } 
include "traintools.gs"
// script can be attached to any scenery object
// enable the message window to view the output in Driver mode
class Messenger isclass Buildable {
  // Global
  Train train;
  // Function prototypes
thread void GetATrainAndJunctions(void);

public void Init(Asset asset) {
  inherited(asset);
  int module = World.GetCurrentModule();
  Interface.Log("Messenger->Init - current module is " + module);
  // see world.gs for module defines
  if (module == World.NO_MODULE) {return;} // do nothing
  AddHandler(Interface,"World","ModuleInit","ModuleInitHandler");
} 
 
// Listens for trigger messages activated by a train.
// Listening terminates if a train stops within the trigger radius.
thread void TriggerMonitor(void) {
  // Trigger messages will be passed to us as a result of the 
  // Sniff() call in GetATrainAndJunctions
  Interface.Log("Messenger->TriggerMonitor - started");
  Message msg;
  wait() {
     // listen for Trigger,Enter, print a message when heard, continue listening
     on "Trigger","Enter",msg: {
        Interface.Log("Messenger->TriggerMonitor - a train has entered a trigger");
        continue;
     }
     // listen for Trigger,Stopped, print a message when heard and then quit the thread
     on "Trigger","Stopped",msg: {
        Interface.Log("Messenger->TriggerMonitor - a train has stopped on a trigger");
        Interface.Log("Messenger->TriggerMonitor - trigger monitoring discontinued");
        break;
     }
  }
}

// A function to take some action when a message "Junction, Toggled" is sent. 
void ToggleHandler(Message msg) {
  // print a message whenever Junction,Toggled is broadcast
  Interface.Log("Messenger->ToggleHandler - a junction has been toggled");
} 

// Seaches for train in the session and returns the first in the list.
// The search uses the asynchonous method that replaces the now obsolete "World.GetTrainList()"
thread void GetATrainAndJunctions(void) {
  // get a train and wait for a result
  NamedObjectInfo[] searchResults;
  AsyncObjectSearchResult searchObject = World.GetNamedObjectList(AssetCategory.Consist, "");
  Sniff(searchObject, "ObjectSearch", "", true);
  // and wait
  Message msg;
  wait()
  {
    on "ObjectSearch", "", msg:
      if (msg.src == searchObject) {
        break;
      }
  }  
  Sniff(searchObject, "ObjectSearch", "", false); // stop listening 
  // proceed if no errors
  if (searchObject.GetSearchErrorCode() == AsyncObjectSearchResult.ERROR_NONE) {
     searchResults = searchObject.GetResults();  // get list of trains (consists)
     if (searchResults.size() > 0) { 
       train = cast <Train> searchResults[0].objectRef;  // could be null if a train isn't loaded
       // This can return the name of the consist or the driver name. 
       Interface.Log("Messenger->GetATrainAndJunctions-> got " + train.GetTrainDisplayName());
     }
   }
   else {  
     train = null; // default anyway
     Interface.Log("Messenger->GetATrainAndJunctions-> could not get a train");
   }
   if (train) {
     Sniff(train,"Trigger","",true); // listen for trigger messages sent to the train
     TriggerMonitor(); // and monitor those messages
   } 
   // Now find some junctions and add handlers for them.
   // Note that the tracksearch will stop when the track is broken, such as a junction point,
   // or at 8000 metres.
   if (train) {
	// get some junctions for the test
 	Vehicle vehicle  = TrainTools.GetFrontVehicle(train);
 	GSTrackSearch gst = vehicle.BeginTrackSearch(TrainTools.VehicleToTrainDriveDirection(vehicle, true)); // Start track search in the driving direction of vehicle
       Junction junction;
       MapObject obj = gst.SearchNext();  // prime the search
       while (obj) {
         if(gst.GetDistance() > 8000.0) break;
         junction = cast <Junction> obj;
         if(junction) {
     	    AddHandler(junction,"Junction","","ToggleHandler");
     	    Interface.Log("Messenger->GetATrainAndJunctions-> found junction-> " + junction.GetLocalisedName());
         }
         obj = gst.SearchNext();
       }  
   }
   else {
     Interface.Log("Messenger->GetATrain-> can't start trigger monitor as there is no train.");
   }
 } 

void ModuleInitHandler(Message msg) {
  Interface.Log("Messenger->ModuleInitHandler-> module is " + World.GetCurrentModule());
  if (World.GetCurrentModule() == World.DRIVER_MODULE) {
    GetATrainAndJunctions();
  }
}
};


See Also