The m4nfo User manual and Technical Report

Functions for houses

Using functions for houses

Introduction

In m4nfo, houses are using four types of functions:

Functions for sprite layout definition

Format

In m4nfo, two slightly 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>)}
)

m4nfo provides auxiliary functions to be used in above sprite layout definitions:

Layout term m4nfo function
<groundsprite>ground() | NOSPRITE
<buildingsprite>regular() | notransparency() | glass() | recolour() | NOSPRITE
<xoffset, yoffset, zoffset>xyz()
<xoffset, yoffset>xy()
<xextent, yextent, zextent>dxdydz()
<xpixeloffset, ypixeloffset>xyoff()

ground(<sprite-ID>[, TTD])

regular(<sprite-ID>[, TTD])

These functions define either a 'ground sprite' or a 'building sprite' (or an additional ground sprite). In case of only one parameter, this is the index into the spriteblock associated with the current layout, in case of two parameters (the second being "TTD") the first one is the sprite-ID of an original TTD sprite.

notransparency(<sprite-ID>[, TTD])

This function works the same as regular(), just with its sprite being displayed normally even in "transparent buildings" mode.

glass(<sprite-ID>[, TTD][, <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.

recolour(<sprite-ID>[, TTD], <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.

For houses, if no recolour sprite is given, there are two possibilities: If you have callback CB_COLOUR enabled, it is called to determine the colour mapping. If that callback is disabled or fails, one of the colours specified in property function randomcolours() will be chosen.

For industry tiles, the colour of the containing industry will automatically be applied.

xyz(<byte>, <byte>, <byte>)

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.

xyoff(<byte>, <byte>)

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.

Examples

Example 1 (simple sprite layout):
def(16) spriteset(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(1) spriteset(
	set(regular(0x5CF,TTD), regular(1), xyz(0,0,0), dxdydz(16,16,36))
	set(glass(2, 0x1213), xyoff(0,0))
)

This sprite layout uses original TTD sprite 0x5CF (dec 1487) as its ground sprite, uses the second sprite from the corresponding sprite block as its building sprite (a house graphics), and its bounding box covers the whole tile (16 * 16) with a height of 36 pixels. There's a child sprite sharing the parent's bounding box with its origin at coordinate x=0 and y=0 with the parent's graphics sprite. This sprite is a 'transparent glass' sprite using a custom made colour map, allocated to sprite-ID 0x1213 (dec 4627).

Example 3 (multiple sprites):
def(50) spriteset(
	set(regular(WATER), regular(2), xyz(0,0,0), dxdydz(16,16,6))
	set(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(
	set(regular(WATER), regular(2), xyz(0,0,0), dxdydz(16,16,6))
	set(regular(LIGREEN5), xyz(1,5,6), dxdydz(8,8,16))
	set(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.

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 5 (using 2nd ground sprite):
def(28) spriteset(
	set(regular(COASTYV), regular(SHD+75), xyoff(0,4)) // 2nd groundtile!
	set(regular(6), xyz(0,0,0), dxdydz(16,16,6)) // quay (1st bb definition)
	set(regular(11), xyoff(20,10)) // warning stripes for quay
	set(regular(SHD+49), xyz(0,0,1),dxdydz(8,8,16)) // shed extension
)

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 houses performance

These functions are used to evaluate game-intrinsic variables, and make them accessible to the house'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 house with its activation function. See here for an example.

Function Description
age(<block>)Get age of house in years
anim_frame([<Coordinate>,] <block>)Get current animation frame
callback(<block>)Check incidence (and type) of callback
cargohist(<CargoType> | REGISTER, <Coordinate>, <block>)Cargo acceptance history of nearby stations
classcount/typecount([<House-ID>,] TOWN | MAP, <block>)Check number of current or specified house class or ID, either in town or on map
constructionstage(<block>)Get construction stage (0 .. 3)
elseThis is really a void statement
findhouse(ID | CLASS | GRFID, [1 .. 63], <block>)Get distance of nearest house matching a given criterion
houseinfo_class(<Coordinate> | REGISTER, <block>)Get class of nearby house tile
houseinfo_grfid(<Coordinate> | REGISTER, <block>)Get GRF-ID of nearby house tile
houseinfo_height(<Coordinate> | REGISTER, <block>)Get height of lowest corner of tile
houseinfo_id(<Coordinate> | REGISTER, <block>)Get House-ID of nearby house
houseinfo_lclass(<Coordinate> | REGISTER, <block>)Get landscape class of nearby tile
houseinfo_slope(<Coordinate> | REGISTER, <block>)Get slope of nearby tile
houseinfo_terrain(<Coordinate> | REGISTER, <block>)Get terrain type of nearby tile
houseinfo_townzone(<Coordinate>, <block>)Get town zone of nearby tile
houseinfo_water(<Coordinate> | REGISTER, <block>)Returns 1 if flat water, else 0
houseinfo_waterclass(<Coordinate> | REGISTER, <block>)Returns the water class of nearby tile
houseinfo_4(<Check>, <Data>, <block>)Examine 4-neighbourhood of house tile
houseinfo_8(<Check>, <Data>, <block>)Examine 8-neighbourhood of house tile
iterations(<block>)Get number of iterations when in CB_CARGOPROD
location(<block>)Get coordinate of building
position(<block>)Check position of tile
randombits(<block>)Get random bits at current tile when in callback
removaltype(<block>)Get reason for building removal
testposition(<block>)Get position in layout for CB_SLOPE
testslope(<block>)Slope check for CB_SLOPE
townexpansion(<block>)Check if town gets expanded
towninfo_location(<block>)Get location of town this house belongs to
towninfo_population(<block>)Get population of town
towninfo_buildingcount(<block>)Get number of buildings in town
towninfo_radius([TZ_CENTRE .. TZ_OUTSKIRTS], <block>)Get radius of townzone
randomrel/randomabs(<trigger>, <randombit> <list::reference>)Get random reference
Auxiliary functions
nibble(<Coordinate>)Get position information from x/y coordinates
pos(<Coordinate>)Construct position information from x/y coordinate

Description

anim_frame([<Coordinate>,] <block>)

This function returns the current animation frame being displayed. The return value is between zero and the number of animation frames specified in the house definition's property anim_info(). Please note that the flag HASANIMATION in definehouse() must be set for this to work.

Enabling animation on a house tile by setting the HASANIMATION flag, property functions anim_info() and probably anim_speed() ensures that the returned value will indeed change with time, and the building is redrawn every time the animation counter changes. Please note that this kind of animation needs more CPU time and more sprites, so you should prefer palette animation if possible.

If an additional coordinate parameter is given, the actual value of the animation counter of the given neighbouring house tile is returned. Please note that coordinates must be given by the auxiliary function pos(<x>, <y>,)

Although it's technically possible to query a 16x16 area, it's currently useless to use offsets other than -1, 0 and +1; if you query a tile that doesn't belong to the same building as the current tile, the result is meaningless. It may even be junk if the queried tile isn't a house tile at all.

Example (lighthouse blink):
def(23) anim_frame(
	ref(51) if(0 .. 2, 5 .. 7) // light
	ref(50) else
)

callback(<block>)

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

cargohist(<CargoType> | REGISTER, <Coordinate>, <block>)

The first parameter of this function is a cargo type. If your newGRF is version 7 or later and has a cargo translation table, this is an index to that table; otherwise, it's a cargo slot number. In case of REGISTER as the first parameter, cargo type is not explicitly given but taken as the result from a preceding calculation, see calculate(). Additionally, the second parameter should contain a coordinate offset relative to the current house tile (use 0 for the current tile). The returned value is a bit mask and looks like this:

ValueLabelMeaning
1SOMETIMEThis cargo was accepted in a nearby station some time in the past
2LASTMONTHThis cargo was accepted in a nearby station last month
4THISMONTHThis cargo was accepted in a nearby station this month
8PERIODICThis cargo was accepted in a nearby station since the last periodic processing (which happens every 250 ticks)
16TRIGGEREDThis cargo is one of the types that triggered callback CB_WATCH (only during callback)

A station is considered nearby if the selected tile is inside its acceptance area. That's why you can give an offset - other tiles of your multi-tile building may have different stations 'nearby'.

The information required for this function is stored in the station2 structure, and therefore works only if the station2 structure is present. The station2 structure is present if any of the following is true:

If the station2 structure isn't present, the returned value is always zero.

Example (play animation if goods had arrived at station):
// check if cargo was accepted last month (or earlier)
def(2) cargohist(GOOD,
	anim_control(0) if(LASTMONTH .. 255) // start anim if accepted
	A_NOP else                   // else next frame
)

// anim stopped on last frame?	
def(3) anim_frame(
	ref(2) if(_lastframe) // yes, check acceptance
	A_NOP else    // else next frame
)

def(4) callback(
	ref(3) if(CB_WATCH) // start anim when cargo arrived
	ref(0) else         // graphics
)

classcount/typecount([<House-ID>,] TOWN | MAP, <block>)

These functions return information about the number of tiles with the current or specified house class or ID, either located in the current town or on the whole map.

For tiles that have no building class set itself, the result of typecount() or classcount() is always zero.

Overridden old types are considered to be the new type they were overridden with. During callback CB_BUILD, the current building isn't on the map yet, and therefore isn't counted. In other cases, the building count is at least one, since the current building is counted as well.

findhouse(ID | CLASS | GRFID, [1 .. 63], <block>)

This function performs a circular search around the current tile, trying to find the nearest house tile that will match the same type, class or grfID. (Note: other tiles of the originating house will not match.) Search result will be the Manhattan distance between both tiles or "0", if no house tile matching the given criteria has been found.

The parameters of this functions work as follows:

The first parameter indicates the type of search to be carried out:

ID : Search by house ID as defined in the grf file
CLASS : Search by building class
GRFID : Search by grfID

The second parameter indicates the radius of the search going to be performed (given in Manhattan metric). Maximum search radius is "63" while the minimum is "1". A radius of 0 is considered reserved - do not use.

Just like other search variables, be aware that this is a CPU intensive one

houseinfo_class(<Coordinate> | REGISTER, <block>)

This function returns the class value of a nearby house tile. Coordinates must be given by 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().

Parameter prerequisites also hold for functions houseinfo_lclass(), houseinfo_grfid(), houseinfo_height(), houseinfo_id(), houseinfo_lclass, houseinfo_slope(), houseinfo_terrain(), houseinfo_water(), and houseinfo_waterclass().

Return values, given as a WORD, are as follows:

0: if the selected house doesn't have a class specified (including old houses, which cannot have a class)
0x01XX: if the selected house has been defined in the current GRF with class "XX"
0x02XX: if the selected house has been defined in a different GRF with class "XX"
0xFFFF: if the selected tile isn't a house tile

houseinfo_grfid(<Coordinate> | REGISTER, <block>)

This function returns the GRF-ID of a nearby house tile. Return values, given as a DWORD, are as follows:

0xFFFFFFFF: if the selected tile isn't a house tile
0x00000000: if the selected house is an old house type
otherwise, the GRFID of the GRF which defined the type of the selected house

houseinfo_height(<Coordinate> | REGISTER, <block>)

This function returns the height of the lowest corner of the tile at the given coordinate.

Note that although all houseinfo_* functions take as its first parameter a coordinate referencing the tile's neighbourhood, it's useless to use offsets other than -1, 0 and +1. Because if querying a tile that doesn't belong to the same building as the current tile, the result would be meaningless, or even junk if the queried tile isn't a house tile at all.

houseinfo_id(<Coordinate> | REGISTER, <block>)

This function returns the ID of a nearby house tile. Return values, given as a WORD, are as follows:

0x00XX: if the selected house is old house type "XX"
0x01XX: if the selected house has been defined in the current GRF with ID "XX"
0x02XX: if the selected house has been defined in a different GRF with ID "XX"
0xFFFF: if the selected tile isn't a house tile

In case the selected house is from another newGRF, you need to use houseinfo_grfid() (see above) to find out the corresponding GRF-ID and identify the exact type of the house. Please note that houseinfo_grfid() is cheaper to calculate than this one, so if you are looking for a specific GRF-ID/house-ID combination, you should try matching the GRF-ID first, and get houseinfo_id() only if the GRF-ID matches.

houseinfo_lclass(<Coordinate> | REGISTER, <block>)

This function returns the landscape class of the house tile at the given coordinate:

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

houseinfo_slope(<Coordinate> | REGISTER, <block>)

This function returns byte-packed slope information for the given house'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. See example.

This figure illustrates which kind of slope belongs to which corners raised:

Example (check slope for building):
def(33) houseinfo_slope(
	ref(0) if(NORTH+WEST) // front x
	ref(1) if(NORTH+EAST) // front y
	ref(2) if(SOUTH+EAST) // back x
	ref(3) else           // back y (SOUTH+WEST)
)

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 house may be placed on a steep slope.

houseinfo_terrain(<Coordinate> | REGISTER, <block>)

This function returns the terrain type for the current tile of a house. Values are NORMAL, DESERT, RAINFOREST, and SNOW (on or above snowline).

houseinfo_townzone(<Coordinate>, <block>)

This function returns the town zone value at the given tile coordinate in relation to the current house's tile.

Returned values are as follows:

ValueMeaning
TZ_CENTREInnermost zone, street lights
TZ_COMMERCIALTrees
TZ_RESIDENTIALPaved roads
TZ_PERIPHERYPlain roads
TZ_OUTSKIRTS

houseinfo_water(<Coordinate> | REGISTER, <block>)

This function returns a value of "1" if the house tile given by <coordinate> is built on flat water, else "0" is returned. Again, the coordinate parameter needs to be given by the auxiliary function pos(<x>, <y>,)

houseinfo_waterclass(<Coordinate> | REGISTER, <block>)

This function returns the "water class" of the tile given by <coordinate>. 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 houseinfo_waterclass() might come in handy in this case.

houseinfo_4(<Check>, <Data>, <block>)

This function carries out the specified check given by first parameter on the 4-neighbourhood of the current house tile for the given data in its second parameter. The function returns a nibble with bits set for each occurence of the given data.

Bit0123
Coordinate1,00,1-1,00,-1


The function's main purpose is to give house tiles a possibility to adapt to neighbour tiles specified by its first parameter, see list:

CheckDataMeaning
CHK_WATER0,1check for water
CHK_WATERCLASSseecheck water class (land, sea, canal, river)
CHK_HEIGHT1,2,4,8,16check height
CHK_LCLASSseecheck landscape class
CHK_SLOPE1,2,4,8,16check slopes
CHK_TERRAIN0,1,2,4check terrain (normal, desert, rain forest, snow)
CHK_ID0 .. 511check for other houses (ID)
CHK_CLASS0 .. 511check for other houses (class)


Example (canal on 2 opposite tiles):
def(30) houseinfo_4(CHK_WATERCLASS, WC_CANAL,
 	ref(0) if(5) // +x, -x
	ref(1) if(10) // +y, -y
	ref(2) else
)
Example (count neighbour houses with ID "7"):
def(30) houseinfo_4(CHK_ID, 7,
 	ref(0) if(0) // no neighbour
 	ref(1) if(1,2,4,8) // one neighbour
 	ref(2) if(3,5,6,9,10,12) // two neighbours
 	ref(3) if(7,11,13,14) // three neighbours
 	ref(4) else // four neighbours
)

houseinfo_8(<Check>, <Data>, <block>)

This function carries out the specified check given by first parameter on the 8-neighbourhood of the current house tile for the given data in its second parameter. The function returns a nibble with bits set for each occurence of the given data, see below.

Coordinate1,00,1-1,00,-1 1,-11,1-1,1-1,-1
Bit01234567
Value1248163264128

For use cases, parameters and examples, see above.

location(<block>)

This function returns the coordinate of the tile this part of the building is located. If the building is not yet constructed, like during callback CB_BUILD, the returned value will be the proposed location. Please note that it must be read by the auxiliary function nibble(<Coordinate>)

position(<block>)

This function returns the position of the given tile in relation to the northernmost tile of the house, inside the house'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 (house on 2 tiles):
def(30) position(
 	ref(0) if(nibble(0,0)) // upper part
	ref(1) else 	       // lower part
)

removaltype(<block>)

This function returns the reason for a building removal when in CB_PROTECT. It returns "0" for 'normal' demolition and "1" when TTD wants to remove the house for the sake of a new industry, e.g. a bank.

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 house construction, and it does not require being set via the house's property function callback(). Same as for function testslope().

Example (house 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 house 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
)

townexpansion(<block>)

This function returns "1" (or TRUE) if TTD is currently creating a random town, "0" (or FALSE) otherwise.

Note:

While TTD is generating a random town, return values of functions towninfo_population() and towninfo_buildingcount() are incorrect. The population counter contains the population of buildings generated yet, which means the final value may be larger than you get. The building count, on the other hand, is surely higher than the final value will be. If you want to check these variables during callback CB_BUILD, you may need to check function townexpansion() as well and make adjustments if it returned TRUE.

towninfo_location(<block>)

This function returns the location of the town on the map this house belongs to. Be aware to use nibble() when reading the x/y-coordinates.

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

Description

trigger

ValueTrigger
PERIODICthe building tile is processed in the periodic tile processing loop
SYNCHRONthe top tile of the building is processed in the periodic tile processing loop

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.

Houses have 8 random bits per tile. Note that the random bits are unique to each tile in the house, and are not shared across the whole house.

list::reference

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

The periodic tile processing loop constantly processes the tiles of the map, processing any given tile in every 256 ticks (approx. 3.5 TTD days). Since no "real" event happens to town buildings, you have only this opportunity to re-randomize the look of your building.

If every 3.5 days is too fast for you, you can multiply the time-out by setting property 16 for the given tile. The time-out is 256 ticks*(prop. 16+1), so 0 means every 3.5 days, 1 means every 7 days, 2 means every 10.5 days and so on.

If trigger 02 is activated, all parts of the building that has this trigger set will get the same random bits, allowing you to randomize a multi-tile building as one unit. On the other hand, if the tiles of a multi-tile building have trigger 1 set, all tiles will be randomized individually. Note that all tiles of a multi-tile building get the same value when building the building.

Example (4 different lighthouses):
def(14) randomrel(CONSTRUCT,0,ref(1),ref(2),ref(3),ref(4))

Auxiliary functions

This pair of functions is used to evaluate coordinate offsets for some of the houseinfo_*() 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.