This chapter deals with "flow of control" in a newGRF. In general, there´s one "chain" for each ID of TTD's features (vehicles, stations, houses, ...), connecting the ID's activation function (makevehicle(), makestation(), ...) with its graphics sprites and/or property values. See also here for an example.
In m4nfo, these chains are made up from function "blocks", which in plain nfo correspond to the evaluation of a so-called "pseudosprite". Such chains could be quite long, containing lots of intermediate decisions, either data-driven or randomly-generated, and they could also include "callbacks", special functions which are "called" in order to modify various properties, an example being the visual effect of train vehicles.
For example, the picture on the left visualizes the chain of control with its diverse function blocks (callback(), cargo(), getubits(), year(), yearbuilt()) for the DB Set's V200 diesel engine. As can be seen, flow of control also ramifies along the chain, displaying a "tree" (upside down), with each of its leaves ending either in a "callback result" (cbr) or a real sprite (graphics).
| Function | Description |
| def(<Byte> [,<Label>]) | Define reference |
| ref(<Byte> | <Label>) | Function reference |
| deflabel(<String>) | Define branch label |
| placelabel(<String>) | Place branch label |
| getothergrfparameter(<target-param>, <grf-ID>, <grf-param> | VERSION) | Read parameter of other newGRF |
| pcalc(<expression>) | Assign newGRF parameters and calculate results |
| setparameter(<Byte>, <Dword>) / setparameterbits(<Byte>, <Dword>) | Set parameter value / bits for this newGRF |
| skip(<Byte>) | Unconditionally skip sprites |
| skipif(<Byte>, <Byte>, <Byte>, <Word>) | Change flow of control |
| getowngrfparameter(<Byte>) | Get parameter for this newGRF |
| patchvar(<Byte>) | Use TTDPatch flag as variable for skipif() |
| reflabel(<String>) | Refering of branch label |
| setbit(<Byte>) | Set bit in parameter |
def(<Byte> [,<Label>]) / ref(<Byte> | <Label>)
Functions def() and ref() are m4nfo's implementation of plain nfo's 'chaining'. Function def() defines a reference for a m4nfo function to be 'chained', and function ref() references a m4nfo function. Both functions are special m4nfo functions (not containing a function block) but just keep track of the chain references, constituting the 'flow of control' in the newGRF code. See here for an in-depth discussion.
Please note that references can be 'labeled' and even 'templated' by m4nfo's macro facilities.
def(0xB0, FP_RHEIN) callback( ref(6) if(CB_LOAD) // load amount ref(3) if(CB_RCAP) // capacity ref(4) if(CB_TSFX) // text suffix ref(5) else // graphics ) ... // mail def(3) yearbuilt( ref(FP_RHEIN) if(1928 .. 1940) // Rheingold refit ref(FP_) else // generic )
Be aware that excessive labeling might make your source unreadable, and it won't work across file boundaries. Also, for security reasons, labels are 'write-once', and in the rare event of a need to redefine a label, it has to be quoted, by the usual curly brackets.
This function defines a branching label in the newGRF, i.e. the normal flow of control is modified by function reflabel() inside a skipif() function to continue at the point where this very label would be placed. See example below.
The parameter for this function, as well as those for placelabel() and reflabel(), is a text string which may be chosen freely. It is translated internally into a unique Byte-sized ID, hence only 255 different labels are allowed in a newGRF file.
Using jump labels is the only way to skip more than 255 sprites at once. The <num-skip> parameter of skipif() only allows to set 255 elements.
In the rare case you want to use jump labels across distributed source files, this is possible in a limited way. See the example in the text handling section.
This function places a branch label at a certain point in the newGRF. See example below.
getothergrfparameter(<target-param>, <grf-ID>, <grf-param> | VERSION)
This function reads the given parameter of another newGRF and writes it into the given target parameter. In case VERSION is given as the other newGRF´s parameter, that newGRF´s version, as given in function grfinit(), is returned. In case no version number had been set for that newGRF, 0 is returned (OpenTTD only).
This function allows to set the value of newGRF parameters (i.e., those usually set as options in the newgrf(w).cfg file), as well as do mathematical operations on them.
pcalc(<target> := <source1> [<operator> <source2>])
The data looks as follows:
<target> - target parameter
<operator> - calculation to carry out
<source1> - first argument
<source2> - second argument
These arguments specify the target and source parameters. They can either be newGRF parameters, defined by auxiliary function grfparameter(), or they can be the special variables used in function skipif(), see description there. In addition, <source> arguments may be simple numerical values.
The operation to carry out on the source arguments. The result of this calculation will be stored in the target parameter.
| Operator | Operation | Result |
| := | Assignment | target := source1 |
| + | Addition | target := source1 + source2 |
| - | Subtraction | target := source1 - source2 |
| * | Unsigned multiplication | target := source1 * source2 |
| S* | Signed multiplication | |
| << | Unsigned bit shift | target := source1 << source2 if source2>0, or target = source1 >> abs(source2) if source2 < 0. source1 is considered to be unsigned |
| S<< | Signed bit shift | same as 05, but source1 is considered signed |
| AND | Bitwise AND | target := source1 AND source2 |
| NOT | Bitwise NOT | target := NOT source1 |
| OR | Bitwise OR | target := source1 OR source2 |
| / | Unsigned division | target := source1 / source2 |
| S/ | Signed division | |
| MOD | Unsigned modulo | target := source1 % source2 |
| SMOD | Signed modulo |
This auxiliary function is needed to define parameters to be used in pcalc(). See examples.
A parameter is taken to be defined if any of the following applies: it has been set to any value in the newgrf(w).cfg parameter list it; or a parameter with higher number has been set to any value by an earlier call to function pcalc().
If, for example, parameter(0) and parameter(1) are set in the newgrf(w).cfg file, and pcalc() sets parameter(4), then parameter(2) and parameter(3) automatically become defined and get a value of zero.
define(SLOW,2) // min snowline define(SHI,8) // max snowline // define named parameters define(max_height, param(0)) define(min_height, param(1)) define(diff, param(2)) define(step, param(3)) pcalc(min_height := eval(SLOW * 8)) // snowline is multiple of 8 pcalc(max_height := eval(SHI * 8)) // ditto pcalc(diff := max_height - min_height) pcalc(step := diff / 12) ...
setparameter(<Byte>, <Dword>) / setparameterbits(<Byte>, <Dword>)
This functions set the given newGRF parameter to the value, respectively to the bit pattern, given by the second parameter. It is most useful for managing user-defined parameters, but also for checking availability of a certain newGRF, ot other things involving parameters:
skipif(6,CLIMATE,==,TEMPERATE) // skip if temperate
// not temp, now check for AlpineClimate active?
skipif(2,GRFACTIVE,+,GRF_ALPINESET)
// not temp, check for AlpineClimate inactive but will be activated?
skipif(1,GRFACTIVE,-+,GRF_ALPINESET)
skip(EXIT) // neither temp nor alpineclimate
setparameterbits(2,_ALPINE) // mark it
skipif(1,getowngrfparameter(2),BITSET,_ALPINE)
skip(EXIT) // Parameter not set
...
skipif(1,getowngrfparameter(2),BITSET,_ALPINE)
definevehicle(_SELF2, {""},
cargotype(COAL)
)
Note that function setparameterbits() sets only the specified bits in the target parameter. It does not modify non-specified bits. You'll have to use function setparameter(), i.e. setting the parameter with a value/number, for those cases.
skipif(<Byte>, <variable>, <condition>, <Word> [, <Word>])
This function allows to skip a specified number of elements in the chain of control (usually functions) in the newGRF file. This can be used to have, for example, climate-specific graphics, TTDPatch version checks and error messages, or deactivation in the presence of other active newGRF files.
Parameters are as follows:
| Parameter | Description | <num-skip> | Number of elements to skip | <variable> | Which variable to base the decision on | <condition> | What condition to check | <value> | Value to compare with | <mask> | Bit mask to use when comparing value |
This parameter sets how many elements will be skipped if <condition> is true. If <num-skip> is set to EXIT, the entire rest of the newGRF file will be skipped, otherwise exactly that many elements will be skipped. If this causes grf_init() to be skipped, the newGRF file will be deemed inactive.
If <condition> is false, processing continues at the following element.
Starting from TTDPatch 2.0.1 alpha 49, it is possible to jump to a certain position in the newGRF file by defining "labels" by function deflabel(). If parameter <num-skip> is set to a label by function reflabel(), located somewhere in the newGRF file, then processing of the newGRF file resumes with the element following that label, instead of skipping that many elements. This is the only way to skip more than 255 elements at once.
Since 2.0.1 alpha 70, duplicate labels are fully supported. The jump will always be to the first matching label that follows. If no matching label follows, the first matching label in the newGRF file will be used instead.
This parameter sets the variable to base the decision on. It can be either one of the newGRF parameters, by use of function getowngrfparameter(), or a built-in TTDPatch variable, see table below. If it's a newGRF parameter that wasn't specified in the newgrf(w).cfg file, or variable GRFACTIVE with a newGRF-ID that doesn't exist, the jump is ignored and no elements are skipped, the only exception being condition type "--" which will skip the elements if the newGRF-ID doesn't exist as well.
| Variable | Description |
| CLIMATE | Current climate |
| WINDOS | TTD version, "DOS" or "WIN" |
| PLATFORM | TTDPatch ("TTDPATCH") or OpenTTD ("OTTD") |
| TTDPATCHVERSION | TTDPatch version |
| OTTDVERSION | OpenTTD version |
| FLAGS | TTDPatch flags |
| MULTIHEAD | |
| STARTYEAR | |
| NEWTRAINS | |
| NEWRVS | |
| NEWSHIPS | |
| SIGNALSONTRAFFICSIDE | |
| ELECTRIFIEDRAILWAY | |
| BUILDONSLOPES | |
| NEWSTATIONS | |
| BUILDONCOASTS | |
| CANALS | |
| FREIGHTTRAINS | |
| NEWHOUSES | |
| SPEEDLIMIT | |
| PBSIGNALLING | |
| NEWINDUSTRIES | |
| TEMPSNOWLINE | |
| NEWCARGOS | |
| NEWSOUNDS | |
| TRAMS | |
| SHORTRVS | |
| ARTICULATEDRVS | |
| DYNAMIC | |
| VARRUNNINGCOST | |
| YEAR | Check current year (long format, year zero based) |
| GRFACTIVE | Check if newGRF-ID is active or not |
| CARGO | Check if Cargo Type is available or not |
| FEATURE | Check if custom feature is available or not |
| RAILTYPE | Check if Rail type is available or not |
This parameter defines the condition to use for the skipif() evaluation. There are several conditions available to choose from (see examples below for application):
| Condition | Description | BITSET | Test for bit given by value being set | BITCLR | Test for bit given by value being clear | == | Parameter is equal to value | != | Parameter is not equal to value | < | Parameter is less than value | > | Parameter is greater to value | + | GRF-ID is active (for variable GRFACTIVE only) | - | GRF-ID is not active (for variable GRFACTIVE only) | -+ | GRF-ID is not active yet but will be activated (for variable GRFACTIVE only) | ++ | GRF-ID is or will be active (for variable GRFACTIVE only) | -- | GRF-ID is not nor will it be active (for variable GRFACTIVE only) | + | Cargo type is available (variable is ignored; value is the label) | - | Cargo type is not available (variable is ignored; value is the label) | + | Rail type label is defined (variable is ignored; value is the label)* | - | Rail type label is not defined (variable is ignored; value is the label)* |
* Only available in OpenTTD
This parameter is what the variable is compared to. Its size depends on the type of <variable>:
For bit tests (BITSET, BITCLR), it must always be a single Byte in the range 0 .. 7, specifying the bit to test.
For testing availability of cargo and rail types, the value must specify the cargo/railtype label. For this to work, the label must be "quoted" like this: {WATER}, else it would be treated as an index into a translation table.
If no cargo or rail type with this label is defined in case of condition "+", the given number of elements are skipped. For condition "-", elements are skipped if the cargo or rail type label has been defined. Both tests work irrespective of the order of newGRF files in newgrf(w).cfg; the cargo is considered to be available even if it is defined by a later newGRF file. For this to work correctly, you must not skip a cargo definition with conditions "+" or "-". The same holds for rail types.
Since TTDPatch r1384, it is possible to use a bit mask, given by the additional parameter, when accessing the variable. This is helpful when checking for a range of newGRF IDs. Please note that the parameter given as a mask must be ordered in the same way as the given value, see examples.
| Parameter | Description |
| skip(5) | skip 5 sprites |
| skip(EXIT) | Terminate newGRF file |
| skipif(reflabel(noDBrails)) | Skip uncondionally until label "noDBrails" |
| skipif(1, FLAGS, BITSET, NEWTRAINS) | skip 1 if bit for "newtrains" is set |
| skipif(1, patchvar(MULTIHEAD), ==, 0) | skip 1 if TTDPatch var "multihead" is set to zero |
| skipif(6, CLIMATE, ==, TEMPERATE) | skip 6 if temperate climate is active |
| skipif(1, TTDPATCHVERSION, >, 2359) | skip 1 if TTDPatch version is higher than r2359 |
| skipif(1, OTTDVERSION, <, 0x14080000) | skip 1 if OpenTTD version os lower than 1.4.0 stable |
| skipif(reflabel(noDBrails), PLATFORM, ==, TTDPATCH) | skip until label "noDBrails" if TTDPatch |
| skipif(2, GRFACTIVE, +, GRF_ALPINESET) | skip 2 if AlpineSet is active |
| skipif(2, GRFACTIVE, +, 6d 62 04 00, FF FF 00 00) | skip 2 if any newGRF with ID "6d 62 xx xx" is active |
| skipif(1, GRFACTIVE, -+, GRF_ALPINESET) | skip 1 if AlpineSet is inactive but will be activated later |
| skipif(1, getowngrfparameter(0), BITSET, _ALPINE) | skip 1 if special bit in newGRF parameter 0 is set |
| skipif(reflabel(noDBrails), getowngrfparameter(1), BITCLR, _DBRAILS) | skip until label "noDBrails" if special bit ("_DBRAILS") in newGRF parameter 1 is not set |
| skipif(reflabel(WINJMP), WINDOS, ==, WIN) | skip until label WINJMP if game runs in Windows (not DOS) |
| skipif(33, CARGO, -, {BRCK}) | skip 33 if cargo type "BRCK" is unknown |
| skipif(EXIT, TRAFFICSIDE, ==, LEFT) | resume newGRF if "drive on traffic side" is set to "left" |
deflabel(noDBrails) // not for TTDPatch! skipif(reflabel(noDBrails),PLATFORM,==,TTDPATCH) skipif(2,GRFACTIVE,+,GRF_DBRAILS) skipif(1,GRFACTIVE,-+,GRF_DBRAILS) skip(reflabel(noDBrails)) // no DBrails skipif(reflabel(noDBrails),getowngrfparameter(0),BITSET,_DBRAILS) // parameter set to "no DBrails" // branch line setproperties(_BR92 .. _BR38, tracktype(SABN) ) ... setproperties(_BR111, tracktype(SACE) ) placelabel(noDBrails)
These auxiliary functions are used in the context of function skipif() to evaluate some of its parameters. They should only be used inside this context.
This function accesses the given newGRF parameter and makes it available as the second parameter in function skipif(). See example above.
This function takes a TTDPatch flag and makes it available for use by skipif(). See table above for usable TTDPatch flags.
This function references a pre-defined label. It must be used as the first parameter in function skipif().
This function sets a specified bit in the given parameter of its parent function. It must be used as the second parameter in function setparameter().