The m4nfo Tutorial

Bridge tutorial

Introduction

Bridges in TTD, TTDPatch and OpenTTD are quite simplistic, they lack most of the attributes other features in TTD offer. In fact, there are only means to define a handful of properties for a bridge, and a layout definition, determining what sprites should be displayed and where they are to be displayed for the various bridge segments and bridge types (rail, road, monorail, maglev).

This tutorial will deal primarily with the two different ways of setting up a bridge layout, either by using and/or overwriting TTD's original sprites, or by allocating and providing new sprites.

In case of new graphic sprites, the bridge's graphics are supplied by a spriteblock() function, either overriding original TTD sprites (most probably those of the toyland climate), or by allocating new sprites for the needed graphics.

How to define bridges

The definition sets the feature properties of a bridge: its ID, its length, price, max speed, etc. Please note that a bridge definition may only occur after a prior grfinit() function call for the newGRF file.

Following definition will be used for both of the examples:

Example (Bridge definition):
// define the bridge purchase text
deftxt(_mybridge,
	ALL,"Test bridge",
	D, UTF8 "Brückentest",
	I, "Ponte di prova"
)

// rebuilt TTD's original suspension steel (golden) bridge
definebridge(5,
	intro(1950)
	minlength(3)
	maxlength(9)
	price(50)
	maxspeed(80 km/h)
	flags(NOFARPILLARS)
	purchasetext(_mybridge)
//	bridgerailtext(_railtext)
//	bridgeroadtext(_roadtext)
)

This establishes a copy of TTD´s original suspension steel bridge (in its gold-coloured variant). The ID is "5", introduction year is 1950, the bridge can be built for all lengths between 3 tiles and 9 tiles, purchase price factor is "50", the bridge allows for a max speed of 80 km/h, there are no "far" pillars being drawn for "higher" bridges, and there´s a purchase text defined by function deftxt().

Examples

Be aware that following examples don't result into 100% complete newGRFs, but instead concentrate on the most important code features. E.g., in the given bridge property functions you'd have to add even more properties to get a well-working newGRF.

Example 1: using original TTD sprites

The first example is the more straightforward application. It will simply overwrite a bridge's original graphics and replace them by newly-defined graphic sprites.

Example (layout specification, using original TTD sprites):
setfeature(_BRIDGE)

// EXIT if Toyland
skipif(EXIT,CLIMATE,==,TOYLAND)

grf_init(GRF_BRIDGETUT1, ...)

// bridge definition as above

