Table of Contents

Construction Basics

The concept of constructions, introduced with Transport Fever, is very powerful. It allows to configure and create a lot of different building types and assets in just one format. This includes stations, depots, industries, town buildings and different kinds of assets. They are stored in .con files in the folder res/construction/.

Overview

The format mainly consists of meta information and an update function. The construction is configured and created with the function. It can return different configurations of the construction depending on the input parameters.

function data()
 
return {
  type = "INDUSTRY",
 
  -- other meta information
 
  params = { },  -- parameters changeable by the player
 
  -- update function, which adjusts the construction according the parameters
  updateFn = function(params)
    local result = { }
 
    result.models = { {
      id = "industry/chemical_plant/building_small.mdl",
      transf = transf.transl(vec3.new(20, -10 , 0))     
    } }
 
    -- terrain alignment, streets, tracks, stock lists, etc.
 
    return result
  end
}
end

Metadata

There are several general properties available for use with constructions:

  ...
  type = "ASSET_DEFAULT",
  description = {
    name = _("Bench"),			  
    description = _("A bench.")
  },
  availability = {
    yearFrom = 1925,
    yearTo = 1925
  },
  ...

The type property is an identifier for the type of construction. See the construction types documentation for further details on their specific specialities and individual properties.

The description block contains a desc property for the description displayed in the menu. The name is set in the name property. Both can be translated in a strings.lua file.

The availability is set by yearFrom for the year from when the construction should be available. Unset or values below 1851 mean from start. The end limit is set by yearTo. It is the year until when the construction should be available. Unset or value 0 means unlimited availability, values below 1850 result in a never available construction.

Parameters

Parameters can be used to let the user set some preferences and influence the outcome of the construction script. They are defined in the params block outside of the updateFn function:

...
  params = {
    {
      key = "postbox_type",
      name = _("Postbox"),
      uiType = "BUTTON",
      values = { _("Germany"), _("Switzerland"), _("Austria"), },
      tooltip = _("Choose the type of postbox you'd like to place."),
      defaultIndex = 0,
    },
    ...
  },
...

Each parameter block has several parameters:

Property Value Default Description
key String mandatory Unique identifier for the parameter
name String “” (empty) Display name for the parameter menu
values {String,String,…} mandatory Display values for the parameter menu, see below
defaultIndex Integer 0 Pre-selected option on first call
uiType “BUTTON” or “SLIDER” or “COMBOBOX”
or “ICON_BUTTON” or “CHECKBOX”
“BUTTON” Type of parameter, see below
yearFrom Integer 0 Parameter is displayed from
yearTo Integer infinite Parameter is displayed until
tooltip String “” (empty) Tooltip that is displayed above the parameter name when hovering

It is recommended to choose key names that are totally unique, not only on mod level. Otherwise it could happen, that a preselected parameter value from another third-party construction is used once your construction is loaded. This could result in unintended behavior, if you do not check for the parameter range.

In the updateFn function of the construction, the selected options can be called up with params.<key>. Numbers starting from 0 are displayed. The first entry of a dropdown, for example, returns a 0. For a checkbox, 0 corresponds to the non-activated state, 1 to the activated state.

The individual characteristics of the different types are shown below.

Button

Simple text buttons are already known from Transport Fever. They are lined up in the menu as a centered list. The labels of the buttons are passed as a string list in the values parameter, they can also be translated in strings.lua.

{
  key = "postbox_type",
  name = _("Postbox"),
  uiType = "BUTTON",
  values = { _("Germany"), _("Switzerland"), _("Austria"), },
  tooltip = _("Choose the type of postbox you'd like to place."),
},

Slider

Especially for many linear values, such as numerical series or size increments, sliders can be used as easy-to-use controllers. The name of the currently selected position is displayed to the right of the slider. The display names of the buttons are passed as a string list in the values parameter, these can also be translated in the strings.lua.

{
  key = "parcelstation_length",
  name = _("Parcel Station Length"),
  uiType = "SLIDER",
  values = { _("5m"), _("8m"), _("10m"), _("12m"), _("15m"), _("20m"), },
  tooltip = _("Choose the length of the parcel station."),
},

Combo Box

