In m4nfo, road vehicles 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:
Where the move() and load() functions take spriteblock() references as their arguments.
spriteset(move(0),load(0))
This bus uses the same sprite (number 0, first sprite of spriteblock) both for its only moving state as for its only loading state.
spriteset(move(1,2,3,4),load(1,2,3,4))
This truck uses 4 different sprites for its moving states, and it uses the same sprites for its loading states.
spriteset(move(0,9,11),load(0,8,10))
This truck uses 3 different sprites for its moving states, and it uses different sprites for its loading states.
Functions for road vehicle 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(<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 |
| cnst_cargoclasses(<block>) | Get information about all cargo classes transported by the consist |
| cnst_cargo(<block>) | Get most common cargo type in 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_speed(<block>) | Get current speed 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 |
| else | This is really a void statement |
| flipped(<block>) | Check if road vehicle is flipped (forward/backward) |
| idcount(<Veh-ID>, <block>) | Check number of vehicles with given ID |
| indepot(<block>) | Check if vehicle is in depot |
| islast(<block>) | Check if last vehicle in consist |
| lastmaintenance(<block>) | Get time since last maintenance in days |
| 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 number 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%) |
| servint(<block>) | Get service interval of vehicle |
| soundevent(<block>) | Check sound event |
| speedlimit(<block>) | Current top speed of engine (w/o bridge, curve etc.), 0 if unlimited |
| 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_currentload(<block>) | Get actual load of vehicle |
| veh_getinfo([-]<Byte>, <function>, [<shiftmask>,] <block>), veh_getinfoabs(FRONT | BACK, <Byte>, <function>, [<shiftmask>,] <block>) | Accessing other vehicle's performance functions |
| veh_id(<Veh-ID>,<block>) | Check vehicle ID for CB_ATAC |
| veh_marketinfo(<block>) | Info about vehicle market availability |
| veh_num(<block>) | Get total number of vehicles in consist |
| veh_posrel/veh_posabs(FRONT | BACK | MOD2 | MOD3 | MOD4 | MOD6,<block>) | Get position of vehicle inside consist |
| veh_status(<block>) | Get special status of vehicle |
| yearbuilt(<block>) | Year built (counted from year 0) |
| randomrel/randomabs(<trigger>, <randombit> <list::reference>) | 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.
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.
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, one for number of frames per vehicle motion, and the other for number of frames in total for motion across an entire tile. Note however, that vehicle graphics are only updated every time the vehicle actually moves.
For an example see the train section.
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.
def(4) articulated( addveh(_BUSSING_KASSBOHRER) if(1) ENDLIST else ) def(5) callback( ref(4) if(CB_ARTI) ref(2) else )
This function checks for a callback incidence, and if so, returns the type of the incurred callback. See example above.
cnst_cargoclasses(<block>)
Following three functions work on whole consists.
This first 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 next two functions, cnst_cargo() and cnst_refit().
def(14) cnst_cargoclasses( ref(13) if(REEF) // refrigerated cargo ref(11) else )
This function 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.
def(14) cnst_cargo( ref(13) if(ttdcargo(PASS)) // use original PASS here (untranslated!) ref(11) elsek )
This function returns the most common refit cycle of the cargo type returned by cnst_cargo().
def(13) cnst_refit(
ref(10) if(2) // green
ref(13) if(1) // dark green
ref(11) else
)
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.
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, ...).
| Value | Meaning |
| VIEWPORT | Vehicle is drawn in a viewport, i.e. on the map |
| DEPOTGUI | Vehicle is drawn in the depot GUI |
| VEHICLEGUI | Vehicle is drawn in the vehicle details GUI (This includes the refit GUI) |
| VEHICLELIST | Vehicle is drawn in the vehicle list |
| PURCHASELIST | Vehicle is drawn in the purchase list (This includes the autoreplace GUI) |
| PREVIEWGUI | Vehicle is drawn in the exclusive preview GUI or in the advertisement news |
PURCHASELIST and PREVIEWGUI are to be used in the purchase list chain (MENU), the others are to be used in the actual vehicle chain.
This feature is only available in OTTD since r23080.
This is a void statement. It's just there to enhance readability. See example above.
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.
def(2) flipped( ref(0) if(FORWARD) ref(1) else )
This function checks the number of vehicles with a given ID in a consist. The additional parameter is the veh-ID to check for.
For an example see the train section.
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.
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 road vehicles:
| Property | Meaning |
| capacity() | capacity |
| cargoaging() | custom cargo ageing (OTTD only) |
| effort() | tractive effort |
| power() | power |
| price() | price |
| runningcost() | running cost |
| speed() | speed |
| userdata() | user data |
| weight() | weight |
Instead of giving the property function with an empty parameter list you might also drop the parentheses at all. Please note that property callbacks for road vehicles are only available for OpenTTD.
randombits/randomtriggers([<shiftmask>,] <block>)
These functions return random bits and random triggers of the current vehicle or the consist's engine. See here for road vehicle 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.
| Function | Value |
| rcost_cargotype | The cargo type being refitted to; translated if a translation table has been installed |
| rcost_cargosubtype | The cargo subtype being refitted to |
| rcost_cargoclass | The cargo class value of the cargo being refitted to |
// 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.
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
)
This function checks the sound event. Following events are handled for road vehicles:
| Event | Meaning |
| SND_START | Start sound, called when leaving a station |
| 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 | Road vehicle 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 |
def(ALLSOUNDS) soundevent( refsnd(SND_BELL1) if(SND_START) cbr(0x0E) if(SND_BRKDOWN) // TTD sound cbfail() else )
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.
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).
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().
def(9) veh_cargoweight(
cbr(20) if(> 16) // reduce capacity if cargo unit weight > 1 tonne
ref(0) else // else keep standard capacity
)
This function returns the actual load (in tons, pieces, bags, ..) of the vehicle.
def(9) veh_currentload(
cbr(80) if(> 10) // reduce speed if actual load > 10 tonnes
ref(0) else // else keep standard speed
)
These are special functions to access the return value of another function addressing a different vehicle in the consist, i.e. only available for articulated road vehicles. 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 consist, 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 consist (i.e., the first vehicle has index 0), and BACK means to count backwards from the end of the consist (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 positive value.
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>), refitted(), reversed(), veh_cargoclass(), veh_cargotype(), veh_cargoweight(), veh_id(), veh_num(), veh_posabs(<direction>), veh_posrel(<direction>), veh_ispowered(), veh_maypowered(), veh_railtype(), yearbuilt().
This function is available since OpenTTD r22997.
def(1) veh_getinfo(4,getubits(),
ref(2) if(0)
ref(3) else
)
def(1) veh_getinfo(-1,idcount(13,),
ref(3) if(0) // veh has ID = 0
ref(4) else
)
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"
)
def(1) veh_getinfoabs(FRONT,0,randombits(),shiftmask(0,0x01),
ref(2) if(0) // get random livery of front vehicle
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)
def(4) veh_id(
ref(3) if(_TRAILER) // OK
attach(ATT_CARBADTYPE) else // "wrong type of trailer"
)
def(5) callback(
ref(4) if(CB_ATAC) // check if trailer might get attached
ref(9) else // graphics
)
This function returns information about market availability of the current vehicle or the consist's engine type. Values returned are:
| Status | Meaning |
| ONMARKET | Vehicle type is available on the market |
| TESTINGPHASE | Vehicle type is in the testing phase |
| TESTINGOFFER | Exclusive testing offer for a human player active |
def(8) veh_marketinfo(
engine(
ref(1) if(TESTINGOFFER) // show special livery
ref(2) else // normal
)
)
This function returns the total number of vehicles in the consist.
def(8) veh_num(
engine(
attach(ATT_OK) if(< 2) // max +2 Wagen
attach(ATT_MOTCARBADNUM) else // "wrong number of motor cars"
)
)
def(9) veh_id(
ref(8) if(__DUEWAG_GT6) // OK
attach(ATT_CARBADTYPE) else // "wrong car type"
)
def(10) callback(
ref(9) if(CB_ATAC) // may car 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 first road vehicle in a consist, the usual vars refer to the trailer that is to be attached, not to the road vehicle it is being attached to. That's why one has to 'force' the function to refer to the road vehicle the trailer is being attached to, if one wants to find out the length of the consist it is being attached to.
veh_posrel/veh_posabs(FRONT | BACK | MOD2 | MOD3 | MOD4 | MOD6, <block>)
These two functions check the position of the current vehicle within the consist.
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 either MOD2, MOD3, MOD4 or MOD6 too, to carry out the 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).
Note the use of bracketing in the illustration, where 'self' means no bracketing at all, and 'engine' means to use the function engine() for bracketing.
def(3) veh_posrel(MOD2,
ref(4) if(0) // first
ref(5) else
)
Example 2 (set different lengths for the parts of an articulated car):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 tram):def(4) veh_posabs(FRONT,
ref(1) if(3) // back
ref(2) if(2) // 3rd car
ref(3) if(1) // 2nd car
ref(0) else // front
)
This function returns the 'special' status of a vehicle. Values returned are:
| Status | Meaning |
| INVISIBLE | Vehicle is invisible, e.g. in a tunnel |
| STOPPED | Vehicle is stopped |
| CRASHED | Vehicle 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.
randomrel/randomabs(<triggers>, <randombit>, <list::reference>)
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:
| Feature | Related object |
| Vehicle | First vehicle of consist (engine) |
| Station | n/a |
| Building | n/a |
| Industry tile | Industry containing this tile |
| Industry | n/a |
| Object | n/a |
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:
| Value | Trigger |
| CONSTRUCT | Vehicle is built/bought |
| LOAD | Vehicle gets new load of cargo (only after it was empty) |
| SERVICE | Vehicle enters a depot and is serviced |
| UNLOAD | The consist has unloaded all cargo |
| ANYLOAD | Any vehicle of the consist receives cargo |
| CB32 | CB_32DAY returned 1 |
| ALLTRIGGERS | Re-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.
def(16) randomrel(CONSTRUCT,1,ref(12),ref(13),ref(14),ref(15))
def(19) randomabs(UNLOAD,0,ref(16),ref(16),ref(16),ref(17),
ref(17),ref(18),ref(18),ref(18))
These functions are used to evaluate special parameters for some of the functions above.
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.
This function returns the untranslated original cargo-ID from TTD.
cargotranslationtable(<list::label>)
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 and "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. See example. In this way, the vehicle newGRF doesn't 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.
Because the vehicle's cargomask() function only allows for 32 cargo types, only the first 32 entries in the 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.
cargotranslationtable(
{"LVST"}, {"GOOD"}, {"FERT"}, {"WOOL"}, // refit start
{"WDPR"}, {"GLAS"}, {"DYES"}, {"FOOD"},
{"FISH"}, {"WATR"}, {"RUBR"}, {"FICR"},
{"CMNT"}, {"VEHI"}, {"COAL"}, {"OIL_"},
{"WOOD"}, {"IORE"}, {"STEL"}, {"PAPR"},
{"FRUT"}, {"CORE"}, {"POTA"}, {"SAND"},
{"OLSD"}, {"RFPR"}, {"PETR"}, {"BRCK"},
{"SULP"}, {"LIME"}, {"MILK"}, {"RSGR"}, // refit end
{"FRVG"}, {"SCRP"}, {"GRVL"}, {"AORE"},
{"BDMT"}, {"CLAY"}, {"SALT"}, {"ENSP"},
{"MNSP"}, {"FMSP"}, {"GRAI"}
)
...
// Truck that has special graphics for livestock, wool, fish, food and milk
// and generic graphics for all other cargoes
makevehicle(_MB124,
link(ref(14),MENU)
link(ref(2),LVST)
link(ref(7),WOOL)
link(ref(8),FISH)
link(ref(11),FOOD)
link(ref(12),MILK)
default(ref(5))
)