Zum Inhalt

Konstruktionen

Bahnhöfe und andere Stationen, Depots, Industrien, Wahrzeichen, Lager, Umsteigeknoten, Signale, Haltestellen am Straßenrand, Deko-Assets und Stadtgebäude sind Konstruktionen. Jede ist eine .con.lua-Datei. Sie beschreibt die Konstruktion für das Baumenü und verweist auf eine Skriptfunktion, die die Geometrie erzeugt. Das Basisspiel hat 586 davon, verteilt auf Ordner wie depots/, industries/ und stations/.

Diese Seite behandelt die .con.lua-Datei, die Update-Funktion und ihr Ergebnis, die Konstruktionstypen, das Baumenü und Bodentexturen. Slots, Module und Vorlagen stehen auf Modulare Konstruktionen. Das offizielle Wiki beschreibt dasselbe Format auf Construction Basics.

Die zwei Hälften einer Konstruktion

Eine .con.lua-Datei gibt aus data() eine Beschreibungstabelle zurück. Ihre Felder entsprechen ConstructionDesc in api/tealdef/api/type.d.tl. Die Geometrie steht nicht in dieser Datei: updateScript nennt eine Funktion in einer .script.lua- oder .script.tl-Datei, und das Spiel ruft diese Funktion auf, um die eigentlichen Modelle, Spuren, Terminals und Kosten zu bekommen.

Am Helipad sieht man die Aufteilung. base/content/stations/air/air/helipad.con.lua (Beschreibung gekürzt):

function data()
    return {
        configureHudIconsScript = {
                fileName = "::/gui/construction/construction_desc_hud_icons.script@configureStationHudIconsFn",
                params = {
                    cargo = true,
                }
        },
        namePrefix = _("{townName} Heliport"),
        description = {
                name = _("STATIONS_AIR_HELIPAD_NAME"),
                description = _("STATIONS_AIR_HELIPAD_DESCRIPTION"),
                icon = "helipad/helipad.tga",
                previewIcon = "helipad_preview.tga",
                attributes = {
                    noise =  { 5, -1 },
                    pollution = { 40, -1 },
                },
                cargoTypeSet = {
                    cargoClassesIncluded = { "PASSENGERS", "UNIVERSAL", },
                },
        },
        availability = {
                yearFrom = 1950,
                notificationGroup = "helicopterBuildings",
        },
        order = 7000,
        params = {
        },
        updateScript = {
                fileName = "helipad/helipad.script@updateFn",
                params = {
                }
        },
        menuCategory = {
                categories = {
                    { category = "air_buildings", order = 20000, },
                },
        },
    }
end

Felder der Beschreibung

Feld Zweck
description Menüeintrag: name, description, icon, previewIcon, die attributes im Infokasten und die angenommenen Güter (ModelMetadata.Description)
availability yearFrom, yearTo und Einstellungen für Benachrichtigungen (ModelMetadata.Availability)
menuCategory Reiter des Baumenüs, in denen die Konstruktion erscheint, siehe Baumenü
order Sortierreihenfolge im Menü
soundConfig soundSet.name (eine Soundset-Datei), effects (Eventname zu Sounddateien, z. B. select) und builderAudioRes, das Bau-Audioset, das beim Bauen läuft. Laut Wiki sind die Pfade relativ zur .con.lua
params Parameter, die der Spieler einstellen kann, siehe Parameter
updateScript Die Funktion, die das Ergebnis baut, samt den festen params, die sie mitbekommt. Das einzige Pflichtskript
weitere ...Script-Felder Siehe Konstruktionsskripte
namePrefix, subConstructionNamePrefix Namensvorlagen für das Fenster der gebauten Konstruktion, z. B. _("{townName} Train Depot") und "{constructionName}"
edgeObject Macht die Konstruktion zu einem Kantenobjekt wie einem Signal oder einer Haltestelle am Straßenrand, siehe Kantenobjekte
placementTag, isIndustry Industrien: Tag für die Wirtschaft und ob die Wirtschaft die Konstruktion platziert (Industrien)
townBuildingParams Stadtgebäude: Flächennutzung, Parzellengröße und Level (Stadtgebäude)
metadata Freie Tabelle; Wahrzeichen legen hier ihre Freischaltregeln ab (Wahrzeichen)
snapping Assets: Flags rail, road, water zum Einrasten entlang von Gleisen, Straßen oder der Wasseroberfläche
heightModView false blendet das Raster aus, das Geländeänderungen vorab zeigt; laut Wiki ist es bei sehr großen Konstruktionen meist aus
undergroundView, heightAdjustable Schalten beim Bauen den „Untergrundmodus“ (Underground Mode) und die Höhenverstellung ein

Laut Wiki bedeutet availability.yearFrom unter 1900 oder ohne Wert „ab Spielbeginn“, und yearTo ohne Wert oder 0 bedeutet kein Ende. Mit einem yearTo unter 1900 ist die Konstruktion nie verfügbar. Das Wiki nennt auch Grenzen für den Menüeintrag: name bis 32 Zeichen, description bis 320 Zeichen, icon eine @2x.tga mit 240×150, previewIcon eine .tga mit 720×405 für den Infokasten. In attributes ist jeder der Werte noise, pollution, maintenanceCost und cost ein Intervall { from, to }. Mit -1 als zweitem Wert wird nur eine Zahl angezeigt.

Die Basics-Seite des Wikis nennt zwei weitere Felder, die nicht in ConstructionDesc stehen und die keine mitgelieferte Datei verwendet: keepBuildingsAndFields (Stadtgebäude, Felder und andere Satelliten nicht abreißen) und, für Assets, buildMode. preProcessScript, das 60 .con.lua-Dateien des Basisspiels verwenden, steht weder in ConstructionDesc noch im Wiki; siehe Modulare Konstruktionen.

Parameter

params ist eine Liste von ConstructionScriptParam. Jeder Eintrag braucht einen key und eine Liste values. Die gewählte Option kommt in der Update-Funktion als params.<key> an.

params = {
    {
        key = "hangar",
        name = _("Depot"),
        group = "hangar",
        values = { _("Yes"), _("No") },
        defaultIndex = 1,
        displayMode = "Horizontal",
    },
},

(aus base/content/stations/air/air/airfield.tl). Dessen createTemplateFn prüft dann params.hangar == 1 für „Yes“.

Die Werte zählen ab 1: Die erste Option liefert 1, und eine Checkbox liefert 1, wenn sie nicht angehakt ist, und 2, wenn sie angehakt ist. Das Wiki weist darauf hin, dass Transport Fever 2 ab 0 gezählt hat. Alte Skripte musst du also anpassen. numbers ersetzt die Standard-Rückgabewerte 1, 2, 3, ....

