The m4nfo User manual and Technical Report

Flow of control

Flow of cparameterontrol and parameter handling

Content

Introduction

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).

Functions for flow of control

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

Description

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.

Example (labeled references):
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.

deflabel(<String>)

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.

placelabel(<String>)

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).

pcalc(<expression>)

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.

Format

pcalc(<target> := <source1> [<operator> <source2>])

The data looks as follows:

<target> - target parameter
<operator> - calculation to carry out
<source1> - first argument
<source2> - second argument

Description

<target, source1, source2>

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.

<operator>

The operation to carry out on the source arguments. The result of this calculation will be stored in the target parameter.

OperatorOperationResult
:=Assignmenttarget := source1
+Additiontarget := source1 + source2
-Subtractiontarget := source1 - source2
*Unsigned multiplicationtarget := source1 * source2
S*Signed multiplication
<<Unsigned bit shifttarget := source1 << source2 if source2>0, or target = source1 >> abs(source2) if source2 < 0. source1 is considered to be unsigned
S<<Signed bit shiftsame as 05, but source1 is considered signed
ANDBitwise ANDtarget := source1 AND source2
NOTBitwise NOTtarget := NOT source1
ORBitwise ORtarget := source1 OR source2
/Unsigned divisiontarget := source1 / source2
S/Signed division
MODUnsigned modulotarget := source1 % source2
SMODSigned modulo

param(<Byte>)

This auxiliary function is needed to define parameters to be used in pcalc(). See examples.

Notes

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.

Examples

Example: setting parameters to be used in snowline calculation:
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:

Example: setting and reading parameter bits:
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

num-skip

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.

Note:

It is generally not safe to skip backwards, i.e. to an earlier position in the newGRF file. While TTDPatch will happily do that, you will get strange results if certain functions are repeated by such a jump.

variable

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
CLIMATECurrent climate
WINDOSTTD version, "DOS" or "WIN"
PLATFORMTTDPatch ("TTDPATCH") or OpenTTD ("OTTD")
TTDPATCHVERSIONTTDPatch version
OTTDVERSIONOpenTTD version
FLAGSTTDPatch 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
YEARCheck current year (long format, year zero based)
GRFACTIVECheck if newGRF-ID is active or not
CARGOCheck if Cargo Type is available or not
FEATURECheck if custom feature is available or not
RAILTYPECheck if Rail type is available or not

condition

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
BITSETTest for bit given by value being set
BITCLRTest 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

value

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.

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"

Example: jump label handling:
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)

Auxiliary functions

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.

getowngrfparameter(<Byte>)

This function accesses the given newGRF parameter and makes it available as the second parameter in function skipif(). See example above.

patchvar(<Byte>)

This function takes a TTDPatch flag and makes it available for use by skipif(). See table above for usable TTDPatch flags.

reflabel(<String>)

This function references a pre-defined label. It must be used as the first parameter in function skipif().

setbit(<Byte>)

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