TNI Controls Interface
TNIControls is a builtin TNILibrary within Trainz which allows controls plugin assets to communicate their control input to Trainz. These commands can then be bound to in-game functions by the player, via the standard control settings UI.
Before continuing, it is recommended you first familiarise yourself with the following pages:
- TrainzNativeInterface - For an overview of TNI and the associated licensing requirements
- TNI Core Interface - For details of the core TNI functionality, object types, etc.
- KIND tni-controls-plugin - For the specification of the controls plugin asset kind.
NOTE: This page is a work in progress, and reflects technologies which are not yet present in retail versions of Trainz.
TNIControls is a singleton system within Trainz native code. It is initialised at game startup (if supported by the install, and enabled by the player), and will automatically locate and load any installed TNI controls plugin assets. Such assets will then have their plugin DLLs loaded into the same TNIContext as TNIControls itself, where the plugins can locate hardware devices, register them, and provide control inputs.
TNIControls itself is entirely thread safe, and may be loaded and called at any time from within a controls plugin DLL, using the TNIGetContextLibrary function. The specific function calls supported by TNIControls are detailed in the next section.
TNIControls Library Calls
This function is called by a plugin library to register a single instance of a hardware controller. It returns a TNIInteger containing a basic result code and takes a TNIArray object containing (in order):
- A TNILibrary reference to the calling plugin
- A TNILabel reference to an ID for the controller itself
- A TNILabel reference to the ID for the type of controller, as defined by the asset
- A TNIInteger containing a register operation code (1 to register, 0 to deregister)
Control settings bindings are currently configured by controller type rather than specific controller, so there's no need for the controller ID to be persistent/identical between runs of the game, but it won't hurt anything if they are. The function will return 0 on success, or a positive value to indicate error. The possible error codes are as follows:
- 0 - No error, operation success
- 1 - Invalid parameters
- 2 - Unknown caller
- 3 - Controller ID is already in use by this plugin (for register only)
- 4 - Unknown controller type (for register only)
- 5 - Unknown controller ID (for unregister only)
Once a plugin has registered a controller TNIControls may attempt to bind it to an active window/world at any time. When this happens TNIControls will call the plugin library DLL with the function name "NotifyContext". This function expects no return object, and takes a TNIArray object containing (in order):
- A TNILabel reference to the previously registered controller ID
- A TNILabel reference to a context ID to which this controller is now bound, or null
The context ID may be somewhat human readable for debugging/info purposes, but no attempt should be made to machine parse it. In the event that the plugin controller is no longer bound to an active window, null will be passed, and the plugin should stop processing. Note that most TNIControls library calls will generate errors if they are called for a controller while it is unbound.
It is recommended that controls plugins send the entire current state for their controllers when they are bound to a new context, so that Trainz may accurately represent the current state of the device. (See SetControlValue for more)
Once a controller is bound it will be able to query the state of various things in the new context, via the GetWorldValue function. This function provides an interface to various pre-defined world state variables that the controller may wish to display, or use to vary its input semantics. For example, the RailDriver levers operate slightly differently based on whether the player is in DCC or Cab mode.
The function takes a TNIArray object containing (in order):
- A TNILibrary reference to the calling plugin
- A TNILabel reference to a previously registered controller ID to query values for
- A TNILabel reference to a world value ID to query
Calls to this function which supply an invalid or unknown controller ID will return null and log an error message. The function return value may be a TNIAssetID, TNIString, TNIFloat or TNIInteger, depending on the value queried, as follows:
- module, TNIString, possible values are: "menu", "driver", "surveyor"
- input-context, TNIString, this is a semicolon separate list of active input contexts, as per the controlset asset kind (e.g. "driver;driver-dcc")
- interface-units, TNIInteger, 0=none/unknown, 1=metric, 2=uk imperial, 3=us imperial, 4=SI
- world-clock, TNIString, this is the current world game time, in human readable clock format
- velocity, TNIFloat
- main-reservoir-pressure, TNIFloat
- brake-cylinder-pressure, TNIFloat
- no3pipe-pressure, TNIFloat
- brakepipe-pressure, TNIFloat
- equalizer-pressure, TNIFloat
- brake-flow, TNIFloat
- ammeter, TNIFloat
- wheelslip, TNIInt (0 - not slipping, 1 - slipping)
- vehicle-assetid, TNIAssetID
- engine-assetid, TNIAssetID
Where relevant, all return values are in SI units. If the values are to be displayed somehow, then plugins should perform their own conversions based on the interface-units world value.
When a controls plugin asset queries the value of a world value TNIControls will automatically register a listener for it, and may notify it of future changes to that variable via the NotifyWorldValue function. Note that this is only used for certain rarely changing values, like module, input-context and active assets. Also note that listeners may be automatically deregistered following context changes, so plugins should requery such values following reactivation. This function expects no return result and will take a TNIArray object containing (in order):
- A TNILabel reference to a context ID (to which one or more relevant controllers are bound)
- A list of changed world values, including for each a:
- A TNILabel reference containing the world value name
- A TNIObject for the new value
The TNIGetArraySize function should be used to determine the number of variables in the passed array. Plugins may safely ignore calls to this function if they have no interest in the variable changes, but they should do so silently.
This call is used to set the value of one of the control-library controls defined in the plugin asset spec. It always returns null and takes a TNIArray object containing (in order):
- A TNILibrary reference for the calling plugin
- A TNILabel reference to a previously registered controller ID
- A TNILabel reference to a control ID within the control-library asset
- A TNIFloat for the new value of this control (0 to 1, regardless of control type)
Errors will be reported in game if this function is called with an unknown controller ID, an unknown control ID, or for a controller that has no active context.
As values are updated TNIControls will queue input commands against the relevant linked window(s) for that controller, allowing players to bind the controls to functions of their choice, and then use them in game. Trainz may smooth the input values slightly to avoid control jitter but plugin DLLs are free to do so as well, based on the specific requirements/limitations of their hardware.