Feld Bedeutung
key Eindeutiger Bezeichner. Das Wiki empfiehlt Schlüssel, die über alle Mods hinweg eindeutig sind. Sonst kann ein Wert, den der Spieler bei einer anderen Konstruktion mit demselben Schlüssel gewählt hat, bei deiner vorausgewählt sein
name, tooltip, tooltips Beschriftung, Tooltip des Parameters, Tooltips pro Option
values Beschriftungen der Optionen; bei IconButton die Pfade von .tga-Bildern relativ zur .con.lua
numbers Rückgabewerte pro Option statt 1, 2, 3, ...
defaultIndex Vorausgewählte Option, ab 1 gezählt (Standard 1). In mod.json wird dagegen ab 0 gezählt, siehe Mod-Parameter
uiType "Button" (Standard), "Slider", "ComboBox", "IconButton" oder "CheckBox". Auch eine Checkbox braucht zwei values, die nicht angezeigt werden
group Parameter einer Gruppe sind durch eine Linie von anderen getrennt
hideLabel Namen ausblenden
location "Default" oder "Toolbar" (linker oder rechter Teil des Baumenüs)
displayMode Das Wiki nennt "Default" und "Compact"; die Definition kennt außerdem "Horizontal", "Vertical" und "Sparse", und das Basisspiel verwendet "Horizontal" (31-mal) und "Sparse"
yearFrom, yearTo Jahre, in denen der Parameter angezeigt wird
postConstructionModifiable Der Parameter lässt sich nach dem Bau noch ändern
checkEnabledScript Funktion, die den Parameter anzeigt, ausgraut oder ausblendet
stepValueScript, formatValueScript Funktionen für eigene Schrittweiten und Wertanzeige (nur in der Definition, nicht im Wiki)

checkEnabledScript nennt eine Funktion in einer Skriptdatei. Die Funktion gibt "Enabled", "Disabled" (ausgegraut) oder "Hidden" zurück; die Definition kennt einen vierten Wert, "InputActionOnly". Sie darf keine anderen Skript-Schnittstellen aufrufen. Laut Wiki bekommt die Funktion drei Argumente, das Spiel ruft sie aber mit zwei auf. In base/content/gui/gui/construction/construction_react_util.tl lautet der Aufruf fn(v.checkEnabledScript.params, params2). Das erste Argument ist also die params-Tabelle neben fileName, das zweite enthält die aktuellen Werte aller Parameter. Das Beispiel im Wiki verwendet sie selbst so. Aus base/content/scripts/scripts/construction/assetbuilderutil.script.lua:

checkEnabledRotationXYFn = function(captureParams, params)
    return params["randomRotation"] ~= 1 and "Enabled" or "Hidden"
end,

Das Wiki erklärt die Hotkeys für die Drehung. Der Spieler dreht eine Konstruktion mit zwei Hotkey-Paaren (standardmäßig O/P und Ü/+ bzw. [/]), jeder Schritt ist 2π/32. Um sie für die Drehung um X und Y zu nutzen, fügst du param_util.makeRotationParam("constructOpt56", _("Rotation X")) und makeRotationParam("constructOpt78", _("Rotation Y")) hinzu (ParamUtil.makeRotationParam, require "::/scripts/construction/param_util.tl") und packst die Transformationen der Modelle in constructionutil.rotateTransf(params, transf) aus ::/scripts/construction/constructionutil.lua.

Konstruktionsskripte

Neben updateScript kann eine Konstruktion diese Funktionen nennen. Jede ist ein Skriptverweis { fileName = "file.script@function", params = { ... } }, und die Tabelle params kommt als erstes Argument in der Funktion an (captureParams).

Feld Aufgerufen In .con.lua-Dateien des Basisspiels
updateScript um die Konstruktion zu bauen, siehe Update-Funktion alle
createTemplateScript um die Startmodule zu setzen, siehe Vorlagen 68
preProcessScript wenn ein Modul hinzugefügt oder entfernt wird (nicht im Wiki) 60
isAffectedByToolScript solange ein Straßen- oder Gleiswerkzeug aktiv ist: Darf das Werkzeug diese Konstruktion ändern? 5
upgradeScript wenn ein solches Werkzeug die Konstruktion ändert 5
hudIconScript um das HUD-Icon der Konstruktion zu holen 38
configureHudIconsScript welche HUD-Icons beim Platzieren angezeigt werden 51
configureLayerScript welcher Karten-Layer angezeigt wird, während der Spieler die Konstruktion platziert 44
entityWindowScript um das Fenster zu bauen, das bei Auswahl der Konstruktion erscheint 38

Fehlt ein UI-Skript, nimmt das Spiel seinen Standard, und der kann auch leer sein.

isAffectedByToolFn(captureParams, params, conConfigAdd, conConfigRevert) gibt true zurück, wenn das Werkzeug angewendet werden darf. Ohne die Funktion gilt true. params enthält die Werte des letzten Updates, conConfigAdd das, was das Werkzeug tun will, und conConfigRevert das, was es tun würde, wenn es schon angewendet wäre. Das Wiki nennt die Schlüssel: catenary (Elektrifizierungs- und Gleiswerkzeug, 1 = aus, 2 = an), streetTemplate (Gleis- und Straßenwerkzeuge, Ressourcenname), busLane, tramTrack und tramCatenary (1 = aus, 2 = an). upgradeFn(captureParams, params, slotId, constructionConfig) bekommt dieselbe Art von Daten in constructionConfig und gibt die geänderten params zurück, die dann in die Update-Funktion gehen. slotId ist -1 bei nicht modularen Konstruktionen. Beide Funktionen dürfen api.res verwenden, aber keinen anderen Teil von api. Der Bahnhof in base/content/stations/rail/rail/modular_station/modular_station.script.lua hat beide:

isAffectedByToolFn = function(captureParams, params, conConfigAdd, conConfigRemove)
    if conConfigAdd.streetTemplate then
        local streetTemplate = api.res.streetTemplateRep.get(api.res.streetTemplateRep.find(conConfigAdd.streetTemplate))
        return streetTemplate.roadType == 0 -- 0: "TRACK"
    end
    return true
end,

Die UI-Skripte im Überblick, laut Wiki:

  • hudIconFn(captureParams, params, userParam) gibt ein Layout zurück; Hilfsfunktionen stehen in ::/gui/main/hud_icon_toolbox.tl. Die Wahrzeichen verwenden ::/landmarks/landmark_hud.script@hudIconFn.
  • configureHudIconsScript gibt eine Tabelle ConstructionActionHudIcons zurück: componentTypes (z. B. api.type.ComponentType.TOWN, STATION_GROUP, INDUSTRY) und mit TOWN die Flags showDistricts, showTownBorders und showPerksAndTowns. Die Standards des Basisspiels stehen in ::/gui/construction/construction_desc_hud_icons.script.tl.
  • configureLayerScript bekommt (captureParams, layerConfig, params), wobei params die aktuellen Parameterwerte enthält, und gibt eine Layer-Konfiguration zurück. Das Baumenü ruft es auf, während der Spieler die Konstruktion platziert (base/content/gui/gui/construction/construction_react_util.tl). Das Basisspiel verwendet ::/gui/construction/construction_desc_layers.script@configureDefaultLayerFn. Laut Wiki gilt der Layer auch, solange das Infofenster der Konstruktion offen ist, aber im Infofenster-Code des Basisspiels liest nichts configureLayerScript.
  • entityWindowScript gibt eine Fensterbeschreibung zurück, meist mit recipe = make_entity_window.ConstructionWindow aus ::/gui/entity_window/make_entity_window.tl, dessen param und einem fallbackTitle. Wie Fenster gebaut werden, steht unter GUI.

