Zum Inhalt

Modulare Konstruktionen

Modulare Konstruktionen lassen sich nach dem Bau noch verändern: Der Spieler setzt Module in Slots ein und entfernt sie wieder. Dazu gehören Bahnhöfe, Flughäfen, Häfen, Lager, das Hauptquartier und Industrien mit ihren Feldern. Die Konstruktion selbst ist eine gewöhnliche .con.lua mit einer Update-Funktion, wie unter Konstruktionen beschrieben. Diese Seite behandelt Slots, Module, die Reihenfolge, in der ihre Skripte laufen, und Vorlagen, die einer neuen Konstruktion ihre ersten Module mitgeben. Am Ende steht unter Eine minimale modulare Konstruktion eine kleine Konstruktion mit einem Modul, mit der du anfangen kannst. Das offizielle Wiki behandelt dasselbe Thema auf Modular Constructions und Construction Templates.

Wie die Skripte laufen

Jedes Mal, wenn eine modulare Konstruktion in der Vorschau erscheint oder gebaut wird, führt construction.constructWithModules in base/content/scripts/scripts/construction/construction.script.tl die Skripte in dieser Reihenfolge aus:

  1. updateFn(captureParams, params) der Konstruktion baut das Grundergebnis, einschließlich result.slots.
  2. cost und bulldozeCost der Konstruktion werden mit costs.infrastructureCostScale aus den Skriptparametern multipliziert. Das Basisspiel setzt diesen Wert aus der Einstellung „Infrastrukturkosten“ (Infrastructure Costs) (base/content/base/base/mod.script.tl). Ist maintenanceCost 0 oder nicht gesetzt, wird daraus cost / 6, berechnet vor der Skalierung. Die Modulpreise kommen später dazu und werden nicht skaliert.
  3. Sind params.modules und result.slots vorhanden, werden die Module nach aufsteigender Slot-ID sortiert. Für jedes Modul wird der Slot mit dieser ID in result.slots gesucht. Fehlt er und ist result.callInvalidModules nicht true, wird das Modul übersprungen.
  4. Hat das Modul ein Update-Skript, wird dessen updateFn mit dem Ergebnis, der Transformation des Slots und dem Tag "__module_<slotId>" aufgerufen. Während es läuft, ist params.seed der Seed der Konstruktion plus die Slot-ID.
  5. Direkt nach diesem Aufruf addiert modulesutil.addCosts(result, module) die Werte metadata.price, metadata.maintenanceCost und metadata.bulldozePrice des Moduls zum Ergebnis.
  6. Nach dem letzten Modul läuft result.terminateConstructionHook(), falls die Konstruktion diese Funktion gesetzt hat. Wie die Schritte 3 bis 5 passiert das nur, wenn params.modules und result.slots beide vorhanden sind.

constructionScript in der Basiskonfiguration zeigt auf constructWithModules (base/content/base/base/mod.script.tl). Für Konstruktionen ohne Module hat die Konfiguration kein eigenes Skript. Vermutlich nimmt das Spiel diesen Weg also für alle Konstruktionen, samt Skalierung der Kosten.

Weil die Module nach der Konstruktion und in der Reihenfolge der Slot-IDs laufen, sieht ein Modul alle Slots und Daten, die die Konstruktion und alle früheren Module in result abgelegt haben. Es kann auch neue Slots für spätere Module anlegen. Konstruktionen legen außerdem eigene Hilfsfunktionen in result ab, die die Module aufrufen. Der Bahnhof macht das mit GetCoord und GetRoofAt.

Die Update-Funktion der Konstruktion

Neben den Parametern des Spielers ordnet params.modules jeder Slot-ID das Modul zu, das dort sitzt (ModuleUpdateFnParams.Module):

modules = {
    [10001000] = {
        metadata = {},
        name = "airfield_main_building.module",
        updateScript = { fileName = "", params = {} },
        variant = 0,
    },
},

metadata ist eine Kopie der Tabelle metadata des Moduls, name die Moduldatei und updateScript seine Update-Funktion. variant ändert sich, wenn der Spieler beim Platzieren eines Moduls M oder N drückt, und kann negativ werden. Der Standardwert ist 0. Das Hauptquartier nutzt den Wert als Drehung in Vierteldrehungen (base/content/landmarks/hq/hq/headquarter_addon.script.tl).

result.cost sind die Kosten der Konstruktion allein. Das Spiel skaliert sie in Schritt 2 und addiert in Schritt 5 die Modulpreise.

