The m4nfo User manual and Technical Report

Station callbacks

Using callbacks for railway stations

Introduction

This is an in-depth description of station callbacks. More basic information about callbacks can be found here.

A 'callback' is a special action that TTDPatch 'calls' in order to modify various attributes of a feature (e.g., a station) previously defined. An example being the modification of the visual effect of a train vehicle, its speed, or its capacity.

This is done by identifying the incidence and the possible type of a callback in the callback() function. Depending on the type of the callback, control is handed over eventually to a function which handles the callback, or lets it fail. In the latter case, the value previously set by the appropriate property function will be kept using.

In addition, all animation callbacks also allow to trigger sound effects by returning a sound-ID value in the high byte of the callback result. This could be achieved in m4nfo by shifting the sound-ID value 8 bits to the left and OR-ing it with the appropriate animation state value, like this: cbr(eval((<sound_ID> <<8) | <animstate>)).

Instead, one of m4nfo's helper functions could be used, supplying a second parameter (in addition to the callback result) which will be interpreted as a sound effect ID, like this: animcontrol(0, SND_HORN) if(CB_ACONTROL). The corresponding sound effect will then be played on the appropriate station tile, according to the state of the corresponding animation.

Here's a list of all available callbacks for stations. Please note, that not all of them need to be explicitly activated in the callbacks() property function during the station definition stage:

Callback Description
CB_ACONTROL Set frame the animation should jump to
CB_AFRAME Set next animation frame
CB_AISELECT AI construction/purchase selection
CB_ASPEED Set animation speed
CB_AVAILABLE Station availability in construction window
CB_LAYOUT Use callback to select sprite layout
CB_SLOPE Custom slope check
CB_TILETYPE Callback to select TTD tile type

Please also note that CB_SLOPE has to be used in the MENU branch.

Description

CB_ACONTROL - set frame the animation should jump to

This callback is called periodically or when an animation trigger happens. It returns the number of the frame the animation should jump to, or one of the following special values:

ValueLabelMeaning
255A_STOPstop animation in its current frame
254A_STARTstart animation with its current frame
253A_NOPleave the animation in its current state (do nothing)

Callback values have to be specified as usual by cbr(), or by using the helper function animcontrol().

From TTDPatch 2.5 beta 2, you can ask for random bits by function randombits(). To enable this, set RANDOMBITS in the station property function flags().

This callback is always available, it doesn't need to be defined in the station's callbacks() property function.

CB_AFRAME - set next animation frame

Called in every animation frame, this callback returns the number of the next frame to display. Additionally, it can return these special values:

ValueLabelMeaning
255A_STOPstop animation. The current frame stays on screen until the animation is restarted.
254A_NEXTcontinue with next frame as usual. You can return this for stages where you don't want to do anything special.

Callback values have to be specified as usual by cbr(), or by using the helper function animframe().

Since TTDPatch 2.5 beta 2, you can ask for random bits by function randombits(). To enable this, set RANDOMBITS in the station property function flags().

CB_AISELECT - AI construction/purchase selection

This is a so-called "generic feature" callback. It is called for various decisions when the AI is constructing a new route, and this callback can be used to make that selection depend on source and destination industries as well as service distance, among other things.

For stations, a number of special performance functions can be used:

FunctionMeaning
AI_destination()destination industry type
AI_distance()distance between source and destination, in (dx+dy)/2
AI_event()AI construction event
AI_number()construction number; for stations 0=source station, 1=destination station
AI_source()source industry type
AI_stationlength()station platform length
AI_stationsize()station size as nibbles <num-platforms>, <length>
AI_stationwidth()station's number of platforms

For stations, the callback will be called only when building a station, and the value returned by function AI_event() will be the station-ID.

CB_ASPEED - set animation speed

Called to decide how long the current animation frame should last. The value of the delay should be given in the same way as for property anim_speed(). Decreasing the return value speeds the animation up instantly. Increasing, on the other hand, doesn't slow it down instantly: the actual duration of the current frame will be somewhere between the old and the new delays. The new delay is applied correctly for later frames.

Note: This is one of the most time consuming callbacks as it is called for every animated tile every ~30 milliseconds. For better performance try to avoid using it where reasonable, e.g. try to use only the properties and put multiple identical looking animation frames after each other.

CB_AVAILABLE - Station availability in construction window