If a large number of text options should be available, a simple list of buttons is not sufficient. Then a combo box, also known as a drop-down menu, can be implemented to offer a compact number of options. The entries of the list are passed as a string list in the values parameter, they can also be translated in strings.lua.

{
  key = "post_assets",
  name = _("Post Assets"),
  uiType = "COMBOBOX",
  values = { _("Few Parcels"), _("Many Parcels"), _("Oversized Parcels"), _("Letterboxes"), _("Empty"), },
  tooltip = _("Choose the decoration that should lay around."),
},

Icon Button

Unlike the text buttons, the icon buttons display small images in TGA format. Unlike the text buttons, the list is currently left-aligned. If the list of buttons becomes wider than the parameter menu, it will wrap to another line. It is therefore advisable that the images for the buttons all have the same height. Also, the border of the buttons should be transparent so that you can see which option is currently selected. The file paths to the images are passed as a string list in the values parameter, the paths are relative to res/textures/. The tooltip is used for hovering over the parameter name. It is currently not possible to add tooltips for the individual icons.

{
  key = "post_horns",
  name = _("Post Horns"),
  uiType = "ICON_BUTTON",
  values = { "ui/parameters/post_de.tga", "ui/parameters/post_ch.tga", "ui/parameters/post_at.tga", },
  tooltip = _("Choose the post horn that should be displayed."),
},

Checkbox

If only one value is assigned with yes or no, a checkbox is also suitable as parameter type. Only the name is displayed, but the values parameter must still be defined with two values, but it's values are never shown.

{
  key = "post_box_open",
  name = _("post box is open"),
  uiType = "CHECKBOX",
  values = {"0", "1"},
  tooltip = _("Choose if the post box is shown in opened state."),
},

UpdateFn

In this function, the construction is configured and specified. You can use the content of the table params to configure the construction. It also contains the selection of the user parameters you have specified in the params list above. See the list of construction types to find out, which additional properties are available in the params table depending on the construction type.

The updateFn returns a data struct with several mandatory and many optional properties. They are described below. Please note, that the return data struct is referred as result.

Models

It is possible and very common to add models to a construction. Almost every model can be added by adding it to the list of models in the result data struct.

result.models = { {
  id = "industry/chemical_plant/building_small.mdl",
  transf = transf.transl(vec3.new(20, -10 , 0))
} }
 
-- alternative with dynamic index depending on the already existing list
result.models = { }
...
result.models[#result.models + 1] = {
  id = "industry/chemical_plant/building_small.mdl",
  transf = transf.transl(vec3.new(20, -10 , 0)),
  tag = "office",
}

Each entry contains some properties:

There is no limitation in the number of models a construction can contain and the order they were added does not matter.

There are two additional properties params.paramX and params.paramY that are controlled by keyboard keys. Per default the keys are O/P and Ü/+ or [/] (depending on keyboard layout). They each have steps of 2π/32 so that 32 steps can be mapped to a full rotation around an axis.

To support rotation around all three axis, encapsulate the transformation matrix of the model with the rotateTransf function transf = constructionutil.rotateTransf(params, { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 }) and make sure to import the constructionutil with local constructionutil = require “constructionutil” at the begin of the file (before function data()).

Edge Lists

Edge lists are used to add streets and tracks to a construction. The following code adds a street segment and a track segment to the construction:

result.edgeLists = {
  -- specify a street segment
  {
    type = "STREET",                             
    params = {
      type = "standard/country_new_small.lua",    
      tramTrackType = "YES"                      
    },
    edgeType = "BRIDGE",                          -- optional
    edgeTypeName = "cement.lua",                  -- optional
    edges = {
      -- one entry refers to a position and a tangent
      { { .0, -79.0,  .0 },  { .0, 15.0, .0 } },  -- node 0 (snap node)
      { { .0, -64.0,  .0 },  { .0, 15.0, .0 } }   -- node 1
    },
    snapNodes = { 0 } , -- node 0 is allowed to snap to other edges of the same type
    freeNodes = {},
    tag2Nodes = {},
  },
 
  -- specify a track segment
  {
    type = "TRACK",
    params = {
      type = "standard.lua",                    
      catenary = true
    },
    edgeType = "BRIDGE",                          -- optional
    edgeTypeName = "cement.lua",                  -- optional
    edges = {
      { { -19.1, .0, .0 }, { -20.0, .0, .0 } },  -- node 0 
      { { -39.1, .0, .0 }, { -20.0, .0, .0 } },  -- node 1 (snap node)
    },
    snapNodes = { 1 },  -- node 1 is allowed to snap to other edges of the same type
    freeNodes = {},
  }
}

Each block in the edgeList has several properties:

The parameters for streets in the params block are:

The parameters for tracks in the params block are:

Edge Objects

Some game objects can be attached to edges, for example signals and waypoints. This is not only possible by the player, but also possible in constructions for edges contained by the constructions, as shown in the following code sample:

-- we need some edges to attach edge objects to them
taxiway = { }
 
-- edge 0
taxiway[#taxiway + 1] = { { 180.0,  20.0,  .0 },  {    .0, -20.0, .0 } } 
taxiway[#taxiway + 1] = { { 180.0,   0.0,  .0 },  {    .0, -20.0, .0 } }
 
-- edge 1
taxiway[#taxiway + 1] = { { 180.0,   0.0,  .0 },  {    .0, -47.1, .0 } }
taxiway[#taxiway + 1] = { { 150.0, -30.0,  .0 },  { -47.1,   0.0, .0 } }
 
-- add taxi way to edge lists...
 
-- attach a signal to one of the above defined edges
result.edgeObjects = {
  {
    edge = 1,                                              -- attach object to edge 1
    param = .5,                                            -- param along the edge
    left = false,
    model = "station/airport/asset/signal_runway_old.mdl"  -- see res/models/model/
  }
}

The edgeObjects list contains all object attachments with properties:

Please be aware that it is not possible to add more than one object per edge by script.

Terrain Alignment

The adjustment of terrain height to the construction is done with the terrain alignment. If the required alignment can't be done due to other nearby constructions or infrastructure, the construction can't be built.

result.terrainAlignmentLists = {
  {
    type = "EQUAL",               -- accepted values: "EQUAL", "LESS" and "GREATER"
    faces = { { { -10, -10, 0 }, { 10, -10, 0 }, { 10, 10, 0 } } }, -- a list of polygons
    slopeLow = 0.3,
    slopeHigh = 0.6,
    triangles = { { -10, -10, 0 }, { 10, -10, 0 }, { 10, 10, 0 }, }
    optional = false,
  },
  ...
}

The terrainAlignmentLists list contains 0 or more blocks with several properties:

Ground Faces

Ground faces are used to paint the terrain below the construction to blend in with the construction.

result.groundFaces = {
  {  
    face = { { -10, -10, 0 }, { 10, -10, 0 }, { 10, 10, 0 } },
    modes = {
      {
        type = "FILL",               
        key = "industry_floor.lua"
      }
    },
    loop = true,
    alignmentOffsetMode = "OBJECT",
    alignmentDirMode = "OBJECT",
    alignmentOffset = { -2.0, -1.0 },
 
  },
  ...
}

The groundFaces list contains 0 or more blocks with the following attributes:

Collider

Colliders are used to define the areas where no other object can be built. This is useful e.g. to reserve space for industry expansions with later level upgrades.

  result.colliders = {
    params = {
      halfExtents = { 1.5, 1.5, 1.5, },
    },
    transf = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, },
    type = "BOX",
  },
  {
    colliderutil.createBox({ 0, 0, 0 }, { 1.5, 1.5, 1.5 })
  }

The colliders list contains 0 or more blocks with the following properties:

The colliderutil.lua in res/scripts/ provides some useful functions to prepare the collider definition based on some parameters.

Costs

There are three properties that can be used to set the costs of the construction:

Label List

For models with “CUSTOM” typed labels, the content can be provided here:

  result.labelText = {
    [12] = { "Label", "SomeText"}
  }

The labelText list contains a mapping of keys to value lists. The key number equals the id of the model in result.models that should be provided with some custom label texts. The list contains the strings. The first string is for the first label of the model, the second for the second label of this model and so on.

To improve performance, for simple constructions that do not contain ground faces, terrain alignments, edges or depot/station/industry/town building definitions, there is no construction placed in the game but just the seperate mdls. These can not be configured afterwards or be deleted all at once. Furthermore these types of assets do not support the labelText custom texts from constructions.