Slots

result.slots listet die Slots, die gerade existieren (Slot):

Feld Bedeutung
id nicht negative, eindeutige ID; sie ist der Schlüssel in params.modules
type hier lassen sich nur Module mit demselben type bauen
transf Position relativ zum Ursprung der Konstruktion
spacing Größe des Slots um transf, { -x, x, -y, y }
height Höhe über transf; dient zusammen mit spacing zum Auswählen und Abreißen
shape Markierungssymbol: 0 Quadrat, 1 Dreieck, 2 Rechteck quer, 3 Rechteck längs
autofill der Slot gilt als belegt, wenn eine Industrie zum ersten Mal gebaut wird (Felder und Satelliten)
alignToTerrain der Slot folgt der Geländehöhe
replaceable ein Modul desselben Typs kann das Modul im Slot ersetzen

Das Wiki und der ausgelieferte Code (base/content/industries/fieldutil.lua) schreiben autofill, die Teal-Definition schreibt autoFill.

Viele Basiskonstruktionen kodieren eine Rasterposition und den Slot-Typ in die ID. Das Lager, aus base/content/warehouses/warehouses/warehouse.script.lua:

local EncodeSlotId = function(coord, type)
    return typesNum * (coordNum * (coord.y + coordOffset) + (coord.x + coordOffset)) + type2id[type]
end

Seine Slots legt es in updateFn mit table.insert(result.slots, { id = ..., transf = ..., type = type, spacing = {...}, height = ..., replaceable = true }) an.

result.slotConfig setzt Grenzen pro Slot-Typ (SlotConfig). maxModules ist die Höchstzahl an Modulen dieses Typs in der ganzen Konstruktion, message der Text, der im Menü auf dem Modul erscheint, wenn es sich nicht bauen lässt. Mit skipCollisionCheck = true werden die Slot-Markierungen nicht mehr rot, wenn etwas im Weg steht (die Collider des Moduls zählen trotzdem). Laut Wiki hat maxModules = -2 eine besondere Bedeutung: Der Modul-Knopf ist aktiv, solange message leer ist, und gesperrt, sobald eine Meldung gesetzt ist. Der Flughafen verlangt damit eine zweite Landebahn, bevor Terminal B gebaut werden kann (base/content/stations/air/air/airport.script.lua):

result.slotConfig = {
    ["2nd_runway"] = { maxModules = -1, message = "" },
    terminal_B = {
        maxModules = -2,
        message = hasSecondRunway and "" or _("You Need a 2nd Runway"),
    },
}

Mit result.callInvalidModules = true laufen Module auch dann, wenn ihre Slot-ID nicht in result.slots steht. Ihr transform ist dann nil.

Module

Ein Modul ist eine .module.lua-Datei (121 im Basisspiel), beschrieben durch ModuleDesc. Aus base/content/warehouses/warehouses/wh_bulk.module.lua (gekürzt):

function data()

return {
    menuCategory = {
        categories = {
            { category = "modules_warehouse", order = 100, },
        },
    },
    description = {
        name = _("WAREHOUSE_BULK_NAME"),
        description = _("WAREHOUSE_BULK_DESCRIPTION"),
        icon = "::/warehouses/wh_bulk_module.tga",
    }, 
    availability = { yearFrom = 1850, yearTo = 0, },
    type = "warehouse_large",
    metadata = {
        type = "bulk",
        specialization = "BULK",
        price = 300000,
        maintenanceCost = 50000,
    },
    updateScript = {
        fileName = "modules.script@bulk.updateFn",
        params = {}
    },
    builderAudioRes = "::/gui/construction/sound/buildoze_module.builder_audio",
    getModelsScript = {
        fileName = "modules.script@bulk.getModelsFn",
        params = {}
    },
}

end
Feld Bedeutung
description, availability, menuCategory wie bei Konstruktionen; Modul-Tabs sind die Kategorien modules_* (siehe Baumenü)
type „a module of a specific type can be built only on slots with the same type“ (Kommentar in der Definition)
metadata freie Daten für die Konstruktion und andere Module; price, maintenanceCost und bulldozePrice werden zu den Kosten addiert
autoRemovable wird entfernt, wenn etwas anderes darüber gebaut wird, wie bei Feldern von Farmen (Standard false)
updateScript „Script function that gets called once per build module after the updateScript of the construction has run“
getModelsScript „Script function used to generate module preview when building with module builder“
builderAudioRes das Bau-Audioset, das beim Bauen spielt; alle 121 Basismodule nutzen ::/gui/construction/sound/buildoze_module.builder_audio (der Dateiname ist tatsächlich so geschrieben)

