For those not familiar with Transport Tyoon, GRFs, or standard nfo, a short introduction in the terminology might be useful:
Transport Tycoon (TTD) uses so-called 'Game Ressource Files' which are collections of 'sprites', most of them being rectangular graphics objects that are drawn to the screen by its game engine. Examples are the vehicle graphics, the landscape tiles, and pretty much everything that can be seen on the screen in game.
These GRF files are simply made up of one sprite following after another, until the end of the file with no meta-information included. There are however sprites which do not represent graphics to be displayed, but serve different purposes, e.g. so-called 'recolour sprites' which are mappings for 'recolouring' graphics sprites, or TTD height maps.
See here for the detailed format of GRF files.
TTDPatch's newGRF files are more sophisticated: in fact they are 'game extensions' for TTD. In contrast to original TTD GRF files, the new format includes both graphics ('real sprites'), like TTD GRF files do, and nfo program code ('pseudo sprites').
Thus, by using newGRFs, a lot of interesting things can be done:
During development of TTDPatch, newGRF specifications have been changed a lot. Thus, the GRF version have to specify the version of the specs to be used when coding a newGRF set. Of course, the GRF version affects the operability of a newGRF, as there might be changes in the behaviour of various NewGRF features over time. For TTDPatch, the current GRF version is version 7, and for OpenTTD, the latest GRF version is version 8.
In m4nfo, the GRF version number is automatically specified in the set's grf_init() function. Since m4nfo r96, default is GRF version 8, i.e. when coding a set for TTDPatch, it needs to be set to version 7, by function setgrfversion() before grfinit(). In case of distributed source files, setgrfversion() has also to be used in each source file, for different GRF versions than version 8.
The programming language for newGRF's 'pseudo sprites' is called nfo. A typical nfo-statement is built from a consecutive 'sprite number', a number giving the sprite's length, and the pseudo sprite itself, including a label for the current feature and a specific number, the 'c-ID', used for reference purposes. This pseudo sprite is given as a plain stream of bytes, which looks more machine-readable than pleasing the human eye.
The vocabulary of nfo is divided into so-called 'actions', each of them responsible for specific tasks like initialisation of objects (action0), declaration of real sprites (action1), managing sprite sets (action2), handling variations based on variables and parameters (varaction2), linking feature-IDs with appropriate chains of variations and sprite sets (action3), administrating text strings (action4), handling branching (action7 and action9), error handling (actionB), etc., pp.
The complete nfo specification can be found here. It is a must-read for a successful newGRF coding using plain nfo, and a valuable source of information for a successful newGRF coding by using m4nfo.
Both in TTDPatch and OpenTTD, the NFO version defines the file format of nfo files, i.e. the input/output of GRFCodec and nforenum. Current version number is 7. Since newer versions of OpenTTD support 32bpp graphics with explicit zoom levels, there is another NFO version 32, which also supports/needs a new GRF file format (container version 2.0).
In m4nfo, all GRF and NFO versions are supported.
The main characteristics of m4nfo are its concepts of 'blocks' and 'chaining'. Both are inherited from nfo. A 'block' in m4nfo corresponds to the evaluation of a nfo 'pseudosprite', and nfo's 'chaining' (representing flow of control) by using so-called 'c-ID's is implemented by use of 'references'.
In case an m4nfo function is part of a 'chain', it needs to declare a reference for making itself accessible, and to contain one or more references to access other m4nfo functions. I.e.:
For better readability, in m4nfo the initial reference of a function is visually detached from the function:
So,
def(<Byte>[, <Label>]) - defines a reference for a function ref(<Byte> | <Label>) - references a function
In the example below, there are two chains of control, one is highlighted in blue, the other in yellow. The blue one is linking def(5) <- ref(5) <- def(6) <- ref(6) <- def(8) <- ref(8), and the yellow one is linking def(7) <- ref(7) <- def(8) <- ref(8).
Note that flow of control, both in nfo and m4nfo, is always 'upside down', i.e., its origin (in this example) is the makevehicle() function, linking its vehicle-ID with its associated graphics sprites by that illustrated 'chain of control'. Consider it a 'reversed' tree, where the leaves have to be defined prior to the trunk.
In this way, in case of a callback CB_POWR, the steam effects in def(7) are performed ('effect()' returning a callback result), while in the graphics branch in def(8) the chain of control points to def(6), and - via the 'else' branch - points to the animation function (def(5)).
// graphics def(5) animation( ref(0) if(3) ref(1) if(2) ref(2) if(1) ref(3) else ) def(6) flipped( ref(4) if(FORWARD) // offsets, animation and lights "forwards" ref(5) else // offsets, animation and lights "backwards" ) // effect def(7) flipped( effect(steam(4)) if(FORWARD) effect(steam(8)) else ) // callbacks engine def(8) callback( ref(7) if(CB_POWR) // steam position ref(LIGHTS) if(CB_RCOL) // switch lights ref(6) else // graphics ) makevehicle(_030TRAMWAY, link(ref(1),MENU) // purchase menu default(ref(8)) // locomotive override(_MSP,ref(4)) // tramway coach )
A block constitutes the inner body of a function, though technically speaking it is one of the functions parameters (although not being restricted to a numerical value, a text string or a function). A block (highlighted in yellow) contains one or more if() functions and exactly one 'else':
def(20) veh_cargotype( ref(1) if(FERT) // fertilizer (ECS) ref(2) if(FMSP) // farm supplies (FIRS) ref(3) if(CERE, GRAI) // cereals (ECS), grain ref(4) if(IORE) // iron ore ref(5) else )
Not every function in m4nfo needs or may use a block. A block is only needed for functions returning a value on which a decision has to be made, most of the time resulting in chaining to another function. E.g., random or spriteset functions do not have a block:
def(5) randomrel(LOAD,0,ref(1),ref(2),ref(3),ref(4)) def(2) spriteset(move(1,23,25,27,5),load(1,23,25,27,5)) // blue
In fact, m4nfo uses quite different function types, which will be explained in detail, namely:
The concept of 'features' in m4nfo is inherited from nfo, because it is fitting its modular approach quite well. To handle a feature in m4nfo, its associated M4 library file has to be loaded, and the appropriate feature has to be pronounced by calling the function setfeature() with the appropriate parameter (see table below).
Features currently supported by m4nfo are:
| Feature | keyword | support | module |
| trains | _TRAIN | trains.m4 | |
| road vehicles | _ROADVEHICLE | roadvehs.m4 | |
| ships | _SHIP | ships.m4 | |
| stations | _STATION | stations.m4 | |
| canals | _CANAL | canals.m4 | |
| bridges | _BRIDGE | bridges.m4 | |
| houses | _HOUSE | houses.m4 | |
| global variables | header.m4 | ||
| cargoes | _CARGO | cargos.m4 | |
| sound effects | _SOUND | ||
| newobjects | _OBJECT | objects.m4 | |
| rail types | _RAILTYPE | railtypes.m4 | |
| road types | _ROADTYPE | roadtypes.m4 | |
| tram types | _TRAMTYPE | ||
| aircraft | _AIRCRAFT | ||
| industry tiles | _INDUSTRYTILE | ||
| industries | _INDUSTRY | ||
| airports | _AIRPORT | ||
| signals | _SIGNAL | ||
| airport tiles | _AIRPORTTILE |
Data types, constants, and functions used in common, by any of those features, are included in the module 'header.m4'. This must be always included in addition to the specific feature's module:
// start of file include(names.m4) // local stuff setfeature(_TRAIN) ... definevehicle(...) ... makevehicle(...)
Note that when using a "precompiled" module, e.g. m4nfo_trains.m4, you don't need to include neither this one, nor the m4nfo header nor the feature file, but only the "private" include files (e.g. names.m4 above).
Structuring m4nfo source files
In m4nfo, unlike in plain nfo, it is not possible to mix 'features' in one and the same newGRF, or in one and the same source file. The first constraint is a weak one, but although it is not widely accepted to design newGRFs containing different features because of compatibility reasons, you may well do so, by keeping each feature in its own source file.
The second constraint is a hard one, though. There are a couple of reasons for this 'limitation', the most significant ones are that m4nfo doesn't need to check for the actual feature for every function, and it doesn't have to check for the validity of functions for a certain feature: the feature modules contain only functions which are guaranteed to be valid for the preset feature.
It is advisable to divide a large project into a number of source files, so that the whole project could be handled more easily. E.g., for a large train set, these files could be: