modding:developerinfo:constructions

Constructions

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. It could possibly return a different construction.

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

Reference

Meta-data

First, you have to specify the type of your construction.

Available construction types

  • STREET_STATION
  • RAIL_STATION
  • AIRPORT, HARBOR
  • STREET_STATION_CARGO
  • RAIL_STATION_CARGO
  • HARBOR_CARGO
  • STREET_DEPOT
  • RAIL_DEPOT
  • WATER_DEPOT
  • INDUSTRY
  • ASSET_DEFAULT
  • ASSET_TRACK
  • TOWN_BUILDING

The meta-data keys description, availability and soundConfig are specified the same way as for models.

If you would like the player to be able to set some parameters to configure the construction, you have to use the params key. The following code shows how we could let the user specify the number of terminals of an airport:

construction_param.jpg

params = {
  {
    key = "numTerminalsIndex",
    name = _("Number of terminals"),
    values = { _("1"), _("2"), _("3") }
  },
}

Note: the key is used to get the result (index of the chosen value, starting with 0) from the params table, which is passed to the update function updateFn. There it can be used to configure the construction:

updateFn = function(params)
  local numTerminals = params.numTerminalsIndex + 1
 
  -- do something with numTerminals
end

Further meta-data keys you can specify include:

  • buildMode: Specifies, whether one can build one (SINGLE) or more constructions (MULTI) until the construction menu is closed. It's also possible to build constructions with a brush (BRUSH).
  • categories: An optional list of categories, used to group constructions in the construction menu.
  • order: Used to sort the constructions in the construction list.
  • skipCollision: Set to skip collisions when building the construction.
  • autoRemovable: Used to allow the construction to be removed, if it collides with something else.
  • townBuildingParams: Used for town building constructions. Please refer to the relevant construction files for more details.

Update function

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 meta-data. Everything is stored in a table and returned by the function. The structure of the result is explained in the following sections.

Models

The following code shows how a model is added to a construction:

result.models = { {
  id = "industry/chemical_plant/building_small.mdl",   -- base-path: res/models/model/
  transf = transf.transl(vec3.new(20, -10 , 0))        -- x/y/z coordinates relative to construction center [m]
} }

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

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",                              -- or "TRACK", see code below
    params = {
      type = "country_new_small.lua",             -- see res/config/street/
      tramTrackType = "YES"                       -- accepted values: "NO", "YES" and "ELECTRIC"
    },
    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
  },
 
  -- specify a track segment
  {
    type = "TRACK",
    params = {
      type = "standard.lua",                     -- see res/config/track/
      catenary = true
    },
    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
  }
}

Edge Objects

Some game objects can be attached to edges, for example signals and way-points. This is also possible for edges contained by constructions, as showed 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/
}

Terminal Groups

Please refer to the existing construction files for more details.

Runways

Runways are used by aircrafts (and ships) for arrival/departure at an airport (or harbor/shipyard). They are specified on the nodes/edges given by the edge lists.

local runway = { }
 
