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 |
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.
// 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 )
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):
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 )
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).
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.
Event | Meaning | |||
SND_START | Start sound, called when leaving a station or depot | |||
SND_TUNNEL | Tunnel sound, called when vehicle enters tunnel | |||
SND_BRKDOWN | Breakdown sound, called when vehicle breaks down | |||
SND_RUNNING | Running sound, called once per engine tick, but no more than once per vehicle motion | |||
SND_EFFECT | Train visual effect generation (steam plume, diesel smoke, electric spark) | |||
SND_RUN16 | Running sound, called every 16 engine ticks if in motion | |||
SND_STOP16 | Stopped sound, called every 16 engine ticks if stopped (e.g. at signal/in station) | |||
SND_LOAD | Load/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.
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 )
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:
Result | Meaning | |
GRF v7 | GRF v8 | |
Disallow attaching and use the defgrftext()'s ID as second line of an error message | ||
0xFD | 0x402 | Disallow attaching with the standard message (incompatible rail types) |
0xFE | 0x401 | Allow attaching |
0xFF | 0x400 | Allow attaching if the rail types match (default) |
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 | |
TTDPatch | OTTD | ||
Cargo aging period | cargoaging() | n/a | r22713 |
Cargo capacity | capacity() | 2.6 rev 1966 | r9828 |
Load amount | loadamount() | use CB_LOAD | ??? |
Power | power() | 2.5 beta 6 | r9671 |
Price | price() | n/a | r9806 |
Running cost factor | runningcost() | 2.6 rev 1420 | r9671 |
Speed | speed() | 2.5 beta 6 | r9671 |
Tractive effort | effort() | 2.5 beta 6 | r9671 |
User data | userdata() | 2.5 beta 6 | r11431 |
Vehicle length | vehlen() | use CB_WLEN | ??? |
Weight | weight() | 2.6 rev 1887 | r9780 |
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.
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.
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.
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:
Result | Meaning | |
GRF v7 | GRF v8 | |
Display error message and use defgrftext()'s ID as second line of an error message | ||
0xFF | 0x400 | Start/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.
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:
Bit | Value | Meaning | ||
0 | 1 | Trigger CB32 | ||
1 | 2 | Update colour map via callback CB_RCOL |
If no bits are set, nothing happens.