The m4nfo User manual and Technical Report

Train callbacks

Using callbacks for train vehicles

Introduction

This is an in-depth description of train callbacks. More basic information about callbacks can be found here.

A 'callback' is a special action that TTDPatch 'calls' in order to modify various attributes of a feature (e.g., a train vehicle) previously defined. An example being the modification of the visual effect of a train vehicle, its speed, or its capacity.

This is done by identifying the incidence and the possible type of a callback in the callback() function. Depending on the type of the callback, control is handed over eventually to a function which handles the callback, or lets it fail. In the latter case, the value previously set by the appropriate property function will be kept using.

This is a list of all available callbacks for trains. Please note, that not all of them need to be activated in the callbacks() property function during the vehicle definition stage:

Callback Description
Callbacks needing activation
CB_ARTI Build articulated engines
CB_LOAD Load amount (not for GRF version 8)
CB_POWR Powered wagons and visual effect
CB_RCAP Set refitted capacity
CB_RCOL Select color mapping for vehicle
CB_SOUND Set sound effect
CB_TSFX Show a suffix after the cargo type name
CB_WLEN Vehicle length (not for GRF version 8)
Callbacks always activated
CB_ARVS Autoreplace vehicle selection
CB_ATAC Can wagon be attached?
CB_PROP Change vehicle properties
CB_RCOST Refit cost callback
CB_STOP Start/stop check
CB_TEXT Additional text in purchase screen
CB_32DAY 32-day callback

Description

CB_ARTI - articulated engine callback

This allows building articulated engines or wagons, i.e. vehicles made from several individual vehicles. If this callback is in use, additional vehicles will be added by function addveh() (or addvehrev()) until the callback returns NONE.

Other than this, the callback return is interpreted as the ID of the vehicle to add. If function addvehrev() is used instead, the vehicle will be added in reverse direction (as if Ctrl was held while bought).

For GRF version 7, veh-IDs must be in the range 0 .. 127, with vehicles to be reversed in the range 0 .. 126. For GRF version 8, veh-IDs may be in the range 0 .. 16383.

Note that since the train is not yet built, the only function you may use in the callback chain is articulated() which will return the rank of the articulated part to be built, i.e. "1" for the first articulated part, "2" for the second and so on.

If more than the first vehicle contains cargo, the callback must be used in the buy menu as well, to add up cargo capacities for all articulated parts. Use cargo type MENU for this.

For an example (locomotive with tender) see here.

CB_LOAD - load amount callback

This callback is used instead of property function loadamount() when loading a game or re-arranging a train in a depot (or when buying other vehicles). If the callback fails, the value of the property function is used instead, see below.

This callback is not available in GRF version 8. Use CB_PROP with loadamount() instead.

Example (adjust SGLM coaches):
// GRF version 7
def(11) callback(
	setlen(5/8) if(CB_WLEN) // wagon length
	cbr(4) if(CB_LOAD)	// load amount := 4
	ref(1) else		// graphics  
)

// GRF version 8
def(10) property(
	setlen(5/8) if(vehlen()) // wagon length
	cbr(4) if(loadamount())  // load amount := 4
	cbfail()
)

def(11) callback(
	ref(10) if(CB_PROP)
	ref(1) else        // graphics  
)

CB_POWR - powered wagon callback

This callback is used to determine whether a train wagon is powered (adding power and weight to the train) and the visual effect to use for a vehicle.

The 'powered wagon' state only applies for train wagons that would by default be powered (i.e. the power property is set). The visual effect applies to all train vehicles (and since OpenTTD r21238 for road vehicles and ships as well).

This callback is only called when loading a game, when the train reverses, or when rearranging the train in a depot. In OpenTTD the 'visual effect' is also updated when the vehicle enters track of a different railtype. The 'powered wagon' state may not change in that case.

If the callback fails for any reason (e.g. not defined, or a performance function does not return a callback result, etc.), the value of the corresponding train property is used instead. For possible return values, refer to these properties as well.

In the example below, the position of the visual effect (steam puffs) is adjusted with regards to direction of travel (forwards, backwards):

Example (steam effect positioning):
def(3) flipped(
	effect(steam(4)) if(FORWARD)
	effect(steam(8)) else
)

def(4) callback(
	ref(3) if(CB_POWR) // steam position
	ref(2) else	   // graphics  
)

CB_RCAP - refitted capacity callback

