The m4nfo Tutorial

Show full frameset for easy navigation

Station coding tutorial

Introduction

Stations are an essential feature in TTD, and since TTDPatch 2.0.1 a6 (Nov 10, 2003) it is possible to code custom stations. Although station coding is a bit more challenging than coding vehicles, it is much more straightforward in m4nfo than doing it in plain nfo.

The most important difference compared to coding vehicles is that stations not only have graphics and access to a number of TTD's game variables in the same way as vehicles have, but also use a sprite and a tile layout, thus introducing 3D information. This offers a number of possibilities to 'recycle' sprite or tile layouts in the code, which might make coding stations quite efficient.

Examples

Be aware that following examples don't always result into 100% complete newGRFs, but instead concentrate on the most important code features rather than implementing it in all details.

Example 1: A simple station

The first example will demonstrate a very basic station type, consisting only of one specific tile, but with the ability to replicate it to build larger station sections. This station type is based on one of the depot stations in the NewStations set (see picture on the left), and in addition it'll demonstrate means to self-adjust by inclusion of PBS and track awareness. In addition, it'll demonstrate how to handle snow (or rather terrain) awareness.

Defining the station

First thing to do would be to define the station at all. In m4nfo, this is done by use of function definestation(), and here it is:

definestation(ENGINESHED,"Engine sheds",
	class(DEPOT)
	callbacks(CB_LAYOUT)                        
	exclude_widths(8)
	exclude_lengths(8)
	pylons(TTD_ALLTILES)
	flags(FOUNDATIONS)
)

As can be seen from the function's parameter list, this station's ID is "ENGINESHED" (some number defined elsewhere), and its (default) name is set to "Engine sheds".

Now, in the function's body, there are more property functions to be found, defining even more attributes of this station:

Next thing to do would be the provision of class and station names. A class name is only given once for each class, but a station name must be given for every single station (in case there will be a need for translating it, rather than including only the default name). The station name given in the definestation() parameter list will be the default station name (most probably in English), but it is possible to attach additional station names for other languages too. Please note that for an UTF-8 encoding the UTF8 attribute is needed before any to be encoded text string.

classnames(DEPOT,
	{US, "Locomotive depot"},
	{D, "Bahnbetriebswerk"},
	{F, UTF8 "Dépôt de locomotives"},
	{E, UTF8 "Depósitos de locomotoras"},
	{NL, "Locomotief depot"},
	{I, "Rimesse locomotive"},
	{PL, "Parowozownia"},
	{HR, "Depo za lokomotive"},
	{S, "Lokverkstad"},
	{H, UTF8 "Mozdonyszín"},
	{RUS, UTF8 "Локомотивное депо"}
)

stationnames(ENGINESHED,
	{US, "Engine sheds"},
	{D, "Lokschuppen"},
	{F, UTF8 "Remises à locomotives"},
	{E, "Naves de locomotoras"},
	{NL, "Locomotiefloods"},
	{I, "Rimesse per locomotive"}
	{PL, "Lokomotywownie"},
	{HR, "Remiza za lokomotive"},
	{S, "Lokstaller"},
	{H, UTF8 "Mozdonyszín"},
	{RUS, UTF8 "Локомотивное депо"}
)

Providing the graphics

Now, some real (graphics) sprites have to be defined, this is done in m4nfo in the following way:

spriteblock(
    set(
// [0 .. 5] locomotive shed
	sprite(shed5.pcx 10 10 09 16 21 -31 4)
	sprite(shed5.pcx 35 10 09 27 43 -32 -7)
	sprite(shed5.pcx 80 10 09 41 66 -31 1)
	sprite(shed5.pcx 150 10 09 41 66 -33 1)
	sprite(shed5.pcx 220 10 09 27 43 -9 -7)
	sprite(shed5.pcx 270 10 09 16 21 12 4)
...
// [10 .. 13] walls & doors
	sprite(shed5.pcx 10 110 09 18 15 0 0)
	sprite(shed5.pcx 28 110 09 18 15 0 0)
	sprite(shed5.pcx 46 110 09 18 15 0 0)
	sprite(shed5.pcx 64 110 09 18 15 0 0)
// [14 .. 15] locomotive shed (snowy roof)
	sprite(shed5.pcx 10 130 09 41 66 -31 1)
	sprite(shed5.pcx 80 130 09 41 66 -33 1)
...

// [18 .. 19] icon locomotive shed
	sprite(shed5.pcx 10 223 09 46 66 -31 -15)
	sprite(shed5.pcx 80 223 09 46 66 -31 -15)
...
   )
)