Die Update-Funktion

Die Funktion hinter updateScript bekommt zwei Argumente. Das erste, captureParams, ist die params-Tabelle, die in der .con-Datei bei updateScript steht. Das zweite, params, enthält die Parameterwahl des Spielers, die Module einer modularen Konstruktion und weitere Werte, die vom Konstruktionstyp abhängen. Die kleinen Bushaltestellen zeigen den ersten Teil gut. base/content/stations/street/street/small_stops/small_new.con.lua übergibt das Modell und den Radius des Einzugsgebiets:

updateScript = {
    fileName = "small_stops.script@updateFn",
    params = {
        twoSided = false,
        modelId = resolve("small_new.mdl"),
        maintenanceCost = 5000,
        catchmentAreaRadius = catchmentAreaRadius,
    }
},

small_stops.script.lua im selben Ordner liest sie wieder aus:

function data()

return {
    updateFn = function(captureParams, params)
        local result = {}
        result.streetTerminal = {
            capacity = 50,
            -- ... Gütereinstellungen ...
            catchmentArea = { 
                position = { 0, 0, 0, },
                radius = captureParams.catchmentAreaRadius,
            },
            passengers = true,
            wantSymmetric = captureParams.twoSided,
        }
        result.edgeModels = {
            {
                model = {
                    id = captureParams.modelId,
                    transf = { -1,0,0,0, 0,-1,0,0, 0,0,1,0, 0,0,0,1, },
                }
            }
        }
        result.cost = captureParams.buildCost or captureParams.maintenanceCost * 6
        result.maintenanceCost = captureParams.maintenanceCost

        return result
    end
}

end

Sechs Varianten der Bushaltestelle teilen sich dieses Skript und unterscheiden sich nur in den params ihrer .con-Dateien. Die Haltestelle ist ein Kantenobjekt, darum hat ihr Ergebnis edgeModels statt Unterkonstruktionen; siehe Kantenobjekte.

Die Ergebnistabelle

Die zurückgegebene Tabelle ist in Teal als ConstructionUtilSerialized.ConstructionResult in base/tealdef/scripts/construction/construction_util_serialized.d.tl beschrieben. Der Kommentar in der Datei sagt, was diese Typen sind:

-- Plain lua table type definitions for construction resource scripts.
-- These types describe the format of lua tables produced by construction scripts
-- and read by C++ via converters. They must NOT reference C++ userdata types.

Die oberste Ebene (IConstructionResult) und die Felder, die das Wiki ergänzt:

Feld Inhalt
subconstructions Liste von SubConstruction, siehe unten
edgeLists Straßen und Gleise, siehe Kantenlisten
edgeObjects Modelle wie Signale, die an diesen Kanten hängen
snapPoint wie die Konstruktion beim Platzieren einrastet, siehe Snap-Punkt
slots, slotConfig Modul-Slots, siehe Modulare Konstruktionen
cost, bulldozeCost, maintenanceCost fällig beim Bau, beim Abriss und als laufende Kosten. Das Wiki nennt die laufenden Kosten hier monatlich und bei den Menü-attributes jährlich
costMultiplier Kostenfaktor, z. B. nach Jahr (Wiki; eine mitgelieferte Datei verwendet ihn)
noCostAtAll schaltet alle Kosten des Vorschlags ab, auch die der Geländeanpassung
terminalGroups, stations fassen Modell-Terminals zu Stationen zusammen, siehe Stationen
streetTerminal Terminal einer Haltestelle am Straßenrand (nur Kantenobjekte)
metadata freie Tabelle; Wahrzeichen legen hier ihre Bauphasen ab

Die edgeModels der Bushaltestelle und signal der Signale stehen nicht in diesen Teal-Typen. Das Wiki dokumentiert sie bei den Kantenobjekten.

Unterkonstruktionen

Fast alles, was in der Welt erscheint oder eine Spielfunktion hat, gehört in eine Unterkonstruktion. Die Felder laut Wiki:

Feld Inhalt
tag Ganzzahl, an der das Spiel die Unterkonstruktion nach einem Update wiedererkennt
models Einträge { id = "x.mdl", transf = ..., tag = "..." }; id ist relativ zur .con.lua, transf eine Matrix aus 16 Zahlen oder ein Wert aus mat4.tl
groundFaces bemaltes Gelände, siehe Bodenflächen
colliders Bereiche, in denen nichts anderes gebaut werden kann
terrainAlignmentLists Geländeformung
laneLists Wege für Personen, Güter und Fahrzeuge
runways Lande- und Startbahnen für Flugzeuge und Schiffe
labelText Texte für "CUSTOM"-Beschriftungen von Modellen: [modelIndex] = { "first label", "second label" }
metadata, emissionEmitter freie Daten, Emissionen
station, depot, industry, field, warehouse, maintenanceStation, townBuilding die Spielfunktion der Unterkonstruktion, siehe Konstruktionstypen
stocks, rules, personCapacity, scaffold Güterbestände und Produktionsregeln, Arbeitsplätze oder Einwohner, Baugerüst von Stadtgebäuden

In der Praxis enthält meist eine Unterkonstruktion das meiste davon, die übrigen nur Modelle. Manche Spielfunktionen können sich keine Unterkonstruktion teilen. Eine Konstruktion mit Station und Depot braucht deshalb für beides je eine. Klickt der Spieler die Konstruktion an, hat ihr Fenster einen Reiter pro Unterkonstruktion mit Spielfunktion.

Collider haben einen type: "BOX" oder "CYLINDER" (beide mit params.halfExtents, drei halben Abmessungen) oder "POINT_CLOUD" (mit params.points; dann wird transf ignoriert). Dazu kommt ein transf für den Mittelpunkt. collider_util_serialized beschreibt das Format, und colliderutil.createBox(...) baut einen Collider.

Listen zur Geländeanpassung haben einen type ("EQUAL" setzt das Gelände auf die Flächen, "LESS" trägt nur höheres Gelände ab, "GREATER" füllt nur tieferes Gelände auf), faces (Polygone) oder triangles (eine flache Punktliste, ein Vielfaches von drei), die Böschungsneigungen slopeLow und slopeHigh und optional = true, um Kollisionsfehler mit anderen Geländeanpassungen derselben Konstruktion zu vermeiden. Ist die Anpassung wegen etwas in der Nähe unmöglich, lässt sich die Konstruktion nicht bauen.

Spurlisten haben transf, nodes, speedLimit in m/s und transportModes. Bei nodes bilden je zwei Knoten eine Kante, jeder Knoten ist { position, tangent, width }. Laut Wiki muss die Länge der Tangente der Kantenlänge entsprechen, sonst sehen Fahrzeuge gestaucht oder gestreckt aus, und die Breite zählt nur für die Kapazität. Fahrzeugspuren kennen außerdem needsReservation und forwardOnly, Fußwege linkable (erreichbar über die automatischen kurzen Fußwegverbindungen) und pedestrianWalkPenalty. laneutil.createLane(curve, modes, speed, width, linkable) baut eine Spurliste (siehe lane_util_serialized).

