NMLTutorial/Callback and switch

From TTWiki
Jump to navigationJump to search

Callbacks allow to do some fancy things on the fly. Where properties are defined static, callbacks allow to change (some) properties depending on the game state (for instance the game year or what cargo a vehicle is refitted to). Apart from that, some callbacks allow things that are not possible from the usual item definition (such as graphic animations or articulated vehicles).

Callbacks are often used in combination with switches. Switches are things that allow to make a decision based on variables. These variables depend on the game state (so if you for instance want to change vehicle properties depending on the game year, you need a switch attached to the callback). Furthermore, switches can be used to "switch graphics" based on the same variables (e.g. pointing to different spritegroups/sets for each part of an articulated vehicle).


Callbacks

What callbacks are available depend on the feature you're working on. Then you can look up the available callbacks in the NML Documentation. These tables list the names of the callbacks, what they do and what value they want to see returned. Some callbacks need to be pointed to spritegroups, while others want a string or number returned.

Callbacks are defined in the graphics block of the item block and from there they can either directly return a value or point to a switch or spritegroup. Let's look at the graphics block once more:

item {
    graphics {
        <callback>: <identifier>|<return_value>;
        [<callback2>: <identifier2>|<return_value2>;]
        [...;]
    }
}

If you want to point the callback to a different block (a switch block or a spritegroup block), you just put the identifier of that block behind the name of the callback. If you want the callback to return a value (string or number) directly, you need to first write the keyword return and then the numeric value or string().

So for example (using fictional callback names):

    graphics {
        callback_to_spritegroup: spritegroup_identifier;
        callback_to_switch:      switch_identifier;
        callback_return_string:  return string(STR_STRINGNAME);
        callback_return_number:  return 20;
        default:                 spritegroup_default_graphics;
    }

Please note that all callbacks need to be defined in a single graphics block.


Switch blocks

As written in the introduction, switch blocks can be used to make decisions on certain variables. A switch block can be used to decide a callback return value depending on a variable, or to decide what spritegroup to use for the graphics depending on a variable. You need to look up what variables are available for the feature you're using. Don't forget the general variables that are available for all features. And instead of a variable you may also use a GRF parameter.

If you think you can make these decisions using if/else statements, you're wrong. Most variables are only available in a switch block of the feature you're working on. So when working with callbacks and graphics, use switches to make decisions based on variables.

The switch block syntax looks like this:

switch (<feature>, <relation>, <identifier>, <expression>) {
	<range>: <return>;
	<range2>: <return2>;
	<default_return>;
}
  • <feature>: the feature you're working on.
  • <relation>: either SELF or PARENT. This indicates if you want to use the variable of the item itself or that of it's related item. See the NML Documentation on switch blocks for a table of what these relations are for each feature. Sometimes it will not be entirely clear which one you need, in that case just try SELF and change it to PARENT if it doesn't work like you think it should. PARENT is mostly needed for things that have to do with randomizations; in other cases SELF mostly does the trick.
  • <identifier>: a self-chosen identifier (name) for this switch. It's useful to start with switch_ followed by the name of the item this is a switch for. Like with any other identifier, also these identifiers need to be unique throughout the NML file.
  • <expression>: A variable or GRF parameter you want to base this decision on. You may do mathematical calculations on a variable or parameter before having to make a decision on it's value and you may even list multiple expressions in the form of an array (we'll see examples of this later), but just a single variable or parameter if of course also fine.
  • <range>: Either a single value or a subrange from the value range this expression can result in. If you want to specify a subrange, use <range_start>..<range_end>
  • <return>: An identifier to a different switch or spritegroup block, or (when switching a callback) the return keyword followed by a number or string reference.
  • <default_return>: Like a <return_value>, but this one is only used if none of the ranges specified above matches the value of the variable.

Now this may sound rather complicated and I agree that it may be a bit overwhelming when presented like this. Switches are one of the most complicated blocks in NML and they represent the VarAction2 in NFO. In case you're familiar with that: VarAction2 is one of the most complicated things in NFO as well.


Let's look at a little example showing some of the possibilities of a switch. Note that doesn't have an actual meaning other than showing some of the options. In reality it will be very uncommon that a single switch blocks needs to have a string returned for one range, a number for another range, point to an other switch for yet another range.

 switch (FEAT_TRAINS, PARENT, some_vehicle_switch, (position_in_consist) {
 	0..2:   return string(STRING_FOO_BAR);  //return a text message
 	3..4:   return;                         //returns 3 in case position_in_consist == 3 or 4 in case position_in_consist == 4
 	5..300: return 42;                      //42 is always a good answer
 	400:    switch_other_identifier;        //chain to some other switch block
 	401:    return num_vehs_in_consist + 1; //return a value with a variable access
 	CB_FAILED;                              //return a failure result as default
 }

Because this particular example returns numbers and strings, it can only be a switch for a callback. Switches used for graphics will (only) return identifiers to spritegroups, for example (this is an actual realistic example for a change):

 switch (FEAT_TRAINS, PARENT, some_other_vehicle_switch, (position_in_consist) {
 	1..2:   spritegroup_vehicle_1; //use this for second and third part of articulated vehicle
 	3..4:   spritegroup_vehicle_2; //use this for fourth and fifth part of articulated vehicle
 	5:      spritegroup_vehicle_3; //use this for sixth part of articulated vehicle
 	spritegroup_vehicle_0;         //use this for first and other parts of articulated vehicle (i.e. default)
 }


There's much more to say about callbacks and switch blocks, but this is probably intimidating enough already. Next we'll make an articulated tram vehicle to show you where these callbacks and switches go in your NML code.


NML Tutorial: Callback and switch