def(1) spriteset(little(0),lots(0))

Sprites 0 .. 5 constitute the locomotive shed building. According to tile layout examples, there are sprites for x and y-direction. In the following, we will only talk about x direction from now on.

Sprites #0 and #1 are the platform graphics, #0 being the back platform, and #1 being the front platform. Sprite #2 is the shed's roof. Now, TTD will draw these sprites in the correct order to avoid graphics glitches in game. This is achieved by taking 3D information into account which will be supplied later in the layout() function. In any way, sprite #0 will be drawn first, then the train vehicles are drawn, then sprite #1, with the roof (#2) being drawn last.

Note that "excessive blue" for the roof sprites #2 and #3. This is needed to be able to position the door and wall child sprites in such a way that they fit entirely into their 'parent sprite' boundaries (that roof sprite).

The spriteset() function in def(1) collects all the given sprites and makes them availaible to the chain of control to be set up now. As there are no cargo loading states for this type of station, both of its parameters are set to zero by auxiliary functions little() and lots().

Setting the tile layout

Now, the needed sprites having been defined in a "spriteblock" function, we need means to compose the station tiles from its individual sprites. This is done by function layout(). Its first parameter is the station-ID associated with this particular layout followed by a block of tile definitions:

layout(ENGINESHED,

// [0 .. 1] open
    tile(_open,
      ground(1012)
      regular(0, xyz(0,0,0),dxdydz(16,5,7))
      regular(1, xyz(0,11,0),dxdydz(16,5,7))
      regular(2, xyz(0,0,16),dxdydz(16,16,26)) // roof
    )
    tile(
      ground(1011)
      regular(5, xyz(0,0,0),dxdydz(5,16,7))
      regular(4, xyz(11,0,0),dxdydz(5,16,7))
      regular(3, xyz(0,0,16),dxdydz(16,16,26)) // roof
    )

// [2 .. 3] closed
    tile(_closed,
      ground(1012)
      regular(0, xyz(0,0,0),dxdydz(16,5,7))
      regular(1, xyz(0,11,0),dxdydz(16,5,7))
      regular(2, xyz(0,0,16),dxdydz(16,16,26)) // roof
      regular(10, xyoff(8,23)) // wall
    )
    tile(
      ground(1011)
      regular(5, xyz(0,0,0),dxdydz(5,16,7))
      regular(4, xyz(11,0,0),dxdydz(5,16,7))
      regular(3, xyz(0,0,16),dxdydz(16,16,26)) // roof
      regular(11, xyoff(43,23)) // wall
    )

// [4 .. 5] door closed
    tile(_door_closed,
      ground(1012)
      regular(0, xyz(0,0,0),dxdydz(16,5,7))
      regular(1, xyz(0,11,0),dxdydz(16,5,7))
      regular(2, xyz(0,0,16),dxdydz(16,16,26)) // roof
      regular(12, xyoff(8,23)) // door
    )
    tile(
      ground(1011)
      regular(5, xyz(0,0,0),dxdydz(5,16,7))
      regular(4, xyz(11,0,0),dxdydz(5,16,7))
      regular(3, xyz(0,0,16),dxdydz(16,16,26)) // roof
      regular(13, xyoff(43,23)) // door
    )

// snow

// [6 .. 7] open
    tile(_open_snow,
      ground(1012)
      regular(0, xyz(0,0,0),dxdydz(16,5,7))
      regular(1, xyz(0,11,0),dxdydz(16,5,7))
      regular(14, xyz(0,0,16),dxdydz(16,16,26)) // roof
    )
    tile(
      ground(1011)
      regular(5, xyz(0,0,0),dxdydz(5,16,7))
      regular(4, xyz(11,0,0),dxdydz(5,16,7))
      regular(15, xyz(0,0,16),dxdydz(16,16,26)) // roof
    )

// [8 .. 9] closed
    tile(_closed_snow,
      ground(1012)
      regular(0, xyz(0,0,0),dxdydz(16,5,7))
      regular(1, xyz(0,11,0),dxdydz(16,5,7))
      regular(14, xyz(0,0,16),dxdydz(16,16,26)) // roof
      regular(10, xyoff(8,23)) // wall
    )
    tile(
      ground(1011)
      regular(5, xyz(0,0,0),dxdydz(5,16,7))
      regular(4, xyz(11,0,0),dxdydz(5,16,7))
      regular(15, xyz(0,0,16),dxdydz(16,16,26)) // roof
      regular(11, xyoff(43,23)) // wall
    )

// [10 .. 11] door closed
    tile(_door_closed_snow,
      ground(1012)
      regular(0, xyz(0,0,0),dxdydz(16,5,7))
      regular(1, xyz(0,11,0),dxdydz(16,5,7))
      regular(14, xyz(0,0,16),dxdydz(16,16,26)) // roof
      regular(12, xyoff(8,23)) // door
    )
    tile(
      ground(1011)
      regular(5, xyz(0,0,0),dxdydz(5,16,7))
      regular(4, xyz(11,0,0),dxdydz(5,16,7))
      regular(15, xyz(0,0,16),dxdydz(16,16,26)) // roof
      regular(13, xyoff(43,23)) // door
    )    
    
// [12 .. 13] icons
    tile(_icon,
      ground(1012)
      regular(18, xyz(0,0,0),dxdydz(16,16,1))
    )
    tile(
      ground(1011)
      regular(19, xyz(0,0,0),dxdydz(16,16,1))
    )
)

