NMLTutorial/Tram articulation

From TTWiki
< NMLTutorial
Revision as of 22:27, 13 December 2011 by Yexo (talk | contribs) (Use CB_RESULT_NO_MORE_ARTICULATED_PARTS instead of hardcodec 0xFF)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigationJump to search

The example used here is the HTM 4001 from the Dutch Tram Set. The original code and graphics for this are by FooBar. Code and graphics are both licensed according to the GPL v2 or later. The code has been modified for the purpose of this tutorial.

This continues the first part of the tram example. Here we'll define the tram's item block and make it articulated.


Item block

The item block is not that different from that of a regular road vehicle. A tram is still a road vehicle, so the feature will again be FEAT_ROADVEHS. Give the item an identifier (here item_tram_htm_4001), add a property sub-block and fill it like you did with the road vehicle.

One important thing to know is that the cargo_capacity property applies to a single part of the articulated vehicle. So the total capacity will be that what you have set for cargo_capacity multiplied by the number of vehicle parts. This means that the capacity can only be a multiple of the number of vehicle parts. The other properties apply to the vehicle as a whole.

Again you need to look all the available properties up in the NML Documentation and whey you do you may arrive at an item block something like this:

item (FEAT_ROADVEHS, item_tram_htm_4001) {
    property {
        name:                        string(STR_NAME_HTM_4001);
        introduction_date:           date(2006,1,1);
        model_life:                  VEHICLE_NEVER_EXPIRES;
        retire_early:                0;
        vehicle_life:                25;
        loading_speed:               25;
        cost_factor:                 224;
        running_cost_factor:         112;
        speed:                       81 km/h;
        power:                       966 hp;
        weight:                      58 ton;
        cargo_capacity:              (38*2+26)/3;
        tractive_effort_coefficient: 0.5;
        air_drag_coefficient:        0;
        //sound_effect:              no sound
        //visual_effect:             use default (none)
        
        //callback_flags:            no need to set this
        reliability_decay:           20;
        climates_available:          ALL_CLIMATES;
        refittable_cargo_classes:    bitmask(CC_PASSENGERS);
        sprite_id:                   SPRITE_ID_NEW_ROADVEH; //use custom sprites
        misc_flags:                  bitmask(ROADVEH_FLAG_TRAM); //make this a tram
        refit_cost:                  0; //refits are free
        running_cost_base:           RUNNING_COST_ROADVEH;
    }
    
    graphics {
        //this will be added next
    }
}


Adding callbacks

As you may have guessed, making the vehicle articulated has something to do with callbacks. These will be referenced from the graphics block within the item block. We'll also be adding a second callback that makes the capacity different for each vehicle part. The middle part of the vehicle (as you will see on the next page) is somewhat shorter and thus can carry less passengers. We can show this in the vehicle details window using a callback.

Articulated vehicle callback

The name of the acticulated vehicle callback that must be used in the graphics block is articulated_part, which you can find in this table. There you'll also find information on how to use this callback.

Because this callback is a little more advanced than just telling how many vehicle parts you want, you need a switch with this callback to be able to use it. This means that from the graphics block you need to reference a switch block:

    graphics {
        articulated_part:        switch_articulated_htm_4001;
    }

How this callback works is that the game runs the switch referenced in the graphics block multiple times. Every time the switch is run, it will increase the variable extra_callback_info1 with 1, starting at 1. And every time the switch must return the identifier of a vehicle item that must be added. This can be the identifier of the item you define the callback for, or a different vehicle item. Because you can change the looks and properties of the attached vehicle parts by means of other switches/callbacks, it is fine to return the identifier of the same vehicle. If you want the game to stop adding more parts to the vehicle, you have the switch return a value of CB_RESULT_NO_MORE_ARTICULATED_PARTS.

This is a lot in words, but not so much in code. The switch you need is this:

switch (FEAT_ROADVEHS, SELF, switch_articulated_htm_4001, extra_callback_info1) {
    1..2: return item_tram_htm_4001; //use same vehicle for all parts
    return CB_RESULT_NO_MORE_ARTICULATED_PARTS; //stop adding vehicle parts
}