This callback is used when a vehicle is refitted, to find the new capacity. It allows return values up to 32511 (0x7EFF) units of cargo.

Function cargo() will return the new, climate-specific cargo type, or a value of "255" when only checking whether the vehicle is refittable.

Unlike regular refitted capacities (including those from CB_PROP), the return value is not subject to the usual division of capacities for cargoes other than passengers on trains and planes, instead the capacity is used exactly as returned by the callback.

CB_RCOL - select colour mapping for vehicle

This callback is called while drawing the vehicle. It should return the number of the colour mapping to be used on the vehicle's sprites. If the callback fails, the company colour of the owner is used.

Single colour maps are adressed by function getcolour(). For colour maps including the company colour, function getcolour_pluscc() is used, which needs to adress a block of 16 colour maps (one for each company colour). If the vehicle also has the 2CC flag set (two company colours), a block of 256 colour maps needs to be allocated, and function getcolour_plus2cc() being used. Alternatively, a return value of "0xC000" to use the default two-colour maps might be supplied.

The return value is cached to speed up sprite processing, and only updated via callback CB_32DAY (or when loading/starting a game or rearranging the consist).

Example (engine recolouring):
def(PETROL) getcolour(0x00)
def(BLACK) getcolour(0x01)

...

def(2) year(
	ref(PETROL) if(< 1928) // petrol -1928
	ref(BLACK) else        // black 1928-
)

def(3) callback(
        grftext(TLH_BR92) if(CB_TEXT) // text BR 92
        ref(1) if(CB_RCOL)	      // recolouring
        ref(0) else		      // graphics
)

CB_SOUND - Sound effect callback

This callback is called for all vehicles (and as generic callback for bridges), for various events that support playing sound effects. The type of event is checked by function soundevent().

The return value is a sound effect number. Values from 0 to 72 (decimal) are TTD's built-in sound effects, values beyond that refer to the sounds from defined by defsnd(). If the callback returns a failure code (not a callback result), the default TTD sound effect will be played. If the callback returns a sound number that is neither a TTD sound, nor a sound from a defsound(), nothing will be played.

EventMeaning
SND_STARTStart sound, called when leaving a station or depot
SND_TUNNELTunnel sound, called when vehicle enters tunnel
SND_BRKDOWNBreakdown sound, called when vehicle breaks down
SND_RUNNINGRunning sound, called once per engine tick, but no more than once per vehicle motion
SND_EFFECTTrain visual effect generation (steam plume, diesel smoke, electric spark)
SND_RUN16Running sound, called every 16 engine ticks if in motion
SND_STOP16Stopped sound, called every 16 engine ticks if stopped (e.g. at signal/in station)
SND_LOADLoad/unload sound, called when consist loads or unloads cargo

For the running sounds, it is advisable to check the motion counter by function animation() and to only return a successful sound effect number every so many vehicle motion ticks. This way, the sounds will be played more frequently the faster the train is. Alternatively, you can check and play the sound effect every so many engine ticks, so that it is played at a constant rate, or, of course, switch between both modes depending on vehicle speed.

CB_TSFX - cargo subtype display callback

This callback allows to display some text after the cargo name when refitting, and in the vehicle capacity display. This is useful to distinguish several subtypes of a certain cargo, e.g. 'goods' could be cars, petroleum, sheet metal, etc.

The return value must use the function grftext() to reference a previously declared text string set by defgrftext() in the same .grf file.

Example (cargo subtext usage):
defgrftext(TSX_CORSE,ALL,
	" (Chemins de fer Corses)"
)

...

def(9) callback(
	grftext(TSX_CORSE) if(CB_TSFX) // text suffix
	ref(M_M_COMP_GR) else	       // graphics  
)

...

makevehicle(_031TC,
	link(ref(1),MENU)
	default(ref(8))	     // locomotive
	override(_MM,ref(9)) // mail
)

Normally, you would use the vehicle function refitted() to distinguish the various subtypes. During refitting, the callback is called with successively increased values until it returns 0xFF/0x400 (i.e., grftext(NONE)). All the returned variations are then displayed as refitting options when in a depot, with all options that have the same cargo type and callback result (irrespective of refit cycle) grouped together in the same line.

Example (cargo subtext usage):
def(9) refitted(
	grftext(TSX_VIVARAIS) if(0)
	grftext(TSX_CORSE) if(1)
	grftext(TSX_INDRE) if(2)
	grftext(TSX_PROVENCE) if(3)
        grftext(NONE) else
)