As can be seen, there are associated pairs of tiles, one in x and one in y direction. Each tile consisting of a 'ground sprite', representing the track (TTD sprites 1011 and 1012), and one or more 'building sprites', representing the back or front wall, and the roof. Also note that the tile function might get an optional label for easy referencing that particular tile.

Each of these sprites is described by a sequential number, associated with its rank in the graphics spriteblock, a 3-dimensional coordinate, indicating its position in 3D space, and its size in x, y, and height. Some of these sprites are so-called 'child' sprites, which have neither coordinates nor sizes but instead define a 2-dimensional 'offset' relative to the top left corner of their parent sprites, sharing the parent's 3D bounding box. Evaluating these numbers, the TTD sprite sorter will draw the sprites in the correct order, clipping away the (to be) hidden parts.

Now, tiles #0 and #1 (label "_open") represent the open shed (both in x and y direction), tiles #2 and #3 ("_closed") display the shed without an entry, by applicating a so-called 'child sprite' to the roof sprite, tiles #4 and #5 ("_door_closed") are doing the same but with a closed door graphics instead of a wall, tiles #6/#7, #8/#9, and #10/#11 are the same only with a snowy roof, and tiles #12/#13 ("_icon") are constituting the icons needed for the building menu.

Putting it all together

After preparing the needed graphics and their layout, we'll have to build a 'chain of control', linking graphics and layout to the station-ID, as well as handling the shed's functionality.

First thing we want to achieve is the shed adjusting its appearance in case a track is connected to it. If there's no track in front of the shed, it should show a plain wall without a door. Second feature we want to code is that, in case there is a track leading into the shed, the shed's door should only open when a train vehicle is driving into or out of the shed.

The first feature will be handled by m4nfo's function tinfo_trackconnect(), and the second one is handled by function pbsinfo().

Last thing which has to be done is to draw custom foundations when being built on a slope, and to show snowy graphics when being built on snowy terrain. The former is handled by use of function spritetype(), the latter is done by using function tinfo_terrain().

Here is the code:

def(0) spritetype(
	ref(CF_NORMAL) if(2) // custom foundation                       
	ref(1) else	     // normal ground or building sprite
)

def(6) pbsinfo(
	reftile(_open) if(PBSRESERVED) // open
	reftile(_door_closed) else     // closed
)

def(7) tinfo_trackconnect(shiftmask(0,1),
	ref(6) if(1)	      // track from below
	reftile(_closed) else // no track, show wall
)

// snow
def(6) pbsinfo(
	reftile(_open_snow) if(PBSRESERVED) // open
	reftile(_door_closed_snow) else	    // closed
)

def(8) tinfo_trackconnect(shiftmask(0,1),
	ref(6) if(1)		   // track from below
	reftile(_closed_snow) else // no track, show wall
)

// check for snow
def(9) tinfo_terrain(
	ref(8) if(SNOW) // snow
	ref(7) else     // no snow
)

def(10) callback(
	ref(9) if(CB_LAYOUT) // tile layout
	ref(0) else	     // graphics chain
)