layout(5,0,
	segment(
		railbridge(front(0x99F+_BYELLOW, 0x997+_BYELLOW), back(0x9A9+_BYELLOW, 0x9A5+_BYELLOW), pillar(0x9B1, 0x9AD))
		roadbridge(front(0x99F+_BYELLOW, 0x997+_BYELLOW), back(0x99D+_BYELLOW, 0x995+_BYELLOW), pillar(0x9B1, 0x9AD))
		monobridge(front(0x99F+_BYELLOW, 0x997+_BYELLOW), back(0x10F2+_BYELLOW, 0x10EE+_BYELLOW), pillar(0x9B1, 0x9AD))
		mlevbridge(front(0x99F+_BYELLOW, 0x997+_BYELLOW), back(0x111A+_BYELLOW, 0x1116+_BYELLOW), pillar(0x9B1, 0x9AD))
	)

	segment(
		railbridge(front(0x9A0+_BYELLOW, 0x998+_BYELLOW), back(0x9AA+_BYELLOW, 0x9A6+_BYELLOW), pillar(0x9B2, 0x9AE))
		roadbridge(front(0x9A0+_BYELLOW, 0x998+_BYELLOW), back(0x99E+_BYELLOW, 0x996+_BYELLOW), pillar(0x9B2, 0x9AE))
		monobridge(front(0x9A0+_BYELLOW, 0x998+_BYELLOW), back(0x10F3+_BYELLOW, 0x10EF+_BYELLOW), pillar(0x9B2, 0x9AE))
		mlevbridge(front(0x9A0+_BYELLOW, 0x998+_BYELLOW), back(0x111B+_BYELLOW, 0x1117+_BYELLOW), pillar(0x9B2, 0x9AE))
	)

	segment(
		railbridge(front(0x9A4+_BYELLOW, 0x99C+_BYELLOW), back(0x9AC+_BYELLOW, 0x9A8+_BYELLOW), pillar(0x9B4, 0x9B0))
		roadbridge(front(0x9A4+_BYELLOW, 0x99C+_BYELLOW), back(0x9A2+_BYELLOW, 0x99A+_BYELLOW), pillar(0x9B4, 0x9B0))
		monobridge(front(0x9A4+_BYELLOW, 0x99C+_BYELLOW), back(0x10F5+_BYELLOW, 0x10F1+_BYELLOW), pillar(0x9B4, 0x9B0))
		mlevbridge(front(0x9A4+_BYELLOW, 0x99C+_BYELLOW), back(0x111D+_BYELLOW, 0x1119+_BYELLOW), pillar(0x9B4, 0x9B0))
	)

	segment(
		railbridge(front(0x9A3+_BYELLOW, 0x99B+_BYELLOW), back(0x9AB+_BYELLOW, 0x9A7+_BYELLOW), pillar(0x9B3, 0x9AF))
		roadbridge(front(0x9A3+_BYELLOW, 0x99B+_BYELLOW), back(0x9A1+_BYELLOW, 0x999+_BYELLOW), pillar(0x9B3, 0x9AF))
		monobridge(front(0x9A3+_BYELLOW, 0x99B+_BYELLOW), back(0x10F4+_BYELLOW, 0x10F0+_BYELLOW), pillar(0x9B3, 0x9AF))
		mlevbridge(front(0x9A3+_BYELLOW, 0x99B+_BYELLOW), back(0x111C+_BYELLOW, 0x1118+_BYELLOW), pillar(0x9B3, 0x9AF))
	)

	segment(
		railbridge(front(0x9BA+_BYELLOW, 0x9B9+_BYELLOW), back(0x9B6+_BYELLOW, 0x9B5+_BYELLOW), pillar(0x9BC, 0x9BB))
		roadbridge(front(0x9BA+_BYELLOW, 0x9B9+_BYELLOW), back(0x9B8+_BYELLOW, 0x9B7+_BYELLOW), pillar(0x9BC, 0x9BB))
		monobridge(front(0x9BA+_BYELLOW, 0x9B9+_BYELLOW), back(0x10F7+_BYELLOW, 0x10F6+_BYELLOW), pillar(0x9BC, 0x9BB))
		mlevbridge(front(0x9BA+_BYELLOW, 0x9B9+_BYELLOW), back(0x111F+_BYELLOW, 0x111E+_BYELLOW), pillar(0x9BC, 0x9BB))
	)

	segment(
		railbridge(front(0x9C1+_BYELLOW, 0x9C2+_BYELLOW), back(0x9BD+_BYELLOW, 0x9BE+_BYELLOW), pillar(0, 0))
		roadbridge(front(0x9C1+_BYELLOW, 0x9C2+_BYELLOW), back(0x9BF+_BYELLOW, 0x9C0+_BYELLOW+_BYELLOW), pillar(0, 0))
		monobridge(front(0x9C1+_BYELLOW, 0x9C2+_BYELLOW), back(0x10F8+_BYELLOW, 0x10F9+_BYELLOW), pillar(0, 0))
		mlevbridge(front(0x9C1+_BYELLOW, 0x9C2+_BYELLOW), back(0x1120+_BYELLOW, 0x1121+_BYELLOW), pillar(0, 0))
	)

	segment(
		railbridge(flat(front(0x985+_BYELLOW, 0x987+_BYELLOW), back(0x986+_BYELLOW, 0x988+_BYELLOW)), ramp(front(0x989+_BYELLOW, 0x98B+_BYELLOW),back(0x98A+_BYELLOW,0x98C+_BYELLOW)))
		roadbridge(flat(front(0x98D+_BYELLOW, 0x98F+_BYELLOW), back(0x98E+_BYELLOW, 0x990+_BYELLOW)), ramp(front(0x991+_BYELLOW, 0x993+_BYELLOW),back(0x992+_BYELLOW,0x994+_BYELLOW)))
		monobridge(flat(front(0x10E6+_BYELLOW, 0x10E8+_BYELLOW), back(0x10E7+_BYELLOW, 0x10E9+_BYELLOW)), ramp(front(0x10EA+_BYELLOW, 0x10EC+_BYELLOW),back(0x10EB+_BYELLOW,0x10ED+_BYELLOW)))
		mlevbridge(flat(front(0x110E+_BYELLOW, 0x1110+_BYELLOW), back(0x110F+_BYELLOW, 0x1111+_BYELLOW)), ramp(front(0x1112+_BYELLOW, 0x1114+_BYELLOW),back(0x1113+_BYELLOW,0x1115+_BYELLOW)))
	)
)


