In m4nfo, ships are using four types of functions:
Functions for ship performance
These functions are used to evaluate game-intrinsic variables, and make them accessible to the ship'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 ship with its activation function. See here for an example.
| Function | Description |
| ageindays(<block>) | Get age of ship counted in days |
| ageinyears(<block>) | Get age of ship counted in years |
| animation(<block>) | Access the animation counter |
| autoreplace(<block>) | Get iteration number from CB_ARVS |
| callback(<block>) | Check incidence (and type) of callback |
| cargo(<block>) | Check cargo type |
| direction(<block>) | Get driving direction of ship |
| displaymode(<block>) | Set display mode for ship sprite |
| else | This is really a void statement |
| indepot(<block>) | Check if ship is in depot |
| lastmaintenance(<block>) | Get time since last maintenance in days |
| property(<block>) | Get property number for CB_PROP |
| 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 |
| soundevent(<block>) | Check sound event |
| 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 ship |
| veh_id(<Veh-ID>,<block>) | Check ship ID |
| veh_status(<block>) | Get special status of ship |
| yearbuilt(<block>) | Year built (counted from year 0) |
| randomrel/randomabs(<trigger>, <randombit> <list::reference>) | Get random reference |
This function returns the age of a ship 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(1) 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 ship in years. As above, its result will be returned as a WORD.
This function returns the amount of motion that a ship has done. Its value is in units of 1/4096 of a tile. A ship 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 ship motion and 4 frames in total for motion across an entire tile. This can be overriden by applying two parameters, one for number of frames per ship motion, and the other for number of frames in total for motion across an entire tile. Note however, that graphics are only updated every time the ship actually moves.
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
)
This function checks for a callback incidence, and if so, returns the type of the incurred callback. See example above.
This function returns the actual driving direction of the ship, 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 ship sprite. In this way, it is possible to use different sprites for the ship in question, depending on the display mode (on map, in depot, in purchase window, ...).
| Value | Meaning |
| VIEWPORT | Ship is drawn in a viewport, i.e. on the map |
| DEPOTGUI | Ship is drawn in the depot GUI |
| VEHICLEGUI | Ship is drawn in the vehicle details GUI (This includes the refit GUI) |
| VEHICLELIST | Ship is drawn in the vehicle list |
| PURCHASELIST | Ship is drawn in the purchase list (This includes the autoreplace GUI) |
| PREVIEWGUI | Ship 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 graphic's 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 the ship is currently in a depot.
def(4) indepot( ref(35) if(INDEPOT) ref(33) else )
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 ship's properties by use of the function cbr().
The following table lists the properties which may be modified for ships:
| Property | Meaning |
| capacity() | capacity |
| cargoaging() | cargo aging (OTTD only) |
| price() | price |
| runningcost() | running cost |
| speed() | speed |
Instead of giving the property function with an empty parameter list you might also drop the parentheses at all.
def(5) yearbuilt(
cbfail() if(<< 1955) // built before 1955, callback fail
cbr(30) else // built after 1955, set 30 km/h
)
def(6) property(
ref(5) if(speed()) // go test if built after 1955
cbfail() else // callback fail
)
def(7) callback(
ref(6) if(CB_PROP) // change speed
ref(4) else // graphics
)
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 ship.
def(3) refitted(
cbr(48) if(0) // 48 passengers
cbr(32) else // 32 passengers
)
def(4) callback(
ref(3) if(CB_RCAP) // refit capacity
ref(2) else // graphics
)
This function checks the sound event. Following events are handled for ships:
| Event | Meaning |
| SND_START | Start sound, called when leaving a station |
| SND_TUNNEL | Tunnel sound, called when ship enters tunnel |
| SND_BRKDOWN | Breakdown sound, called when ship breaks down |
| SND_RUNNING | Running sound, called once per engine tick, but no more than once per ship 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 |
This function checks the cargo class value of the cargo transported by the ship.
def(9) veh_cargoclass(
cbr(40) if(REEF) // refrigerated cargo, set 40 km/h
cbfail() else // else keep 25 km/h
)
This function returns the cargo type transported by the ship. The cargo type is translated if a cargo translation table has been installed.
Please note that if the newGRF 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(40) if(FISH) // refrigerated cargo (fish), set 40 km/h
cbfail() else // else keep 25 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(200) if(> 16) // reduce capacity if cargo unit weight > 1 tonne
cbfail() else // else keep standard capacity
)
This function returns the actual load (in tons, pieces, bags, ..) of the ship.
def(9) veh_currentload(
cbr(20) if(> 100) // reduce speed if actual load > 100 tonnes
cbfail() else // else keep standard speed
)
This function returns the 'special' status of a ship. Values returned are:
| Status | Meaning |
| INVISIBLE | Ship is invisible, e.g. in a tunnel |
| STOPPED | Ship is stopped |
| CRASHED | Ship 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 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.
Ships have 8 random bits, and the following triggers:
| Value | Trigger |
| CONSTRUCT | Ship is built/bought |
| LOAD | Ship gets new load of cargo (only after it was empty) |
| SERVICE | Ship 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 versions before 2.0.1 alpha 30, leave this at "0".
Since 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 makeship() 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"}
)
...
// Ship that has special graphics for livestock, wool, fish, food and milk
// and generic graphics for all other cargoes
makeship(_DEMOL,
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))
)