// menu
def(11) callback(
	reftile(_icon) if(CB_LAYOUT) // tile layout
	ref(0) else		     // graphics chain
)

makestation(ENGINESHED,
	link(ref(11), MENU)
	default(ref(10))
)

Now, let's sum up what we have done. As usual, let's start at the end, at the makestation() function.

This function connects to two chains, one chain for the building menu entry, and the main chain constitutes a description of the station in game. Now, the chain for the menu entry is simple: in case of CB_LAYOUT, it'll show tile(s) #12/#13 from the layout (i.e., the icon), and otherwise, it'll refer to def(0) which represents both the main graphics set and the custom foundation graphics set. Since there's no need for a foundation in the menu, the graphics for tiles #12/13 will be shown in the menu.

Now, let's take a look onto the main (the 'default') chain now. First (in def(10)) we are checking for callback CB_LAYOUT as well. In case of no callback, we're chaining again to def(0) and are showing appropriate graphics, either custom foundation sprites (in case spritetype() returns "2") or just building sprites.

In case of CB_LAYOUT, we check for snowy terrain (in def(9)). In case of snow, we're chaining to def(8), and if there's no snow the chain proceeds at def(7). Since both chains are basically doing the same, only the no-snow chain is described further.

First of all, we're checking for a track connection to the shed's front door in def(7). This is done by calling function tinfo_trackconnect(). To check only for track leading to the (visible) front door, we'll have to check for bit0 returned by this function. This is done by checking the function's result only for this bit, which can be done by using auxiliary function shiftmask(). In case, the track exists, we're proceeding to def(6) checking the PBS state, and in case of no track, we just show the plain wall by returning tile #2 to the callback.

Function pbsinfo() in def(6) questions the PBS state and in case of PBSRESERVED (i.e., track has been reserved by the pathfinder), we like to show tile #0 (door open), otherwise tile #4 (door closed) is shown.

And that's all for this example. In the next one, we will expand on this one to show how a multiple of similar stations types could be handled by using the very same structure, only by making some extensions in the stations' tile layout.

Example 2: Shared layouts and layout structures

For stations, the tile layout is kept apart from the real sprites defined in a spriteblock() and accessed by spriteset() functions. This opens up interesting possibilities to either share tile or sprite layouts between stations. In this second example we want to extend the station from example 1 with another similar station (the "old warehouse", see image on the left), in such a way that the tile layouts for both differ only with regards to the sprite addresses. In this way, the second station needs no own control chain, but can use the one from the first station.

Defining the stations

First thing to add would be the definition of the second station as above.

definestation(ENGINESHED,{"Engine sheds", "Old warehouses"},
	class(DEPOT, PIECEGOODS)
	callbacks(CB_LAYOUT, CB_LAYOUT)                        
	exclude_widths(8, 8)
	exclude_lengths(8, 8)
	pylons(TTD_ALLTILES, TTD_ALLTILES)
	flags(FOUNDATIONS, FOUNDATIONS)
)

As can be seen, the definestation() function can handle more than one station definition. The second station-ID would be allocated automatically in this case, i.e. it will be ENGINESHED + 1. Because the second station should get a different class, we need to add new class and station names for this station.

classnames(PIECEGOODS,
	{US, "Freight stations (piece goods)"},
	{D, UTF8 "Güterbahnhöfe (Stückgut)"},
	{F, "Gares de marchandises (r.o.)"},
	{E, UTF8 "Estaciónes de mercancías (bulto)"},
	{NL, "Goederenloodsen (stukgoederen)"},
	{I, "Scali merci (collettame)"},
	{PL, "Stacje towarowe (drobnica)"},
	{HR, "Teretne stanice (komadni teret)"},
	{S, "Godsstationer (styckegods)"},
	{H, UTF8 "Teherpályaudvar (darabáru)"},
	{RUS, UTF8 "Грузовые станции (товары)"}
)

stationnames(ENGINESHED+1,
	{US, "Old warehouses"},
	{D, UTF8 "alte Lagerhäuser"},
	{F, UTF8 "Vieux entrepôts"},
	{E, "Almacenes viejos"},
	{NL, "Oude magazijnen"},
	{I, "Vecchi magazzini"}
	{PL, "Stare magazyny"},
	{HR, UTF8 "Stara skladišta"},
	{S, "Packhus"},
	{H, UTF8 "Régi raktár"},
	{RUS, UTF8 "Старые склады"}
)