Example 2: using newly allocated sprites

The second example deals with the more sophisticated method of using new sprites for the bridge's graphics. It is the more flexible method, because it doesn't need to compete for original TTD sprites against other sets.

If recolouring is needed, the required recolour maps are to be defined as well.

Example (layout specification, using allocated sprites):
setfeature(_BRIDGE)
setpath(..\sprites)

// EXIT if Toyland
skipif(EXIT,CLIMATE,==,TOYLAND)

grf_init(GRF_BRIDGETUT1, ...)

// bridge definition as above ...

// allocating bridge sprites
spriteblock(ALLOCATE,
	set(
// [0] b_front
		sprite(test_bridge.pcx 2 40 01 47 47 -44 -35)
		sprite(test_bridge.pcx 66 40 01 42 52 -49 -30)
		sprite(test_bridge.pcx 130 40 01 47 52 -49 -35)
		sprite(test_bridge.pcx 194 40 01 42 47 -44 -29)
		sprite(test_bridge.pcx 258 40 01 47 47 1 -34)
		sprite(test_bridge.pcx 322 40 01 42 52 1 -29)
		sprite(test_bridge.pcx 386 40 01 47 52 1 -34)
		sprite(test_bridge.pcx 450 40 01 42 47 0 -29)
// [8] b_rail
		sprite(test_bridge.pcx 514 40 01 47 50 -23 -24)
		sprite(test_bridge.pcx 578 40 01 41 50 -23 -18)
		sprite(test_bridge.pcx 642 40 01 47 50 -23 -24)
		sprite(test_bridge.pcx 706 40 01 41 50 -24 -17)
		sprite(test_bridge.pcx 2 104 01 47 50 -23 -23)
		sprite(test_bridge.pcx 66 104 01 41 50 -23 -17)
		sprite(test_bridge.pcx 130 104 01 47 50 -23 -23)
		sprite(test_bridge.pcx 194 104 01 41 50 -23 -17)
// [16] b_road
		sprite(test_bridge.pcx 258 104 01 47 50 -23 -24)
		sprite(test_bridge.pcx 322 104 01 41 50 -23 -18)
		sprite(test_bridge.pcx 386 104 01 47 50 -23 -24)
		sprite(test_bridge.pcx 450 104 01 41 50 -23 -17)
		sprite(test_bridge.pcx 514 104 01 47 50 -23 -23)
		sprite(test_bridge.pcx 578 104 01 41 50 -23 -17)
		sprite(test_bridge.pcx 642 104 01 47 50 -23 -23)
		sprite(test_bridge.pcx 706 104 01 41 50 -23 -17)
// [24] b_mono
		sprite(test_bridge.pcx 2 168 01 47 50 -23 -24)
		sprite(test_bridge.pcx 66 168 01 41 50 -23 -18)
		sprite(test_bridge.pcx 130 168 01 47 50 -23 -24)
		sprite(test_bridge.pcx 194 168 01 41 50 -23 -17)
		sprite(test_bridge.pcx 258 168 01 47 50 -23 -23)
		sprite(test_bridge.pcx 322 168 01 41 50 -23 -17)
		sprite(test_bridge.pcx 386 168 01 47 50 -23 -23)
		sprite(test_bridge.pcx 450 168 01 41 50 -23 -17)
// [32] b_maglev
		sprite(test_bridge.pcx 514 168 01 47 50 -23 -24)
		sprite(test_bridge.pcx 578 168 01 41 50 -23 -18)
		sprite(test_bridge.pcx 642 168 01 47 50 -23 -24)
		sprite(test_bridge.pcx 706 168 01 41 50 -23 -17)
		sprite(test_bridge.pcx 2 232 01 47 50 -23 -23)
		sprite(test_bridge.pcx 66 232 01 41 50 -23 -17)
		sprite(test_bridge.pcx 130 232 01 47 50 -23 -23)
		sprite(test_bridge.pcx 194 232 01 41 50 -23 -17)
// [40] b_pillar
		sprite(test_bridge.pcx 258 232 01 13 12 -5 -7)
		sprite(test_bridge.pcx 290 232 01 13 12 -5 -7)
// [42] b_pillar_s (shifted pillars)
 		sprite(test_bridge.pcx 258 232 01 13 12 -37 9)
		sprite(test_bridge.pcx 290 232 01 13 12  27 9)
// b_pal (recolour tables)
		// [44] blue grey, rail
		colourtable(DOSMAP,
			47 .. 48, D7 .. D8,
			49 .. 4F, 10 .. 16
		)
		// [45] ocean blue, road
		colourtable(DOSMAP,
			47 .. 48, D7 .. D8,
			49 .. 4F, 9A .. A0
		)
		// [46] golden, mono + maglev
		colourtable(DOSMAP,
			47 .. 4F, 3C .. 44,
		)
	)
)