Alle 121 Basismodule setzen menuCategory. Ohne das Feld hat das Modul keinen Tab im Modulmenü.

Das Modulbeispiel im Wiki enthält außerdem buildMode = "SINGLE". Dieses Feld gibt es in ModuleDesc nicht, und kein ausgeliefertes Modul nutzt es.

getModelsFn

getModelsFn(captureParams) gibt die Modelle zurück, die dem Mauszeiger folgen, solange das Modul über keinem Slot ist: eine Liste von { id = "<model>.mdl", transf = ... }. Wenn die Modelle des Moduls relativ zu einem Slot sitzen, der gegenüber der Konstruktion versetzt ist, schiebst du sie hier zurück. Das Lokschuppen-Beispiel im Wiki gibt seine drei Schuppenmodelle mit transf.transl(vec3.new(-30, 0, 0)) zurück, weil seine Slots 30 m von der Mitte der Drehscheibe entfernt liegen.

updateFn

Das Spiel ruft die Update-Funktion des Moduls mit sieben Argumenten auf:

Argument Inhalt
captureParams die params der updateScript-Referenz des Moduls
result das Ergebnis der Konstruktion und aller Module davor
transform die Transformation des Slots; wende sie auf alles an, was relativ zum Slot platziert wird. Mit callInvalidModules ist sie nil
tag "__module_<slotId>"; setze ihn auf die Modelle des Moduls, damit sie mit dem Modul entfernt werden
slotId die Slot-ID
addModelFn addModel(name, transf, tag?, models?): multipliziert transf mit der Transformation des Slots, setzt den Tag und hängt an models an (Standard result.models); gibt den Index zurück
constrParams die params, die die Update-Funktion der Konstruktion bekommen hat, einschließlich modules

Hintere Argumente darfst du weglassen. Laut Wiki soll die Funktion result zurückgeben. constructWithModules ignoriert den Rückgabewert aber. Das Modul muss result also direkt verändern. Das Wiki-Beispiel für das Hallendach des Bahnhofs deklariert ein achtes Argument params, das constructWithModules gar nicht übergibt.

Eine kurze Fassung aus dem Wiki, die ein Hangarmodell und eine Bodenfläche hinzufügt:

updateFn = function(captureParams, result, transform, tag)
    local sub = result.subconstructions[1]
    sub.models[#sub.models + 1] = { id = "hangar.mdl", transf = transform, tag = tag }

    local faces = { {-25.0, -25.0, 0.0, 1.0}, {25.0, -25.0, 0.0, 1.0}, ... }
    modulesutil.TransformFaces(transform, faces)
    sub.groundFaces[#sub.groundFaces + 1] = {
        face = faces,
        modes = { { type = "FILL", key = "airfield_hangar.gtex.lua" } },
    }
end,

In den Lagermodulen lautet die Signatur (captureParams, result, transform, tag, slotId, addModelFn, params). Sie fügen dem result, das die Konstruktion gebaut hat, Modelle und neue Slots hinzu.

Über constrParams.modules können Module auf ihre Nachbarn reagieren. Das Lokschuppen-Beispiel im Wiki berechnet die IDs der Nachbarslots aus slotId und setzt eine Seitenwand nur dort, wo der Nachbar kein Schuppen ist:

local moduleLeft = constrParams.modules[leftIndex]
if not (moduleLeft ~= nil and moduleLeft.metadata ~= nil and moduleLeft.metadata.type == "shed") then
    addModel("shed_wall_left.mdl", transf.transl(vec3.new(-30, 0, 0)), tag, result.subconstructions[1].models)
end

Eine Skriptdatei kann die Funktionen mehrerer Module in einer verschachtelten Tabelle enthalten. Das Modul gibt sie dann als "roundhouse_modules.script@shed.updateFn" an:

function data()
  return {
    shed = { updateFn = updateFnShed, getModelsFn = getModelsFnShed },
    empty = { updateFn = updateFnEmpty, getModelsFn = getModelsFnEmpty },
  }
end

Ein neues Modul für eine Basiskonstruktion braucht nur einen Slot-type dieser Konstruktion. Das Hallendach-Beispiel im Wiki hat type = "passenger_platform_roof", den Typ der Bahnsteigdächer aus dem Basisspiel. Es passt daher überall dorthin, wo auch diese hinpassen.

Die Preprocess-Funktion

Wird ein Modul hinzugefügt oder entfernt, passt eine Preprocess-Funktion die Parameter der Konstruktion an, bevor das Update läuft. Weder ConstructionDesc noch das Wiki dokumentieren sie. 60 .con.lua-Dateien des Basisspiels setzen preProcessScript. Die Standardfunktion steht in base/content/base/base/base_config.lua:

defaultPreprocessFn = function(captureParams, params, change)
    local modules = params.modules
    if change.added then
        modules[change.slotId] = change.module
    else
        modules[change.slotId] = nil
    end
    return params
end

warehouse.con.lua setzt mit preProcessScript = { fileName = "warehouse.script@preProcessFn" } eine eigene, ebenso airport.con.lua und modular_terminal.con.lua.

Upgrade-Skript

Bei einer modularen Konstruktion erhält upgradeFn(captureParams, params, slotId, constructionConfig) die ID des Slots, auf den der Mauszeiger zeigte, als der Spieler das Werkzeug angewendet hat. Sonst funktioniert es wie unter Konstruktionen beschrieben.

Vorlagen

Eine Vorlage ist ein Menüeintrag, der eine Konstruktion mit einer Anfangsausstattung an Modulen baut. In den Spieldateien gibt es zwei Formen.

Im Basisspiel kommt eine modulare Konstruktion über ihre Vorlagen ins Baumenü. airfield.con.lua, airport.con.lua, harbor_modular.con.lua und warehouse.con.lua haben kein menuCategory. Ihre Einträge sind die .metacon.tl-Dateien. In den 11 Dateien mit constructionTemplates trägt jede Vorlage ihr eigenes menuCategory, und das Baumenü übergeht das menuCategory der .con.lua: Es legt einen Eintrag pro Vorlage an (getConstructionDefinitions in base/content/gui/gui/construction/construction_react_util.tl). Eine .con.lua ohne Vorlagen bekommt einen eigenen Eintrag, wenn sie ein menuCategory hat, wie die Industrien. Eine Metakonstruktion, die sie baut, fügt einen zweiten hinzu. Das createTemplateFn der Industrien ist leer. Was der eigene Eintrag einer Konstruktion platziert, wenn ihr createTemplateScript Module zurückgibt, geht aus den Dateien deshalb nicht hervor.

Eine Metakonstruktion ist eine .metacon.tl-Datei (26 im Basisspiel), beschrieben durch MetaConstructionDesc. Laut dem Kommentar dieses Typs ersetzt sie die veralteten Konstruktionsvorlagen („replaces the deprecated construction templates“). Sie hat die Menüfelder einer Konstruktion (availability, description, menuCategory, soundConfig, params, order, heightModView, ...) und ein createTemplateScript. Die Flugplätze sind ein Beispiel, hier base/content/stations/air/air/airfield_era_a.metacon.tl:

local airfield = ug_require "::airfield.tl"

return airfield.makeTemplate(airfield.eraAStart, airfield.eraBStart)

Eine .metacon.tl gibt ihre Beschreibungstabelle auf oberster Ebene zurück, ohne function data(). Alle 26 im Basisspiel enden mit so einem return. .metacon.lua-Dateien gibt es im Spiel nicht, schreib diese Dateien also in Teal.

airfield.tl gibt pro Epoche eine Beschreibungstabelle mit createTemplateScript = { fileName = "airfield.script@createTemplateFn", params = { } } zurück. Die Einträge der Lager funktionieren genauso über warehouse.makeTemplate.

Die ältere Form ist eine Liste constructionTemplates in der .con.lua. Jeder Eintrag ist ein ConstructionTemplate mit type = "DYNAMIC", availability, description, data.params und menuCategory. ConstructionDesc markiert das Feld mit „[DEPRECATED] Use metaconstructions instead“, trotzdem nutzen es noch 11 Basisdateien, darunter der Bahnhof (modular_station.con.lua), die Depots und die modulare Straßenhaltestelle. Diese .con.lua-Dateien setzen außerdem selbst ein createTemplateScript, ebenso Industrien wie forest.con.lua. 68 .con.lua-Dateien des Basisspiels haben eines.

Die Wiki-Seite Construction Templates vermischt beide Formen. Sie sagt, Vorlagen würden in metacon.lua-Dateien definiert, und zeigt sie als Liste von Einträgen mit type = "DYNAMIC". Im Basisspiel heißen die Dateien .metacon.tl, und jede gibt eine einzelne Beschreibungstabelle ohne type zurück. Die Liste mit type = "DYNAMIC" hat die Form der veralteten constructionTemplates.

createTemplateFn

createTemplateFn(captureParams, params) legt fest, welche Konstruktion mit welchen Modulen gebaut wird. Laut Wiki enthält params die Werte paramX und paramY (die Hotkeys), einen seed, der mit jeder Platzierung oder jedem Vorschlag steigt, year, templateIndex (welche Vorlage benutzt wurde) und die eigenen Parameter der Vorlage. Der Flugplatz, aus base/content/stations/air/air/airfield.script.lua:

createTemplateFn = function(captureParams, params)
    local result = {}
    local hangar = params.hangar == 1
    local terminals = params.terminals and params.terminals or 1

    if hangar then 
        result[terminalSlotId       + 0 ] = "airfield/af_hangar.module"
    end
    local offset = hangar and 2 or 0

    result[terminalSlotId + offset  ] = "airfield/af_main.module"
    result[terminalSlotId     + offset + 2 ] = "airfield/af_terminal.module"
    -- ... weitere Terminals für params.terminals > 1 ...

    return {
        constructions = {
            {
                modules = result,
                constructionFileName = "airfield.con",
            },
        }
    }
end,

Jeder Eintrag von constructions hat modules (ordnet Slot-IDs Moduldateien zu; {} für eine Konstruktion ohne Module) und constructionFileName, optional außerdem transf (Versatz zum Ursprung der Vorlage, standardmäßig keiner) und params (Parameter für die Konstruktion, standardmäßig die params der Vorlage). Das createTemplateFn des Lagers in warehouse.script.lua setzt ein Modul aus seinen captureParams in einen Slot:

result[EncodeSlotId({x = 0, y = 3}, "warehouse_large")] = captureParams.initialModule

Eine minimale modulare Konstruktion

Die modularen Konstruktionen des Basisspiels sind groß (modular_terminal.script.lua hat über 3.000 Zeilen). Die Dateien unten ergeben zusammen die kleinste funktionierende Form: eine Konstruktion mit einem Slot, ein Modul für diesen Slot und eine Metakonstruktion, die die Konstruktion mit bereits platziertem Modul ins Baumenü bringt. Sie folgen dem Lager (base/content/warehouses/warehouses/), das seine Dateien genauso aufteilt. Die Mod-ID jdoe_yard ist ausgedacht, und das Beispiel wurde im Spiel nicht getestet. Alle fünf Dateien liegen in einem Ordner, content/jdoe_yard/, damit die relativen Namen zwischen ihnen so funktionieren wie im Ordner des Lagers.

yard.con.lua hat keinen eigenen Menüeintrag und nennt nur die Update-Funktion:

function data()
    return {
        order = 1000,
        params = {},
        updateScript = {
            fileName = "yard.script@updateFn",
            params = {},
        },
    }
end

yard.script.lua baut die Grundlage: eine Unterkonstruktion mit einem Modell, einer Bodenfläche und Geländeanpassung sowie einen Slot eines neuen Typs. Auf Bodenfläche und Geländeanpassung kommt es an: Eine Konstruktion mit einer einzigen Unterkonstruktion, die nur Modelle enthält, wird als lose Modelle platziert, und die nehmen keine Module auf (siehe Konstruktionsarten). createTemplateFn nennt das Modul für Slot 1:

local transf = require "::/scripts/mat4.tl"
local vec3 = require "::/scripts/vec3.tl"

local SHED_SLOT = 1
local area = { { -12, -6, 0 }, { 12, -6, 0 }, { 12, 6, 0 }, { -12, 6, 0 } }

function data()
return {
    createTemplateFn = function(captureParams, params)
        return {
            constructions = {
                {
                    modules = { [SHED_SLOT] = "yard_shed.module" },
                    constructionFileName = "yard.con",
                },
            }
        }
    end,
    updateFn = function(captureParams, params)
        return {
            subconstructions = {
                {
                    tag = 1,
                    models = {
                        { id = "::/assets/buildings/ind/aeration/aeration_06.mdl", transf = transf.transl(vec3.new(-8, 0, 0)) },
                    },
                    groundFaces = {
                        { face = area, modes = { { type = "FILL", key = "::/warehouses/wh_universal/wh_universal.gtex" } } },
                    },
                    terrainAlignmentLists = {
                        { type = "EQUAL", faces = { area } },
                    },
                },
            },
            slots = {
                {
                    id = SHED_SLOT,
                    type = "jdoe_yard_shed",
                    transf = transf.transl(vec3.new(5, 0, 0)),
                    spacing = { -5, 5, -5, 5 },
                    height = 6,
                },
            },
            cost = 50000,
        }
    end,
}
end

yard_shed.module.lua deklariert das Modul. Sein type passt zum Slot, menuCategory gibt ihm einen Tab im Modulmenü, und metadata.price wird zu den Kosten der Konstruktion addiert:

function data()
return {
    menuCategory = {
        categories = {
            { category = "modules_misc", order = 100, },
        },
    },
    description = {
        name = _("JDOE_YARD_SHED_NAME"),
        description = _("JDOE_YARD_SHED_DESCRIPTION"),
        icon = "yard_shed.tga",
    },
    availability = { yearFrom = 0, yearTo = 0, },
    type = "jdoe_yard_shed",
    metadata = {
        price = 20000,
        maintenanceCost = 2000,
    },
    updateScript = {
        fileName = "yard_modules.script@shed.updateFn",
        params = {},
    },
    getModelsScript = {
        fileName = "yard_modules.script@shed.getModelsFn",
        params = {},
    },
    builderAudioRes = "::/gui/construction/sound/buildoze_module.builder_audio",
}
end

yard_modules.script.lua platziert das Modell des Moduls mit addModelFn. Die Funktion multipliziert die Transformation mit der des Slots und setzt den Tag des Moduls. Gib die Modellliste der Unterkonstruktion als viertes Argument mit. Sonst hängt addModelFn an result.models an, eine Liste, die das Ergebnis der Konstruktion nicht hat.

local transf = require "::/scripts/mat4.tl"
local vec3 = require "::/scripts/vec3.tl"

local shedModel = "::/warehouses/wh_universal/wh_universal_shack.mdl"

function data()
return {
    shed = {
        updateFn = function(captureParams, result, transform, tag, slotId, addModelFn, params)
            addModelFn(shedModel, transf.transl(vec3.new(0, 0, 0)), tag, result.subconstructions[1].models)
        end,
        getModelsFn = function(captureParams)
            return { { id = shedModel, transf = transf.transl(vec3.new(0, 0, 0)) } }
        end,
    },
}
end

yard.metacon.tl ist der Eintrag im Baumenü. Die Datei gibt ihre Tabelle auf oberster Ebene zurück und verweist auf createTemplateFn:

local desc : MetaConstructionDesc = {
    description = {
        name = _("JDOE_YARD_NAME"),
        description = _("JDOE_YARD_DESCRIPTION"),
        icon = resolve("yard.tga"),
    },
    availability = { yearFrom = 0, yearTo = 0 },
    menuCategory = {
        categories = {
            { category = "road_buildings", order = 9000 },
        },
    },
    order = 9000,
    params = {},
    createTemplateScript = {
        fileName = "yard.script@createTemplateFn",
        params = {},
    },
}

return desc

Die beiden .tga-Icons und die Übersetzungsschlüssel (siehe Übersetzungen) musst du selbst ergänzen. Die Modelle und die Bodentextur sind Dateien des Basisspiels und werden mit ::/ angesprochen.

Das ausgelieferte Beispiel für einen Mod, der einer Basiskonstruktion ein Modul hinzufügt, ist dlcs/urbangames_deluxe_upgrade_pack/content/landmarks/landmarks/hq/modules/hq_maze.module.lua. Es bringt kein eigenes Skript mit. Es nutzt den Slot-Typ hq_fountain_l des Hauptquartiers und das Modulskript des Basisspiels und übergibt diesem Skript seine generierten Daten für Modelle und Bodentexturen über captureParams:

type = "hq_fountain_l",
-- ...
updateScript = {
    fileName = "::/landmarks/hq/headquarter_addon.script@updateFn",
    params = {
        generatedData = generatedData
    }
},

Pfade in generatedData tragen die ID des DLCs ("urbangames_deluxe_upgrade_pack::/landmarks/hq/modules/hq_maze.mdl"). Vermutlich löst das Basisskript sie auf, und ein Pfad ohne die ID würde ins Basisspiel zeigen.

Offizielles Wiki: Modular Constructions, Construction Templates, Construction Basics, Construction Menu.