Landebahnen sind { type = "LANDING" | "TAKEOFF", node = { laneList, node }, edges = { { laneList, lane }, ... } }. Am node trifft die Bahn auf die anderen Spuren. Dort hat ein Flugzeug nach der Landung abgebremst oder beginnt, für den Start zu beschleunigen. Die Richtung ergibt sich aus der Tangente der ersten oder letzten Kante. Ein erster Index von -1 verweist auf die edgeLists der Konstruktion statt auf eine Spurliste. Dieselben Indexpaare verwenden Terminals, Pools und Depots.

Bodenflächen

groundFaces = {
    {
        face = { { -10, -10, 0 }, { 10, -10, 0 }, { 10, 10, 0 } },
        modes = {
            { type = "FILL", key = "industry_floor.gtex" },
        },
        loop = true,
    },
},

face ist ein Polygon gegen den Uhrzeigersinn aus mindestens drei Punkten (z wird ignoriert). Jeder Modus hat einen type ("FILL", "STROKE", "STROKE_INNER" oder "STROKE_OUTER"), den key der Bodentextur relativ zur .con.lua und optional texCoords (eine Koordinate von 0 bis 1 pro Punkt der Fläche). loop = false lässt die Kante vom letzten zum ersten Punkt ohne Strich. alignmentOffsetMode/alignmentDirMode ("OBJECT" oder "WORLD") verschieben und drehen die Textur zusammen mit alignmentOffset/alignmentDir. Die Skripte des Basisspiels rufen meist stattdessen modulesutil.addGroundFaces("file.gtex", generatedData, subconstruction) auf.

Kantenlisten

Kantenlisten fügen dem Verkehrsnetz Straßen und Gleise hinzu. Gekürzt aus dem Wiki:

result.edgeLists = {
    {
        type = "STREET",
        params = {
            type = "::/infrastructure/street/country/country_new_small.street_template",
            tramTrackType = "YES",
        },
        edges = {
            { { .0, -79.0, .0 }, { .0, 15.0, .0 } },  -- node 0
            { { .0, -64.0, .0 }, { .0, 15.0, .0 } },  -- node 1
        },
        snapNodes = { 0 },
        freeNodes = {},
    },
}

type ist "STREET" oder "TRACK". params.type nennt die Straßenvorlage (auch bei Gleisen), und tramTrackType ist "NO", "YES" oder "ELECTRIC". edgeType "BRIDGE" oder "TUNNEL" zusammen mit edgeTypeName macht eine Brücke oder einen Tunnel daraus; das Wiki empfiehlt für beide absolute Pfade. Je zwei Einträge in edges ergeben eine Kante, jeder ist { position, tangent } mit einem optionalen dritten String-Tag, der Knoten an verschiedenen Positionen zusammenführt. snapNodes sind die Knotenindizes, an die später gebaute Straßen oder Gleise anschließen können, freeNodes Knoten, die nicht zur Konstruktion gehören, und alignTerrain = true formt das Gelände wie beim normalen Straßenbau. Die Knotengruppen für Tags heißen im Wiki tag2Nodes, in der Definition und in base/content/stations/rail/rail/trainstationutil.lua aber tag2nodes. Für Ampeln beschreibt das Wiki trafficLightNodePreference (Knotenindex und "YES", "NO" oder "AUTO"). Die Definition hat stattdessen trafficLightYes und trafficLightNo, und keine mitgelieferte Konstruktion verwendet eines davon. street_util_serialized hat Hilfsfunktionen wie addStraightEdge.

result.edgeObjects hängt Modelle an diese Kanten: { edge = 1, param = .5, left = false, model = "::/stations/airport/asset/signal_runway_old.mdl" }. Kante n liegt zwischen den Knoten 2n und 2n+1, param ist der Abstand vom Startknoten in Metern, und per Skript ist nur ein Objekt pro Kante möglich.

Wie die eigenen Fahrzeugspuren einer Konstruktion an ihre Straßen anschließen, steht weder im Wiki noch in den Definitionen. Das Straßendepot zeigt, wie es das Basisspiel macht (base/content/depots/road/road/road_depot/road_depot.script.lua). Sein Straßenstück kommt aus der generierten Kurve lane1 und läuft von (0, -35, 0), dem Snap-Knoten, bis (0, -20.26, 0) an der Depotfront:

local snapNodes = { 0 }
constructionutil.addEdges(generatedData["base"].curves["lane1"], result, "STREET", { type = streetType, }, false, snapNodes, freeNodesList)
-- ...
local lane = laneutil.createLane(generatedData["base"].curves["in"], transportModes , 30, 30, true)

Die Straßenvorlage ::/infrastructure/street/constructions/entrance_new.street_template hat Spuren mit 3, 5, 5 und 3 m Breite. Die beiden Fahrspuren liegen also 2,5 m links und rechts der Straßenmitte. Die BUS/TRUCK-Spuren des Depots enden und beginnen bei (-2.5, -20.26, 0) und (2.5, -20.26, 0), genau dort, wo diese Straßenspuren enden. Vermutlich gilt: Eine Spur der Konstruktion schließt an eine Straßenspur an, wenn ihre Endpunkte an derselben Position liegen. Keine Datei sagt das, und ob das Spiel eine kleine Lücke duldet, ist offen.

Snap-Punkt

result.snapPoint = {
    transf = transf.rotZTransl(0, vec3.new(0, -25, 0)),
    snapToBaseEdgeTypes = {"STREET"},
}

transf ist der Punkt der Konstruktion unter dem Mauszeiger. transportModes und snapToBaseEdgeTypes ("TRACK", "STREET") legen fest, woran sie einrastet. allowSnapToBaseEdgeEnds erlaubt das Einrasten an Kreuzungen und Enden, alignWithCoast lässt sie an der Küste einrasten, placeAtTerrainHeight hält sie beim Einrasten auf Geländehöhe. Für Häfen gibt es forceSnapToWaterSurfaceHeight, snapHeightOffsetAboveWater und onlySnapToWaterSurfaceHeightWhenInWater. Mit allowSnapToMesh kann der Spieler mit gedrückter ALT-Taste an Modellgeometrie einrasten.

Kommentiertes Beispiel: das Helipad

base/content/stations/air/air/helipad/helipad.script.lua baut eine vollständige kleine Station. Die Datei beginnt mit einem Block generierter Daten, danach kommt die Update-Funktion (gekürzt, Kommentare ergänzt):

local transf = require "/scripts/mat4.tl"  -- in einem Mod: require "::/scripts/mat4.tl"
local constructionutil = require "::/scripts/construction/constructionutil.lua"
local laneutil = require "::/scripts/construction/laneutil.lua"
local modulesutil = require "/scripts/construction/modulesutil.lua"  -- in einem Mod: require "::/scripts/construction/modulesutil.lua"

