The m4nfo User manual and Technical Report

m4nfo internal functions

Functions to administer m4nfo source files

Introduction

These are internal functions which don't compile any m4nfo sources into newGRF files, but serve to administer various aspects in handling m4nfo source files.

Function Description
asl_on()/asl_off()Switch over to 'Advanced Sprite Layout'
export(m4_<String>)/import(m4_<String>)Interface between source files
forloop(<variable>, <m4nfo-expression>, <List> | <Range>)General iterative templating function
include(<String>)include another source file (M4)
setbaselabel(<Word>)Set base sprite according to sprite block
setfeature(<Feature>)Assign TTD "feature"
setgrfversion(<version>)Sets GRF version to use
setpath(<String>)Assign directory path for graphic sprites
textmode_on()/textmode_off()Temporarily deactivate macro processing
word(<Byte>)Force WORD-size value
zoom32_on()/zoom32_off()special handling for 32 bpp and/or zoomed sprites, see here, and and the tutorial

Unlike setfeature(), these functions may be used multiple times.

Description

export(m4_<String>)/import(m4_<String>) - Interface between source files

To reduce compile time and improve clarity for the coder, m4nfo supports distributed source files, even linking of pre-compiled grf part files. Therefore, an interface between source files is needed which is provided by functions export() and import().

At the moment, there are four modules available:

Example 1 (initializing "real" price and cost):
// file "dbxl90_0.nfx"

setfeature(_TRAIN)
import(m4_sys)

setcurrency(DEM) // init vehicle prices & running costs