This callback specifies whether a station can be constructed or not, i.e. whether it can be selected in the station construction window (e.g. depending on year). A non-zero callback return indicates the station can be built, and a return code of zero removes the station from the purchase selection.

CB_LAYOUT - Use callback to select sprite layout

This callback selects an entry from the station's sprite layout set by function tile(). If its return value is invalid then the sprite layout given from the default tile type will be used. This is the only way to have more than the default 4 TTD different sprite sets to choose from.

Bit 0 (station orientation) of the return value is ignored, and instead set to bit 0 of the actual tile, so that you do not have to check the orientation explicitly and return the two corresponding values, instead just organize the layouts such that even numbers correspond to X orientation (NE-SW) and odd numbers corresponding to Y orientation (NW-SE). See examples here.

The effect of this callback is like having additional tile types that are however not actually built, but only show different graphics depending on the callback.

Function cbset() can be used to select specific sprite layouts depending on their loading state.

Example (using tile labels and helper function reftile()):
layout(_PLAT,
// empty
   tile(__plat,
	ground(1012)
	regular(0, xyz(0,0,0), dxdydz(16,5,3))
	regular(2, xyz(0,11,0), dxdydz(16,5,3))
   )
   tile(
	ground(1011)
	regular(1, xyz(0,0,0), dxdydz(5,16,3))
	regular(3, xyz(11,0,0), dxdydz(5,16,3))
   )

// fences front
   tile(
	ground(1012)
	regular(0, xyz(0,0,0), dxdydz(16,5,3))
	regular(2, xyz(0,11,0), dxdydz(16,5,3))
	regular(20, xyz(0,16,3), dxdydz(16,0,3)) // fence
   )
   tile(
	ground(1011)
	regular(1, xyz(0,0,0), dxdydz(5,16,3))
	regular(3, xyz(11,0,0), dxdydz(5,16,3))
	regular(21, xyz(16,0,3), dxdydz(0,16,3)) // fence
   )

// fences back
   ...
)

...

def(1) plt_numrev(
	reftile(__plat+1) if(0) // fence front
	reftile(__plat) else    // no fence
)

def(2) plt_num(
	reftile(__plat+2) if(0) // fence back
	ref(1) else
)

def(3) plt_total(
	reftile(__plat+3) if(1) // fence both
	ref(2) else
)

def(10) callback(
	ref(3) if(CB_LAYOUT)
	ref(0) else
)

CB_SLOPE - custom slope check

This callback is called for each tile the new station part will be built. Return cbr(0) (or 400 in grf version 8) to accept the current tile or cbr(1) (or 401 in grf version 8) to make the station building fail with the "Land sloped in wrong direction" error message. Other return values are reserved for future use, don't use them for now.

Since the station isn't built yet, only some special station performance functions can be used: test_slope(), test_pltpos(), test_pltnum(), test_pltlength(), test_plttotal().

This callback is called only after the normal checks TTD does for slopes, so it's not possible to allow a slope that isn't allowed by default; you can only narrow the set of allowed slopes. In particular, it is not possible to built station segments with platforms on different levels. If the callback fails, the tile will be accepted.

Please note that the callback has to be used in the purchase menu. Use cargo type MENU for this.

Example (Check for slopes):
// only allow slopes with N and E/W corners elevated (southern)
def(12) test_slope(
	ALLOW if(NORTH+WEST) // allow
	ref(11) else         // check for northern slope ...
)

// menu
def(13) callback(
	cbr(216) if(CB_LAYOUT) // icon
	ref(12) if(CB_SLOPE)
	ref(159) else          // sprite block
)

CB_TILETYPE - select TTD tile type

This callback may be used to further customize the station's tile layout. It is called once for every tile that is being built, and selects the TTD tile type for the tile to build, or to leave the tile as TTD's default if the callback fails. The only possible values for this callback are 0, 2, 4 and 6 (see tiletypelayout()).

This callback allows to set properties depending on the tile type like pylons, wires or non-track behaviour on a single-tile level, instead for the whole station.

Since the station hasn't been built yet, none of the plt_*() performance functions will be available, instead function test_position() can be used to request the position of the tile to be built inside the tile layout. For the same reason, it uses the same cargo type in function makestation() as the construction menu does (i.e., type MENU if defined or else the default).

This callback is always available, it doesn't need to be defined in the station's callbacks() property function. Please note that it has to be used in the purchase menu. Use cargo type MENU for this.