The m4nfo User manual and Technical Report

Functions for objects

Using functions for objects

Introduction

In m4nfo, objects are using four types of functions:

Functions for sprite layout definition

Format

In m4nfo, three different formats for sprite layouts are provided. First one is a simple layout for just one building sprite:

spriteset(<groundsprite>, <buildingsprite>, <xoffset, yoffset>, <xextent, yextent, zextent>)

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:

spriteset(
   {set(<groundsprite>, <buildingsprite>, <xoffset, yoffset, zoffset>, <xextent, yextent, zextent>)}
   {(<buildingsprite>, <xpixeloffset, ypixeloffset>)}
)

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
1SKIPSkip bounding box (including child sprites)Skip sprite1
2OFFSET_SPRITEAdd offset to sprite number, disable default usage of construction stage or railtype-offset1
4OFFSET_RCSPRITEAdd offset to recolour sprite number
8CUSTOM_RCSPRITERecolour sprite is from custom spriteblock, OFFSET_SPRITE may be used as welln/a
16OFFSET_XYAdd 3D offset in <x> and <y> 2
32OFFSET_ZAdd 3D offset in <z> 1
48PIXOFFSET_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 functions

m4nfo provides auxiliary functions to be used in above sprite layout definitions, which are the same for houses, objects and industry tiles, see here.

Examples

Example 1 (simple sprite layout):
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.

Example 2 (1 child sprite):
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.

Example 3 (multiple 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.

Example 4 (multiple sprites plus child sprite):
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.

Example 5 (including custom foundations):
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:

Example 6 (using 2nd ground sprite):
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
)

Example 7 (using advanced sprite layout for recolour sprites):
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
elseThis 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

Description

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>)

Example (lighthouse blink):
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.

Example (run 3 loops of 8 frames each):
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
)

callback(<block>)

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

getviews(<block>)

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.

Example (object with 4 views):
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
)

objinfo_e2distance(<block>)

This function returns the square of the Euclidean distance between the object's current tile and the nearest town.

objinfo_founder(<block>)

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.

ValueLabelDescription
0LC_TERRAINbare land, grass, rocks, fields
1LC_RAILrailway track, with and w/o signals, fences
2LC_ROADroad, level crossings, depots
3LC_HOUSEtown buildings
4LC_TREESclimate-dependent trees
5LC_STATIONTILErailway station, airport, lorry and bus station, ship dock
6LC_WATERwater, coast, river bank, ship depot
7LC_VOIDinvisible border at bottom edges of map
8LC_INDUSTRYTILEindustry tile types
9LC_TUNNELBRIDGErailway or road tunnel, bridge
10LC_OBJECTtransmitter, lighthouse, statue, company-owned land, headquarter

objinfo_mdistance(<block>)

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:

Example (check slope for building):
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).

objinfo_townzone(<block>)

This function returns the town zone value at the current tile.

Returned values are as follows:

ValueMeaning
TZ_CENTREInnermost zone, street lights
TZ_COMMERCIALTrees
TZ_RESIDENTIALPaved roads
TZ_PERIPHERYPlain 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>,)

Example (check for mole or pier in front):
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.

Bit0123
Coordinate1,00,1-1,00,-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:

Example (check for object type and its view in one go):
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>,)

Example (find end of straight line of objects):
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:

ValueMeaning
0WC_LANDUndefined / land
1WC_SEASea, ocean
2WC_CANALCanal
3WC_RIVERRiver

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>)

position(<block>)

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.

Example (object on 2 tiles):
def(30) position(
 	ref(0) if(nibble(0,0)) // upper part
	ref(1) else 	       // lower part
)

testposition(<block>)

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().

Example (object on 2 tiles):
def(47) testposition(
 	ref(45) if(nibble(0,1)) // front should be sloped
	ref(46) else 	        // back should be flat
)

testslope(<block>)

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().

Example (build object on slopes):
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
)

yearbuilt(<block>)

This function returns the building year of the object.

Example (get building year of 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.

Description

trigger

There are no triggers for objects. You should always use CONSTRUCT.

randombit

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.

List::ref()

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

Example (4 different lighthouses):
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.

Example (multi-tile randomisation):
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
)

Auxiliary functions

This pair of functions is used to evaluate coordinate offsets for some of the objinfo_*() functions. They work in a somewhat "inverse" way.

nibble(<Coordinate>)

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.

pos(<Coordinate>)

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.

shiftmask(<shift>,<mask>)

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.

Example (checking the global animation counter):
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.

FunctionViews
allviews201 --
allviews401 23
allviews_x0- 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
)