In m4nfo, objects are using four types of functions:
Functions for sprite layout definition
In m4nfo, three different formats for sprite layouts are provided. First one is a simple layout for just one building sprite:
The second format allows to combine more than one sprite per tile, as well as the inclusion of 'child sprites', i.e. those sharing their 3D-bounding boxes with the prior sprite:
In newer versions of OpenTTD, a third (advanced) format for sprite layouts is supported, which allows 'dynamic' layout modifications by using registers.
m4nfo formal specification for this one is as above, except there are two additional parameters in m4nfo functions regular(), recolour(), glass(), namely <flags> and <registers>. Function aslflags() takes a quoted list of following flags as its parameter(s):
Flags in advanced sprite layouts | ||||
Value | Label | parent sprite | ground/child sprite> | registers |
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 |
The needed registers are set by function registers(), also as a quoted list. The order of registers must be the same as the flag ranks from above. For flags not used, no register is needed. 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. Skipping a parent sprite will also skip all child sprites within the parent's bounding box.
For more information, see info about station sprite layout, and for an example see Example 7 below.
m4nfo provides auxiliary functions to be used in above sprite layout definitions, which are the same for houses, objects and industry tiles, see here.
def(16) spriteset( ground(NOSPRITE) regular(0, xy(0,0), dxdydz(16,16,16)) )
This sprite layout uses no ground sprite, uses the first sprite from the coresponding sprite block as its building sprite, and its bounding box covers the whole tile (16 * 16) and has a height of 16 pixels.
def(22) spriteset( ground(WATER) regular(1, xyz(0,0,0), dxdydz(16,16,6)) regular(MOLESTAIRS, xyoff(19,6)) )
This sprite layout uses the original water tile as its ground sprite, uses the second sprite from the coresponding sprite block as its building sprite, and its bounding box covers the whole tile (16 * 16) with a height of 6 pixels. There's a child sprite sharing the parent's bounding box with its origin at coordinate x=19 and y=6 with the parent's graphics sprites.
def(50) spriteset( ground(WATER) regular(2, xyz(0,0,0), dxdydz(16,16,6)) regular(LIGREEN5, xyz(1,5,6), dxdydz(8,8,16)) )
This sprite layout uses the original water tile as its ground sprite, uses the third sprite from the coresponding sprite block as its building sprite, and its bounding box covers the whole tile (16 * 16) with a height of 6 px. There's another sprite located at coordinates x=1 and y=5, starting at a height of 6px with its own bounding box of 8 * 8 px and a height of 16 px.
def(51) spriteset( ground(WATER) regular(2, xyz(0,0,0), dxdydz(16,16,6)) regular(LIGREEN5, xyz(1,5,6), dxdydz(8,8,16)) regular(ANIMGREEN, xyoff(4,2)) )
This sprite layout is in principal the same, except of an additional child sprite added to its second sprite. The graphics associated with this child sprite is located at x=4 and y=2 in relation with its parent sprite.
def(0) spriteset( ground(NOSPRITE) notransparency(2, xyz(0,0,0), dxdydz(16,16,7)) // foundation notransparency(1, xyoff(0,1)) // real groundsprite regular(0, xyz(0,0,0), dxdydz(16,16,16)) // building )
This example shows how to use custom foundations sprites: Include the custom foundation as the first building sprite (with no groundsprite), include a child sprite for the real groundsprite (its graphic sprite has to have (0,0) as its offsets), and add the building sprite as third with its own bounding box. For this to work, the flag NOFOUNDATION has to be set in property function flags().
The extended format also allows the use of multiple ground sprites for a tile, which is helpful if you want to use the usual TTD (or custom) terrain ground tiles, but still need to add features to it without using a new bounding box. To do so use the syntax of sprites sharing the previous bounding box, but use it before the first bounding box definition:
def(28) spriteset( ground(COASTYV) regular(SHD+75, xyoff(0,4)) // 2nd groundtile! regular(6, xyz(0,0,0), dxdydz(16,16,6)) // quay (1st bb definition) regular(11, xyoff(20,10)) // warning stripes for quay regular(SHD+49, xyz(0,0,1),dxdydz(8,8,16)) // shed extension )
asl_on() ... def(4) spriteset( ground(GRASS) recolour(2, xyz(0,0,0), dxdydz(16,16,1), _MINERALS, aslflags(OFFSET_RCSPRITE), registers(REGISTER)) // dump x regular(4, xyz(0,0,1), dxdydz(8,16,16)) // crane ) ... def(5) setregisters(REGISTER, __COAL, ref(4)) def(6) setregisters(REGISTER, __IRONORE, ref(4)) def(7) getviews( ref(5) if(0,1) // coal ref(6) else // iron ore )
Please note that x- and y pixel offsets refer to different spots of the "parent" sprite when being used for ground or building tiles. See picture.
Functions for object performance
These functions are used to evaluate game-intrinsic variables, and make them accessible to the object'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 object with its activation function. See here for an example.
Function | Description |
anim_frame([<Coordinate>,] [<var-adjust>,] <block>) | Get animation frame |
callback(<block>) | Check incidence (and type) of callback |
colour (<block>) | Get colour of object |
constructiondate(<block>) | Construction date from year 0 (in days) |
constructionyear(<block>) | Construction year |
else | This is really a void statement |
getviews (<block>) | Get the object 'views' |
objinfo_e2distance(<block>) | Get square of Euclidean distance of closest town |
objinfo_founder(<block>) | Get founder company ID |
objinfo_height([<Coordinate>,] <block>) | Get height of lowest corner of tile |
objinfo_lclass([<Coordinate>,] <block>) | Get landscape class of tile |
objinfo_mdistance(<block>) | Get Manhattan distance to next town |
objinfo_randombits([<Coordinate>,] <block>) | Get random bits at tile |
objinfo_slope([<Coordinate>,] <block>) | tile info: slopes, steep slopes ignored |
objinfo_terrain([<Coordinate>,] <block>) | Get terrain type |
objinfo_townzone(<block>) | Get town zone (TZ_CENTRE .. TZ_OUTSKIRTS) |
objinfo_type(<Coordinate>, <block>) | Get object type at tile |
objinfo_type4(<type>, <block>) | Examine 4-neighbourhood of tile |
objinfo_typeview(<Coordinate>, <block>) | Get object type and view at tile |
objinfo_view(<Coordinate>, <block>) | Get object view at tile (since OpenTTD r26316) |
objinfo_water([<Coordinate>,] <block>) | Returns 1 if flat water, else 0 |
objinfo_waterclass([<Coordinate>,] <block>) | Returns "class" of water |
position(<block>) | Check position of tile in layout |
testposition(<block>) | Get position in layout for CB_SLOPE |
testslope(<block>) | Slope check for CB_SLOPE |
yearbuilt(<block>) | Construction year of object |
randomrel/randomabs(<trigger>, <randombit> <list::reference>) | Get random reference |
anim_frame([<Coordinate>,] [<var-adjust>,] <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 object definition's property anim_info(). Please note that the flag HASANIMATION in defineobject() must be set for this to work.
If an additional coordinate parameter is given, the value for the thus addressed tile of the object (relative from the current tile) is returned instead of the value for the current tile. Please note that coordinates must be given by the auxiliary function pos(<x>, <y>)
def(23) anim_frame( ref(51) if(0 .. 2, 5 .. 7) // light ref(50) else )
Optional parameter <var-adjust> may be used to adjust the function's result to a more useful range, see here.
defineobject(_TEST, ... anim_info(24) // 3 * 8 frames ) def(10) anim_frame(shiftmask_add_mod(0,0xFF,0,8), ref(1) if(0) ref(2) if(1) ref(3) if(2) ref(4) if(3) ref(5) if(4) ref(6) if(5) ref(7) if(6) ref(8) else )
This function checks for a callback incidence, and if so, returns the type of the incurred callback.
This function returns the current view of the object. The returned value is either 1, 2 or 4, depending on the number of views specified in the property function numviews() during the object´s definition.
def(60) getviews( ref(50) if(0) // mole in x ref(54) if(1) // mole in y ref(52) if(2) // mole in x with stairs ref(56) else // mole in y with stairs )
This function returns the square of the Euclidean distance between the object's current tile and the nearest town.
This function returns the ID of the company that funded the object, or the value "16" if the object had been placed in the scenario editor.
objinfo_height([<Coordinate>,] <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.
objinfo_lclass([<Coordinate>,] <block>)
This function returns the landscape class of the tile at the given coordinate: If the coordinate parameter is omitted, the curent tile is assumed.
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 |
This function returns the Manhattan distance between the current tile and the nearest town.
objinfo_randombits([<Coordinate>,] <block>)
This function returns the random bits for the given object's tile.
If an additional coordinate parameter is given, the value for the thus addressed tile (relative from the current tile) is returned instead of the value for the current tile. Please note that coordinates must be given by the auxiliary function pos(<x>, <y>).
objinfo_slope([<Coordinate>,] <block>)
This function returns byte-packed slope information for the given object'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. Slope values available are: FLAT, NORTH, WEST, EAST, SOUTH, STEEP. See example.
If an additional coordinate parameter is given, the value for the thus addressed tile (relative from the current tile) is returned instead of the value for the current tile. Please note that coordinates must be given by the auxiliary function pos(<x>, <y>).
This figure illustrates which kind of slope belongs to which corners raised:
def(33) objinfo_slope( ref(0) if(NORTH+WEST) // front x ref(1) if(NORTH+EAST) // front y ref(2) if(SOUTH+EAST) // back x ref(3) if(SOUTH+WEST) // back y ref(4) else // error )
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 not every object may be placed on a steep slope.
objinfo_terrain([<Coordinate>,] <block>)
This function returns the terrain type for the current tile of an object, or of a neighbour tile if <coordinate> is given. Values are NORMAL, DESERT, RAINFOREST, and SNOW (on or above snowline).
This function returns the town zone value at the current tile.
Returned values are as follows:
Value | Meaning |
TZ_CENTRE | Innermost zone, street lights |
TZ_COMMERCIAL | Trees |
TZ_RESIDENTIAL | Paved roads |
TZ_PERIPHERY | Plain roads |
TZ_OUTSKIRTS |
objinfo_type(<Coordinate>, <block>)
This function returns the object type at the given tile coordinate in relation to the current object's tile. Please note that this function's first parameter has to be given by the auxiliary function pos(<x>, <y>,)
def(23) objinfo_type(pos(0,1), ref(22) if(_MOLE .. _MARINA1) // every mole tile ref(30) if(_PIER) // wooden piers ref(0) else // plain quay front )
objinfo_type4(<type>, <block>)
This function examines the 4-neighbourhood of the current tile for the given tyle type. The function returns a nibble with bits set for each occurence of the given tile type.
The function's main purpose is to give object tiles a possibility to adapt to neighbour tiles of a given type, e.g. when placing fences or walls around a whole set of objects.
Bit | 0 | 1 | 2 | 3 |
Coordinate | 1,0 | 0,1 | -1,0 | 0,-1 |
objinfo_typeview(<Coordinate>, <block>)
This function returns the object type and its view at the given tile coordinate in relation to the current object's tile. Please note that this function's first parameter has to be given by auxiliary function pos(<x>, <y>,)
To query the function result, helper function typeview(<type>|<Range::type>,<view>) is needed:
def(14) objinfo_typeview(pos(1,0), // check upper tile ref(47) if(typeview(_QUAY2,3)) // view is '3' ref(43) else // diff tile )
Returning the object view at a given offset is only available since OpenTTD r26316.
objinfo_view(<Coordinate>, <block>)
This function returns the object view at the given tile coordinate in relation to the current object's tile. Please note that this function's first parameter has to be given by the auxiliary function pos(<x>, <y>,)
Again, this feature is only available since OpenTTD r26316. For offset (0,0) this function returns the same value as function getviews().
objinfo_water([<Coordinate>,] <block>)
This function returns a value of "1" if the object tile given by <coordinate> has water on it, else "0" is returned. If the coordinate parameter is omitted, the curent tile is assumed.
A value of "1" is set for coasts as well. If you need a full water tile, check the slope data as well. If it's zero (flat tile), the tile is fully watered. Again, the coordinate parameter needs to be given by the auxiliary function pos(<x>, <y>,)
def(47) objinfo_water(pos(-1,0), // back ref(2) if(1) // end piece ref(46) else // check for possible mole connection )
objinfo_waterclass([<coordinate>,] <block>)
This function returns the "water class" of the object tile given by <coordinate>. If the coordinate parameter is omitted, the curent tile is assumed.
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 objinfo_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 position of the given tile in relation to the northernmost tile of the object, inside the object's layout. The function requires to test its return value by using the auxiliary function nibble(), because its return value is a packed byte containing both coordinates. See example.
def(30) position( ref(0) if(nibble(0,0)) // upper part ref(1) else // lower part )
This function evaluates the current tile's position in association with a "land slope check" callback (CB_SLOPE). Returned position values are byte-packed and have to be evaluated by the auxiliary function nibble(). The associated callback (CB_SLOPE) is executed just before object construction, and it does not require being set via the object's property function callback(). Same as for function testslope().
def(47) testposition( ref(45) if(nibble(0,1)) // front should be sloped ref(46) else // back should be flat )
This function evaluates the current tile's slope in association with a "land slope check" callback (CB_SLOPE). Returned slope values are the same as in objinfo_slope().
def(45) testslope( ref(44) if(NORTH+WEST, NORTH+EAST) // in x: back + front ref(44) if(SOUTH+WEST, SOUTH+EAST) // in y: back + front DISALLOW else )
This function returns the building year of the object.
def(5) yearbuilt( ref(0) if(<1919) ref(1) if(1920 .. 1929) ref(2) if(1930 .. 1939) ref(3) if(1940 .. 1949) ref(4) else )
randomrel(<trigger>, <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.
There are no triggers for objects. You should always use CONSTRUCT.
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.
Objects have 8 random bits per tile. Note that the random bits are unique to each tile in the object, and are not shared across the whole object.
This might lead to problems when randomizing multiple-tile objects, see below.
The number of referenced sets to choose from must be a power of 2, i.e. 2, 4, 8, 16 etc.
def(14) randomrel(CONSTRUCT,0,ref(1),ref(2),ref(3),ref(4))
To randomize a multiple-tile object in a consistent way, i.e. A-A-A, B-B-B, C-C-C, ..., but not A-B-B, A-B-C, ... you may not use function randomrel(), but have to access the random bits of a certain single tile of that multi-tile object from all the other tiles instead. Coordinate access is done by function pos(), but pay attention to the fact that the coordinates are different for the x- and y direction. See example below for a three-tile object with two random appearances.
def(7) ... // 1st random view def(8) ... // 2nd random view // top x/y def(9) objinfo_randombits(pos(0,0), ref(8) if(<127) ref(7) else ) // middle x def(10) objinfo_randombits(pos(-1,0), ref(8) if(<127) ref(7) else ) // bottom x def(11) objinfo_randombits(pos(-2,0), ref(8) if(<127) ref(7) else ) // middle y def(12) objinfo_randombits(pos(0,-1), ref(8) if(<127) ref(7) else ) // bottom y def(13) objinfo_randombits(pos(0,-2), ref(8) if(<127) ref(7) else ) def(14) position( ref(9) if(nibble(0,0)) // top ref(10) if(nibble(1,0)) // middle ref(11) else // bottom ) def(15) position( ref(9) if(nibble(0,0)) // top ref(12) if(nibble(0,1)) // middle ref(13) else // bottom ) def(16) getviews( ref(14) if(0, 2) // x ref(15) else // y )
This pair of functions is used to evaluate coordinate offsets for some of the objinfo_*() functions. They work in a somewhat "inverse" way.
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 here.
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.
def(22) anim_counter(shiftmask(0,0x3FF), animframe(A_NEXT) if(0) // go on animframe(10) else // stay in pause )
typeview(<type>|<Range::type>,<view>)
This function is used to check the byte-packed return value of function objinfo_typeview(). For an example see there.
For checking more than one typeview, helper macros allviews2(<type>), allviews4(<type>), allviews_x(<type>), and allviews_y(<type>) might be used. See table.
Function | Views | |||
allviews2 | 0 | 1 | - | - |
allviews4 | 0 | 1 | 2 | 3 |
allviews_x | 0 | - | 2 | - |
allviews_y | - | 1 | - | 3 |
You may also define ranges of types, see example.
def(4) objinfo_typeview(pos(0,1), // check tile in +y ref(14) if(typeview(_QUAYEXT1 .. _QUAYEXT4,0)) // view 0 for all 4 object types ref(15) if(typeview(_QUAYEXT2,3), allviews_y(_QUAYEXT2)) // views 1 and 3 for QUAYEXT2 ref(16) if(allviews_x(_QUAYEXT1 .. _QUAYEXT4)) // views 0 and 2 for QUAYEXT4 ref(17) if(allviews4(_QUAYEXT3)) // all 4 views for QUAYEXT3 ref(3) else )