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\
.
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
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:
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.
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.
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 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 } }
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/ }
Please refer to the existing construction files for more details.
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.
To let a construction consume/produce and store cargo items, you need to specify stocks and stock rules.
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.
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.
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 } }
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 } }
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/ } } } }
cost
bulldozeCost
maintenanceCost
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:
models
should be set, but must be emptyedgeLists
and edgeObjects
are allowed
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.