NMLTutorial/Train single engine

From TTWiki
Jump to: navigation, search

The example used here is from the NML source. The code for this was originally written in NFO by DJNekkid for the 2cc Trainset and rewritten in NML by Hirundo. The graphics used in the example are by Purno. 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 train example. On this page we'll make a single engined train.


Item block

The first step towards a train is defining an item block for it.

/* Define the actual train */
item(FEAT_TRAINS, item_icm) {
    /* Define properties first, make sure to set all of them */
    property {
        name:                         string(STR_ICM_NAME);
        // not available in toyland:
        climates_available:           bitmask(CLIMATE_TEMPERATE, CLIMATE_ARCTIC, CLIMATE_TROPICAL); 
        introduction_date:            date(1983, 1, 1);
        model_life:                   VEHICLE_NEVER_EXPIRES;
        vehicle_life:                 30;
        reliability_decay:            20;
        refittable_cargo_classes:     bitmask(CC_PASSENGERS);
        non_refittable_cargo_classes: bitmask();
        // refitting is done via cargo classes only, no cargo types need explicit enabling/disabling
        // It's an intercity train, loading is relatively slow:
        loading_speed:                6; 
        cost_factor:                  45;
        running_cost_factor:          100; // Changed by callback
        sprite_id:                    SPRITE_ID_NEW_TRAIN;
        speed:                        141 km/h; // actually 140, but there are rounding errors
        misc_flags:                   bitmask(TRAIN_FLAG_2CC, TRAIN_FLAG_MU);
        refit_cost:                   0; //refit costs don't apply to subcargo display 
        // callback flags are not set manually
        track_type:                   ELRL; // from rail type table
        ai_special_flag:              AI_FLAG_PASSENGER;
        power:                        1260 kW; // Changed by CB
        running_cost_base:            RUNNING_COST_ELECTRIC;
        dual_headed:                  0;
        cargo_capacity:               36; // per part, changed by callback
        weight:                       144 ton; // Total, changed by callback
        ai_engine_rank:               0; // not intended to be used by the ai
        engine_class:                 ENGINE_CLASS_ELECTRIC;
        extra_power_per_wagon:        0 kW;
        // 4/12 of weight on driving wheels, with a default friction coefficient of 0.3:
        tractive_effort_coefficient:  0.3 / 3; // changed by callback
        air_drag_coefficient:         0.06;
        shorten_vehicle:              SHORTEN_TO_8_8;
        // Overridden by callback to disable for non-powered wagons:
        visual_effect_and_powered:    visual_effect_and_powered(VISUAL_EFFECT_ELECTRIC, 2, DISABLE_WAGON_POWER);
        extra_weight_per_wagon:       0 ton;
        bitmask_vehicle_info:         0;
    }
    /* Define graphics and callbacks
     * Setting all callbacks is not needed, only define what is used */
    graphics {
        default:                      set_icm_front_lighted;
    }
}

The property block is again a list with properties for this vehicle. You can look the available properties up in the NML Documentation. The properties are self-explanatory with a little help from their descriptions in the NML Documentation. Properties that have a comment that they will be changed by a callback actually need not be changed by a callback for the single engined train, these callbacks are only relevant for when we get to refitting the train in a three and four part version.

From the graphics block we'll add the default sprites for this vehicle.


Language file

The properties block references one string (the vehicle name), which of course must be added to the language file. Open the language file and add the string.

STR_ICM_NAME                 :ICM 'Koploper' (Electric)


Railtype table

For the track_type we've used the ELRL railtype label. Apart from the fact that this is not available by default, we want our train to be working regardless of the track sets loaded in OpenTTD. This means adding a railtype table:

railtypetable {
    ELRL
}

We only use one railtype label, so that's really all we need in the railtypetable.


Graphics

Below you'll find the complete graphics file that will be used throughout the example. For now we'll only use the first block of eight sprites for the vehicle, but define templates and spritesets for all others while we're at it.

Dutch ICM 'Koploper' sprites. Drawn by Purno.

Templates

This example will show you some advanced tricks that you can use with templates. Of course you're free to use more simple templates like those used in the road vehicle and tram examples. In total this example defines four templates for all sprite uses (including those that will be added later) and uses templates inside templates (yes, that's possible).

Below each template will be introduced and explained.

/* Basic template for 4 vehicle views */
template tmpl_vehicle_basic(x, y) {
    // arguments x, y: coordinates of top-left corner of first sprite
    [x,      y,  8, 24,  -3, -12] //xpos ypos xsize ysize xoff yoff
    [x +  9, y, 22, 20, -14, -12]
    [x + 32, y, 32, 16, -16, -12]
    [x + 65, y, 22, 20,  -6, -12]
}

The template above is a normal template as we've seen those before. Arguments are used for the top-left corner of the first sprite. Nothing fancy here, except that only four sprites are listed and we need eight.

/* Template for a vehicle with only 4 views (symmetric) */
template tmpl_vehicle_4_views(num) {
    // argument num: Index in the graphics file, assuming vertical ordering of vehicles
    tmpl_vehicle_basic(1, 1 + 32 * num)
}