Providing the graphics

Again, the graphics sprites have to be defined, this time for both stations:

spriteblock(
    set(
// [0 .. 5] locomotive shed
	sprite(shed5.pcx 10 10 09 16 21 -31 4)
	sprite(shed5.pcx 35 10 09 27 43 -32 -7)
	sprite(shed5.pcx 80 10 09 41 66 -31 1)
	sprite(shed5.pcx 150 10 09 41 66 -33 1)
	sprite(shed5.pcx 220 10 09 27 43 -9 -7)
	sprite(shed5.pcx 270 10 09 16 21 12 4)
// [6 .. 9] old warehouse
	sprite(shed5.pcx 35 60 09 27 43 -32 -7)
	sprite(shed5.pcx 80 60 09 44 64 -31 -2)
	sprite(shed5.pcx 150 60 09 44 64 -31 -2)
	sprite(shed5.pcx 220 60 09 27 43 -9 -7)
// [10 .. 13] walls & doors
	sprite(shed5.pcx 10 110 09 18 15 0 0)
	sprite(shed5.pcx 28 110 09 18 15 0 0)
	sprite(shed5.pcx 46 110 09 18 15 0 0)
	sprite(shed5.pcx 64 110 09 18 15 0 0)
// [14 .. 15] snowy roof locomotive shed
	sprite(shed5.pcx 10 130 09 41 66 -31 1)
	sprite(shed5.pcx 80 130 09 41 66 -33 1)
// [16 .. 17] snowy roof warehouse
	sprite(shed5.pcx 10 175 09 44 64 -31 -2)
	sprite(shed5.pcx 80 175 09 44 64 -31 -2)

// [18 .. 19] icon locomotive shed
	sprite(shed5.pcx 10 223 09 46 66 -31 -15)
	sprite(shed5.pcx 80 223 09 46 66 -31 -15)
// [20 .. 21] icon warehouse
	sprite(shed5.pcx 10 273 09 49 64 -31 -18)
	sprite(shed5.pcx 80 273 09 49 64 -31 -18)
   )
)

def(1) spriteset(little(0),lots(0))

The sprite set is the same as in example 1, with only the old warehouse sprites being added.

Setting the tile layout

Again, the needed sprites for the second station having been defined in the "spriteblock" function, a new layout for the second station has to be defined:

