In m4nfo, stations are using four types of functions:
Functions for tile layout definition
For station tiles, the tile layout defines what sprites should be displayed, where they are to be displayed, and in what order.
Because in TTD, the tile layout for stations is somewhat different from that for buildings and objects, this has to be reflected by m4nfo as well. The main reason for this difference is that for stations, the tile layout is kept apart from the real sprites defined in a spriteblock() and accessed by spriteset() functions, and in this way, a layout can easily handle different sprite sets with the same layout structure.
For station tiles, there are two types of sprites. The first type establishes a new 3D bounding box for use by the sprite sorter. The second type shares the 3D bounding box of the previous sprite. It must not be larger than the sprite which established the bounding box, nor must any part of it be outside this box. For simplicity, it might have the exact same dimensions as the sprite it shares the bounding box with.
The 3D bounding box is used by TTD's sprite sorter to figure out the order in which to draw the sprites, as well as telling it which sprites to draw, because those whose bounding box falls outside the currently updated screen rectangle will not be drawn. Make sure the 3D bounding box is large enough to contain the entire sprite, but not so large that the sprite sorter can't determine which sprites should be in front.
This means that the order in which the sprites are specified is irrelevant. Sprites will always get drawn from back to front, in the order which the sprite sorter determines as correct, from their bounding boxes. There are two exceptions, however:
Like for houses, industry tiles and objects, m4nfo provides the same auxiliary functions to be used in tile layout definitions, but with a slightly different syntax than for the above mentioned features:
Layout term | m4nfo function |
<tile> | tile() | xtile() |
<groundsprite> | ground() | NOSPRITE |
<buildingsprite> | regular() | notransparency() | glass() | recolour() | NOSPRITE |
<xoffset, yoffset, zoffset> | xyz() |
<xoffset, yoffset> | xy() |
<xextent, yextent, zextent> | dxdydz() |
<xpixeloffset, ypixeloffset> | xyoff() |
This function sets up the tile layout for a certain station-ID. It needs at least two calls of function tile() (or one of xtile()): one for the tile in x- and the second one for the y-direction. The calls to tile()/xtile() are consecutively numbered internally, i.e. the first station tile gets numbers 0/1 (x/y-direction), the second one gets 2/3 and so forth. These are the tile numbers to be referenced by callback CB_LAYOUT from the makestation() function when drawing all station tiles in the proper way. (It is of course possible to use 'labels' instead of those numbers given implicitily, see next paragraph.)
Layout structures could be used multiple times, by referencing different sets of graphics sprites.
This function takes a block of sprite definitions, either for ground sprites or building sprites, see below. A tile may be labeled, to reference it later, e.g. in a CB_LAYOUT chain:
layout(_ROOFS, ... tile(__roofs, ground(1011) regular(21, xyz(0,0,0), dxdydz(5,16,12)) regular(24, xyz(0,0,0), dxdydz(16,16,35)) ) ... ) ... def(3) plt_num( self( reftile(__roofs) if(4) reftile(__roofs+1) if(3) reftile(__roofs+2) if(2) reftile(__roofs+3) if(1) reftile(0) else ) )
Function xtile() might be used to generate sprite definitions for the tile in y-direction automatically, derived from the tile in x-direction. Using this method needs a number of additional requirements and is more limited than using tile(): building sprites have to be ordered in such a way that their sprite numbers are +1 for the tile in y-direction, and for ground sprites the difference must be -1 for original TTD sprites and +1 for custom ground sprites. 3D coordinates will be swapped accordingly. Graphics sprites associated with the x- and y-direction of a xtile() layout have to be symmetric to assure correct bounding boxes, and you cannot use function xyoff() because graphics sprite coordinates are totally unknown inside tile layouts.
This function defines the ground sprite to use for the tile in question. There may only be one ground sprite defined by ground() in a tile block. In case of multiple (stacked) ground tiles these must be supplied by using function regular() prior to definition of any bounding box.
You may either define original TTD ground sprites, or custom ground sprites, defined in a spriteblock(). In this case, you'll need the second parameter set to CUSTOM.
If no ground sprite is needed you should use macro NOSPRITE instead calling function ground(). This might be useful for displaying icon graphics or when drawing custom foundations.
regular(<tile-id>, (<xyz()>, <dxdydz()>) | <xyoff()> [,TTD])
This function defines a 'normal' building sprite (or an additional ground sprite). First parameter is the index into the spriteblock associated with the current layout, remaining parameters are either offsets from the northern tile corner and size of sprite, or offsets relative to the previous sprite (see pictures below). In the latter case, this sprite will share its bounding box with the current bounding box. Setting the optional parameter "TTD" allows to access original TTD sprites.
notransparency(<tile-id>, (<xyz()>, <dxdydz()>) | <xyoff()> [,TTD])
This function works the same as regular(), just with its sprite being displayed normally even in "transparent buildings" mode.
compcol(<tile-id>, (<xyz()>, <dxdydz()>) | <xyoff()>)
Again the same behaviour as regular(), but this time using the default company colour translation.
recolour(<tile-id>, (<xyz()>, <dxdydz()>) | <xyoff()>, <recolour-sprite>)
Yet again the same behaviour as regular(), but this time with colour translation defined by a recolour table given by the last parameter.
glass(<tile-id>, (<xyz()>, <dxdydz()>) | <xyoff()> [,<recolour-sprite>])
Same behaviour as regular(), but sprite is drawn in 'transparent' mode, either using the default 'glass' mode, or using a custom transparent recolouring when supplied an additional parameter. This parameter must be the address of the recolour map to be used for the glass effect.
This function defines the x/y/z-offsets of the current sprite from the northern tile corner. Note that coordinates are 3D coordinates with x running from top-right to bottom-left and y running from top-left to bottom-right (see picture below), with tile dimensions being 16x16 px for x and y and 8 px for one height level. This means the x and y values should always be within 0 .. 15.
dxdydz(<byte>, <byte>, <byte>)
This function defines the size of the current sprite in x/y/z-direction. Coordinates given are equal to those above.
This function defines the x/y-offsets of a child sprite in relation to the previous sprite setting the bounding box. Coordinates refer to the upper left corner of the preceding sprite, i.e. it is not a 3D-coordinate.
In newer versions of OpenTTD, a so-called 'advanced format for sprite layout' (ASL) is supported, which allows 'dynamic' layout modifications by using registers.
m4nfo formal specification for this format is as above, except there are two additional parameters in m4nfo functions ground(), regular(), recolour(), and glass(), namely <flags> (set by function aslflags()) and <registers> (set by function registers()). E.g., ground(<sprite> [,CUSTOM]) gets ground(<sprite> [,CUSTOM], <flags>, <registers>), or regular(<tile-id>, (<xyz()>, <dxdydz()>) | <xyoff()> [,TTD]) gets regular(<tile-id>, (<xyz()>, <dxdydz()>) | (<xyoff()> [,TTD]), <flags>, <register>.)
Function aslflags() takes a quoted list of following flags as its parameter:
Flags in advanced sprite layouts | ||||
Value | Label | parent sprite | ground/child sprite> | register(s) |
1 | SKIP | Skip bounding box including child sprites | Skip sprite | 1 |
2 | OFFSET_SPRITE | Add offset to sprite number, disable default usage of construction stage or railtype-offset | 1 | |
4 | OFFSET_RCSPRITE | Add offset to recolour sprite number | ||
8 | CUSTOM_RCSPRITE | Recolour sprite is from custom spriteblock, OFFSET_SPRITE may be used as well | n/a | |
16 | OFFSET_XY | Add 3D offset in <x> and <y> | 2 | |
32 | OFFSET_Z | Add 3D offset in <z> | 1 | |
48 | PIXOFFSET_XY | Add pixel offset in <x> and <y> | 2 | |
64 | RESOLVE_SPRITE | Resolve sprite by using spritetype() with a specific value | 1 | |
128 | RESOLVE_RCSPRITE | Resolve recolour sprite by using spritetype() with a specific value | 1 |
The needed registers are set by function registers(), also as a quoted list. The order of registers must be the same as the given flags, (see examples). For flags not being used, no register is specified. Since offsets for <x> and <y> can only be enabled together, two registers are needed. The register for SKIP uses a value of "1" to draw the sprite, and "0" to skip it. Flag CUSTOM_RCSPRITE uses no register at all.
Last two flags (RESOLVE_SPRITE, RESOLVE_RCSPRITE) are only valid for stations. For stations, the chain of control is resolved multiple times and sprites may be part of different sprite blocks (ground sprites (0), building sprites (1), custom foundation sprites (2)) already without using the advanced sprite layout format.
Unlike the first 6 flags, both station-only flag parameters in function registers() are not registers but represent a value in the range 0 .. 7, which is accessed by function spritetype() while processing the chain of control and resolving appropriate sprites.
Remember that some values might already being used, e.g. '2' for resolving custom foundation sprites. Even if a sprite is not from a spriteblock but an original TTD sprite, the value still defines which chain of control defines the values for reference by function registers().
For more information, see Example 4 below.
In TTD, 'normal' station tiles (there might be exotic tiles consisting only of a ground sprite or an additional single platform sprite) are usually assembled from three sprites: the track base (ground sprite), a 'background' sprite and a 'foreground' sprite. These sprites are arranged to build up a station tile in such a way that trains run over the background and the ground sprites, while the foreground sprite covers everything. See picture below.
In case of an additional station roof or overpass, these sprites would be either added last or could be integrated into the foreground sprite.
In addition, station tiles always have to be defined twice (even for the building menu icons), i.e. in x- and y-direction.
layout(_ROOFS, tile( ground(1012) // track x regular(1, xyz(0,0,0),dxdydz(16,5,8)) regular(3, xyz(0,11,0),dxdydz(16,5,8)) ) tile( ground(1011) // track y regular(2, xyz(0,0,0),dxdydz(5,16,8)) regular(4, xyz(11,0,0),dxdydz(5,16,8)) ) ... )
In m4nfo, the need for defining both x- and y-tiles might be avoided by exchanging function tile() with function xtile(), which will generate tiles for y-direction automatically, in case the tiles are mirror images of each other, graphics-wise.
I.e., in the example above, only the first tile needs to be given, and by using xtile(), a second tile with ground sprite #1011 and normal sprites #2 and #4 will be added automatically, together with their coordinates being swapped accordingly.
layout(_GLASS, // modern glass 'tinted green' tile( ground(1012) compcol(502, xyz(0,0,0),dxdydz(16,5,11)) glass(538, xyoff(0,0), GREEN) compcol(504, xyz(0,11,0),dxdydz(16,5,11)) glass(540, xyoff(0,0), GREEN) ) tile( ground(1011) compcol(503, xyz(0,0,0),dxdydz(5,16,11)) glass(539, xyoff(0,0), GREEN) compcol(505, xyz(11,0,0),dxdydz(5,16,11)) glass(541, xyoff(0,0), GREEN) ) ... )
Example 2 shows the tile layout of the 'modern glass' platform from NewStations. It consists of five sprites: the ground sprite, two platform sprites to be drawn in 'company colour', each with a transparent 'glass' sprite which are defined as 'child sprites', i.e. sprites sharing their bounding box with the preceding sprites using a bounding box (the platform sprites).
Details to be represented in one of the 8 available company colours have to be drawn using a special colour, take a look on TTD palettes.
However, areas representing the transparent glass effect may be drawn in any colour (red in this example). Also, this example shows how to use a 'custom glass effect' (tinted green glass).
This needs setting up a custom colourtable to get the desired effect to work.
Example 3 shows how to use multiple ground sprites for a tile. This is useful if you want to use the usual rail/grass/concrete ground sprite, but still need to add features to it without using a new bounding box. To do so, the syntax of sprites sharing the previous bounding box is used, but before the first bounding box definition has been defined.
The example defines two types of waypoints, the first one made up from ground sprites only, the second one with an added building sprite.
layout(_WAYP2, // w/o building tile( ground(1012) // track regular(0, xyoff(0,0)) // overlay ) tile( ground(1011) // track regular(1, xyoff(0,0)) // overlay ) // w building tile( ground(1012) // track regular(0, xyoff(0,0)) // overlay regular(2, xyz(0,0,0),dxdydz(16,5,10)) // building ) tile( ground(1011) // track regular(1, xyoff(0,0)) // overlay regular(3, xyz(0,0,0),dxdydz(5,16,10)) // building ) )
Note that <xpixeloffset> and <ypixeloffset> in OpenTTD refer to the usual spot of groundtiles, but are ignored in TTDPatch and set to zero. So, in case of developing a newGRF that needs to be compatible with both programs, you should always keep <xpixeloffset> and <ypixeloffset> zero to get the same effect in both.
Note however that due to a bad specification in plain nfo, both implementations do not consider the setting of GROUNDSPRITES in property function flags(), hence these 'multiple ground sprites' have to be always part of the building sprites set and cannot be part of a different sprite set for ground sprites. Therefore, you cannot check for multiple ground sprites by function spritetype().
Unlike custom foundations for houses, industry tiles and objects, foundations for stations do not need any special handling inside the tile layout definition.
Instead, the needed foundation sprites are defined in a special spriteblock and accessed inside the graphics chain by use of function spritetype(). Please note that this needs to set flag NOFOUNDATION in property function flags().
Example 4 shows how to benefit from the 'advanced sprite layout', in case of repetitive station layouts. A good example would be a station layout with a need to repeat itself, only for different cargo sprites, which could be represented by recolouring a sample cargo sprite. The advanced sprite layout for stations gives the possibility to simply include recolour sprites in their own sprite block, and easily access them by the layout functions.
asl_on() layout(ADVTEST, tile( ground(1012) recolour(0, xyz(8,8,0), dxdydz(8,8,8), 0, aslflags({OFFSET_RCSPRITE, CUSTOM_RCSPRITE, RESOLVE_RCSPRITE}), registers({11,4})) ) tile( ... ) ) // building sprites spriteblock( set( sprite(advtest.pcx 10 10 09 22 32 -14 -16) ) ) def(0) spriteset(little(0)) // recolour sprites - orange and blue spriteblock( set( colourtable(DOSMAP, 62 .. 67, C0 .. C5, ) colourtable(DOSMAP, 62 .. 67, 92 .. 97, ) ) ) def(1) spriteset(little(0)) def(2) spritetype( ref(1) if(4) // recolour sprites ref(0) else // building sprites ) def(3) setregisters(11,1,ref(2)) // 2nd recolour entry: blue def(4) callback( cbr(0) if(CB_LAYOUT) ref(3) else // graphics ) makestation(ADVTEST, default(ref(4)) )
Note that registers used in the advanced sprite layout must be set inside the graphics' chain, not in the callback chain. This is done in def(3), where function setregisters() sets register '11' to a value of '1', i.e. the second recolour sprite entry. In def(2), the graphics' chain is split into a chain for the building sprites, def(0), and an additional chain for the recolour sprites, def(1).
In the layout, a sprite is defined to be recoloured by using function recolour(), with first parameter setting the building sprite, and the 4th parameter setting the recolour sprite, both '0' in this case. Parameter 5 specifies flags OFFSET_RCSPRITE, CUSTOM_RCSPRITE, and RESOLVE_RCSPRITE, and parameter 6 specifies the register to be used for defining the recolour sprite's offset, and the graphic's chain for resolving the recolour sprites (4).
copylayout(<Station-ID>, <Station-ID> | Range::<Station-ID>)
This function allows to copy a tile layout definition. First argument is the station-ID for which the tile layout got defined, remaining arguments are station-ID(s) to copy the tile layout to. These IDs must be a range of consecutive station-IDs.
copytilelayout(<Station-ID>, <Station-ID> | Range::<Station-ID>)
Same as above, but this function allows to copy a custom TTD tile type layout definition. First argument is the station-ID for which the custom TTD tile type layout got defined, remaining arguments are station-ID(s) to copy the custom tile layout to. As before, these IDs must be a range of consecutive station-IDs.
patchlayout(<Parameter>, <num-tiles>, <num-sprites>)
This function might be used to modify large layouts with regards to recolouring. It addresses the upper 2 bytes of the sprite number which contains its recolour translation, see examples. In this way, different custom recolourings could be applied, depending on a supplied parameter. In addition, it would be possible to change a sprite using company colour into a sprite to be recoloured by custom recolouring, or vice versa.
First parameter must be a grf parameter defining the recolour sprite number to be used, with the number of tiles to be processed given by parameter 2, and the number of building sprites per tile given by parameter 3. Function patchlayout() must directly precede its associated layout() function.
define(_COLOUR,4) setparameter(_COLOUR, 0x314) // TTD recolour sprite ... skipif(1, getowngrfparameter(0), BITSET, _COLOUR) patchlayout(_COLOUR, 90, 1) layout(LOW_BLOCK, // 0 // F4 03 00 00 00 00 00 10 10 18 45 84 <00 00> 80 tile( ground(1012) compcol(24, xyz(0,0,0), dxdydz(16,16,24)) ) ... // 89 // F3 03 00 00 00 00 00 10 10 18 51 84 <00 00> 80 tile( ground(1011) compcol(36, xyz(0,0,0), dxdydz(16,16,24)) ) )
This sets the recolour sprite number from parameter 4 for this layout's 90 tiles.
define(_COLOUR,4) setparameter(_COLOUR, 0x314) // TTD recolour sprite ... skipif(1, getowngrfparameter(0), BITSET, _COLOUR) patchlayout(_COLOUR, 2, 2) layout(OPASS_LOW, // F4 03 00 00 00 00 00 05 05 08 2D 84 <00 00> // 00 00 00 10 10 18 2E 84 <00 00> 80 // F3 03 00 00 00 00 00 05 05 08 2F 84 <00 00> // 00 00 00 10 10 18 30 84 <00 00> 80 tile( ground(1012) compcol(0, xyz(0,0,0), dxdydz(5,5,8)) compcol(1, xyz(0,0,0), dxdydz(16,16,24)) ) tile( ground(1011) compcol(2, xyz(0,0,0), dxdydz(5,5,8)) compcol(3, xyz(0,0,0), dxdydz(16,16,24)) ) )
This sets the recolour sprite number from parameter 4 for this layout's 2 tiles with 2 building sprites each.
Note that it is not possible to recolour neither ground sprites nor 'child' sprites (i.e., those sharing the bounding box with a preceding sprite with an own bounding box). See description for functions regular(), compcol() and recolour().
Functions for station performance
These functions are used to evaluate game-intrinsic variables, and make them accessible to the station's activation function. A typical application would use a multitude of these functions linked together to form a "chain", connecting the graphics' sprites of an station with its activation function. See here for an example.
anim_frame([<Coordinate> | REGISTER,] <block>)
This function returns the actual value of the animation counter of the current tile to decide the frame to be displayed. The return value is between zero and the number of animation frames specified in the station definition's property anim_info().
If an additional coordinate parameter is given, the actual value of the animation counter of the given tile is returned. Please note that coordinates must be given by auxiliary function pos(<x>, <y>,). In case of REGISTER as the first parameter, the coordinate is not explicitly given but taken as the result from a preceding calculation.
This function returns the current animation trigger when in a CB_ACONTROL callback.
def(8) anim_trigger( animcontrol(0, SND_HORN) if(BUILT) // start animation on construction ref(6) if(NEWCARGO) // probably start of animation when cargo arrives animcontrol(A_NOP) else // do nothing ) def(9)callback( ref(8) if(CB_ACONTROL) // animation control ref(7) if(CB_AFRAME) // animation frames ref(1) else // graphics )
This function checks for a callback incidence, and if so, returns the type of the incurred callback. See example above.
cargohist(<CargoType> | REGISTER, <block>)
This function returns a 4-bit value about history of the given cargo type having been accepted on the station in the past:
Bit | Value | Description |
n/a | 0 | cargo was never accepted at this station |
0 | 1 | cargo was ever accepted at this station |
1 | 2 | cargo was accepted last month |
2 | 4 | cargo was accepted this month |
3 | 8 | cargo was accepted since last periodic processing (every 250 ticks) |
This function returns the cargo type for animation triggers NEWCARGO or NOCARGO being set.
If the newGRF has installed a cargo translation table, the cargo type will be an index into that table, or "255" (0xFF) if the cargo isn't included in the table. If there's no cargo translation table, the cargo type will simply be the climate-dependent cargo type.
This function returns the number of months exclusive transport rights, having been bought in the area, will still be in effect. If nonzero, the station will not receive any cargo.
Path-based signalling (PBS) is a feature that allows multiple trains to share a signal block, as long as their paths do not interfere. This greatly enhances entrances and exits of stations as well as junctions.
As a train approaches a path-based junction, it will reserve a path through it. If another train's desired path would cross the path of that train, it waits at the signal to reserve a path before entering the junction.
This function returns the following value:
Value | Label | Description | |
7 | 111 | PBSRESERVED | reserved track |
4 | 100 | PBSUNRESERVED | unreserved track |
2 | 010 | PBSNONE | no PBS |
The bits are defined this way to allow easy fallback to non-PBS cases. If you need graphics to show the "unreserved" state in non-PBS cases, use bit 0, but if you need the "reserved" state in non-PBS cases, use bit 1. To explicitly check whether PBS is active or not, use bit 2.
All other bits are reserved and must not be used.
At the moment, PBS on/off refers to the switch setting, in a future alpha version it will actually refer to whether the switch is on and the current block actually uses PBS.
This set of functions returns information about the current tile, which platform it is on and how far along the platform. Coordinates are defined as shown in the picture below:
Depending on the type of station, above functions consider different sections of the station in question. For regular stations, the whole station is evaluated; and for irregular stations, the entire length and all adjacent platforms are being counted.
The term "counting" here refers to starting at the tile in question, and counting tiles in all four directions. The two directions aligned with the station direction will be the length of the station, and the other two directions give the number of platforms, see picture. For all of the above functions, this counting stops at the edge of the station, i.e. the first non-station tile.
As shown in above picture, functions either count from the northern most edge of the station, or from its southern most edge (plt_numrev(), plt_posrev()). For functions plt_midpos() and plt_midnum(), their return format are the positions counted from the middle, i.e. the center tile has "number of platform" = 0 and "position along platform" = 0. For even lengths and numbers of platforms, the middle tile is at position length/2 resp. number of platforms/2, e.g. for length = 6, it is tile 3 (i.e. the fourth tile). Counting prefers an extra negative number for an even count (because there are 8 negative numbers available but only 7 positive ones):
Count | Numbers |
1 | 0 |
2 | -1 0 |
3 | -1 0 1 |
4 | -2 -1 0 1 |
5 | -2 -1 0 1 2 |
6 | -3 -2 -1 0 1 2 |
Negative numbers are to be accessed by using aux function signed() in function if(). See example.
To difference between counting number of platforms and position on platforms over the whole station, or only about a section of station tiles being built in association with the current tile, m4nfo uses bracket function self(). See example below.
Some of above functions allow use of function shiftmask(). Since number and lengths of station platforms is restricted to 15, the shift parameter is restriceted to the range 0 .. 3, and the mask parameter to the range 1 .. 15 (0x0F).
// single or multi tile? def(6) plt_total( ref(24) if(1) // single ref(5) else // not single ) // position at platform def(7) plt_pos( ref(6) if(0) // edge back ref(3) else // edge front or middle ) // index of station tiles def(8) plt_index( self( cbr(2) if(nibble(0,0)) // x=0, y=0 cbr(4) if(nibble(0,1)) cbr(6) if(nibble(0,2)) cbr(8) if(nibble(1,0)) cbr(10) if(nibble(1,1)) cbr(12) else ) ) def(9) plt_midpos( self( ref(0x76) if(signed(-2)) ref(0xB0) if(signed(-1)) ref(0xB1) if(0) ref(0x7A) if(1) ref(0x79) else ) )
plt_edges() examines the location of the current tile inside a station with respect to the station's edges. The function returns a nibble with bits set for each station edge associated with the current tile. Bit counting is clockwise starting at the north-eastern edge, see picture:
def(2) plt_edges( cbr(0) if(0) cbr(2) if(1) cbr(4) if(2) cbr(6) if(3) cbr(8) if(4) cbr(10) if(5) cbr(12) if(6) cbr(14) if(7) cbr(16) if(8) cbr(18) if(9) cbr(20) if(10) cbr(22) if(11) cbr(24) if(12) cbr(26) if(13) cbr(28) if(14) cbr(30) else )
This function returns the type(s) of vehicles(s) having been visiting this station in the past, either TRAIN, BUS, TRUCK, AIRCRAFT, SHIP, or any combination:
def(5) servicedbytype( ref(4) if(BUS + TRUCK) // service by rvs ref(3) else )
This function returns information about the current sprite type, i.e. whether it is a ground sprite (GROUNDSPRITE), a custom foundation sprite (FOUNDATION) or a building sprite (BUILDING). For this to work, the appropriate flag(s) have to be set in the station's flags() property function.
// ground sprite, foundation or building? def(8) spritetype( ref(__groundsprites) if(GROUNDSPRITE) // ground sprite overlays ref(__foundations) if(FOUNDATION) // custom foundations ref(__buildingsprites) else // building sprites ) def(9) callback( ref(7) if(CB_LAYOUT) ref(8) else )
When using the Advanced Sprite Layer feature of OpenTTD, the allowed range of values are 0 .. 7.
This function returns the station age in years since 1920. The result is returned as a WORD:
def(14) stationage( ref(2) if(<40) ref(4) if(41 .. 59) ref(6) if(60 .. 79) ref(10) else )
These functions return information about individually to be built station sections when in a CB_TILETYPE callback. Same format as plt_* functions above, but because these functions deal only with a station section yet to be built, no call to self() for "individually built sections" would be needed.
Functions test_position() and test_size() require auxiliary function nibble() to extract single return values.
definestation( ... nontrack(TTD_ROOFBOTH) // TTD tile types 4/5 and 6/7 ) ... def(3) test_position( cbr(4) if(nibble(0,0)) // make tile x=0/y=0 non-track cbfail() else ) // menu def(13) callback( cbr(18) if(CB_LAYOUT) ref(3) if(CB_TILETYPE) ref(ALL_ICON) else )
These functions allow access of platform info when in a CB_SLOPE callback, i.e. before the station tile is built. All functions are counting platform numbers and lengths from the northern most corner of the station, as shown here.
Like above, but this function evaluates the to be built tile's slope in association with a "land slope check" callback (CB_SLOPE). Returned slope values are the same as in tinfo_slope().
def(45) test_slope( ref(44) if(NORTH+WEST, NORTH+EAST) // in x: back + front ref(44) if(SOUTH+WEST, SOUTH+EAST) // in y: back + front DISALLOW else )
tinfo_flatwater(<Coordinate> | REGISTER, <block>)
This function returns a value of "32" if the tile given by <coordinate> is a flat/full water tile, else the slope value for this tile is returned in the lower 5 bits.
The coordinate parameter defines the offset relative to the current tile. Both nibbles are considered signed. Negative offsets move north/westwards, positive ones south/eastwards, accordingly. Coordinate parameter needs to be given by the auxiliary function pos(<x>, <y>,). In case of REGISTER as the first parameter, coordinates are not explicitly given but taken as the result from a preceding calculation, see calculate(). Both prerequisites also hold for functions tinfo_lclass(), tinfo_slope(), tinfo_statid(), tinfo_terrain(), tinfo_water(), and tinfo_waterclass().
tinfo_grfid(<Coordinate> | REGISTER, <block>)
This function returns the grf ID of the station tile given by <coordinate>, or "0" if the tile's station is a default station, or "0xFFFFFFFF" if the selected tile is not a station tile. Note that the result has to be checked by using auxiliary function label() in function if(). See example there.
tinfo_height([<Coordinate> | REGISTER,] <block>)
This function returns the height of the lowest corner of the tile at the given coordinate. If the coordinate parameter is omitted, the curent tile is assumed.
tinfo_lclass(<Coordinate> | REGISTER, <block>)
This function returns the landscape class of the tile at the given coordinate (s.a.):
Value | Label | Description |
0 | LC_TERRAIN | bare land, grass, rocks, fields |
1 | LC_RAIL | railway track, with and w/o signals, fences |
2 | LC_ROAD | road, level crossings, depots |
3 | LC_HOUSE | town buildings |
4 | LC_TREES | climate-dependent trees |
5 | LC_STATIONTILE | railway station, airport, lorry and bus station, ship dock |
6 | LC_WATER | water, coast, river bank, ship depot |
7 | LC_VOID | invisible border at bottom edges of map |
8 | LC_INDUSTRYTILE | industry tile types |
9 | LC_TUNNELBRIDGE | railway or road tunnel, bridge |
10 | LC_OBJECT | transmitter, lighthouse, statue, company-owned land, headquarter |
tinfo_slope(<Coordinate> | REGISTER, [<shift-mask>,] <block>)
This function returns byte-packed slope information for the given station's tile. Given information is in relation to the lowest corner of the tile, i.e. WEST means that the western corner is above the lowest corner, etc.
This figure illustrates which kind of slope belongs to which corners raised:
You may as well check for so-called "steep slopes" (i.e. the corner opposite to the lowest one is two units higher) by using the value STEEP together with any three other corners, but keep in mind that stations may not be placed on a steep slope.
Extra parameter <shift-mask> might be applied to further refine the function's result. For this to work, the first parameter of function shiftmask() (the <shift> parameter) has to be set to zero (0), i.e. no shifting at all. The <mask> parameter should be set to a reasonable value, depending on the aim to achieve with result masking.
def(11) tinfo_slope(pos(0,0), shiftmask(0, NORTH+WEST+SOUTH+EAST), cbr(10) if(0) // flat: ticket machine left cbr(12) if(NORTH+WEST, NORTH+WEST+EAST, NORTH+WEST+SOUTH) // ticket machine left cbr(10) if(SOUTH+EAST, SOUTH+EAST+WEST, SOUTH+EAST+NORTH) // fence left cbr(40) else // fence left and right )
In this example, slopes to check for are limited to any combinations of NORTH, WEST, SOUTH, and EAST, i.e. STEEP slopes would be automatically excluded from the "else" branch.
tinfo_statid(<Coordinate> | REGISTER, [<shift-mask>,] <block>)
This function returns the station ID at the given tile coordinate in relation to the current station tile in its lower Byte. In addition, it returns additional information in its high Byte (s.a.):
Bit | Value | Meaning | |
0x100 / 0x200 | _THISGRF / _OTHERGRF | Tile membership (0: original TTD, 1: defined in current newGRF, 2: defined in foreign newGRF) | |
2 | 0x400 | _THISSTAT | Tile belongs to current station if bit set |
3 | 0x800 | _PERPENDICULAR | Tile is parallel (bit clear) or perpendicular (bit set) to the current one |
TTD type platform info (0: plain platform; 1: platform with building; 2: platform with roof, left side; 3: platform with roof, right side) | |||
Reserved, do not use |
You should use auxiliary function shiftmask() to access the needed bits. E.g., when checking a tile's ID, you should always check if it was defined in the current newGRF, hence you'd have to check not only for the ID itself, but for the value _THISGRF + ID. In case you want to check for a foreign newGRF, you should use _OTHERGRF + ID.
If the tile is not a station tile at all, the return value will be 0xFFFF, i.e. all bits set.
Please note that this function's first parameter has to be given by the auxiliary function pos(<x>, <y>)
define(ROOFS,0x0E) def(1) tinfo_statid(pos(0,-1), shiftmask(0,_THISGRF+ROOFS), cbr(1) if(_THISGRF+ROOFS) // station-ID is 0x0E cbr(2) if(0xFFFF) // not station cbr(0) else // other station-ID )
Sometimes it is needed to check for a station tile's "tile type" as well (since the IDs might be the same). This can be done as well:
define(_THISGRF_TT, 0x3100) // mask "tiletype" and "defined in this GRF" def(17) tinfo_statid(pos(1,0), shiftmask(0,_THISGRF_TT+0xFF), cbr(2) if(0x2100+PTRACK_OPASS) // tiletype for overpass is "4" (2) cbr(6) if(0x3100+PTRACK_OPASS) // tiletype for overpass is "6" (3) cbfail() else )
tinfo_terrain([<Coordinate> | REGISTER,] <block>)
This function returns the terrain type for the current tile of the station (s.a.). Return values are NORMAL, DESERT, RAINFOREST, and SNOW (on or above snowline).
If an additional coordinate parameter is given, the terrain type of the given tile is returned. Please note that coordinates must be given by the auxiliary function pos(<x>, <y>,)
tinfo_trackconnect([<shift-mask>,] <block>)
This function returns bit-coded information whether the rail tracks continue in the eight tiles adjacent to the station tile.
Bit | Value | Set if rail continues in direction of: |
0 | 0x01 | +Length |
1 | 0x02 | -Length |
2 | 0x04 | +Platforms |
3 | 0x08 | -Platforms |
4 | 0x10 | +Length, +Platforms |
5 | 0x20 | -Length, +Platforms |
6 | 0x40 | +Length, -Platforms |
7 | 0x80 | -Length, -Platforms |
The following picture illustrates which bits represent which tile for the two possible station orientations:
Bits 0 to 3 are set if there is track on the given tile, and it has a connection to the station tile. For bits 2, 3, the station of course has itself no connection to those tiles, but this doesn't matter for this variable. Bits 4 to 7 check connections to the neighbouring platform tile, i.e. bits 4 and 5 resp. 6 and 7 indicate a connection from that tile to tile 2 resp. 3.
Extra parameter <shift-mask> might be applied to further refine the function's result bits. For this to work, the first parameter of function shiftmask() (the <shift> parameter) has to be set to zero (0), i.e. no shifting at all. The <mask> parameter should be set to a reasonable value, depending on the aim to achieve with bit masking.
def(24) tinfo_trackconnect( ref(21) if(1) // top ref(22) if(2) // bottom ref(25) if(4,8) // sideways - only on (false) slope ref(23) else // double buffer )
tinfo_trackexist([<shift-mask>,] <block>)
This function returns bit-coded information whether there are rail tracks on the tile and disregard whether the track is connected to the station or not entirely. Bit-coded results are handled in the same way as for tinfo_trackconnect().
This function returns the railway track type, with 0 = regular rail, 1 = electrified rail, 2 = monorail, 3 = maglev, and any additional rail according to its slot number.
tinfo_water(<Coordinate> | REGISTER, <block>)
This function returns a value of "1" if the tile given by <coordinate> is a full water or a coast tile, else "0" is returned. Again, the coordinate parameter needs to be given by the auxiliary function pos(<x>, <y>,)
def(47) tinfo_water(pos(-1,0), // back ref(2) if(1) // is water ref(46) else )
tinfo_waterclass(<Coordinate> | REGISTER, <block>)
This function returns the "water class" of the tile given by <coordinate>. Water class values are as follows:
Value | Meaning | |
0 | WC_LAND | Undefined / land |
1 | WC_SEA | Sea, ocean |
2 | WC_CANAL | Canal |
3 | WC_RIVER | River |
Unlike the landscape class "water", the "water class" is not changed when a water tile is built over, e.g. by an object showing a ship. Using tinfo_waterclass() might come in handy in this case.
Again, the coordinate parameter needs to be given by the auxiliary function pos(<x>, <y>)
This function returns the year the station was built. The base year is 1920.
def(4) yearbuilt( cbr(2) if(<1940) cbr(4) if(1940 .. 1959) cbr(6) if(1960 .. 1979) cbr(8) if(1980 .. 1999) cbr(10) else )
random(<list::trigger>, <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.
Trigger | Meaning |
BUILT | when constructed (no trigger) |
NEWCARGO | new cargo waiting |
NOCARGO | no more cargo |
ARRIVE | train arrives (starts unloading/loading) |
LEAVE | train leaves (done unloading & loading) |
LOADING | train loads or unloads cargo |
PBSRESERVE | train reserves platform (using PBS) |
ALLTRIGGERS | re-randomize only if all triggers have occurred |
Also note that none of the above triggers will actually trigger unless property function setcargotriggers() has at least one trigger set. NEWCARGO will be triggered for any of the cargo types set in setcargotriggers(), but NOCARGO will only be triggered if all of those cargo types have no more cargo waiting. Triggers ARRIVE, LEAVE and PBSRESERVE are triggered no matter what cargo types the train transports, as long as at least one trigger has been set.
Triggers ARRIVE, LEAVE, LOADING and PBSRESERVE only affect the platform on which they occur, as well as the random bits of the station, but not other platforms.
Because already occured triggers are only stored once per station (not distinguishing tiles or cargo types), adding ALLTRIGGERS to random triggers does not make much sense for stations.
Stations have 16 random bits (bits 0 .. 15) shared for the whole station, and 4 random bits (bits 16 .. 19) per station tile. TTDPatch actually only implements 8 shared random bits, i.e. bits 8 .. 15 are always zero.
Setting randombit determines the first bit to be re-randomized, as well as basing the random graphics on. Only those bits that actually get triggered will be re-randomized. This will make it possible to have independent sets of bits for independent triggers. 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.
To get tile-based randomness, therefore one has to use randombit=16 and a number of references of no more than 16 (since only 4 random bits are available per tile).
The number of referenced sets to choose from must be a power of 2, i.e. 2, 4, 8, 16 etc.
def(13) random(ARRIVE,16,ref(1),ref(2),ref(2),ref(3)) def(13) random({ARRIVE, LEAVE},16,ref(1),ref(2),ref(2),ref(3))
Note that re-randomisation, both in TTDPatch and in OpenTTD, only takes place in the graphics chain, but not for callbacks. I.e., if a random action is used inside a callback chain and it should re-randomise the trigger bits, there has to be added a "dummy" random action into the graphics chain as well, for the only purpose to re-randomise the bits.
Therefore, for using random functions in callback chains, m4nfo supplies two special functions:
This function is used in callback chains like the normal random() function is used in the graphics chain, except that it won't ever re-randomize the specified trigger(s). Instead, this is done by function
which has to be placed in the graphics chain. By specifying different labels, different randomisations can be used in parallel. See example.
// top platform def(6) randomcb(_TOP, ARRIVE, 16, ref(1), ref(2), ref(2), ref(3)) // bottom platform def(7) randomcb(_BOTTOM, {ARRIVE, NOCARGO}, 16, ref(1), ref(2)) def(8) plt_num( self( ref(6) if(0) // top platform ref(7) else // bottom ) ) [...] def(9) rerandom(_TOP, ref(0) // graphics ) def(10) rerandom(_BOTTOM, ref(0) // graphics ) def(11) plt_num( self( ref(9) if(0) // top platform ref(10) else // bottom ) ) // check callbacks def(12) callback( ref(8) if(CB_LAYOUT) // callback ref(11) else // graphics/re-randomisation )
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 adjusts a performance function result to a more useful range. The first parameter defines the value to add to the result, and the second parameter specifies the modulo (remainder of division by) on the sum of the performance function result and the first parameter.
def(10) plt_midnum(addmodulo(1,2), self( ref(5) if(1) ref(6) else ) )
This function is used to check a returned byte-packed coordinate value in the usual form (<x>, <y>). Parameter range is [-8 ... +7], both for x- and y-offsets. For an example see here.
This function supplies the given coordinates in a byte-packed form to another function. Parameter range is [-8 ... +7], both for x- and y-coordinates. Please note that the coordinate (0,0) refers to the current tile itself. For an example see below.
This function defines a hierarchy level. Instead of the whole station, only the section of station tiles being built in association with the current tile are addressed by the appropriate performance function, see here for examples.
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.
// (middle) def(8) tinfo_slope(pos(0,0),shiftmask(0,NORTH+SOUTH), ref(5) if(NORTH) ref(6) if(SOUTH) ref(7) else )