spriteset(0)

// entry points for sprites
define(b_base,0)
define(b_front,{eval(b_base+$1)})
define(b_rail,{eval(b_front(8)+$1)})
define(b_road,{eval(b_rail(8)+$1)})
define(b_mono,{eval(b_road(8)+$1)})
define(b_mlev,{eval(b_mono(8)+$1)})
define(b_pillar,{eval(b_mlev(8)+$1)})
define(b_pillar_s,{eval(b_pillar(2)+$1)})
define(b_pal,{eval((b_pillar_s(2)+$1) << 16)})

// general bridge segment
define(b_seg,{
	segment(
		railbridge(front(b_front($1)+b_pal(0), b_front($2)+b_pal(0)), back(b_rail($1)+b_pal(0), b_rail($2)+b_pal(0)), pillar($3, $4))
		roadbridge(front(b_front($1)+b_pal(1), b_front($2)+b_pal(1)), back(b_road($1)+b_pal(1), b_road($2)+b_pal(1)), pillar($3, $4))
		monobridge(front(b_front($1)+b_pal(2), b_front($2)+b_pal(2)), back(b_mono($1)+b_pal(2), b_mono($2)+b_pal(2)), pillar($3, $4))
		mlevbridge(front(b_front($1)+b_pal(2), b_front($2)+b_pal(2)), back(b_mlev($1)+b_pal(2), b_mlev($2)+b_pal(2)), pillar($3, $4))
	)}
)

// special bridge segments:
//
// segment entry
// bridge entry gate
// segment exit
// segment body
// segment connector