This template includes the previous templated and is intended to be used for blocks of four sprites, like the passenger carriage in the image above. This template takes one argument where you set in which row of the graphics file to find the sprites. NML then calculates the actual values for x and y that the included template needs. The value for x in this case is a constant 1, as each block of sprites in the graphics file starts one pixel from the left. The distance from the top is calculated: each block of sprites is 32 pixels apart and the first block starts one pixel from the top. So with the number of the row as input argument, the calculation is to multiply this input by 32 and add 1. This gives the correct value for y.

/* Template for a vehicle with 8 views (non-symmetric) */
template tmpl_vehicle_8_views(num, reversed) {
    // argument num: Index in the graphics file, assuming vertical ordering of vehicles
    // argument reversed: Reverse visible orientation of vehicle, if set to 1
    tmpl_vehicle_basic(reversed ? 89 : 1, 1 + 32 * num)
    tmpl_vehicle_basic(reversed ? 1 : 89, 1 + 32 * num)
}

This template also includes the first template and uses a same argument for the row number as the previous template. This argument is again used to calculate the value of y for the first template. The first template is included twice, which means it's intended for blocks of eight sprites.

The template also takes a second argument, which adds a trick to swap the first four and last four sprites. If the argument is set to 1, the last four sprites are included first and the first four second. If it's set to any other value, the normal sprite order is used for the eight sprites. That way a spriteset for a reversed vehicle can be created. This argument is used to calculate the value of x by means of a conditional assignment. How this works is that the expression in front of the ? is evaluated to true or false. If the expression returns true, the value between the ? and : is used. If the expression returns false the value after the : is used.

A value of 1 in this case is equal to true. Then a value for x of 89 is used for the first included template, otherwise value 1 is used for x. The same reasoning applies to the second included template, but of course the other way round.

/* Template for a single vehicle sprite */
template tmpl_vehicle_single(num, xsize, ysize, xoff, yoff) {
    [1, 1 + 32 * num, xsize, ysize, xoff, yoff]
}

This last template has arguments for almost everything, so sprite size and offsets must be set every time this template is used. Only the distance from the top is calculated using the row number (like in the previous two templates) and the distance from the left is again a constant 1.

Spritesets

With these templates, spritesets can be defined:

/* Define the spritesets, these allow referring to these sprites later on */
spriteset (set_icm_front_lighted, "gfx/icm.png") { tmpl_vehicle_8_views(0, 0) }
spriteset (set_icm_rear_lighted,  "gfx/icm.png") { tmpl_vehicle_8_views(1, 1) }
spriteset (set_icm_front,         "gfx/icm.png") { tmpl_vehicle_8_views(2, 0) }
spriteset (set_icm_rear,          "gfx/icm.png") { tmpl_vehicle_8_views(3, 1) }
spriteset (set_icm_middle,        "gfx/icm.png") { tmpl_vehicle_4_views(4)    }
spriteset (set_icm_purchase,      "gfx/icm.png") { tmpl_vehicle_single(5, 53, 14, -25, -10) }
spriteset (set_icm_invisible,     "gfx/icm.png") { tmpl_vehicle_single(6,  1,  1,   0,   0) }

The first and third spriteset are for the front of the EMU, with and without lights. The template for eight sprites, unreversed, is used for these two spritesets. The second and fourth spriteset are similar, this time for the back of the EMU, using the same template but this time reversed. The fifth spriteset is for the middle parts of the EMU, the wagons if you like, only using the template for four sprites. The sixth spriteset is for the purchase menu sprite, using the last template setting the fifth row and manual values for size and offsets. The last spriteset is a single transparent pixel sprite, which will be used to hide one of the middle parts when we get to adding the four part refit.


For the single engine we now only use the set_icm_front_lighted spriteset. It is already referenced from the graphics block. This will also be used for the purchase menu, which is fine for non-articulated trains and road vehicles. If you want to encode this intermediate result, you best remove the other spritesets or comment them out (as is done for the total code so far below).


Total code so far

When put in the correct order, this should now encode as a working NewGRF. The total code so far:

/* Define grf */
grf {
    grfid: "NML\00";
    /* GRF name and description strings are defined in the lang files */
    name: string(STR_GRF_NAME);
    desc: string(STR_GRF_DESC);
    /* This is the first version, start numbering at 0. */
    version: 0;
    min_compatible_version: 0;
}

/* Define a rail type table,
 * this allows referring to railtypes
 * irrespective of the grfs loaded.
 */
railtypetable {
    RAIL, ELRL, MONO, MGLV,
}

/* Basic template for 4 vehicle views */
template tmpl_vehicle_basic(x, y) {
    // arguments x, y: coordinates of top-left corner of first sprite
    [x,      y,  8, 24,  -3, -12] //xpos ypos xsize ysize xrel yrel
    [x +  9, y, 22, 20, -14, -12]
    [x + 32, y, 32, 16, -16, -12]
    [x + 65, y, 22, 20,  -6, -12]
}