--Begin Generated
local generatedData = {
    ["base"] = {
        models = {
            ["::/assets/buildings/ind/aeration/aeration_06.mdl"] = {
                { 0.0, -1.0, 0.0, 0.0,  1.0, 0.0, 0.0, 0.0,  -0.0, -0.0, 1.0, 0.0,  10.0, 5.18, 0.0, 1.0, },
            },
            -- ... eine Liste von 4x4-Transformationen pro Modell ...
--End Generated

function data()
    return {
        updateFn = function(captureParams, params)
            local result = {}

            -- eine Unterkonstruktion enthält alles; tag identifiziert sie
            local stationSubconstruction = {
                runways = {},
                laneLists = {},
                models = {},
                groundFaces = {},
                tag = 1000,
            }

            -- die generierten Modelle platzieren
            constructionutil.addModelsAndGroups(params, generatedData["base"], stationSubconstruction)

            -- eine Station mit einem Pool für Personen und Güter
            local station = {
                terminals = {},
                tag = 1,
                pool = { edges = {}, capacity = 100 },
                catchmentAreas = {{ radius = 100 }},
            }

            -- Spuren: ein Fußweg für PERSON/CARGO, dann zwei HELICOPTER-Spuren
            local lane = laneutil.createLane(lanes["lanes"].curves["in"], { "PERSON", "CARGO"} , 10, 5, true)
            table.insert(stationSubconstruction.laneLists, lane)
            -- ... zwei weitere createLane-Aufrufe für "HELICOPTER" ...

            -- das Terminal verweist auf Spuren mit {Index der Spurliste, Knotenindex}
            local terminal = {
                passengers = true,
                cargo = true,
                personEdges = { { #stationSubconstruction.laneLists-3, 0},  { #stationSubconstruction.laneLists-3, 1},  },
                personNodes = { { #stationSubconstruction.laneLists-3, 2}, },
                vehicleNode = { #stationSubconstruction.laneLists-1, 0},
                transportModes = {"HELICOPTER"},
                capacity = 30,
            }
            table.insert(station.terminals, terminal)

            -- Bahnen für Landung und Start
            stationSubconstruction.runways[#stationSubconstruction.runways + 1] = {
                type = "LANDING",
                node = { #stationSubconstruction.laneLists-2, 0},
                edges = { {#stationSubconstruction.laneLists-2, 0}, },
            }
            -- ... TAKEOFF-Bahn ...
            stationSubconstruction.station = station

            -- Bodentextur aus helipad.gtex
            modulesutil.addGroundFaces( "helipad/helipad.gtex", generatedData, stationSubconstruction)

            result.subconstructions = { stationSubconstruction }

            -- die Station 25 m davor an einer Straße einrasten lassen
            result.snapPoint = {
                transf = transf.rotZTransl(0, vec3.new(0, -25, 0)),
                snapToBaseEdgeTypes = {"STREET"},
            }

            result.cost = 180000
            result.maintenanceCost = 25000
            return result
        end,
    }
end

Die Markierungen --Begin Generated und --End Generated kommen in vielen Konstruktions- und Modulskripten des Basisspiels vor. Der Import von Konstruktionsdaten im Model Editor füllt den Block dazwischen aus einer .fbx-Datei, siehe Model Editor.

Das Helipad ist eine Datei des Basisspiels, /scripts/mat4.tl meint hier also das Basisspiel. In einem Mod zeigt / in den Mod selbst. Schreib dort ::/, wie es die Kommentare zeigen (Dateiverweise).

Konstruktionstypen

Eine Konstruktion hat kein Typfeld. Was sie tut, hängt von den Unterkonstruktionen in ihrem Ergebnis ab, und eine Konstruktion kann mehrere davon kombinieren. Die Wiki-Seite Construction Types beschreibt jede. Sie nennt auch einen Sonderfall: Eine einfache Konstruktion mit einer Unterkonstruktion und ohne Bodenflächen, Geländeanpassung, Station, Depot, Industrie oder Stadtgebäude wird gar nicht als Konstruktion platziert, nur als ihre Modelle. Der Spieler kann sie dann nicht anklicken, nicht nachträglich konfigurieren und nicht in einem Zug entfernen, und labelText funktioniert für sie nicht.

Stationen

Eine Unterkonstruktion mit einer Tabelle station ist eine Station (StationDef):

subconstruction.station = {
    tag = 1,
    terminals = { ... },
    pool = { edges = { { 1, 1 } }, capacity = 100 },
    catchmentAreas = { { position = { 0, 0, 0 }, radius = 200 } },
}

Jedes Terminal (TerminalDef) verweist mit Paaren { laneListIndex, index } auf Spuren derselben Unterkonstruktion oder mit einem ersten Wert von -1 auf die edgeLists der Konstruktion:

Feld Bedeutung
tag daran erkennt das Spiel das Terminal nach einem Update wieder
personEdges Wartekanten für Passagiere und Güter; ihre Länge und Breite ergeben die Kapazität. Sie müssen "PERSON" oder "CARGO" erlauben
personNodes wo aussteigende Passagiere und entladene Güter erscheinen
vehicleNode wo das Fahrzeug hält (vorne oder in der Mitte, je nach Fahrzeugtyp)
vehicleEdges Alternative zu vehicleNode, bei der mehrere Fahrzeuge gleichzeitig laden können
passengers, cargo was das Terminal abfertigt
comfortFactor wie schnell wartende Passagiere ungeduldig werden
cargoTransferSpeeds Liste von { cargoTypeSet, loadSpeedModifier, length } für spezialisierte Abschnitte, wichtig für gemischte Züge

Der pool ist ein Überlauf-Wartebereich für die ganze Station. Innerhalb eines Einzugsgebiets gelten Orte als erreichbar, wenn Passagiere einen durchgehenden Fußweg haben. Andere Güter werden teleportiert.

Stationen aus Modellen, die eigene Terminals mitbringen (die Bahnsteige des Bahnhofs), gruppieren diese in result.terminalGroups. terminals listet Paare { modelIndex, terminalIndex }, und vehicleNodeOverride ersetzt den Fahrzeugknoten durch einen Knoten aus result.edgeLists. Laut Wiki landet der Fahrzeugknoten bei Straßenkanten auf der ersten Spur, und das ist meist der Gehweg. Sobald terminalGroups existiert, werden Terminals ohne Gruppe ignoriert. result.stations ordnet die Gruppen dann Stationen zu, zum Beispiel um Passagier- und Güterteil zu trennen: { { terminals = { 0, 1 }, tag = 0 }, { terminals = { 2, 3 }, tag = 1 } }. Der tag sorgt dafür, dass Linien erhalten bleiben, wenn sich die Reihenfolge ändert.

Depots

depot = {
    transportModes = { "TRAIN", "ELECTRIC_TRAIN", },
    filterTags = { "default", },
    inNodes = { { 1, 0 }, },
    outNodes = { { 0, 0 }, },
},

transportModes sind die Fahrzeuge, die hier verkauft werden, und filterTags beschränkt den Laden auf Fahrzeuge mit passendem Tag. inNodes und outNodes sind Ein- und Ausfahrtsknoten, an denen jeweils nur eine Spur hängen darf. Wie die Spuren des Straßendepots die Straße erreichen, steht unter Kantenlisten. Modelle des Depots können für die Tore die Animationen open und close haben. Ein Wartungsgebäude verwendet dieselbe depot-Tabelle mit catchmentArea (radius in Metern um position) und maintenancePool (wie viele Fahrzeuge gleichzeitig gewartet werden können). Fahrzeuge innerhalb des Radius werden gewartet (Depot).

Industrien

Eine Unterkonstruktion mit einer Tabelle industry ist eine Industrie. In der .con.lua verbindet placementTag sie mit der Wirtschaft, und isIndustry = true erlaubt der Wirtschaft, sie zum Ausgleich von Angebot und Nachfrage zu platzieren. Welche Industrien eine Karte bekommt, entscheidet die Wirtschaftsdatei des Klimas, siehe Wirtschaftssysteme. Der Güterfluss steht in stocks und rules:

subconstruction.stocks = {
    { type = "INPUT_STOCK", cargoType = "::/cargos/grain/grain.cargo", capacity = 400, loadIndicators = { 10, 11 }, tag = 1 },
}
subconstruction.rules = {
    { input = { { 8, 3 } }, output = { ["::/cargos/beverages/beverages.cargo"] = 8 }, capacity = 45, priority = 1 },
}

Der type eines Bestands ist "INPUT_STOCK", "OUTPUT_STOCK" oder "STORAGE_STOCK" (Lager). loadIndicators sind Indizes von Modellen in derselben Unterkonstruktion, die die gelagerte Menge zeigen, und tag erhält die gelagerten Güter über Updates hinweg. Das input einer Regel nennt eine Menge pro Eingangsbestand; jede innere Liste ist eine Alternative. capacity begrenzt, wie oft die Regel pro Jahr läuft. Können mehrere Regeln laufen, gewinnt die mit der höheren priority, sonst wechseln sie sich ab.

Booster: Eine Regel mit booster = true und einem boostFactor (z. B. 1.4) läuft für sich und beschleunigt die normalen Regeln, solange sie läuft. Eine personCapacity vom Typ "INDUSTRIAL" fügt Arbeitsplätze hinzu, die die Produktion ebenfalls steigern. Das Wiki empfiehlt beide Booster für Rohstoffindustrien und nur Arbeiter für verarbeitende Industrien.

Felder und Satelliten sind Module in Slots (siehe Modulare Konstruktionen). industry.maxLevel ist die Zahl der Felder im vollen Ausbau; 0 heißt keine, wie bei den meisten verarbeitenden Industrien. Die Update-Funktion bekommt dann params.sizeFactor, mit dem Skripte die Regeln skalieren, z. B. consumptionFactor = params.sizeFactor.

Lager

Ein Lager hat subconstruction.warehouse = { } und Lagerbestände vom Typ "STORAGE_STOCK". Diese nehmen über cargoTypes eine Menge von Güterarten an (ein Cargo-Type-Set wie bei Fahrzeugen) statt eines einzelnen cargoType. Normalerweise legt das erste eingelagerte Gut den Bestand auf diese Güterart fest. Mit mixedTypes = true bleibt er für alle angenommenen Arten offen. Laut Wiki verlängern ungemischte Bestände die Haltbarkeit gelagerter Güter. loadSpeedModifier skaliert die Ladegeschwindigkeit.

Assets

Assets sind Deko-Konstruktionen und können Kanten enthalten. Das Wiki nennt type = "ASSET_DEFAULT" (frei platziert) und "ASSET_TRACK" (rastet entlang von Gleisen ein, SHIFT unterdrückt das). Diese Werte entsprechen ConstructionType, aber keine mitgelieferte .con.lua setzt einen solchen type. Weitere Felder für Assets: skipCollision (Modelle dürfen sich überlappen), autoRemovable (wird entfernt, wenn etwas anderes darüber gebaut wird), categories zum Filtern und snapping. Update-Funktionen von Assets bekommen paramX und paramY (die Hotkeys), einen seed, der mit jeder Platzierung steigt, state (zwischengespeicherte Asset- und Gleisdaten) und year, dazu die eigenen Parameter dieser und früherer Asset-Konstruktionen. assetbuilderutil.makeAssetCon(...) aus ::/scripts/construction/assetbuilderutil.lua baut eine ganze Asset-Sammlung. Sieben Kampagnenmissionen liefern eine solche Datei als content/mission0Xconlua.txt mit (eine .txt, wird also vermutlich nicht als Konstruktion geladen).

Stadtgebäude

Die Stadt wählt Gebäude nach den townBuildingParams in der .con.lua aus: landUseType ("RESIDENTIAL", "COMMERCIAL" oder "INDUSTRIAL"), parcelSize in Schritten von 8 m ({ 1, 2 } sind 8 m entlang der Straße und 16 m Tiefe) und level 1 bis 4. Wenn die Stadt ein Gebäude platziert, bekommt die Update-Funktion zusätzlich capacity, cargoTypes, width, depth und parcelFace. Die Unterkonstruktion gibt townBuilding = { level, width, depth } zurück, eine personCapacity (type, capacity, die personNodes, an denen Menschen das Gebäude betreten) und für die Bauphase ein scaffold (Polygon buildingFace und height-Versatz unterhalb des Dachs). Der Güterbedarf ist ein INPUT_STOCK pro Eintrag in params.cargoTypes plus eine Regel, deren Eingänge Alternativen sind ({ {1,0,0}, {0,1,0}, {0,0,1} }). Eine gelieferte Güterart genügt also.

Das Basisspiel schreibt das alles über base/content/buildings/townbuildingutil.lua. Aus base/content/buildings/a/c1/1x1_01/1x1_01/a_com_l1_1x1_01.con.lua:

local townbuildingutil = require "/buildings/townbuildingutil.lua"  -- in einem Mod: require "::/buildings/townbuildingutil.lua"

function data()
    return townbuildingutil.makeBuildingStatic(
        { { -4.60, -0.10 }, { 4.60, -0.10 }, { 4.60, 9.10 }, { -4.60, 9.10 } },
        "COMMERCIAL", 
        "A", 
        1,
        { 1, 1 },
        "a_com_l1_1x1_01.script"
    )
end

makeBuildingStatic(buildingFace, landUseType, era, level, parcelSize, scriptFile) setzt die Verfügbarkeit nach Epoche, die Sounds und townBuildingParams und gibt townBuildingParams und buildingFace an das Update-Skript weiter. townbuildingutil.makeBuildingScript(modelData, groundTexture) gibt eine fertige Update-Funktion zurück.

Wahrzeichen

Wahrzeichen sind einmalige Gebäude, die der Spieler freischaltet. Die Freischaltregel steht in den metadata der .con.lua:

metadata = {
    company = {
        perkCategory = "LANDMARK",
        rankAndPermits = {{2, 1}},
    },
},

perkCategory ist "LANDMARK", "HQ" (Module des Hauptquartiers), "PROSPECTION" oder "MECHANICS". Jedes Paar in rankAndPermits besteht aus einem Unternehmensrang und der Zahl der Exemplare, die auf diesem Rang freigeschaltet werden. Bauphasen und Flächeneffekte kommen in result.metadata.constructionConfig der Update-Funktion (construction_config_metadata):

result.metadata = {
    constructionConfig = {
        numLevels = 4,
        level = 0,
        underConstructionMessage = _("Supply the construction site with resources."),
        requirements = { { cargoType = "::/cargos/stone/stone.cargo", amount = 800 } },
        effects = { { carrier = "ROAD", type = "noise", value = 0.75 } },
    },
}

numLevels zählt die Bauphasen einschließlich der fertigen. Mit level = 0 schaltet das Game-Script der Wahrzeichen die Phase weiter, sobald die requirements geliefert sind. underConstructionDescription steht bis zur Fertigstellung im Fenster. Ein Effekt hat einen carrier ("ROAD", "RAIL", "TRAM", "AIR", "WATER"), einen type ("topSpeed", "noise", "pollution", "comfort") und einen Multiplikator value für Fahrzeuge in der Nähe.

Derselbe company-Block schaltet die anderen Boni frei. Mechaniken-Boni sind Werkzeuge oder Konstruktionen mit perkCategory = "MECHANICS", etwa die Reinigungsanlage base/content/game_mechanics/game_mechanics/emission/pollution_cleanup_facility.con.lua und das Werkzeug industry_greenify_tool (siehe Werkzeuge im Baumenü). Boni der Kategorie „Prospektion“ (Prospecting) und Genehmigungsschlüssel sind allgemeine Ressourcen, keine Konstruktionen. Alle drei beschreibt die Seite Spielmechaniken.

Das Hauptquartier ist selbst eine modulare Konstruktion: base/content/landmarks/hq/hq/headquarter.con.lua mit 14 Modulen in landmarks/hq/hq/modules/, jedes mit perkCategory = "HQ" in seinem metadata.company. Das DLC-Modul hq_maze.module.lua fügt von außerhalb des Basisspiels ein fünfzehntes hinzu. Es ist das Beispiel in Eine minimale modulare Konstruktion.

Kantenobjekte

Signale und Haltestellen am Straßenrand sind Konstruktionen mit einer Tabelle edgeObject (ConstructionDesc.EdgeObject) in der .con.lua: snapToStreet, snapToTrack, minDistToCrossing und für Haltestellen die Vorschau-Einstellungen des Einzugsgebiets catchmentCargo, catchmentPassengers und catchmentAreaRadius. Ihre Update-Funktion bekommt zusätzlich params.baseEdgeType (NORMAL, BRIDGE oder TUNNEL) und params.roadType (STREET oder TRACK).

Das Ergebnis eines Kantenobjekts nimmt nur wenige Felder an: cost, maintenanceCost, edgeModels, signal, streetTerminal und edgeObjectEmissionEmitter (noise, pollution). Darum gibt die Bushaltestelle oben edgeModels zurück. Jeder Eintrag in edgeModels ist { edgeOffset = ..., model = { id, transf } }. Das optionale edgeOffset verschiebt das Modell entlang der Kante, und das Modell folgt dabei ihrer Kurve und Steigung. signal hat einen type ("PATH_SIGNAL" für beide Richtungen oder "ONE_WAY_PATH_SIGNAL"), optional ein soundevent und upright (true hält das Modell senkrecht, false folgt der Steigung). Ein vollständiges Signal mit seinen Animations-Events steht auf Infrastruktur.

streetTerminal (StreetTerminal) hat capacity, catchmentArea, passengers, cargoLoad, cargoUnload, cargoTransferSpeeds und wantSymmetric (dieselbe Haltestelle auf der anderen Straßenseite bauen).

Das Baumenü

menuCategory.categories listet die Reiter, in denen eine Konstruktion, ein Modul oder eine Metakonstruktion erscheint. Jeder Eintrag hat einen Tag category, optional filterCategories und optional order (ModelMetadata.MenuCategory):

menuCategory = {
    categories = {
        { category = "rail_buildings", filterCategories = {"building"}, order = 5000 },
    },
},

Das Basisspiel definiert die Reiter in base/content/gui/gui/construction/menu/menu_categories/*.res.lua (jeder mit category, order, name, icon und menu), die Straßenreiter in base/content/infrastructure/street/street/shared/category_roads_*.res.lua, die Modulreiter in .../menu/modules_categories/ und die Filter in .../menu/filter_categories/ (75 Dateien). Die Wiki-Seite Construction Menu listet die Reiter:

Menü Tags
Strasse (Road) road_buildings, road_tools, road_assets
Schiene (Rail) rail_buildings
Gleise (Tracks) tracks, rail_tools, rail_constructions, rail_signals, rail_assets
Wasser (Water) water_buildings, water_tools, water_assets
Luft (Air) air_buildings, air_tools, air_assets
Strassen (Roads) roads_small, roads_medium, roads_large, roads_country, roads_highway, street_constructions, roads_tools
Lager (Warehouses) warehouses
Boni (Perks) landmarks, prospections, perks_mechanics
Industrie (Industry) industries, industries_assets (beide nur im Sandkastenmodus)
Landschaftsbau (Landscaping) landscaping_terrain, landscaping_ground, landscaping_vegetation, landscaping_animals, landscaping_people, landscaping_vehicles, landscaping_assets, town_buildings_res, town_buildings_com, town_buildings_ind, town_buildings_other
Stadt (Town) town_tools
Module (Modules) modules_building, modules_platforms, modules_plots, modules_decoration, modules_street_access, modules_tracks, modules_landing, modules_warehouse, modules_misc

Einige davon stammen aus den Spieldateien und fehlen in der Tabelle des Wikis: roads_tools (Menü ROADS, 17 Verwendungen), town_tools (Menü TOWN) und die neun modules_*-Reiter (Menü MODULES, verwendet von .module.lua-Dateien). Das Wiki ordnet rail_assets dem Menü „Schiene“ (Rail) zu; in der zugehörigen .res.lua steht menu = "TRACKS". Die Asset-Reiter, rail_constructions, rail_signals, die Landschaftsbau-Reiter für Tiere, Menschen, Fahrzeuge und Assets und die vier Reiter für Stadtgebäude markiert das Wiki als „mod only“. Das Basisspiel legt dort also nichts ab.

Filterkategorien grenzen einen Reiter weiter ein. Das Wiki ordnet sie nach Themen:

Thema Tags
Boden asphalt, ground, gravel, grass, plant, rock, sand, misc
Vegetation tree, bush, plant, flower
Tiere bird, land, fish
Menschen residential, worker
Fahrzeuge bike, car, bus, truck, tram, train, passenger_wagon, cargo_wagon, plane, helicopter, ship
Gebäude culture, sport, public, emergency, tower, lvl_1 bis lvl_4
Industrien farm, mine, factory, water
Infrastruktur stop, station, depot, tracks, bridge, tunnel, intersection
Gleise 600mm, 750mm, 1000mm, 1435mm, 1500mm, 3rd_rail, catenary, cogwheel, high_speed, wood, concrete
Brücken, Tunnel bridge_viaduct, bridge_arch, bridge_beam, bridge_truss, bridge_suspension, tunnel_round, tunnel_rectangular
Sonstiges cargo, passenger, building, barrier, construction, lamp, sign, spline, tools

Eigene Reiter und Filter sind möglich. Das Wiki bittet Modder aber, die vordefinierten zu verwenden und sich vor dem Anlegen neuer an Urban Games zu wenden.

Werkzeuge im Baumenü

Nicht jeder Eintrag im Baumenü ist eine Konstruktion. Pinsel, Geländewerkzeuge, das Busspur- und das Tramgleis-Werkzeug, das Städtebau-Werkzeug und die Marketingkampagne sind Ressourcen vom Typ construction_tool: 22 .res.lua-Dateien in base/content/gui/gui/construction/tools/. Jede verweist auf eine Funktion, die den Menüeintrag zurückgibt. asset_eraser_tool.res.lua:

function data()
    return {
        type = "construction_tool",
        data = {
            getDefinitionFn = {
                fileName = resolve("asset_eraser_tool.script@getDefinition"),
            }
        }
    }
end

data ist ein ConstructionToolData. getDefinitionFn ist Pflicht. Ein optionales isDisabledFn, das true zurückgibt, lässt den Eintrag weg (kein Werkzeug des Basisspiels setzt es). Das Baumenü sammelt alle Ressourcen dieses Typs mit api.res.genericRep.getAllOfType("construction_tool") und ruft getDefinitionFn(resName, repository, params) auf (base/content/gui/gui/construction/construction_react_util.tl). Die Funktion gibt eine ConstructionDefinition zurück. Aus asset_eraser_tool.script.tl (gekürzt):

getDefinition = function(constructionDefinition : ResName, _repository : ConstructionActionRepository, _params : table) : ConstructionDefinition
    return {
        resName = constructionDefinition,
        constructions = {},
        builderAudioRes = { "::/gui/construction/sound/terraform_erase.builder_audio" },
        icon = { icon = resolve("asset_reset_material.tga") },
        previewIcon = { icon = resolve("asset_reset_material_preview.tga") },
        name = _("Asset Eraser Tool"),
        description = _("Erase assets with a brush."),
        action = "ACTION_ASSET_BRUSH",
        availability = { yearFrom = 0, yearTo = 0, },
        -- ... hudIcons, Pinselparameter ...
        menuCategory = {
            categories = {
                { category = "landscaping_assets", filterCategories = {"*"}, order = -10000, },
            },
        },
    }
end,

action wählt eines der eingebauten Werkzeuge aus ConstructionMenuAction. Ein Mod kann so ein vorhandenes Werkzeug mit anderen Einstellungen in einen anderen Reiter legen. Die Mechaniken-Boni industry_greenify_tool und marketing_campaign_tool ergänzen eine customAction mit eigenem GUI-Recipe und setzen perkCategory = "MECHANICS" in ihre metadata. Kein mitgelieferter Mod und kein DLC fügt ein construction_tool hinzu. Ob die Datei eines Mods mit diesem Typ im Menü erscheint, ist darum nicht bestätigt, auch wenn der Menü-Code jede Ressource des Typs liest.

Bodentexturen

Eine Bodenfläche bemalt das Gelände mit einer Bodentextur, einer .gtex.lua-Datei (710 im Basisspiel). Sie ordnet den Grauwerten eines Maskenbilds Geländematerialien zu. base/content/stations/air/air/helipad/helipad.gtex.lua (gekürzt):

local tu = require "/scripts/tex_util.tl"  -- in einem Mod: require "::/scripts/tex_util.tl"

function data()
return {
    texture = tu.makeMaterialIndexTexture("helipad.tga", "CLAMP_TO_EDGE", "CLAMP_TO_EDGE"),

    texSize = { 36, 38 },
    materialIndexMap = {
        [95] = "::/terrain/materials/asphalt_02/asphalt_02.tmat",
        [8] = "::/terrain/materials/asphalt_03/asphalt_03.tmat",
        [199] = "::/terrain/materials/gravel_02/gravel_02.tmat",
        -- ...
    }
}
end
Feld Bedeutung
texture die Maske, aus TexUtil.makeMaterialIndexTexture(fileName, wrapS, wrapT); die Datei ist relativ zur .gtex.lua. "CLAMP_TO_EDGE" streckt die Maske über die Fläche, "REPEAT" kachelt sie
texSize Größe des Musters in Metern
materialIndexMap Grauwert (1 fast schwarz bis 255 weiß) zu Geländematerial .tmat. Werte, die nicht in der Tabelle stehen, bleiben transparent und zeigen das normale Gelände
priority welche Textur gewinnt, wo sich mehrere überlappen
indices statt texture: data (ein String mit einer Ziffer pro Pixel) und size

Das Beispiel im Wiki lädt ::/scripts/texutil.lua. Diese Datei gibt es noch, aber 699 der 710 .gtex.lua-Dateien des Basisspiels verwenden tex_util.tl, und keine verwendet texutil.lua. Laut Wiki sind die Materialpfade relativ zur .gtex.lua. Die Dateien des Basisspiels verwenden absolute Pfade ::/terrain/materials/.... Ein Beispiel mit einem Pixel aus base/content/buildings/shared/shared/building_paving_fill.gtex.lua:

indices = {
    data = "1",
    size = { 1, 1 },
},
texSize = { 8.0, 2.0 },
materialIndexMap = {
    "::/terrain/materials/asphalt_03/asphalt_03.tmat",
},
priority = 10

Hilfsbibliotheken für Konstruktionen

base/tealdef/scripts/construction/ enthält die Definitionen der Hilfsfunktionen. Die Implementierungen sind Lua- oder Teal-Dateien in base/content/scripts/scripts/construction/.

Definition Inhalt
construction_util_serialized Alle Ergebnistypen (ConstructionResult, SubConstruction, LaneList, StationDef, TerminalDef, Slot, SnapPoint, EdgeList, Stocks usw.) und addModelsAndGroups(params, group, subconstruction, tag?, transf2?)
lane_util_serialized createLane(curve, transportModes, number, number, boolean), gibt eine LaneList zurück
street_util_serialized Kanten-Hilfsfunktionen: addStraightEdge, addRamp, addEdge, addEdgeAutoTangents, freeAllNodes, calcScale
collider_util_serialized Das Collider-Format: Typ NONE, BOX, CYLINDER oder POINT_CLOUD, transf, params.halfExtents
modules_util_serialized addCosts(result, module), TransformAlignmentFaces, addGroundFaces
param_util Fertige Parameter: makeTrackTypeDefaultParam, makeTrackCatenaryParam, makeTramTrackDefaultParam, makeRotationParam, getRailTrackTypes
asset_util assets: Zuordnung von Asset-Gruppennamen zu Listen von .mdl-Pfaden
construction_config_metadata Level, Anforderungen, Lagerbestände und Effekte in den Metadaten der Konstruktion
construction_specialization_metadata Metadaten zur Spezialisierung

Konstruktionen aus einem Mod ändern

Der mitgelieferte Code zeigt zwei Wege. Beim Laden bekommt addModifier("loadConstruction", fn) (und loadModule, loadMetaConstruction) die Daten jeder Datei (siehe Ressourcen-Modifier). Nach dem Laden sind api.res.constructionRep und api.res.moduleRep gewöhnliche Repositorys. Das postRunFn des Basisspiels fügt Module mit api.res.moduleRep.add(mod.fileName, mod, true) hinzu.

Um eine Konstruktion per Skript zu platzieren, gibt es api.cmd.makeWorldBuildProposalCmd und verwandte Proposal-Typen. Die Aufgabe build_construction des Missions-Frameworks prüft Vorschläge des Spielers gegen einen Konstruktions-Dateinamen (siehe Missionen). Das Format der Vorschläge ist in der api.type-Referenz dokumentiert, nicht hier.

Offizielles Wiki: Construction Basics, Construction Types, Construction Menu, Ground Textures, Modular Constructions, Construction Templates.