define(b_segentr,{b_seg(0,4, NONE, NONE)})		     // this is '/', without pillar
define(b_segent0,{b_seg(0,4, b_pillar_s(0), b_pillar_s(1))}) // this is '/', with front pillar
define(b_segexit,{b_seg(1,5,b_pillar(0), b_pillar(1))})	     // this is '\', with pillar
define(b_segbody,{b_seg(2,6, NONE, NONE)})		     // this is '=', without pillar
define(b_segconn,{b_seg(3,7, b_pillar(0), b_pillar(1))})     // this is '^', with pillar

// make all 6 bridge elements
layout(5,0,
	b_segexit()
	b_segent0()
	b_segentr()
	b_segexit()
	b_segbody()
	b_segconn()
)

// bridge ramps - use original sprites but recolor them
layout(5,6,
	segment(
		railbridge(flat(front(0x985, 0x987), back(0x986, 0x988)),
		           ramp(front(0x989, 0x98B), back(0x98A, 0x98C)))
		roadbridge(flat(front(0x98D+b_pal(1), 0x98F+b_pal(1)), back(0x98E+b_pal(1), 0x990+b_pal(1))),
		           ramp(front(0x991+b_pal(1), 0x993+b_pal(1)), back(0x992+b_pal(1),0x994+b_pal(1))))
		monobridge(flat(front(0x10E6+_BYELLOW, 0x10E8+_BYELLOW), back(0x10E7+_BYELLOW, 0x10E9+_BYELLOW)),
		           ramp(front(0x10EA+_BYELLOW, 0x10EC+_BYELLOW), back(0x10EB+_BYELLOW,0x10ED+_BYELLOW)))
		mlevbridge(flat(front(4366+_BWHITE, 4368+_BWHITE), back(4367+_BWHITE, 4369+_BWHITE)),
		             ramp(front(4370+_BWHITE, 4372+_BWHITE), back(4371+_BWHITE,4373+_BWHITE)))
	)
)

Due to the way sprites are allocated and used in TTDPatch/OpenTTD, a fixed range of parameters has to be used by m4nfo internally in bridge sprite allocation. To directly access bridge sprites (0 .. n) for a single bridge ID, m4nfo uses parameters starting with 0x20 (decimal 32). In addition, for each bridge ID (0 .. 12) an extra parameter is needed which holds the starting address of the bridge's sprite block. This means that parameter range 0 .. 31 - 13 (max number of bridges) is freely available for parameters to be defined by the coder of a bridge set, to set bridge properties like cost or bridge speed.

The way m4nfo handles sprite allocation allows re-use of bridge sprites, e.g. bridge ramps, which had already been allocated for a different bridge ID. This is done by copying the needed sprites from the 'source' bridge's spriteblock into the 0x20 parameter block used for direct sprite access:

Example (re-use of bridge ramps by using sprites of a different bridge sprite block):
// rail ramps
forloop(X,{pcalc(parameter(0x20 + X - 44) := parameter(3) + X)
},44 .. 51)
// road ramps
forloop(X,{pcalc(parameter(0x20 + X - 44) := parameter(3) + X)
},52 .. 59)

layout(GIRDER_1,6,
	segment(
		railbridge(flat(front(2+_BBLUE,3+_BBLUE), back(0+_BBLUE,1+_BBLUE)),
			ramp(front(6+_BBLUE,7+_BBLUE), back(4+_BBLUE,5+_BBLUE)))
		roadbridge(flat(front(10+_BBLUE,11+_BBLUE), back(8+_BBLUE,9+_BBLUE)),
			ramp(front(14+_BBLUE,15+_BBLUE), back(12+_BBLUE,13+_BBLUE)))
		...
	)
)

Here, the flats and ramps from the 'concrete bridge' (ID 01, parameter = 3), namely sprites 44 .. 51 (for rail) and 52 .. 59 (for road), are copied using function pcalc() into the 0x20 parameter block to be addressed like this:

bridge sprite new sprite old
rail044
 145
 ......
 751
road852
 953
 ......
 1559