Templating in m4nfo is straightforward and needs no additional tools. Because m4nfo itself is implemented as a collection of M4 macros, it is both easy and efficient for the user to add his own macros, e.g. for templating purposes.
Using templates would be especially useful either for graphics sprite definition but also for substitution of (repetitive) code blocks. For the first task, m4nfo comes with a set of pre-implemented functions, the latter can be easily achieved by defining own macros.
Graphics templating
Graphics templating aims at simplifying repetitive real sprite (graphics) arrangements, usually inside the spriteblock() function.
Function | Description | template(<template_name>, <file_name>, x(<list>), y(<word>)) | defines graphics sprite block to be used | x(<list>) | specifies x coordinates of sprites | y(<word>) | specifies y coordinate of sprites |
template(<template_name>,<file_name>x(<list>),y(<word>))
This function creates a block of graphics sprites from the given file name and coordinates by using the specified template.
For vehicles, function x() either specifies the 4 or 8 x coordinates of the sprites, and function y() addresses the sprites' y coordinate.
template({TEMP4_normal},db#ayse.pcx,x(66,82,114,146),y(56))expands into:
sprite(db#ayse.pcx 66 56 01 23 8 -3 -14) sprite(db#ayse.pcx 82 56 09 16 21 -14 -8) sprite(db#ayse.pcx 114 56 01 12 32 -15 -8) sprite(db#ayse.pcx 146 56 09 16 21 -5 -8)
The templates themselves are simple M4 macros. The one used above would look like this and would be put into an extra include file, probably named "templates.m4" or similar:
define(TEMP4_normal,{ sprite($1 $2 $6 01 23 8 -3 -14) sprite($1 $3 $6 09 16 21 -14 -8) sprite($1 $4 $6 01 12 32 -15 -8) sprite($1 $5 $6 09 16 21 -5 -8)} )
Code templating in m4nfo is even more straightforward than graphics templating, because you don't even need to use any pre-defined functions.
Instead, code templating is easily achieved by defining own M4 macros.
Function templating
Consider the need to template the livery change of the German DB over the years. From the early 1950s to 1974 most of the electrics were painted green, from 1974 to 1986 they were painted in ocean-blue/cream, from 1986 to 1996 they were painted in "orient red" and from 1997 until today, they're in "traffic red".
So, instead of having to write something like the code below for almost all of the DB electrics in a set,
// engine 1 def(0) yearbuilt( ref(1) if(1950 .. 1974) // green ref(2) if(1975 .. 1986) // blue/cream ref(3) if(1987 .. 1996) // orient red ref(4) else // traffic red ) ... // engine 2 def(1) yearbuilt( ref(5) if(1950 .. 1974) // green ref(6) if(1975 .. 1986) // blue/cream ref(7) if(1987 .. 1996) // orient red ref(8) else // traffic red ) ... // engine 3 def(2) yearbuilt( ref(9) if(1950 .. 1974) // green ref(10) if(1975 .. 1986) // blue/cream ref(11) if(1987 .. 1996) // orient red ref(12) else // traffic red )
you could define a macro "DB_yearswitch()" which simplifies the source to something like this:
define(DB_yearswitch,{ $1 if(1950 .. 1974) $2 if(1975 .. 1986) $3 if(1987 .. 1996) $4 else} )Example (usage of private macro):
// engine 1 def(0) yearbuilt( DB_yearswitch(ref(1), ref(2), ref(3), ref(4)) ) ... // engine 2 def(1) yearbuilt( DB_yearswitch(ref(5), ref(6), ref(7), ref(8)) ) ... // engine 3 def(2) yearbuilt( DB_yearswitch(ref(9), ref(10), ref(11), ref(12)) )
As mentioned above, these "private" macros would be put best into their own include file(s).
Especially when coding freight wagons, it would need lots of callbacks to set capacities, load amounts and/or cargo subtype texts for the different freight types. So, instead of having to write something like the code below
// WDPR def(8) property( cbr(5) if(loadamount()) cbfail() ) def(9) callback( cbr(15) if(CB_RCAP) // 15 t grftext(TSF_PLYW) if(CB_TSFX) ref(8) if(CB_PROP) // 3 ticks ref(13) else // graphics )
for every cargo, setting cargo capacity, load amount and cargo subtype text, for every freight car, this can be easily replaced by using a macro "setload()":
// setload(<capacity>, <num-loadamount-ticks>, [<cargo-subtext>], <ref>) def(9) setload(15 t, 3 ticks, TSF_PLYW, ref(13))
Here is the macro:
define(__firstarg,{$1}) define(__number,{__firstarg(patsubst($1,{ +},{,}))}) define(setload,{dnl property( cbr(__number($1) / __number($2)) if(loadamount()) cbfail() ) def(__lastdef) callback( cbr(__number($1)) if(CB_RCAP) // load ref(__lastdef) if(CB_PROP) // ticks ifelse($#,4,{grftext($3) if(CB_TSFX) $4},$3) )})
The first two helper functions are used to extract the real numbers from the parameters to setload(). The setload() macro itself computes the load amount per given "tick", then writes a second def() with the needed callback handling. Note that it also checks for the cargo subtype callback, whether to be used or not.
It's also quite easy to replace text strings by macros and thus keep all strings of some distributed source files in a single "string file" (resp. a "language file").
It's even possible to replace not only single strings but complete parameter structures containing strings for most of m4nfo's functions.
So, instead of using function stationnames() explicitly in the source files:
stationnames(FREIBURG, {US,"Freiburg (back view)", "Freiburg (front view)"}, {D, UTF8 "Freiburg (Rückansicht)", "Freiburg (Vorderansicht)"}, {F, UTF8 "Freiburg (vue de l'arrière)", "Freiburg (vue de face)"}, {E, UTF8 "Freiburg (vista de atrás)", "Freiburg (vista frontal)"}, {I, "Freiburg (vista di dietro)", "Freiburg(vista di fronte)"}, {NL, "Freiburg (achterpaneel)", "Freiburg (vooranzicht)"} )
the text strings might be placed in some file "strings.nfx":
define(STR_STAT_FREIBURG,{ {US,"Freiburg (back view)", "Freiburg (front view)"}, {D, UTF8 "Freiburg (Rückansicht)", "Freiburg (Vorderansicht)"}, {F, UTF8 "Freiburg (vue de l'arrière)", "Freiburg (vue de face)"}, {E, UTF8 "Freiburg (vista de atrás)", "Freiburg (vista frontal)"}, {I, "Freiburg (posteriore)", "Freiburg(vista di fronte)"}, {NL, "Freiburg (achterpaneel)", "Freiburg (vooranzicht)"}} )
(please note the extra quoting!), and thus be used (probably together with other strings) in any ~.nfx file like this:
include(strings.nfx) ... stationnames(FREIBURG, STR_STAT_FREIBURG)
Using M4 macro wizardry, even more flexible and more powerful templating can be achieved. Consider iterative function
forloop(<variable>, <m4nfo-expression>, <arguments>)
which is part of m4nfo (i.e. you don't need to define it yourself). 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.
Then, the following code snippet:
// connection with mole, front def(40) spriteset(normal(WATER), normal(14), xy(0,0), dxdydz(16,16,6)) def(42) spriteset(normal(WATER), normal(16), xy(0,0), dxdydz(16,16,6)) def(44) spriteset(normal(WATER), normal(18), xy(0,0), dxdydz(16,16,6))
could be easily reduced by introducing above function:
forloop(X, {def(40+X) spriteset(normal(WATER), normal(14+X), xy(0,0), dxdydz(16,16,6)) }, 0,2,4)
Another example shows how to reduce lengthy definitions of spritesets for rail cars:
def(0) spriteset(move(0),load(1,2,3)) def(1) spriteset(move(4),load(5,6,7)) def(2) spriteset(move(8),load(9,10,11)) def(3) spriteset(move(12),load(13,14,15)) def(4) spriteset(move(16),load(17,18,19)) def(5) spriteset(move(20),load(21,22,23)) def(6) spriteset(move(24),load(25,26,27)) def(7) spriteset(move(28),load(29,30,31))
With
forloop(X, {def(X) spriteset(move(4*X),load(4*X+1,4*X+2,4*X+3)) }, 0 .. 7)
Doing exactly the same.
Of course, it is also possible to use forloop() on named spritesets:
forloop(X,{def(0+X) spriteset(move(__oldred+X),load(__oldred+X)) }, 0 .. 3)
Yet another example how to reduce lengthy animation sequences:
forloop(X,{ tile( ground(GRASS) normal(69, xyz(1,1,0),dxdydz(8,16,40)) // building normal(X, xyz(0,14,40),dxdydz(16,15,16), TTD) // smoke ) tile( ground(GRASS) normal(68, xyz(1,1,0),dxdydz(16,8,40)) // building normal(X, xyz(14,0,40),dxdydz(16,15,16), TTD) // smoke ) },0xE75 .. 0xE7C)
This forloop produces code for a station depot boiler house, using the original TTD 'smoke' sprites (0xE75 .. 0xE7C) for each pair of x/y tiles.
Template templating
It is also possible to "template" templates (s.a.), i.e. using macros on macros, e.g.
forloop(X, {set(template({NG_A80D},remorque.pcx,x(LAYOUT_STANDARD),y(X*SPACING_LOCOS+10))) }, 0 .. 16)
with LAYOUT_STANDARD = {10,26,58,100,128,144,176,218} and SPACING_LOCOS = 30, these macros will generate a huge block of real sprites:
sprites/remorque.pcx 10 10 01 15 6 -2 -11 sprites/remorque.pcx 26 10 09 12 15 -9 -6 sprites/remorque.pcx 58 10 01 11 21 -3 -7 sprites/remorque.pcx 100 10 09 12 15 -2 -5 sprites/remorque.pcx 128 10 01 15 6 -2 -5 sprites/remorque.pcx 144 10 09 12 15 -11 -5 sprites/remorque.pcx 176 10 01 11 21 -16 -7 sprites/remorque.pcx 218 10 09 12 15 -4 -6 sprites/remorque.pcx 10 40 01 15 6 -2 -11 sprites/remorque.pcx 26 40 09 12 15 -9 -6 sprites/remorque.pcx 58 40 01 11 21 -3 -7 sprites/remorque.pcx 100 40 09 12 15 -2 -5 sprites/remorque.pcx 128 40 01 15 6 -2 -5 sprites/remorque.pcx 144 40 09 12 15 -11 -5 sprites/remorque.pcx 176 40 01 11 21 -16 -7 sprites/remorque.pcx 218 40 09 12 15 -4 -6 ... sprites/remorque.pcx 10 490 01 15 6 -2 -11 sprites/remorque.pcx 26 490 09 12 15 -9 -6 sprites/remorque.pcx 58 490 01 11 21 -3 -7 sprites/remorque.pcx 100 490 09 12 15 -2 -5 sprites/remorque.pcx 128 490 01 15 6 -2 -5 sprites/remorque.pcx 144 490 09 12 15 -11 -5 sprites/remorque.pcx 176 490 01 11 21 -16 -7 sprites/remorque.pcx 218 490 09 12 15 -4 -6