basecost(
	...
	{BUILD_ENGINES, maxengineprice(3'000'000)},
	{BUILD_WAGONS, maxwagonprice(500'000)},
)

cargotranslationtable(
	{"PASS"}, {"MAIL"}, {"OIL_"}, {"LVST"},
	...
	{"BRCK"}
)

Example 2 (defining vehicles):
// file "dbxl90_3.nfx"

// file "dbxl90_3.nfx"
setfeature(_TRAIN)
import(m4_sys)
import(m4_ctt)
import(m4_cost)

definevehicle(_SLOW, {STR_LOW},
	newgraphics()
	climate(TEMPERATE, ARCTIC)
	enginetype(WAGON)
	railtype(RAIL)
	lifecycle(1-1-1900, 1940, 1960)
	price(3000)
	runningcost(60)
	cargoclasses(+BULK, +PGOODS, +EXPRESS, -LIQUID, -REEF, -TRVL)
	cargolist(+FRUT, -LVST, -WOOL, -VEHI, -COPR, -BEAN, -NUTS, -JAVA)
	...
)

Example 3 (exporting user-defined text labels in MariCo):
deftxt(warn_water,
	US, T_BLACK "Mole lights can only be built on water.
	  Lights will be positioned automatically.",
	...
	RUS, UTF8 T_BLACK "Маяки могут быть построены только на открытой воде.
	  Позиция для маяка будет выбрана автоматически."
)

...

export(m4_text, {warn_water}, {warn_coast}, {moles}, {molelight}, {specialquaytiles})

Please note that string-IDs in export() must be quoted! See also deftxt().

forloop(<variable>, <m4nfo-expression>, <List> | <Range>)

This function writes out <m4nfo-expression> for each of its arguments, given either as a range (1 .. 3), or a list (1,2,3). <variable> specifies the symbol used in <m4nfo-expression> to be replaced by the current arguments, with <variable> itself might be used inside an algebraic expression. See examples here.

import()

This function is used to "import" previously defined and exported labels into the current source file. At the moment, it is not possible to import only specific labels, you always have to import all labels.

Example (importing text labels in MariCo):
include(mbobjects.m4)

setfeature(_OBJECT)
import()
...

import_ctt()

This function imports cargo-IDs previously defined by function cargotranslationtable() in a different source file. It is only needed in case of distributed source files, and you should mind dependency between cargo-IDs defined by the CTT in one file, and cargo-IDs used in others, by managing it e.g. by a dependency-tracking build utility like 'Make'.

include(<String>)

This M4 macro includes file <String> at the current position. File may contain any valid m4nfo code, private constants and/or macro definitions.

setbaselabel(<Word>)

This function sets the 'base sprite' as a reference for labels to be defined in an extended format sprite block. Its parameter must agree with the parameter given to the following sprites block, identifying the first set in the block. See example:

Example (setting the base label in extended format sprite blocks):
setbaselabel(333)
spriteblock(+333,
        forloop(X, {set(sp_tomb_vi_guer0_{}X,template({NG_6p6M},tombereau.png,x(LAYOUT_SYMMETRIC),y(10)))}, 3 .. 5)
)

def(0) spriteset(move(sp_tomb_vi_guer0_3), load(sp_tomb_vi_guer0_4))
def(1) spriteset(move(sp_tomb_vi_guer0_3), load(sp_tomb_vi_guer0_5))

setfeature(<Feature>)

This function sets the TTD "feature" (vehicle, station, house, industry, ...) to be used for all functions in this particular source file. It has to be set at the beginning of the file (see example above), before using any of m4nfo's functions. When working with distributed source files, it must be set in every file. A list of implemented features can be found here.

Please note that you can only set (and use) one feature per file. In case you'd have to use more than one feature in a newGRF (e.g., industry tiles, industries, and cargoes), you'd have to split them into different source files.

setgrfversion(<version>)

This function sets the GRF version to be used for the newGRF in question. Parameter <version> must be either '7' or '8', depending on the GRF version to be used. Version 7 is the last GRF version of TTDPatch, and version 8 is the current GRF version of OpenTTD.

Main differences between these versions are:

Since the GRF version affects behaviour of various newGRF features, this is also the case in m4nfo. Some of these differences are handled automatically, but others, mostly when users supply their own macros for callback results, must be taken care of. Another important change in version 8 is the removal of CB_LOAD and CB_WLEN (nfo callbacks 12 and 11). Their functionality being substituted by CB_PROP and usage of property functions loadamount() and vehlen(). Default for m4nfo is Version 8. In case you need version 7, this must be set before function grfinit().

setpath(<String>)

This function sets the directory path for the graphics sprites used. This may either be an absolute or a relative path, whichever sees fit. It is only required in files specifying graphics, i.e. using function sprite().

Example (setting path for graphic sprites):
setfeature(_TRAIN)
setpath(C:\eigene~1\mb\ttdlx\sprites\newvehicles\DBXL09\sprites)

...

spriteblock(
  set(
   sprite(db_br18.pcx 226 30 01 13 48 -23 -9)
  )
)

Setting the path is only required for source files including real (graphics) sprites, and in case the sprite() function specifies only the file name (which is recommended).

Alternatively, you could set m4nfo internal variable PATH directly:

define(PATH,{__NUMBER C:\eigene~1\mb\ttdlx\sprites\newvehicles\DBXL09\sprites})

textmode_on()/textmode_off()

Because the M4 macro processor uses the comma (",") as its parameter delimiter, commas in text strings could be problematic. In addition, M4 and m4nfo macro names in text strings are a problem as well, because M4 likes to expand them also in this context. Usually, it helps to quote them (e.g., "Special quay tiles: ticket office{,} benches{,} marina{,} dolphins at quays") but nested function calls could need quite a number of quotes depending on the level of nesting (e.g., "Flat bed wagon (heavy {{load}})").

To save the coder from having to count quoting levels, functions textmode_on() and textmode_off() are used to disable any M4 macro processing in m4nfo strings (" ... "). It does so by temporarily exchanging the string quotation mark (") with the comment mark (//). In consequence, you can't use the normal comment inside a textmode_on() .. textmode_off() block. Instead, you may use "#" as a fall-back if you should need a comment inside such a block.

word(<Byte>)

In general, m4nfo function blocks are using automatic evaluation, either Byte-, Word-, or Dword-sized. This is done by checking the first parameter in the first if() function, and taking the result type of the function into consideration (either BYTE, WORD or DWORD). However, there might be situations where this does not work as intended, and in some of these cases function word() can be used to enforce evaluation using WORD-sized values. Please note that this is already obsolete and might be deprecated in the future.

Example (using word() to enforce Word-size evaluation):
// last maintenance after 1930?
def(8) lastmaintenance(
    engine(
	ref(4) if(word(0) .. 3653) // still bavarian brown
	ref(5) else		   // carry on with DRG brown
    )
)