HowTo/Search for objects in the world

From TrainzOnline
< HowTo
Revision as of 09:35, 26 February 2024 by Pw3r (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Scripted asset may sometimes wish to search for other objects within the world, either in bulk lists, or for a specific single object. This page is intended to provide an overview of how this is achieved in the latest versions of Trainz. Details about certain legacy search methods and the best modern techniques to update them to are also provided in the final section of this page.

Contents

Object Identification

Scripted objects identified in script using several different methods, many of which have become obsolete as Trainz and the script environment has grown in complexity. As of trainz-build 4.5 (TANE SP2 and later) scripted objects should be identified using their GameObjectID, where present. This ID format has been designed to be future-proof/extensible, should the need arise, and supports various lookup and generic storage functions as per the legacy integer and string based IDs. For more information about legacy ID formats, see here.

The GameObjectID class is defined within GameObjectID.gs, which can be found in the Trainz core script folder (resources/scripts). For the most up to date and accurate usage instructions please see the comments within that file.

Named Object Searches

It is occasionally desirable for scripted assets to be able to search for objects within the world. Most commonly, this is a requirement of session rules, which frequently require a session creator to specify target trains, junctions, signals, etc, in order to perform some gameplay function. Occasionally, placable assets such as industries etc may also require this feature. One such example is the Interlocking Tower asset type, which performs complex management of track blocks using management of user defined signals, junctions and crossings.

As of trainz-build 4.5 (TANE SP2 and later) manual object searches are performed via the World.GetNamedObjectList() function. This function takes both a category list and a partial name, allowing for very specific searches, or quite broad ones. Once called, this function returns an object of type AsyncObjectSearchResult, which is used to monitor for the search completion, and then to retrieve the results. For the most up to date and accurate information on how to use GetNamedObjectList and AsyncObjectSearchResult, please see their script comments in World.gs and AsyncObjectSearch.gs (respectively), which can be found in the Trainz core script folder (resources/scripts).

Usage example:

thread void SearchForTrains()
{
  // Start a search for any traincars within the world
  AsyncObjectSearchResult searchObj = World.GetNamedObjectList(AssetCategory.TrainCar, "");
  
  // Sniff for search related messages, and then wait for either completion or failure
  Sniff(searchObj, "ObjectSearch", "", true);
  Message msg;
  wait()
  {
    on "ObjectSearch", "Failure", msg:
      if (msg.src == asyncSearch)
        break;
      continue;
    
    on "ObjectSearch", "AsyncLoadComplete", msg:
      if (msg.src == asyncSearch)
        break;
      continue;
  };
  
  // Check the results
  int errCode = asyncSearch.GetSearchErrorCode();
  if (errCode != AsyncObjectSearchResult.ERROR_NONE)
  {
    // TODO: Add any error handling here, such as waiting and reattempting the
    // search later, showing an error message, throwing script exceptions, etc.
    return;
  }
  
  // Get the search results
  NamedObjectInfo[] results = searchObj.GetResults();
  
  // TODO: Add any processing of the results here
}

Storing References to Specific Objects

As shown in the example above, named object searches return their results as an array of NamedObjectInfo objects. Currently, this class has 4 member variables:

  • objectRef - A reference to the object, if it is currently loaded
  • objectId - The GameObjectID for the object (see GameObjectID.gs)
  • localisedUsername - The localised name of the object, for displaying to the player
  • categoryString - A category string, usable to further determine the exact type and features of the object

Any script which wishes to save a reference to specific named object should /always/ do so via the GameObjectID. This is currently the /only/ safe and reliable way to store and later retrieve a specific object. If the object is currently loaded it is of course also safe to access and store a reference to the object itself, but note that Trainz may choose to unload the object at any time (if it is inactive, and sufficiently far enough away from the player, etc). Attempts to use an object which native code has unloaded may result in native call errors. To avoid this scripts should call Router.DoesGameObjectStillExist() before attempting to use any object reference that has been stored for some time.

Unlike the named object itself, a referenced GameObjectID will never be deleted by native code, and may be held by a script indefinitely. It can also be stored directly into a Soup database for storage and retrieval between runs of the game, over a network, etc. For details about saving a GameObjectID into a Soup database please see the comments in Soup.gs, located in the core Trainz script folder (resources/scripts).

In addition to Soup storage, a GameObjectID can be serialised to a string for *temporary* storage. This allows scripts to do things like store the ID directly in a trainzhttp link, etc (provided it is also appropriately escaped). However, it is crucial to note that the format of this string is *not* guaranteed and may change between versions of the game. As such, no attempt should be made to machine parse it, save it between runs or send it over a network. To convert the string back into a GameObjectID, use Router.SerialiseGameObjectIDFromString().

It is also safe to cache and store the localised display name for an object, but it is important to remember that objects can be renamed, and that this object name may become out of date at any time. As such, scripts which store it should remember to update their cache whenever an opportunity arises (e.g. during any validation steps).

Searching for Specific Objects by ID

Several functions exist for looking up an object which has been saved via it's GameObjectID. Each of these serves a slightly different use-case, so it's important to understand them all before selecting the one that best suits the needs of a specific script.

Two functions exist to retrieve a loaded object via it's ID. These both behave identically, and will return a reference to a matching object *only* if that object exists and is currently loaded. In all other cases the functions will return null. There is no way to determine the cause of a null result using these functions.

GameObject objectRef1 = World.GetGameObjectByIDIfLoaded(mySavedID);

GameObject objectRef2 = Router.GetGameObject(mySavedID);

Another function exists to allow scripts to search for objects which are not currently loaded, and to optionally request that they are loaded. Scripts should only request loading of objects when absolutely necessary, and scripts which abuse this feature may have their requests ignored by native code in order to maintain performance. As this function may take a considerable amount of time to run, it uses an AsyncObjectSearchResult, much like the named object list functions described above.

AsyncObjectSearchResult searchObj = World.GetGameObjectByID(mySavedID, false);

As with GetNamedObjectList(), calling scripts must Sniff the returned search object and wait for the appropriate result message(s) to be posted. To avoid the need for frequent code duplication another function also exists which allows threaded objects to perform this functionality in a pseudo-synchronous way.

GameObject loadedObjectRef = World.SynchronouslyLoadGameObjectByID(mySavedID);

This function will throw exceptions if used incorrectly (e.g. if called outside of a thread function) so be sure to read and understand the comments on the function itself, before attempting to use it.

Script Library Lookups

Script library assets are unique within script in that they exist without any visual presence to the end player, and are shared across all scripts that access them. i.e. It is not possible to instantiate multiple instances of the same library asset within a single script context. This makes them extremely useful for managing, organising or communicating between other scripted asset types. For more information about library assets, see the KIND_Library page.

As libraries are added to the world by script, and the not the player or session creator, they require a script accessible function to create and load them. This function is TrainzScriptBase.GetLibrary(). When this function is called the library asset will be immediately loaded and returned by Trainz native code. Once loaded into the current script context, the library will remain loaded, and any future calls to GetLibrary() will return the same instance.

Example usage:

public void Init(Asset asset)
{
  inherited(asset);
  
  // Load our shared library asset
  KUID libraryKuid = asset.LookupKUIDTable("shared-library");
  m_sharedLibrary = World.GetLibrary(libraryKuid);
  
  // TODO: Add any library specific init, registration, etc code here
}

Legacy Search Functions

Prior to trainz-build 4.5 (TANE SP1 and earlier) all world objects were loaded on Route/Session load, and remained that way until the session was ended. As Routes grow larger and larger this technique becomes unmanageable, and it is thus necessary for Trainz (and it's scripted assets) to support the unloading of objects which are not currently gameplay relevant. As a result of this, numerous script functions have been declared obsolete, as they are incompatible with this concept. Below is a summary of these functions and a mention of their modern replacements.

Note that none of the legacy functions listed here will operate correctly if the game has the "Compatibility mode" setting set to "Maximise performance" or "Show errors on legacy calls".

GameObject.GetId()

All GameObject instances within a Router are assigned a numeric integer ID. This ID was traditionally retrieved via this function. As the ID varies between runs it was never safe for long term storage, but it was suitable for short term storage and lookup. As objects can now be unloaded at any time, and these IDs then reused by totally different objects, the function has been declared obsolete. Any scripts which use it should be updated to instead use GameObjectID and the related storage functions. See the comments on GameObjectID.gs for more information.

GameObject.GetName()

In addition to their integer ID certain GameObject types also supported a "script name", which was generally automatically assigned on creation, but could also be edited by the player in some scenarios. Despite the script comments in certain older versions of Trainz, this name was never guaranteed unique, but it was generally considered to be so for ease of use (and that assumption rarely failed). This script name was typically used for object identification in medium to long term storage, including within Soups for session config, savegames, etc. Any scripts which use it should be updated to instead use GameObjectID and the related storage functions. See the comments on GameObjectID.gs for more information.

Router.GetGameObject(int)

Much like GetGameObject(GameObjectID), this function would return any loaded object with the integer Router ID passed. As this ID format is now considered obsolete, so is this lookup function. Any scripts which use it should be updated to instead use GameObjectID and that function variant (or the async search functions on World). See the comments on GameObjectID.gs for more information.

Router.GetGameObject(string)

Much like GetGameObject(GameObjectID), this function would return any loaded object with the "script name" string passed. As this ID format is now considered obsolete, so is this lookup function. Any scripts which use it should be updated to instead use GameObjectID and that function variant (or the async search functions on World). See the comments on GameObjectID.gs for more information.

World.Get*List()

Prior to GetNamedObjectList() multiple functions existed on World to manually retrieve lists of objects of a specific type (e.g. GetIndustryList(), GetTrainList(), etc). These search functions were slow, and scripts would often stall Trainz while the searches were performed. Because of this, and the fact that the functions can now only return those objects that are currently loaded, they have been declared obsolete. Any scripts which use them for custom functionality should be updated to instead call World.GetNamedObjectList() with an appropriate category string. See the comments on this function and the AsyncObjectSearchResult in World.gs for more information on their use. Alternatively, any scripts using them for PropertyObject "list" types may be better served by the new "map-object" type, detailed here.

See Also

Personal tools