The m4nfo User manual and Technical Report

Functions for industry tiles

Using functions for industry tiles

Introduction

In m4nfo, industry tiles 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 industry tile 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 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(NOSPRITE, normal(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(
	set(normal(WATER), normal(1), xyz(0,0,0), dxdydz(16,16,6))
	set(normal(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(
	set(normal(WATER), normal(2), xyz(0,0,0), dxdydz(16,16,6))
	set(normal(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(normal(WATER), normal(2), xyz(0,0,0), dxdydz(16,16,6))
	set(normal(LIGREEN5), xyz(1,5,6), dxdydz(8,8,16))
	set(normal(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(normal(COASTYV), normal(SHD+75), xyoff(0,4)) // 2nd groundtile!
	set(normal(6), xyz(0,0,0), dxdydz(16,16,6)) // quay (1st bb definition)
	set(normal(11), xyoff(20,10)) // warning stripes for quay
	set(normal(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 industry tiles performance

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

Function Description
anim_frame([<Coordinate>,] <block>)Get current animation frame
callback(<block>)Check incidence (and type) of callback
elseThis is really a void statement
constructionstage(<block>)Get construction stage (0 .. 4)
height(<Coordinate>, <block>)Get height of lowest corner of tile
ismember(<Coordinate>, <block>)Whether given tile belongs to current industry
lclass(<Coordinate>, <block>)Get landscape class of tile
slope(<Coordinate>, <block>)Get slope info of given tile
terrain(<Coordinate>, <block>)Get terrain type
townzone(<Coordinate>, <block>)Get town zone
type(<Coordinate>, <block>)Get industry tile ID at offset
water(<Coordinate>, <block>)Returns 1 if flat water, else 0
waterclass(<Coordinate>, <block>)Returns "class" of water
position(<block>)Check position of tile in industry
randombits(<block>)Get random bits at current tile when in callback
testposition(<block>)Get position in layout for CB_SLOPE
testslope(<block>)Slope check for CB_SLOPE
randomrel/randomabs(<trigger>, <randombit> <List::ref()>)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 industry tile's definition property anim_info(). Please note that the flag HASANIMATION in defineindustrytile() must be set for this to work.

Enabling animation on an industry 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 industry tile is returned. 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
)

callback(<block>)

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

height(<Coordinate>, <block>)

This function returns the height of the lowest corner of the tile at the given coordinate. Please note that coordinates must be given using the auxiliary function pos(<x>, <y>,)

ismember(<Coordinate>, <block>)

This function returns "1" if the given tile is an industry tile, and belongs to the same industry as the current one.

lclass(<Coordinate>, <block>)

This function returns the landscape class of the 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

slope(<Coordinate>, <block>)

This function returns byte-packed slope information for the given tile. 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) 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.

terrain(<Coordinate>, <block>)

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

townzone(<Coordinate>, <block>)

This function returns the town zone value for the tile at the given coordinate in relation to the current industry tile. This is mostly useful for banks and other urban industries.

Returned values are as follows:

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

type(<Coordinate>, <block>)

This function returns the industry tile ID from the geiven offset. Point of reference is the current tile instead of the north tile of the industry, and the offsets are considered to be signed.

water(<Coordinate>, <block>)

This function returns a value of "1" if the 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>,)

waterclass(<Coordinate>, <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 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 îndustry, inside the industry'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.

This function should be used to spare industry IDs because the same tile type can choose different representations depending on where exactly it is inside the industry layout.

Example (bank 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 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 (buld 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
)

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

ValueTrigger
PERIODICThe tile is processed in the periodic tile processing loop.
SYNCHRONSimultaneous trigger for all tiles of the industry every 256 ticks. If the industry is a primary one, output cargo is generated at the same time.
DELIVERYCargo is delivered to the industry. If the industry is a processing one, output cargo is generated at the same time.

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.

Industry tiles have 8+16 random bits. Accessed through function randomrel(), you get 8 tile-specific bits, and accessed through function randomabs(), you get 16 industry-specific bits. The triggers are the same for both.

List::ref()

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.

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.