layout(ENGINESHED +1,

// [0 .. 1] open
    tile(_open,
      ground(1012)
      regular(0, xyz(0,0,0),dxdydz(16,5,7))
      regular(6, xyz(0,11,0),dxdydz(16,5,7))
      regular(7, xyz(0,0,16),dxdydz(16,16,26)) // roof
    )
    tile(
      ground(1011)
      regular(5, xyz(0,0,0),dxdydz(5,16,7))
      regular(9, xyz(11,0,0),dxdydz(5,16,7))
      regular(8, xyz(0,0,16),dxdydz(16,16,26)) // roof
    )
// [2 .. 3] closed
    tile(_closed,
      ground(1012)
      regular(0, xyz(0,0,0),dxdydz(16,5,7))
      regular(6, xyz(0,11,0),dxdydz(16,5,7))
      regular(7, xyz(0,0,16),dxdydz(16,16,26)) // roof
      regular(10, xyoff(8,26)) // wall
    )
    tile(
      ground(1011)
      regular(5, xyz(0,0,0),dxdydz(5,16,7))
      regular(9, xyz(11,0,0),dxdydz(5,16,7))
      regular(8, xyz(0,0,16),dxdydz(16,16,26)) // roof
      regular(11, xyoff(43,26)) // wall
    )
// [4 .. 5] door closed
    tile(_door_closed,
      ground(1012)
      regular(0, xyz(0,0,0),dxdydz(16,5,7))
      regular(6, xyz(0,11,0),dxdydz(16,5,7))
      regular(7, xyz(0,0,16),dxdydz(16,16,26)) // roof
      regular(12, xyoff(8,26)) // door
    )
    tile(
      ground(1011)
      regular(5, xyz(0,0,0),dxdydz(5,16,7))
      regular(9, xyz(11,0,0),dxdydz(5,16,7))
      regular(8, xyz(0,0,16),dxdydz(16,16,26)) // roof
      regular(13, xyoff(43,26)) // door
    )

// snow
// [6 .. 7] open
    tile(_open_snow,
      ground(1012)
      regular(0, xyz(0,0,0),dxdydz(16,5,7))
      regular(6, xyz(0,11,0),dxdydz(16,5,7))
      regular(16, xyz(0,0,16),dxdydz(16,16,26)) // roof
    )
    tile(
      ground(1011)
      regular(5, xyz(0,0,0),dxdydz(5,16,7))
      regular(9, xyz(11,0,0),dxdydz(5,16,7))
      regular(17, xyz(0,0,16),dxdydz(16,16,26)) // roof
    )
// [8 .. 9] closed
    tile(_closed_snow,
      ground(1012)
      regular(0, xyz(0,0,0),dxdydz(16,5,7))
      regular(6, xyz(0,11,0),dxdydz(16,5,7))
      regular(16, xyz(0,0,16),dxdydz(16,16,26)) // roof
      regular(10, xyoff(8,26)) // wall
    )
    tile(
      ground(1011)
      regular(5, xyz(0,0,0),dxdydz(5,16,7))
      regular(9, xyz(11,0,0),dxdydz(5,16,7))
      regular(17, xyz(0,0,16),dxdydz(16,16,26)) // roof
      regular(11, xyoff(43,26)) // wall
    )
// [10 .. 11] door closed
    tile(_door_closed_snow,
      ground(1012)
      regular(0, xyz(0,0,0),dxdydz(16,5,7))
      regular(6, xyz(0,11,0),dxdydz(16,5,7))
      regular(16, xyz(0,0,16),dxdydz(16,16,26)) // roof
      regular(12, xyoff(8,26)) // door
    )
    tile(
      ground(1011)
      regular(5, xyz(0,0,0),dxdydz(5,16,7))
      regular(9, xyz(11,0,0),dxdydz(5,16,7))
      regular(17, xyz(0,0,16),dxdydz(16,16,26)) // roof
      regular(13, xyoff(43,26)) // door
    )
    
// [12 .. 13] icons
    tile(_icon,
      ground(1012)
      regular(20, xyz(0,0,0),dxdydz(16,16,1))
    )
    tile(
      ground(1011)
      regular(21, xyz(0,0,0),dxdydz(16,16,1))
    )
)

As can be seen, the layout structure is identical to that of ENGINESHED, only the sprite numbers are different.

Putting it all together

Now, with the second station using the same tile layout as the first station, and of course needing the same behaviour as the first one with regards to track connection, opening doors and terrain awareness, the additional code gets quite small:

def(0) spritetype(
	ref(CF_NORMAL) if(2) // custom foundation                       
	ref(1) else	     // normal ground or building sprite
)

def(6) pbsinfo(
	reftile(_open) if(PBSRESERVED) // open
	reftile(_door_closed) else     // closed
)

def(7) tinfo_trackconnect(shiftmask(0,1),
	ref(6) if(1)	      // track from below
	reftile(_closed) else // no track, show wall
)

// snow
def(6) pbsinfo(
	reftile(_open_snow) if(PBSRESERVED) // open
	reftile(_door_closed_snow) else	    // closed
)

def(8) tinfo_trackconnect(shiftmask(0,1),
	ref(6) if(1)		   // track from below
	reftile(_closed_snow) else // no track, show wall
)

// check for snow
def(9) tinfo_terrain(
	ref(8) if(SNOW) // snow
	ref(7) else     // no snow
)

def(10) callback(
	ref(9) if(CB_LAYOUT) // tile layout
	ref(0) else	     // graphics chain
)

// menu
def(11) callback(
	reftile(_icon) if(CB_LAYOUT) // tile layout
	ref(0) else		     // graphics chain
)

makestation(ENGINESHED,
	link(ref(11), MENU)
	default(ref(10))
)

// additional code for the "old warehouse" station
makestation(ENGINESHED+1,
	link(ref(11), MENU)
	default(ref(10))
)

That's all! Because the new station uses exactly the same code and the same pointers into the sprite and layout structures. The only difference are the real sprite numbers in its tile layout.

There are more possibilities. One of them is to use same layouts including even the same real sprite numbers, by using two different spriteblocks with real sprites. In this case, only one tile layout is needed, and will be copied by function copylayout() for the other station-IDs. This will be shown in the next example.

Example 3: Animation