/* Template for a vehicle with only 4 views (symmetric) */
template tmpl_vehicle_4_views(num) {
    // argument num: Index in the graphics file, assuming vertical ordering of vehicles
    tmpl_vehicle_basic(1, 1 + 32 * num)
}

/* Template for a vehicle with 8 views (non-symmetric) */
template tmpl_vehicle_8_views(num, reversed) {
    // argument num: Index in the graphics file, assuming vertical ordering of vehicles
    // argument reversed: Reverse visible orientation of vehicle, if set to 1
    tmpl_vehicle_basic(reversed ? 89 : 1, 1 + 32 * num)
    tmpl_vehicle_basic(reversed ? 1 : 89, 1 + 32 * num)
}

/* Template for a single vehicle sprite */
template tmpl_vehicle_single(num, xsize, ysize, xoff, yoff) {
    [1, 1 + 32 * num, xsize, ysize, xoff, yoff]
}

/* Define the spritesets, these allow referring to these sprites later on */
spriteset (set_icm_front_lighted, "gfx/icm.png") { tmpl_vehicle_8_views(0, 0) }
spriteset (set_icm_rear_lighted,  "gfx/icm.png") { tmpl_vehicle_8_views(1, 1) }
spriteset (set_icm_front,         "gfx/icm.png") { tmpl_vehicle_8_views(2, 0) }
spriteset (set_icm_rear,          "gfx/icm.png") { tmpl_vehicle_8_views(3, 1) }
spriteset (set_icm_middle,        "gfx/icm.png") { tmpl_vehicle_4_views(4)    }
spriteset (set_icm_purchase,      "gfx/icm.png") { tmpl_vehicle_single(5, 53, 14, -25, -10) }
spriteset (set_icm_invisible,     "gfx/icm.png") { tmpl_vehicle_single(6,  1,  1,   0,   0) }

/* Define the actual train */
item(FEAT_TRAINS, item_icm) {
    /* Define properties first, make sure to set all of them */
    property {
        name:                         string(STR_ICM_NAME);
        // not available in toyland:
        climates_available:           bitmask(CLIMATE_TEMPERATE, CLIMATE_ARCTIC, CLIMATE_TROPICAL); 
        introduction_date:            date(1983, 1, 1);
        model_life:                   VEHICLE_NEVER_EXPIRES;
        vehicle_life:                 30;
        reliability_decay:            20;
        refittable_cargo_classes:     bitmask(CC_PASSENGERS);
        non_refittable_cargo_classes: bitmask();
        // refitting is done via cargo classes only, no cargo types need explicit enabling/disabling
        // It's an intercity train, loading is relatively slow:
        loading_speed:                6; 
        cost_factor:                  45;
        running_cost_factor:          100; // Changed by callback
        sprite_id:                    SPRITE_ID_NEW_TRAIN;
        speed:                        141 km/h; // actually 140, but there are rounding errors
        misc_flags:                   bitmask(TRAIN_FLAG_2CC, TRAIN_FLAG_MU);
        refit_cost:                   0; //refit costs don't apply to subcargo display 
        // callback flags are not set manually
        track_type:                   ELRL; // from rail type table
        ai_special_flag:              AI_FLAG_PASSENGER;
        power:                        1260 kW; // Changed by CB
        running_cost_base:            RUNNING_COST_ELECTRIC;
        dual_headed:                  0;
        cargo_capacity:               36; // per part, changed by callback
        weight:                       144 ton; // Total, changed by callback
        ai_engine_rank:               0; // not intended to be used by the ai
        engine_class:                 ENGINE_CLASS_ELECTRIC;
        extra_power_per_wagon:        0 kW;
        // 4/12 of weight on driving wheels, with a default friction coefficient of 0.3:
        tractive_effort_coefficient:  0.3 / 3; // changed by callback
        air_drag_coefficient:         0.06;
        shorten_vehicle:              SHORTEN_TO_8_8;
        // Overridden by callback to disable for non-powered wagons:
        visual_effect_and_powered:    visual_effect_and_powered(VISUAL_EFFECT_ELECTRIC, 2, DISABLE_WAGON_POWER);
        extra_weight_per_wagon:       0 ton;
        bitmask_vehicle_info:         0;
    }
    /* Define graphics and callbacks
     * Setting all callbacks is not needed, only define what is used */
    graphics {
        default:                      set_icm_front_lighted;
    }
}

Language file so far

english.lng now contains this:

##grflangid 0x01

STR_GRF_NAME                 :NML Example NewGRF: Train
STR_GRF_DESC                 :{ORANGE}NML Example NewGRF: Train{}{BLACK}This NewGRF is intended to provide a coding example for the high-level NewGRF-coding language NML.{}Original graphics by {SILVER}Purno, {BLACK}coding by {SILVER}DJNekkid.{}{BLACK}This NewGRF defines a Dutch EMU, the ICM 'Koploper'.

STR_ICM_NAME                 :ICM 'Koploper' (Electric)


This is now a working train engine, but not an EMU. On the next page we'll expand the code we have so far to make this train into a proper EMU which can be purchased as prebuilt consist without the need to add wagons yourself.


NML Tutorial: Train single engine