-- edge 0
runway[#runway + 1] = { { -180.0, 20.0,  .0 },  {  20.0, .0, .0 } }  -- node 0   
runway[#runway + 1] = { { -160.0, 20.0,  .0 },  {  20.0, .0, .0 } }  -- node 1
 
-- edge 1 (runway edge)
runway[#runway + 1] = { { -160.0, 20.0,  .0 },  { 320.0, .0, .0 } }  -- node2 (takeoff node)
runway[#runway + 1] = { {  160.0, 20.0,  .0 },  { 320.0, .0, .0 } }  -- node3 (landing node)
 
-- edge 2
runway[#runway + 1] = { {  160.0, 20.0,  .0 },  {  20.0, .0, .0 } }  -- node 4
runway[#runway + 1] = { {  180.0, 20.0,  .0 },  {  20.0, .0, .0 } }  -- node 5
 
-- add runway to edge lists
 
result.runways = {
  {
    type = "LANDING",          -- or "TAKEOFF"
    node = 3,                  -- node 3 is landing node
    edges = { 1 }              -- edge 1 is used for landing
  },
  {
    type = "TAKEOFF",
    node = 2,
    edges = { 1 }
  }
}

Note: an aircraft tries to land “in front” of the landing node. After passing it, it will taxi to the terminal. On departure, it will take off after passing the takeoff node. The landing/takeoff direction is given by the tangent of the first/last taxi way edge. Same holds for ships.

Stocks Lists

To let a construction consume/produce and store cargo items, you need to specify stocks and stock rules.

Stocks

A stock entry defines for a cargo type whether it's ordered/shipped and on which edges it can be piled up.

result.models = { }
 
-- Add some edges for the stock piles, they can be distributed over an arbitrary number of models.
-- In this example, each model contains exactly one edge, and we add for each cargo type one model.
for i = 1, 5 do
  result.models[#result.models + 1] = { id = "industry/common/stock_lane_8m.mdl", transf = { ... } }
end
 
-- Specify the necessary cargo types, whether they are ordered/shipped...
-- ...and which edges are used for stock piling
result.stocks = {
  { cargoType = "PLANKS",   type = "RECEIVING",  edges = { { 0, 0 } },
  { cargoType = "STEEL",    type = "RECEIVING",  edges = { { 1, 0 } },
  { cargoType = "PLASTIC",  type = "RECEIVING",  edges = { { 2, 0 } },
  { cargoType = "MACHINES", type = "SENDING",    edges = { { 3, 0 } },
  { cargoType = "TOOLS",    type = "SENDING",    edges = { { 4, 0 } }
},

cargoType can be any type defined in base_config.lua. type can be either RECEIVING or SENDING. edges stores a list of edges, where the cargo can be piled up. It consists of one or more pairs of indices. The first index refers to a model, the second index to the particular edge in the edge list of that model.

Rules

So far, the construction knows which cargo types it can receive/send and where it should store them. But it knows nothing about how many cargo items it can produce/consume per year and how the required items relate to the produced items. This is specified by the stock rules. Following code shows the initial rule for the machine factory and it refers to the above example for the stock lists:

result.stockRules = {
  { input = { { 2, 0, 0, 0, 0 }, { 0, 1, 0, 0, 0 } }, output = { { 0, 0, 0, 0, 1 } }, capacity = 50 },
  { input = { { 2, 1, 0, 0, 0 }, { 0, 1, 1, 0, 0 } }, output = { { 0, 0, 0, 1, 0 } }, capacity = 50 }
}

The rules are applied independently and at most capacity time a year. Each entry of input or output refers to a specific cargo item configuration. In order the rule can be applied, at least one of the input configurations must be valid. That means these cargo items must be on stock. Further, at least one of the output configurations must be valid, which means that the produced item can be stored.

For example, the first rule of the machine factory reads as follows: we need either two planks or one steel to produce one tool and we can produce at most 50 tools per year.

Examples

There are constructions that only produce cargo items, but don't consume anything. For example, the coal mine.

result.stocks = {
  { cargoType = "COAL", type = "SENDING", edges = { ... } }
},
 
result.stockRules = {
  { input = { { 0 } }, output = { { 1 } }, capacity = 200 }
}

Or the farm, which can produce different items:

result.stocks = {
  { cargoType = "GRAIN", type = "SENDING", edges = { ... } },
  { cargoType = "LIVESTOCK", type = "SENDING", edges = { ... } }
},
 
result.stockRules = {
  { input = { { 0, 0 } }, output = { { 1, 0 }, { 0, 1 } }, capacity = 100 }					
}

Terrain Alignment

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
} }

Ground Faces

result.groundFaces = { {  
  face = { { -10, -10, 0 }, { 10, -10, 0 }, { 10, 10, 0 } },
  modes = {
    {
      type = "FILL",            -- accepted values: "FILL", "STROKE", "STROKE_INNER" and "STROKE_OUTER"
      key = "industry_floor"    -- ground texture, see res/config/ground_texture/
    }
  }
} }

Costs

  • cost
  • bulldozeCost
  • maintenanceCost

Build free streets and tracks

It is possible to build streets and tracks without creating an actual construction. This way one can build predefined complex street and track arrangements, which are not locked by a construction. They can be modified or removed as streets and tracks built with the standard building tools.

The example below demonstrates how a segment of track can be built without creating a construction:

function data()
return {
  type = "ASSET_DEFAULT",
 
  updateFn = function(params)
    local result = { }
 
    result.models = { } -- key 'models' is required, but MUST be empty
 
    result.edgeLists = {
      {
        type = "TRACK",
        params = { type = "standard.lua" },
        edges = { { { -10, 0, 0}, { 10, 0, 0 } }, { { 10, 0, 0 }, { 10, 0, 0 } } },
        snapNodes = { 0, 1 },
        freeNodes = { 0, 1 }
      },
    }
 
    result.edgeObjects = {
      {
        edge = 0,
        param = .5,
        left = false,
        model = "railroad/signal_new_block.mdl"
      }
    }
 
    return result
  end
}
end

To build free streets and tracks it requires:

  • the tag models should be set, but must be empty
  • only edgeLists and edgeObjects are allowed
  • all edges should be “free”

To “free” an edge, their nodes should be “free”. Free nodes are either snap nodes (defined in snapNodes) or nodes defined in freeNodes. They can be both at the same time.

But on the other hand, it is allowed to “free” some nodes/edges of a regular construction. They wont be owned by the construction, thus if it gets removed, the “free” edges and their nodes remain.

modding/developerinfo/constructions.txt · Last modified: 2019/03/19 10:18 (external edit)