The m4nfo User manual and Technical Report

Functions for train vehicles

Using functions for train vehicles


In m4nfo, trains are using four types of functions:

Functions for sprite layout definition


In m4nfo, all vehicles (trains, road vehicles, ships and planes) are using the same format for defining sprite layouts:

spriteset(move([<moving states>]), load([<loading states>]))

Where the move() and load() functions take spriteblock() references as their arguments. Note that either load() or move() might be used without any parameter in special cases, see below.

Example 1 (locomotive):

This locomotive uses the same sprite (number 0, first sprite of spriteblock) both for its only moving state as for its only loading state.

Example 2 (freight wagon):

This wagon uses 4 different sprites for its moving states, and it uses the same sprites for its loading states.

Example 3 (freight wagon):

This wagon uses 3 different sprites for its moving states, and it uses different sprites for its loading states.

Example 4 (stacked sprites):
def(0) spriteset(move(0),load(0))
def(1) spriteset(move(),load(1,2,3,4))

def(2) spritestack(RC_NONE, ref(0), MORE)
def(3) spritestack(RC_DEFAULT, ref(1))

def(4) spritelayer(
	ref(2) if(0) // sprites "body"
	ref(3) else  // sprites "freight"

This wagon uses 'stacked' sprites, one sprite (0) for the wagon's body, and 4 sprites (1 .. 4, to be re-coloured by use of CB_RCOL) for freight. When moving, sprite 0 is displayed for the body, and no freight sprite is displayed at all. When loading, sprite 0 is displayed for the body as well, and for freight, sprites 1 .. 4 are displayed according to the loading cycle.

Functions for train performance

These functions are used to evaluate game-intrinsic variables, and make them accessible to the vehicle's activation function. A typical application would use a multitude of these functions linked together to form a "chain", connecting the graphics' sprites of a vehicle with its activation function. See here for an example.

Function Description
ageindays(<block>)Get age of vehicle counted in days
ageinyears(<block>)Get age of vehicle counted in years
animation([<shiftmask>,] <block>)Access the animation counter
articulated(<block>)Get number of articulated vehicle part
autoreplace(<block>)Get iteration number from CB_ARVS
callback(<block>)Check incidence (and type) of callback
cargo(<block>)Check cargo type
clrubit([0 .. 7])Clear bit in user data area
cnst_cargo(<block>)Get most common cargo type in consist
cnst_cargoclasses([<shiftmask>,] <block>)Get information about all cargo classes transported by the consist
cnst_refit(<block>)Get most common refit cycle of most common cargo type in consist
current_capacity(<block>)Get current capacity of vehicle
current_load(<block>)Get current load of vehicle
current_maxspeed(<block>)Current max speed including speed limits (e.g. by track type or timetable settings), only valid for front vehicle (since OTTD r24246)
current_speed(<block>)Get current speed of vehicle
curvature_front(<block>)Get curvature info of vehicle
daycounter(<block>)Day counter, increased daily
daysintravel(<block>)Number of days travelling since last load/unload
dayspastservice(<block>)Elapsed time in days since the last service
direction(<block>)Get driving direction of vehicle
displaymode(<block>)Set display mode for vehicle sprite
elseThis is really a void statement
flipped(<block>)Check if engine or head is flipped (forward/backward)
getubits([<shiftmask>,]<block>)Check user data as bit(s)
getubyte(<block>)Check user data as value
idcount(<Veh-ID> | REGISTER, [<shiftmask>,] <block>)Check number of vehicles with given ID
incline(<block>)Incline of train: 0 = downhill, 1 = flat, 2 = uphill
indepot(<block>)Check if vehicle is in depot
intransit(<block>)Number of days in transit from source
islast(<block>)Check if last vehicle in consist
lastmaintenance(<block>)Get time since last maintenance in days since 1920 (TTDPatch)
lastservicedate(<block>)Date of last service in days since year 0
lastserviceyear(<block>)Date of last service in years since year 0
loadtime(<block>)Get time to load/unload remaining
maxspeed(<block>)Get maximum speed of vehicle
property(<block>)Get property for CB_PROP
randombits([<shiftmask>,] <block>)Get random bits of vehicle
randomtriggers([<shiftmask>,] <block>)Get random triggers of vehicle
rcost_cargotype/rcost_cargosubtype(<block>) rcost_cargoclass([<shiftmask>,] <block>)Get target cargo type information from CB_RCOST
refitted(<block>)Check number of refits to same cargo type
reliabilitystate(<block>)Get current reliability state of vehicle (65535=100%)
reversed(<block>)Check if vehicle travels reversed in push-pull service
servint(<block>)Get service interval of vehicle
setubit([0 .. 7])Set bit in user data area
setubyte(<Byte>)Write byte to user data area
shorter(<block>)How much shorter the vehicle is
soundevent(<block>)Check sound event
speedlimit(<block>)Current top speed of engine (w/o bridge, curve etc.), 0 if unlimited
spritelayer(<block>)Return layer number of stacked sprite
spritestack(<recolour-sprite>, <reference> [,<par>])Compose stacked sprite
veh_cargoclass(<block>)Get cargo class transported by the consist
veh_cargotype(<block>)Get cargo type transported (slot no in CTT!)
veh_cargoweight(<block>)Get cargo unit weight (in 1/16 tons)
veh_getinfo([-]<Byte>, <function>, [<shiftmask>,] <block>),
veh_getinfoabs(FRONT | BACK, <Byte>, <function>, [<shiftmask>,] <block>)
Accessing other vehicle's performance functions
veh_id(<block>)Check vehicle ID for CB_ATAC
veh_ispowered(<block>)Check if vehicle is powered (engine or powered wagon)
veh_isunloading(<block>)Vehicle is still unloading, has not yet start loading new cargo (OTTD r26430)
veh_marketinfo(<block>)Info about vehicle market availability
veh_maypowered(<block>)Check if vehicle would be powered (engine or powered wagon) if there were suitable track
veh_num(<block>)Get total number of vehicles in consist
veh_posabs/veh_posart/veh_posrel(FRONT | BACK | MOD<Byte>, <block>)Get position of vehicle
veh_railtype(<block>)Get rail type the vehicle is currently driving on
veh_status(<block>)Get special status of vehicle
veh_straight(<signed-Byte> | REGISTER, <block>)Check direction and z-position of vehicle
yearbuilt(<block>)Year built (counted from year 0)
randomrel/randomabs(<trigger>, <randombit>, <List::ref()>)Get random reference
randomcount(<trigger>, <randombit>, <type>, <count>, <List::ref()>)Get special random reference



This function returns the age of a vehicle in days since its purchase date. Please note that the result will be returned as a WORD, and requesting the result by function if() has to ensure this.

Example (age of coaches):
def(RF_SILVER) ageindays(
	ref(0x14) if(0 .. 1825) // new: < 5 years
	ref(0x15) if(1826 .. 3650)    // medium: < 10 years
	ref(0x16) else		      // old


This function returns the age of the vehicle in years. As above, its result will be returned as a WORD.

animation([<shiftmask>,] <block>)

This function returns the amount of motion that a vehicle has done. It is only valid for the first vehicle in a consist. Its value is in units of 1/4096 of a tile. A vehicle actually moves visibly every time the motion counter increases by 256, and since a tile consists of 16 such subunits, 16*256=4096 motion units mean motion across one tile.

This function is predefined to achieve an animation with one frame per vehicle motion and 4 frames in total for motion across an entire tile. This can be overriden by applying two extra parameters by using auxiliary function shiftmask, using the shift parameter for number of frames per vehicle motion, and the mask parameter for number of frames in total for motion across an entire tile.

If the vehicle is going very fast (>160 mph for trains), it may move by several 1/16ths of a tile at once, and thus some frames may be skipped, but the animation will still remain in sync with the motion. Note however, that vehicle graphics are only updated every time the vehicle actually moves.

Example (steam locomotive moving rods):
// 1 frame per vehicle motion, 4 frames total (default)
def(4) animation(
         ref(0) if(3) // use frame3 graphics
         ref(1) if(2) // use frame2 graphics
         ref(2) if(1) // use frame1 graphics
         ref(3) else  // use frame0 graphics

// 1 frame every two vehicle motions, 8 frames total
def(4) animation(shiftmask(9,7),
         ref(0) if(5) // use frame7 graphics
         ref(0) if(5) // use frame6 graphics
         ref(0) if(5) // use frame5 graphics
         ref(1) if(4) // use frame4 graphics
         ref(2) if(3) // use frame3 graphics
         ref(3) if(2) // use frame2 graphics
         ref(3) if(1) // use frame1 graphics
         ref(3) else  // use frame0 graphics


When building articulated vehicles by use of CB_ARTI, this function will return the number of the articulated part, i.e. "1" for the first articulated part, "2" for the second and so on.

Example (steam locomotive and tender):
def(23) articulated(
	addveh(_TENDER1) if(1) // tender
	addveh(NONE) else

def(0) callback(
        ref(23) if(CB_ARTI) // articulated
        ref(12) else	    // graphics


This function checks for a callback incidence, and if so, returns the type of the incurred callback. See example above.


Following three functions work on the whole consist, not on single vehicles.

Function cnst_cargo() returns the most common cargo type transported by the consist. Note that even if the grf file has installed a cargo translation table, the returned value is the actual cargo. The reason for this is that it cannot be translated in general, because different vehicles can be from different grf files and have different translation tables. Use veh_cargotype() if you want the translated cargo type.

Example (graphics change by refit only when "most common cargo" == PASS):
def(14) cnst_cargo(
	ref(13) if(ttdcargo(PASS)) // use original PASS here (untranslated!)
	ref(11) else		   // always black

cnst_cargoclasses([<shiftmask>,] <block>)

This function returns a bit mask of all cargo classes transported by the consist. Information returned is only for cargo classes from this vehicle on. To get information about the whole consist, you might 'bracket' the associated evaluation block for this function, using the function engine(). This also holds for the other two functions, cnst_cargo() and cnst_refit().

In addition, function cnst_cargoclasses() allows to adjust its result to your needs, using auxiliary function shiftmask(). Please note that its shift-parameter must be 0 in this case, and only the mask-parameter being used.

Example (graphics change by refit only when "most common cargoclass" is REEF):
def(14) cnst_cargoclasses(
	ref(13) if(REEF) // refrigerated cargo
	ref(11) else	 // always brown


This function returns the most common refit cycle of the cargo type returned by cnst_cargo().

Example (check common refit cycle in consist):
def(13) cnst_refit(
        ref(10) if(2) // green: ISERE
	ref(13) if(1) // dark green: Tramways de la Corrèze 
	ref(11) else  // ANJOU     

curvature_front / curvature_back / curvature_triplet(<block>)

These functions return the amount of curvature between adjacent wagon pairs. It is especially useful for train vehicles that normally tilt in curves. The curvature is the difference in direction between the surrounding vehicles and is given by the three functions:

Possible return values are:

c_180LEFT180° curve left (triplet only)
c_135LEFT135° curve left (triplet only)
c_90LEFT90° curve left
c_45LEFT45° curve left
c_STRAIGHTno curve
c_45RIGHT45° curve right
c_90RIGHT90° curve right
c_135RIGHT135° curve right (triplet only)
c_180RIGHT180° curve right (triplet only)


This function returns the actual driving direction of the vehicle, i.e. 0 = up, 1 = up/right, 2 = right, 3 = right/down, 4 = down, and so on, in a clockwise manner.

Example (does front car travel downwards?):
def(3) direction(
         ref(1) if(3) // \
         ref(1) if(4) // |
         ref(1) if(5) // /
         ref(0) else  // no


This function returns the actual display mode associated with the current vehicle sprite. In this way, it is possible to use different sprites for the vehicle in question, depending on the display mode (on map, in depot, in purchase window, ...).

DP_VIEWPORTVehicle is drawn in a viewport, i.e. on the map
DP_DEPOTGUIVehicle is drawn in the depot GUI
DP_VEHICLEGUIVehicle is drawn in the vehicle details GUI (This includes the refit GUI)
DP_VEHICLELISTVehicle is drawn in the vehicle list
DP_PURCHASELISTVehicle is drawn in the purchase list (This includes the autoreplace GUI)
DP_PREVIEWGUIVehicle is drawn in the exclusive preview GUI or in the advertisement news

DP_PURCHASELIST and DP_PREVIEWGUI are to be used in the purchase list chain (MENU), while the others are to be used in the actual vehicle chain.

This feature is only available in OTTD since r23080.


This function checks if a vehicle is 'flipped', i.e. in a 2-part Multiple Unit (MU), the front car is usually travelling in forward direction, and the back one is travelling backwards.

Example (2-part electric locomotive):
def(2) flipped( 
	ref(0) if(FORWARD) 
	ref(1) else


This function checks the user data bits set for vehicles in the consist. It returns the result of OR-ing the bits from all vehicles in the train from the current vehicle on, bracketing the if-block returns the result of OR-ing the bits from all vehicles in the consist, including its engine(s).

The <shiftmask> parameters, set by auxiliary function shiftmask(), defines the bit pattern to check for. In this way, it's easily possible to check for different bits being set, e.g. only for the relevant bits in a certain context. In the example below, UB_N (by an include file) is set to 0x02, i.e. bit1, is set in the parameter and is checked for:

Example (check for local coaches included in consist and adapt train speed):
define(UB_ALL,0xFF) // all bits set
define(UB_N,0x02)   // is local coach


def(9) getubits(shiftmask(0,UB_ALL),
         cbr(120) if(UB_N) // local coach included, set 120 km/h
         ref(0) else       // no local coach, keep 140 km/h

By using functions setubit()/clrubit(), user data bits can be set dynamically by using callback CB_PROP.


This function checks certain user data bits interpreted as a value for all vehicles in the consist. Like getubits(), it returns the result of OR-ing the bits from all vehicles in the train from the current vehicle on, bracketing the if-block returns the result of OR-ing the bits from all vehicles in the consist, including its engine(s).

To use this function, the required part of the user data area has to be allocated for value interpretation before by a call to auxiliary function allocuserdata().

Example (check for engine livery refit):
define(UB_RB,0x01)  // Réseau Breton
define(UB_CFC,0x02) // Chemin de Corrèze
define(UB_SNCF,0x03) // SNCF 


allocuserdata(3) // allocate 3 bits for values


def(9) getubyte(
         cbr(60) if(UB_RB) // Réseau Breton, set 60 km/h
         cbr(70) if(UB_RB) // Chemin de Corrèze, set 70 km/h
         cbfail(0) else    // default

By using function setubyte(), a value can be written into the allocated part of the user data area dynamically, by using callback CB_PROP.

idcount(<Veh-ID> | REGISTER, [<shiftmask>,] <block>)

This function checks the number of vehicles with a given ID in a consist. The additional parameter is the veh-ID to check for. In case of REGISTER as the first parameter, coordinates are not explicitly given but taken as the result from a preceding calculation, see calculate().

Only the current vehicle and onwards will be checked, unless the function block is bracketed by the engine() function, resulting into all vehicles in the consist being counted.

Example (allow max 4 motor cars in train set):
def(1) idcount(_VT95,
	        attach(ATT_OK) if(1 .. 3)      // OK
        	attach(ATT_MOTCARBADNUM) else  // "wrong number of motor cars"


This function checks if the vehicle is currently in a depot.

def(4) indepot(
	ref(35) if(INDEPOT)
	ref(33) else


This function checks if the current vehicle is the last one in the consist.

Example (backlights on coaches):
def(8) islast( 
	ref(0) if(NOTLAST) // normal
	ref(4) else	   // use backlights


This function returns the property type needed for applying the 'property callback' CB_PROP. By using this callback, it is possible to change most of the vehicle's properties by use of the function cbr() (CallBack Result).

The following table lists the properties which may be modified for train vehicles:

cargoaging()custom cargo ageing (OTTD only)
effort()tractive effort
runningcost()running cost
userdata()user data

Instead of giving the property function with an empty parameter list you might also drop the parentheses at all.

Example (increase speed to 80 km/h after 1935):
def(5) yearbuilt(
         cbfail() if(< 1935) // built before 1935, callback fail
         cbr(80) else        // built after 1935, set 80 km/h

def(6) property(
         ref(5) if(speed()) // go test if built after 1935
         cbfail() else      // callback fail  

def(7) callback(
         ref(6) if(CB_PROP) // change speed
         ref(4) else        // graphics

randombits/randomtriggers([<shiftmask>,] <block>)

These functions return random bits and random triggers of the current vehicle or the train's engine. See here for train triggers.

You may also make use of auxiliary function shiftmask() to adjust the result to your needs. This must be done according to the number of random sets and the (starting) random bit being used in randomrel()/randomabs(). I.e., you should shift right by the number of the random bits used, and mask with the number of random sets - 1. E.g., when there are two random sets, using random bit 0, you should set shiftmask(0,1), in case there are four random sets and the random bit had been set to 2, you should use shiftmask(2,3), to only acces the valid random bits.

rcost_cargotype/rcost_cargosubtype(<block>) rcost_cargoclass([<shiftmask>,] <block>)

These three functions provide information about the target cargo type from callback CB_RCOST. These are only available since OpenTTD r23086.

rcost_cargotypeThe cargo type being refitted to; translated if a translation table has been installed
rcost_cargosubtypeThe cargo subtype being refitted to
rcost_cargoclassThe cargo class value of the cargo being refitted to

Example (autorefittng for bulk cargo):
// refit from bulk
def(1) rcost_cargoclass(shiftmask(0,BULK),
	autorefit(0) if(BULK) // free refit[*]
	cbfail() else // other classes need depot visit

// check cargo class
def(2) veh_cargoclass(
	ref(1) if(BULK) // refit from bulk
	cbfail() else   // refit from non-bulk, needs depot visit

def(3) callback(
	ref(2) if (CB_RCOST) // refit cost/autorefitting
	... else

The example uses callback helper function autorefit()

[*] free refit only for class "BULK", but not for other classes like "BULK, COVERED", "BULK, REEF", "BULK,PGOODS", etc.


This function checks the number of refits to the same cargo type of the vehicle.

Example (refitted capacity):
def(3) refitted(
         cbr(48) if(0) // 48 pass
         cbr(32) else  // 32 pass  

def(F_OLD) callback(
         ref(3) if(CB_RCAP) // refit capacity
         ref(2) else	    // graphics

See also CB_TSFX for applying this function in connection with cargo subtype texts.


This function checks if the vehicle travels reversed in push-pull service.

def(0) reversed(
         ref(3) if(1) // normal offsets when driving backwards
         ref(4) else  // special offsets when driving forwards

setubit/clrubit([0 .. 7])

These two functions are used to dynamically set, resp clear, bits in the user data area. These bits might be checked by getubits() at a later point in time. Both functions have to be used inside a callback CB_PROP chain.

Example (set user bit6 by callback CB_PROP):
def(1) setubit(6)

def(2) property(
	ref(1) if(userdata()) // set recolour flag 
	ref(33) else     // 

def(22) callback(
	ref(2) if(CB_PROP)
	ref(33) else

Be aware that you cannot directly use setubit() or clrubit() in the property() block. Both are "complex" functions (i.e., not simply returning a callback result) and hence need an own def().


This function is used to dynamically write a value into a certain part of the user data area, e.g. to specify a certain livery scheme. This value might be checked by getubyte() at a later point in time. This function has to be used inside a callback CB_PROP chain, and the used part of the user data area has to be allocated before by a call to auxiliary function allocuserdata().

Example (set refitted livery for engine):
// graphics
def(0) refitted(
         ref(11) if(0) // Réseau Breton
         ref(12) if(1) // Chemin de Corrèze
         ref(13) else  // SNCF

def(1) setubyte(U_RB)   // Réseau Breton
def(2) setubyte(U_CFC)  // Chemin de Corrèze
def(3) setubyte(U_SNCF) // SNCF

// set user data
def(4) refitted(
         ref(1) if(0)
         ref(2) if(1)
         ref(3) else

def(5) property(
	ref(4) if(userdata()) // change livery
	cbfail() else         

def(6) callback(
	ref(5) if(CB_PROP) // change properties
	ref(0) else        // graphics  


This function checks the sound event. Following events are handled for trains:

SND_STARTStart sound, called when leaving a station
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

Example (different sounds depending on sound event):
def(2) soundevent(
	refsnd(SND_BR38S) if(SND_START) // start
	refsnd(SND_BR38) if(SND_TUNNEL) // tunnel
	cbfail() else


def(5) callback(
	ref(2) if(CB_SOUND) // sound
	ref(3) else         // graphics


This function returns the layer number of a stacked sprite. Currently this is limited to at most 4 sprites per articulated part, i.e. layer numbers 0 .. 3.

spritestack(<recolour-sprite>, <reference> [,<par>])

This function links a real sprite with a recolour sprite to be used (or RC_DEFAULT to use the default vehicle recolouring from CB_RCOL, or RC_NONE for no recolouring at all). In addition, it composes a stacked sprite from the real sprite given by the second parameter by resolving further sprites with incremented layer numbers as long as a third parameter is present. Please note that this is limited to at most 4 sprites per articulated part of a vehicle. Composition of a stacked sprite is completed when the third parameter is omitted.

Depending on the type of <recolour-sprite> (either static/TTD or dynamically allocated) helper functions ttdsprite(), randomttdsprite(), allocsprite(), randomallocsprite(), or cargomapsprite() have to be used.

For a more sophisticated use of sprite sets in case of multiple moving or loading stages, please see example 4 over here.

This function is only available for OpenTTD >r27668 and needs STACKEDSPRITES set in the vehicle's property function flags().

Example (stacked sprites):
define(GREEN,0x30D) // TTD green
define(PETROL,0x1B) // entry in custom recolour maps

definevehicle(_TEST, {"stacked sprite test"},

// real sprites

def(0) spriteset(move(0),load(0)) // "chassis"
def(1) spriteset(move(1),load(1)) // "body"
def(2) spriteset(move(2),load(2)) // "cab"

def(3) spritestack(RC_NONE, ref(0), MORE)
def(4) spritestack(ttdsprite(GREEN), ref(1), MORE)
def(5) spritestack(allocsprite(PETROL), ref(2))

def(6) spritelayer(
	ref(3) if(0) // sprite 0 "chassis"
	ref(4) if(1) // sprite 1 "body", recolour TTD sprite
	ref(5) else  // sprite 2 "cab", recolour CB result




This function checks the cargo class value of the cargo transported by this vehicle. Unlike the cnst_*() functions, this and the next 2 functions return the info of the current vehicle only, not of the consist.

Example (increase speed if refrigerated cargo):
def(9) veh_cargoclass(
         cbr(120) if(REEF) // refrigerated cargo, set 120 km/h
         ref(0) else       // else keep 80 km/h


This function returns the cargo type transported by the vehicle. The cargo type is translated if a cargo translation table has been installed. Please note that if the grf has installed a cargo translation table, the return value is the slot number in that table, irrespective of which actual slot the cargo is using in the game. If a table has been installed, but the current cargo is not listed there, the returned value will be set to 255 (hex FF).

Example (increase speed if cargo is 'fish'):
def(9) veh_cargotype(
         cbr(120) if(FISH) // refrigerated cargo (fish), set 120 km/h
         ref(0) else       // else keep 80 km/h


This function returns the cargo unit weight (in 1/16 tons), i.e., the value defined in cargo property function weight().

Example (reduce wagon capacity if cargo unit weight > 1 tonne ):
def(9) veh_cargoweight(
         cbr(20) if(> 16) // reduce capacity if cargo unit weight > 1 tonne
         ref(0) else      // else keep standard capacity

veh_getinfo([-]<Byte>, <function>, [<shiftmask>,] <block>), veh_getinfoabs(FRONT | BACK, <Byte>, <function>, [<shiftmask>,] <block>)

These are special functions to access the return value of another function addressing a different vehicle in the consist. Both functions are not supported during a callback that is used to modify vehicle properties, to avoid circular dependencies. This currently limits these functions to callbacks CB_ATAC, CB_RCOL, CB_STOP and CB_32DAY when inside a callback scope.

Function veh_getinfo() uses a relative offset to address a certain vehicle in the consist, whereas function veh_getinfoabs() addresses other vehicles by using absolute offsets, either counting from the front of the train, or from the back.

For veh_getinfo(), the first parameter specifies the offset in the chain from the current vehicle. Positive values are interpreted as towards the end, negative values as towards the front. If the offset points outside the consist, the result value will be 0. Special parameter LAST will access the last vehicle of the consist.

For veh_getinfoabs(), the first parameter specifies the direction of counting. FRONT means to start counting from the front of the train (i.e., the first vehicle has index 0), and BACK means to count backwards from the end of the train (i.e., the last vehicle has index 0). The second parameter gives the absolute offset, depending on the direction of counting, which in both cases must be a value ≥ 0.

Following parameter for both functions specifies the m4nfo-function to be called for the other vehicle. If that function needs a parameter it has to be supplied as well to avoid error message ERR_NUMPAR. You may also make use of auxiliary function shiftmask() to adjust the result to your needs.

Due to implementation restrictions, only a limited number of functions are allowed as a parameter for veh_getinfo():

ageindays(), cnst_cargo(), cnst_cargoclasses(), cnst_refit(), curvature_front(), curvature_back(), curvature_triplet(), flipped(), getubits(), getubyte(), idcount(<Veh-ID>), randombits(), randomtriggers(), refitted(), reversed(), veh_cargoclass(), veh_cargotype(), veh_cargoweight(), veh_id(), veh_marketinfo(), veh_num(), veh_posabs(<direction>), veh_posrel(<direction>), veh_ispowered(), veh_maypowered(), veh_railtype(), yearbuilt().

This function is available since OpenTTD r22997.

// get user bits from 4th vehicle in consist
def(1) veh_getinfo(4,getubits(),
         ref(2) if(0)
         ref(3) else

// check for number of vehicles with ID = 13 
def(1) veh_getinfo(-1,idcount(13,),
         ref(3) if(0) // no veh with ID = 13
         ref(4) else

// check whether preceding vehicle has ID = 2
def(1) veh_getinfo(-1,veh_id(),
         ref(3) if(2) // this veh has ID = 2
         ref(4) else

// check livery of last vehicle in consist
def(1) veh_getinfoabs(BACK,0,getubyte(),
	 ref(2) if(1) // livery of last vehicle is "1"
	 ref(3) if(2) // livery of last vehicle is "2"
	 ref(4) else  // must be livery "4"

// get random livery of front vehicle
def(1) veh_getinfoabs(FRONT,0,randombits(),shiftmask(0,0x01),
	 ref(2) if(0)
	 ref(3) else


This function checks the vehicle ID of the current vehicle. This might be useful in deciding whether a certain vehicle might be attached (see callback CB_ATAC)

Example (check if certain coach may be attached to train):
def(4) veh_id(
         ref(3) if(_ICETD)	     // OK
         attach(ATT_CARBADTYPE) else // "wrong type of coach"

def(5) callback(
         ref(4) if(CB_ATAC) // check if coach may get attached
         ref(9) else	    // graphics


This function returns information about market availability of the current vehicle or the train engine type. Values returned are:

ONMARKETVehicle type is available on the market
TESTINGPHASEVehicle type is in the testing phase
TESTINGOFFERExclusive testing offer for a human player active

Example (check if exclusive testing offer for train engine is active):
def(8) veh_marketinfo(
         	ref(1) if(TESTINGOFFER) // show special livery
         	ref(2) else             // normal


This function returns the total number of vehicles minus one (i.e., excluding the first locomotive) in the consist.

Example (only allow for 2 'SVT137' cars, not more):
def(8) veh_num(
         	attach(ATT_OK) if(< 2)        // max +2 wagons
         	attach(ATT_MOTCARBADNUM) else // "wrong number of motor cars"

def(9) veh_id(
         ref(8) if(_SVT137)	     // OK
         attach(ATT_CARBADTYPE) else // "wrong car type"

def(10) callback(
         ref(9) if(CB_ATAC) // may wagon be attached?
         ref(6) else        // graphics

Note that we used a 'bracket' in def(8). The engine() function keeps care of a specialty when using callback CB_ATAC. Because this callback is called for the train engine, the usual vars refer to the wagon that is to be attached, not to the engine it is being attached to. That's why one has to 'force' the function to refer to the engine the wagon is being attached to, if one wants to find out the length of the train it is being attached to.

veh_posabs/veh_posart/veh_posrel(FRONT | BACK | MOD<Byte>, <block>)

These functions check the position of the current vehicle within the consist, or, for a vehicle being part of an articulated vehicle, within this one.

The first parameter decides on the direction of counting, it can either be set to FRONT or BACK, depending on the direction of counting. In addition, the same parameter might get set to MOD<Byte> too, to carry out appropriate 'modulo' operations.

For veh_posabs(), these numbers refer to the whole consist, but for veh_posrel(), they only refer to the chain of consecutive vehicles with the same ID as the current vehicle (including itself, but possibly excluding the engine).

For veh_posart(), the position of the current vehicle being part of an articulated vehicle is returned. This feature is only available in OTTD since r26157. Use case for this function are articulated vehicles which are not assembled from the same vehicle ID, since veh_posrel() only checks for the same veh-ID.

Note the use of bracketing in the illustration on the left, where 'self' means no bracketing at all, and 'engine' means to use the function engine() for bracketing.

Example 1 (show engine or tender for articulated steam locomotives):
def(3) veh_posrel(MOD2, 
         ref(4) if(0)      // engine
         ref(TENDER2) else // tender
Example 2 (set different lengths for the parts of an articulated locomotive):
def(3) veh_posrel(FRONT,
         cbr(3) if(1) // set length to 37,5%
         cbr(5) else  // set length to 62,5%

def(5) callback(
         ref(3) if(CB_WLEN) // wagon length
         ref(2) if(CB_ARTI) // articulated
         ref(1) else        // graphics
Example 3 (handle all cars of a 4-part MU):
def(4) veh_posrel(MOD4, 
         ref(1) if(3) // back
         ref(2) if(2) // *front
         ref(3) if(1) // *back
         ref(0) else  // front

Note however, that accessing the "related" object (i.e. the locomotive) doesn't make much sense for these functions, except for veh_posabs() when in a CB_ATAC chain.


This function returns the (translated) rail type the vehicle is currently driving on. If the rail type has no entry in the rail type translation table of the grf, the return value will be 255 (hex FF). If no translation table is present, the raw value will be returned.

This game feature is only available in OpenTTD since r20165.

Example (check the current railtype):
def(3) veh_railtype(
         ref(2) if(DBNE) // increase speed if on electrified branch line
         ref(0) else


This function returns the 'special' status of a vehicle. Values returned are:

INVISIBLEVehicle is invisible, e.g. in a tunnel
STOPPEDVehicle is stopped
CRASHEDVehicle is crashed

Note that the result is a bitmask, i.e. you might check for more than one bit set by simply adding above values in the if() function.

veh_straight(<signed-Byte> | REGISTER, <block>)

This function checks both direction and z-position differences between the selected vehicle and the current one. Parameter is a signed number, specifying the offset in the chain from the current one. Positive values are interpreted as counting towards the end, negative values as towards the front. If the offset is outside the vehicle chain, the result value will be zero.

Useful parameter values depend on the number of vehicles to check. E.g., for a 3-part articulated vehicle, values would be 1 and 2 for the front part vehicle, -1 and 1 for the middle one, and -1 and -2 for the back one (see example). Result for a "straight" multi-part vehicle will be zero.

Example (function to check "straightness" of multi-part vehicle):
// return values: 0 - VOID, 1 - front, 2 - middle, 3 - back, 4 - normal

// front
def(1) veh_straight(1,
	cbr(1) if(0) // front part
	cbr(0) else
def(2) veh_straight(2,
	ref(1) if(0)
	cbr(0) else

// middle
def(1) veh_straight(-1,
	cbr(2) if(0) // middle part
	cbr(4) else  // normal
def(3) veh_straight(1,
	ref(1) if(0)
	cbr(4) else // normal

// back
def(1) veh_straight(-1,
	cbr(3) if(0) // back part
	cbr(0) else
def(4) veh_straight(-2,
	ref(1) if(0)
	cbr(0) else

// show vehicle or void (transparent)
def(STRAIGHT) veh_posart(MOD3,
	ref(2) if(0) // front
	ref(3) if(1) // middle
	ref(4) else  // back

randomrel/randomabs(<triggers>, <randombit>, <List::ref()>)

Unlike the performance functions above, whose results are always determined by a predictable decision, one can also use random functions to pick one of several graphics sets or callback results.

These two functions base their randomisation on different prerequisites: randomrel() randomises the feature (vehicle, station, building, industry, object) based on its own triggers and random bits, and randomabs() randomises the feature based on its "related" object:

FeatureRelated object
VehicleFirst vehicle of consist (engine)
Industry tileIndustry containing this tile



This is a bit mask of triggers which cause re-randomizing. Normally, any matching trigger causes the graphics to be randomized again, but if you add ALLTRIGGERS to the bitmask, the re-randomizing only happens if all triggers have occurred.

Vehicles have 8 random bits, and the following triggers:

0CONSTRUCTVehicle is built/bought
1LOADVehicle gets new load of cargo (only after it was empty)
2SERVICEVehicle enters a depot and is serviced
4UNLOADThe consist has unloaded all cargo
8ANYLOADAny vehicle of the consist receives cargo
16CB32CB_32DAY returned 1
+128ALLTRIGGERSRe-randomizing only if all triggers have occurred

The consist triggers UNLOAD and ANYLOAD allow re-randomizing whenever the consist receives cargo after fully unloading. They should be used with randomrel(), not randomabs(), and the random-triggers should have ALLTRIGGERS added to it, to make the re-randomizing happen only if the consist was empty and then received new cargo.


For TTDPatch versions before 2.0.1 alpha 30, leave this at "0".

Since TTDPatch 2.0.1 alpha 30, only those bits that actually get triggered will be re-randomized. Prior versions always re-randomized all bits. This will make it possible to have independent sets of bits for independent triggers (or untriggered bits, set only at the time of purchase). Setting randombit determines the first bit to be re-randomized, as well as basing the random graphics on. The total number of bits used is the 2-logarithm of the number of references used, e.g., for 16 references, 4 bits are used.


The number of referenced sets to choose from must be a power of 2, i.e. 2, 4, 8, 16 etc.

Example (4 different liveries by random when being bought):
def(16) randomrel(CONSTRUCT,1,ref(12),ref(13),ref(14),ref(15))

Example (load either "pinot gris", "merlot" or "chardonnay"):
def(19) randomabs(UNLOAD,0,ref(16),ref(16),ref(16),ref(17),

randomcount(<triggers>, <randombit>, <type>, <count>,<List::ref()>)

This function specifies which vehicle's random bits this vehicle will be using and/or modifying for the random decision. Parameter <count> specifies how far to count from the starting vehicle. Parameter <type> (see below) specifies which vehicle is the starting vehicle, and which direction to count:

BACKRELcount back (away from the engine), starting at this vehicle
FRONTRELcount forward (toward the engine), starting at this vehicle
BACKABScount back, starting at the engine
FRONTABScount forward (toward the engine), starting at the last vehicle
BACKIDcount back, starting at the first vehicle in this chain of vehicles with the same veh-ID

Example ('child' vehicle using random bits from 'parent'):
def(0) spriteset(move(0),load(0))
def(1) spriteset(move(1),load(1))
def(2) spriteset(move(2),load(2))
def(3) spriteset(move(3),load(3))

def(4) randomrel(SERVICE,0,ref(0),ref(1),ref(2),ref(3))


// child gets randomisation from (preceding) parent

def(4) randomcount(SERVICE,0,FRONTREL,1,ref(0),ref(1),ref(2),ref(3))


Auxiliary functions

Auxiliary functions are used in the context of some of the functions above. As such, they don't get a def() nor do they ref() anything else. They're mainly used to evaluate special parameters for performance functions.


This function addresses an allocated recolour sprite. To be used in function spritestack().

allocuserdata([0 .. 8])

This function allows to split the user data area into a range used for bit setting and checking, and a second part for writing and reading numerical values, which are both handled independantly. The parameter defines the size in bits reserved for numerical values. I.e., allocuserdata(3) reserves the low three bit positions to be used by values 0 .. 7, and keeps the high 5 bits for bit setting and testing.

The default (0) reserves all 8 bits of the user data area for bit setting. Please note that concurrently used bits are adjusted with regards to the size allocated to be accessed as values. See figure.

In case you want to use "mixed" user bits and values in distributed source files, you'll have to use allocuserdata() in each of these source files, because all internal allocation is done on a local level.


This function addresses an allocated recolour sprite depending on the cargo type of the vehicle in question. Parameter is the numerical difference between the start of a contiguous block of cargo recolour sprites minus the position of that particular cargo in the vehicle's cargo translation table. For an example see the tutorial on use of stacked sprites.

Example 1 (recolour stacked sprites for loading stages):
	[...] // body
// freight loading stages

def(0) spriteset(move(0),load(0)) // empty body, recoloured by CB_RCOL
def(1) spriteset(move(1,2,3,4),load(1,2,3,4)) // - freight, cargo recoloured

def(2) spritestack(RC_DEFAULT, ref(0), MORE) // body
def(3) spritestack(cargomapsprite(0x85 - GRAI), ref(1))

def(4) spritelayer(
	ref(2) if(0) // "body"
	ref(3) else  // "freight"


This function is used in an if() function when checking for a date, e.g. by function lastservicedate().

Example 1 (test for date of last service):
def(2) lastservicedate(
	ref(0) if(<date(31-12-1949)) // beige
	ref(1) else // red


This function establishes a 'bracket' around the parent function's block to reference the consist's engine instead of the current vehicle.

Example 1 (check age of engine):
def(6) yearbuilt(
		ref(4) if(< 1922)
		ref(5) else
Example 2 (check engine livery):
def(6) getubyte(
		ref(3) if(UB_TIV, UB_CFC) // TIV or CFC
		ref(2) else               // generic black
Example 3 (check direction of engine in push-pull service):
def(6) flipped(
		ref(4) if(FORWARD) // offsets, animation and lights "backwards"
		ref(1) else        // offsets, animation and lights "forwards"
Example 4 (check number of certain engine type in train):
def(6) idcount(_120T,
		ref(3) if(0) // 030_030T leading
		ref(2) else  // 030_030T leading with 120T: RB


This function randomly addresses one of the given range of the 16 original TTD recolour sprites. To be used in function spritestack().

Range must contain either 2, 4, 8 or 16 recolour sprites, e.g.:

Range Description
0x02 .. 0x03 / (2 .. 3) 2 recolour sprites
0x08 .. 0x0B / (8 .. 11) 4 recolour sprites
0x0F .. 0x16 / (15 .. 22) 8 recolour sprites
0x00 .. 0x0F / (0 .. 15) 16 recolour sprites

Example: use all 16 TTD recolour sprites for tarpaulins
def(3) spritestack(RC_DEFAULT, ref(0), MORE) // body
def(4) spritestack(cargomapsprite(0x85 - GRAI), ref(1), MORE) // freight
def(5) spritestack(randomttdsprite(0 .. 15), ref(2)) // tarpaulin

def(6) spritelayer(
	ref(3) if(0) // body
	ref(4) if(1) // freight
	ref(5) else  // tarpaulin


This function randomly addresses one of the given range (s.a.) of allocated recolour sprites. To be used in function spritestack().


This function adjusts a performance function result to a more useful range. The first parameter defines the value to right-shift the result, and the second parameter gives the value with which to AND the result after shifting.

Please note that parameter <mask> might be of WORD-size in a given context. See example.


These functions are used to set the named engine effect as well as reposition it. They are intended to be used with function seteffect(). For an example see here.


This function returns the untranslated original cargo-ID from TTD.


This function addresses an original TTD recolour sprite. To be used in function spritestack().

Global functions


This function installs a so-called "cargo translation table" (CTT) to aid with coding vehicle newGRFs that wish to support more than the standard cargo types. This table is a list of cargo labels, each put in double quotes like this: "BEER"; and, in case of overriding a standard cargo type, "quoted", like this: {"LVST"}.

Each entry represents a corresponding cargo which is meant when using its ID in a makevehicle() function, or when being listed in the vehicle's cargomask() function, or in a vehicle's performance function. See example below.

In this way, the vehicle newGRF does not need to know or care which cargo slot and cargo bit a certain cargo type uses. It can define its own ID for each cargo that it wishes to support, and thus be independent of both what cargo types are really available in the game and what slots/bits they use.

All new (non-standard) cargo types listed in a CTT are also automatically defined as a cargo-ID to be used in cargo-relevant vehicle performance functions, callbacks, or the makevehicle() function. In case of distributed source files, you'd have to make them available by importing those IDs by function import_ctt() by all files not containing the CTT but wishing to reference cargo types newly defined by the CTT.

Because the vehicle's cargomask() function only allows for 32 cargo types, only the first 32 entries in a cargo translation table can make use of this cargo mask. Other cargo types have to be added via the cargo classes, so put the cargos that need exceptions to the cargo class based refitting first, so that they can go in the cargo mask.

Note that a cargo translation table cannot be set incrementally, you must set all cargo types to be used in a single cargo translation table.

Example (set Cargo Translation Table):
	{"PASS"}, {"COAL"}, {"MAIL"}, {"OIL_"}, // refit start
	{"LVST"}, {"GOOD"}, {"GRAI"}, {"WOOD"}, 
	{"IORE"}, {"STEL"}, {"VALU"}, {"FOOD"}, 
	{"PAPR"}, {"FRUT"}, "FISH", "WOOL",
	"LIME", "SAND", "GLAS", "WDPR",
	"DYES", "FERT", "OLSD", "RFPR",
	"VEHI", "PETR", "AORE", {"WATR"},
	"BDMT", "FICR", "TOUR", "CERE", // refit end
	"MILK", "SGBT", "CLAY", "MNSP",
	"SCMT", "POTA", "SULP", "FMSP",


// Box car that has special graphics for livestock, wool, fish, food and milk
// and generic graphics for all other cargoes


This function provides ability to specify rail types via a translation table, similar to using a cargo translation table (see above). Default labels are RAIL, ELRL, MONO and MGLV. If a table is installed, then changing engine traction type will not affect the rail type.

Example (set Rail type Translation Table):
	{"RAIL"}, {"ELRL"}, {"MONO"}, {"MGLV"},
	{"NAAN"}, {"NAAE"}, {"NAA3"}, {"NRAN"},{"NRAE"},{"NBAN"}