For this callback to work, property function cargoclasses() needs to be set.

CB_WLEN - vehicle length callback

This callback is used instead of property shortening() given a number in range 0 .. 7, or property vehlen() given a vehicle length unit in range 1/8 .. 8/8 by helper function setlen(), whenever the train leaves a depot or is displayed inside a depot. If the callback fails, the value of the property function is used instead. See example for CB_LOAD below.

The callback result may change only when the whole vehicle consist is inside a depot.

This callback is not available in GRF version 8. Use CB_PROP with vehlen() instead. See example above.

CB_ARVS - Autoreplace vehicle selection

This callback is called whenever an old vehicle needs replacing. It returns the vehicle IDs to upgrade the current vehicle to. The vehicle variables are available as usual to decide which IDs would be appropriate to upgrade to.

The callback is repeatedly called, with the iteration number returned by function autoreplace(). The GRF file should return all possible IDs one by one, with the best ones listed first. TTDPatch will use the first vehicle from the list that is available to the player and has the required minimum reliability as set by the autoreplace switch, and abort the callback sequence at this point.

The callback sequence is also aborted when the returned ID is equal to the current vehicle ID. This is to prevent downgrading, and so you must take care to always return the ID of the vehicle for which the callback is called at the appropriate place in the sequence.

If the callback fails, i.e. if no suitable ID could be found by any of the callbacks in the generic chain, the vehicle is not upgraded, and is simply renewed instead. If an ID is available but the player lacks the cash to upgrade, TTDPatch waits for the cash to become available instead of either picking a worse engine or renewing the vehicle.

For trains, each engine head will be replaced by one vehicle of the new type. As such, it is advisable for only return "compatible" IDs for trains that require a particular construction or arrangement of engine heads.

The callback is always used when defined, it needs not to be activated.

CB_ATAC - wagon attach callback

This callback is called for the train engine when attaching a new vehicle to the current train, and determines whether it may in fact be attached. The callback is always used when requested, no entry in the train's callbacks() property function is needed to activate it.

You may use train performance functions during the callback. By default, they refer to the wagon that is to be attached, not the engine it is being attached to, so you can check the wagon's properties like veh-ID or cargo type to see whether it is appropriate for the given train engine.

You may use brackets to check for the train engine (plus wagons in front of the one being attached to) instead. This would be useful to find out e.g. the length of the train it is being attached to. See here for an in-depth explanation.

Likewise, there's a sublty when CB_ATAC is used in connection with veh_getinfo() / veh_getinfoabs() because there might be no wagon at the referenced position. Instead, in these cases, you'd need to use brackets and check for the proper position inside the consist.

Resulting callback values are as follows:

ResultMeaning
GRF v7GRF v8
0 .. 0xFC0 .. 0x3FFDisallow attaching and use the defgrftext()'s ID as second line of an error message
0xFD0x402Disallow attaching with the standard message (incompatible rail types)
0xFE0x401Allow attaching
0xFF0x400Allow attaching if the rail types match (default)

Note that returning 0xFE/0x401 would allow attaching vehicles with non-matching railtypes too.

Example (selective attaching of coaches):
define(ATT_OK,0xFE) // 0x401 for GRF v8!
define(ATT_VAN,0x60)
define(ATT_CAR,0x61)
define(ATT_REQLDCAR,0x62)
define(ATT_CARBADNUM,0x63)
define(ATT_CARBADTYPE,0x64)

[..]

defgrftext(ATT_VAN,ALL,
	" (freight car cannot be attached)",
	" (coach cannot be attached)",
	" (long-distance cars required)",
	" (wrong number of cars)",
	" (wrong type of car)",
	[...]
)

[..]

// allow _VT137 only
def(4) veh_id(
	attach(ATT_OK) if(_VT137)	// OK
	attach(ATT_CARBADTYPE) else	// "wrong type of car"
)

def(5) callback(
	ref(4) if(CB_ATAC) // attach
	ref(2) else	   // graphics  
)

CB_PROP - Change vehicle properties

This callback allows modification of certain properties of a vehicle for which there exists no specific callback (like callback CB_WLEN, CB_POWR, CB_LOAD). The associated function property() is needed to check for the specific property before being set by the callback.

The following properties are supported:

Property Function Min. version
TTDPatchOTTD
Cargo aging periodcargoaging()n/ar22713
Cargo capacitycapacity()2.6 rev 1966r9828
Load amountloadamount()use CB_LOAD???
Powerpower()2.5 beta 6r9671
Priceprice()n/ar9806
Running cost factorrunningcost()2.6 rev 1420r9671
Speedspeed()2.5 beta 6r9671
Tractive efforteffort()2.5 beta 6r9671
User datauserdata()2.5 beta 6r11431
Vehicle lengthvehlen()use CB_WLEN???
Weightweight()2.6 rev 1887r9780

Most properties will only change when the vehicle is bought, serviced (enters a depot), visits a station or on loading of a saved game. Other ones such as tractive effort are called every time a TE calculation is run.

If the callback fails, the corresponding property value will be used. The callback is always used when defined, it needs not to be activated.

Example (speed & capacity change):
def(3) property(
	cbr(11) if(capacity())        // change capacity := 11 t
	setspeed(45 km/h) if(speed()) // change speed := 45 km/h
	cbfail() else         
)

def(4) callback(
	ref(3) if(CB_PROP) // change properties
	ref(2) else        // graphics  
)

For setting the property callback, helper functions setpower() and setspeed() might be needed, to return the correct values. Changing tractive effort by CB_PROP requires the special helper function seteffort(<unit_term>,<unit_term>), its first parameter being the new tractive effort value, and the second being the weight of this engine (has to be the same as specified in its definevehicle() function). The latter is required because of the special way tractive effort is calculated in TTD. Please note that callback helper functions setpower(), setspeed() and seteffort() return a callback result, hence no extra cbr() required.

Example (tractive effort change):
def(2) veh_railtype(
	seteffort(240 kN, 15 t) if(FRNR) // set TE for rackrail track: 240 kN
	cbfail() else
)

def(3) property(
 	ref(2) if(effort())
 	cbfail() else
)

def(4) callback(
	ref(3) if(CB_PROP) // change properties
	ref(1) else        // graphics  
)

CB_RCOST - Refit cost callback

This callback can be used to override the refit cost of vehicles as specified in property function refitcost(). The callback will also be called in the purchase list, so you should either limit the used performance functions to what is available in the purchase list, or branch on the special purchase list cargo MENU.

Performance functions rcost_cargotype(), rcost_cargosubtype() and rcost_cargoclass() contain information about the target cargo type.

Return values:

As usual, function cbr() may be used for returning the callback value. In this case, use eval() to set bit14 as well (by OR-ing with 16384) if needed: cbr(eval(<value> | 16384)), or use callback helper function autorefit()

If the callback fails, the value specified by refitcost() is used.

This callback is only available since OpenTTD r23086.

CB_STOP - Start/stop check

This callback is called whenever a player (or the AI) tries to start or stop a vehicle. This is mainly useful for preventing vehicles from leaving the depot unless a given condition is met. To check whether a vehicle is being started or stopped, check the result of function veh_status().

The callback return value is a text-ID to be used as an error message, or a special value to indicate that the start/stop action should succeed:

ResultMeaning
GRF v7GRF v8
0 .. 0xFE0 .. 0x3FFDisplay error message and use defgrftext()'s ID as second line of an error message
0x100 .. 0x1FE
0x200 .. 0x2FE
0x300 .. 0x3FE
0xFF0x400Start/stop action shall succeed

The callback is always used when defined, it needs not to be activated.

CB_TEXT - additional text in purchase menu

This callback is called when TTDPatch displays the vehicle stats of any vehicle in the purchase menu. The return value is a grftext(), previously defined by defgrftxt(), to be shown below all other stats. The callback is always used when defined, it needs not to be defined by property function callbacks() in the train definition stage.

How many lines of text are available depends on the type of vehicle and its other properties, for instance the presence or absence of a "refittable to" line, or powered wagons etc. Lines are wrapped automatically, or may be broken explicitly using the newline character 0D.

If cargo MENU is handled explicitly for a vehicle, then it must be used in the function chain for the purchase menu, not for the vehicle graphics.

CB_32DAY - 32-day callback

This callback is called every 32 game days for each vehicle, although not for all vehicles on the same day. The callback is always used when defined, it needs not to be activated.

The return value is a bit mask of the following bits:

BitValueMeaning
01Trigger CB32
12Update colour map via callback CB_RCOL

If no bits are set, nothing happens.