Defining the station

Once again, first thing to do would be to define the station, this time including special definitions for animation:

definestation(ANIMTEST,{"animation test"},
	class(DEPOT)
	callbacks(CB_LAYOUT)                        
	include_widths(1)
	include_lengths(1)
	setcargotriggers({PASS, MAIL})
	anim_info({8,LOOP})
	anim_triggers({ARRIVE, LEAVE})
	anim_speed(2)
)

Most definitions should be familiar by now, except the animation specific ones.

There's another specific function, namely setcargotriggers({PASS, MAIL}), which sets the cargo types for which trigger re-randomisation should occur.

Providing the graphics

Graphic sprites are defined in the usual way:

spriteblock(
    set(
	sprite(animtest.pcx 10 10 09 31 64 -31 0) // 0
	sprite(animtest.pcx 78 10 09 31 64 -31 0) // 1
	sprite(animtest.pcx 146 10 09 31 64 -31 0) // 2
	sprite(animtest.pcx 214 10 09 31 64 -31 0) // 3
	sprite(animtest.pcx 10 48 09 31 64 -31 0) // 4
	sprite(animtest.pcx 78 48 09 31 64 -31 0) // 5
	sprite(animtest.pcx 146 48 09 31 64 -31 0) // 6
	sprite(animtest.pcx 214 48 09 31 64 -31 0) // 7
    )
)
    
def(1) spriteset(little(0),lots(0))

Setting the tile layout

In the next step, a tile layout using the sprites defined above has to be set up:

layout(ANIMTEST,
// [0/1]
    tile(
      ground(1012))
      regular(0, xyz(0,0,0),dxdydz(16,16,1))
    )
    tile(
      ground(1011)
      regular(0, xyz(0,0,0),dxdydz(16,16,1))
    )
// [2/3]
    tile(
      ground(1012)
      regular(1, xyz(0,0,0),dxdydz(16,16,1))
    )
    tile(
      ground(1011)
      regular(1, xyz(0,0,0),dxdydz(16,16,1))
    )
// [4/5]
    tile(
      ground(1012)
      regular(2, xyz(0,0,0),dxdydz(16,16,1))
    )
    tile(
      ground(1011)
      regular(2, xyz(0,0,0),dxdydz(16,16,1))
    )
// [6/7]
    tile(
      ground(1012)
      regular(3, xyz(0,0,0),dxdydz(16,16,1))
    )
    tile(
      ground(1011)
      regular(3, xyz(0,0,0),dxdydz(16,16,1))
    )
// [8/9]
    tile(
      ground(1012)
      regular(4, xyz(0,0,0),dxdydz(16,16,1))
    )
    tile(
      ground(1011)
      regular(4, xyz(0,0,0),dxdydz(16,16,1))
    )
// [10/11]
    tile(
      ground(1012)
      regular(5, xyz(0,0,0),dxdydz(16,16,1))
    )
    tile(
      ground(1011)
      regular(5, xyz(0,0,0),dxdydz(16,16,1))
    )
// [12/13]
    tile(
      ground(1012)
      regular(6, xyz(0,0,0),dxdydz(16,16,1))
    )
    tile(
      ground(1011)
      regular(6, xyz(0,0,0),dxdydz(16,16,1))
    )
// [14/15]
    tile(
      ground(1012)
      regular(7, xyz(0,0,0),dxdydz(16,16,1))
    )
    tile(
      ground(1011)
      regular(7, xyz(0,0,0),dxdydz(16,16,1))
    )
)

Putting it all together

Now, after setting up the needed graphics and their tile layout, we'll have to build a 'chain of control', linking graphics and layout to the station-ID, as well as handling the tile animation.

def(2) anim_frame(
	cbr(0) if(0)
	cbr(2) if(1)
	cbr(4) if(2)
	cbr(6) if(3)
	cbr(8) if(4)
	cbr(10) if(5)
	cbr(12) if(6)
	cbr(14) else
)

def(3) anim_trigger(
	animcontrol(A_START) if(ARRIVE) // arrive
	animcontrol(A_STOP) if(LEAVE)	// leave
	animcontrol(A_NOP) else		// do nothing
)

def(4) callback(
	ref(2) if(CB_LAYOUT)
	ref(3) if(CB_ACONTROL)
	ref(1) else
)

makestation(ANIMTEST,
	default(ref(4))
)