Note that the identifier used here is the same as the one in the graphics block. So the first time the switch is run, the value of extra_callback_info1 will be one. For value 1, item_tram_htm_4001 is returned as vehicle identifier for the part to add to this vehicle. Then the switch is run again, now with extra_callback_info1 set to 2. Again vehicle identifier item_tram_htm_4001 is returned and another vehicle part added. Then the switch is run again with extra_callback_info1 set to 3. This time the switch will return 0xFF, the game will add no new part and will stop running this switch.

Of course, this switch block goes above the item block, as it needs to be defined before you can use it. This particular code will now build a three part articulated vehicle.

Cargo capacity callback

A different callback can set the capacity of each of these three parts. This is useful if you want different capacities for each part (if each part must have the same capacity, the cargo_capacity property is enough and you don't need this callback).

The name of this callback for the graphics block is cargo_capacity and you can look that up in the same place as where you found the articulated vehicle callback. You can directly return a value for this callback from the graphics block, but then this would be the same for each part and that kinda defeats the purpose of this callback. So we'll have to use a switch block here as well, referred to from the graphics block:

    graphics {
        articulated_part:        switch_articulated_htm_4001;
        cargo_capacity:          switch_capacity_htm_4001;
    }

In order to return a different value for each vehicle part, we somehow need to use the switch block to make a decision based on the position in the vehicle. Luckily, there's a variable that does just that: it is called position_in_consist and you can find it here in the NML Documentation.

For each vehicle part the game will tell the switch of what vehicle part number it wants to know the capacity. The switch must then return this value. In code:

switch (FEAT_ROADVEHS, SELF, switch_capacity_htm_4001, position_in_consist ) {
    1: return 26;
    return 38; //default
}

Also here the identifier of the switch is the same as the one used in the graphics block. For position_in_consist 1 (the second vehicle part) this switch will return a capacity of 26. For all other vehicle parts (in this case only the first and the last, as we have a three part vehicle) it will return the default value of 38.

Also this switch block goes above the item block.


Total code so far

If you put everything in the correct order, you should now have this:

// define the newgrf
grf {
	grfid:	"\FB\FB\01\02";
	name:	string(STR_GRF_NAME);
	desc:	string(STR_GRF_DESCRIPTION);
	version:		REPO_REVISION;
	min_compatible_version:	87;
}

/////////////////////
//vehicle callbacks//
/////////////////////

//set number of articulated parts
switch (FEAT_ROADVEHS, SELF, switch_articulated_htm_4001, extra_callback_info1) {
    1..2: return item_tram_htm_4001; //use same vehicle for all parts
    return CB_RESULT_NO_MORE_ARTICULATED_PARTS; //stop adding vehicle parts
}

//set capacity of each part
switch (FEAT_ROADVEHS, SELF, switch_capacity_htm_4001, position_in_consist ) {
    1: return 26;
    return 38; //default
}

//////////////////////
//vehicle properties//
//////////////////////

item (FEAT_ROADVEHS, item_tram_htm_4001) {
    property {
        name:                        string(STR_NAME_HTM_4001);
        introduction_date:           date(2006,1,1);
        model_life:                  VEHICLE_NEVER_EXPIRES;
        retire_early:                0;
        vehicle_life:                25;
        loading_speed:               25;
        cost_factor:                 224;
        running_cost_factor:         112;
        speed:                       81 km/h;
        power:                       966 hp;
        weight:                      58 ton;
        cargo_capacity:              (38*2+26)/3;
        tractive_effort_coefficient: 0.5;
        air_drag_coefficient:        0;
        //sound_effect:              no sound
        //visual_effect:             use default (none)
        
        //callback_flags:            no need to set this
        reliability_decay:           20;
        climates_available:          ALL_CLIMATES;
        refittable_cargo_classes:    bitmask(CC_PASSENGERS);
        sprite_id:                   SPRITE_ID_NEW_ROADVEH; //use custom sprites
        misc_flags:                  bitmask(ROADVEH_FLAG_TRAM); //make this a tram
        refit_cost:                  0; //refits are free
        running_cost_base:           RUNNING_COST_ROADVEH;
    }
    
    graphics {
        articulated_part:        switch_articulated_htm_4001;
        cargo_capacity:          switch_capacity_htm_4001;
    }
}

Because we haven't added graphics, you cannot compile this into a NewGRF just yet. So we'll look into the graphics next.


NML Tutorial: Tram articulation