The m4nfo User manual and Technical Report

Road vehicle callbacks

Using callbacks for Road vehicles

Introduction

This is an in-depth description of road vehicle 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 road vehicle) previously defined. An example being the modification of the visual effect of a road 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 road vehicles. 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
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 Wagon length
Callbacks always activated
CB_ARVS Autoreplace vehicle selection
CB_ATAC Can wagon be attached?
CB_PROP Change vehicle properties
CB_STOP Start/stop check
CB_RCOST Refit cost callback
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() until the callback returns ENDLIST.

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).

Note that since the road vehicle 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 being 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.

In OpenTTD the cargo class of the first road vehicles is used to determine whether the vehicle can stop at bus or truck stops regardless of the capacity of the first vehicle. If the cargo class for passengers is set it can go to bus stops, when it is not it can go to truck stops.

For an example (locomotive with tender) see here.

CB_LOAD - load amount callback

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

Example (adjust SGLM coaches):
def(11) callback(
	cbr(3) if(CB_WLEN) // wagon length := 3 (5/8)
	cbr(4) if(CB_LOAD) // load amount := 4
	ref(2) if(CB_PROP) // change properties
	ref(0) else        // graphics  
)

CB_POWR - powered wagon callback

This callback is used to determine the visual effect to use for a road vehicle (since OpenTTD r21238).

This callback is only called when loading a game, when the vehicle reverses, or when rearranging the vehicle in a depot.

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 road vehicle 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 rvs 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. 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 (tram recolouring):
def(0x22) getcolour(2) // Berlin, Kassel, Nürnberg (very old)
def(0x26) getcolour(6) // Bochum, Brandenburg, Görlitz, Mühlheim (very old)
def(0x2C) getcolour(0x0C) // Chemnitz, Schwerin (very old)

...

// colours
def(5) refitted(
	ref(0x22) if(1)
	ref(0x26) if(2)
	ref(0x2C) if(3)
	...
	cbfail() else
)

def(7) callback(
	ref(6) if(CB_TSFX)
	ref(5) if(CB_RCOL)
	ref(ALLSOUNDS) if(CB_SOUND)
	ref(3) 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_EFFECTrv 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 rv 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):
// in include file:
define(TSX_CITY,0x00)
define(TSX_REGIONAL,0x01)
define(TSX_COACH,0x02)
define(TSX_2CC,0x03)
define(TSX_BERLIN,0x04)
define(TSX_BOCHUM,0x05)
...

defgrftext(0,ALL,
	T_BLACK "Usage: " T_LORANGE "city lines",
	T_BLACK "Usage: " T_LORANGE "regional lines",
	T_BLACK "Usage: " T_LORANGE "coach",
	" (2CC)", 
	" (Berlin)",
	" (Bochum)",
	...
)

...

// names
def(8) refitted(
	grftext(TSX_2CC) if(0)
	grftext(TSX_BERLIN) if(1)
	grftext(TSX_BOCHUM) if(2)
	...
	ENDLIST else
)

def(9) callback(
	ref(8) if(CB_TSFX)
	ref(4) else // graphics
)

makevehicle(_TW21,
	link(ref(0),MENU)
	default(ref(9))
)

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 "255". 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.

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.

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

CB_ATAC - wagon attach callback

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

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

You may use brackets to check for the leading rv instead, this would be useful to find out the length of the articulated rv it is being attached to. See here for an in-depth explanation.

Example (selective attaching of coaches):
def(4) veh_id(
        attach(ATT_NOHEAVYFREIGHT) if(_SLOW15T) // "freight wagon cannot be attached"
        attach(ATT_NOTHISPAX) if(_MP)	    // can only carry passengers in short coaches
        attach(ATT_OK) else
)

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
Speedspeed()2.5 beta 6r9671
Powerpower()2.5 beta 6r9671
Running cost factorrunningcost()2.6 rev 1420r9671
Cargo capacitycapacity()2.6 rev 1966r9828
Weightweight()2.6 rev 1887r9780
Priceprice()n/ar9806
Tractive efforteffort()2.5 beta 6r9671
User datauserdata()2.5 beta 6r11431
Cargo aging periodcargoaging()n/ar22713

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
	cbr(45) if(_SPEED)    // change speed := 45 km/h
	ref(0) else         
)

def(4) callback(
	ref(3) if(CB_PROP) // change properties
	ref(2) 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 use as second line of an error message, or "255" to indicate that the start/stop action should 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 rv 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_WLEN - wagon length callback

This callback is used instead of property shortening() whenever the road vehicle 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.

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.