Aufbau eines Mods¶
Diese Seite nimmt die Mods auseinander, die mit dem Spiel kommen: die Skript-Mods in mods/release/, die acht Kampagnenmissionen und die beiden DLC-Pakete in dlcs/. Das Basisspiel in base/ ist genauso aufgebaut. Wo das offizielle Modding-Wiki eine Datei beschreibt, folgt diese Seite dem Wiki und nennt die Stellen, an denen die ausgelieferten Dateien davon abweichen.
Ordnerstruktur¶
Die Seite zur Mod-Definition im Wiki zeigt, welche Dateien ein Mod haben kann:
my_mod/
├── mod.json technische Definition: ID, Revision, Hooks, Flags (Pflicht)
├── strings.json Übersetzungen der eigenen Texte des Mods (optional)
├── _metadata/
│ ├── modinfo.json Name, Kurzbeschreibung, Beschreibung, Autoren, Tags (Pflicht)
│ ├── description.html Beschreibung für den Mod-Browser und mod.io (optional)
│ ├── 0.png, 1.png, ... Titelbild (0.png) und Galeriebilder, 1920 x 1080 (optional)
│ └── mod.io_fileid.txt schreibt das Spiel nach dem ersten Hochladen
└── content/ alles, was das Spiel lädt
└── mod.script.tl Skriptfunktionen für pre/run/post (optional)
Innerhalb von content/ unterscheidet das Spiel Ressourcen an der Dateiendung, nicht am Ordner (resource types). Das Wiki empfiehlt trotzdem die Ordnerstruktur des Basisspiels: einen Unterordner pro Asset und einen Ordner shared für Dateien, die mehrere Assets verwenden (guidelines).
Ein vollständiger Mod aus den Spieldateien, mods/release/urbangames_campaign_mission_01/ (Archive entpackt, Inhalt gekürzt):
urbangames_campaign_mission_01/
├── mod.json Mod-ID, Mod-Skript-Hooks, Flags
├── _metadata/modinfo.json Name, Beschreibung, Autoren, Tags
├── _content.json Liste der Inhaltsarchive und losen Dateien
├── texture_metadata_cache.lua Größe und Mipmap-Daten pro Textur
├── tealdef/ Teal-Typdefinitionen dieses Mods
│ ├── urbangames_campaign_mission_01_def.d.tl
│ └── mission/mission_params_01.d.tl
└── content/ alles, was das Spiel lädt
├── mod.script.tl Skriptfunktionen für pre/run/post
├── info.mission.lua Missionsbeschreibung
├── animal.zip assets.zip audio.zip cargos.zip clima.zip
├── gui.zip guide_system.zip industries.zip init.zip
├── mission.zip terrain.zip vehicle.zip voiceover.zip
Nicht jeder Mod hat alle Teile:
| Mod | Inhalt |
|---|---|
urbangames_no_costs, urbangames_sandbox, urbangames_tycoon |
mod.json, _metadata/modinfo.json, content/mod.script.tl |
urbangames_vehicles_no_end_year |
dasselbe, mit content/mod.script.lua in Lua |
urbangames_campaign |
mod.json, _metadata, tealdef, content/info.campaign.lua |
urbangames_campaign_mission_01 … _08 |
vollständige Struktur wie oben |
dlcs/urbangames_preorder_pack |
mod.json, _metadata, Inhaltsarchive animal, landmarks, vehicle |
dlcs/urbangames_deluxe_upgrade_pack |
dasselbe, dazu tealdef/fun_elements/balloon.d.tl und die Archive characters, fun_elements, music |
Die DLC-Pakete sind wie Mods gebaut. Sie haben keine Mod-Skript-Hooks und unterscheiden sich nur in den Flags ("autoActivate": true, "cosmetic": true, "severityRemove": "Warning").
mod.json¶
mod.json beschreibt den Mod technisch. Diese Schlüssel kommen in den ausgelieferten Dateien vor:
| Schlüssel | Werte in den ausgelieferten Mods | Bedeutung |
|---|---|---|
modId |
"urbangames_no_costs_1", "urbangames_campaign_mission_01", "urbangames_deluxe_upgrade_pack" |
Kennung, nur a-z, 0-9 und _. Skriptverweise stellen sie als Präfix voran: urbangames_no_costs_1::/mod.script@preRunFn. Im Basisspiel ist modId in base/mod.json leer. |
revision |
1 |
Version des Mods, steht auf der Detailseite des Mods. Erhöhe sie bei jedem veröffentlichten Update. |
preRunScript, runScript, postRunScript |
{ "fileName": "<modId>::/mod.script@<fn>" } oder "" |
Skriptfunktionen, die das Spiel beim Laden aufruft, siehe Mod-Skript-Hooks |
dependencies |
immer null |
Mods, die dieser Mod braucht, siehe Abhängigkeiten |
incompatibilities |
immer null |
Mods, mit denen dieser Mod nicht laufen kann |
params |
null, außer in base/mod.json |
Parameter, die der Spieler für ein Spiel einstellt, siehe Mod-Parameter |
options |
immer null |
Weder in den Definitionen noch im Wiki beschrieben |
severityAdd, severityRemove |
"None", "Warning" |
Warnstufe, wenn der Mod einem Spielstand hinzugefügt oder daraus entfernt wird: None, Warning oder Critical. Ohne Angabe gelten None (Hinzufügen) und Warning (Entfernen). |
cosmetic |
true bei Kampagne, Missionen und DLCs |
Erfolge bleiben erreichbar, solange alle aktiven Mods kosmetisch sind. Lass den Wert auf false, wenn der Mod die Simulation verändert. |
visible |
false bei den Kampagnenmissionen |
false blendet den Mod in der Mod-Liste aus. Standard ist true. Ein ausgeblendeter Mod kann sich nicht automatisch aktivieren. |
autoActivate |
true bei den DLCs |
„If the mod is automatically activate by the game“ |
url |
"" in base/mod.json |
„URL to the mod page“ |
Die drei Warnstufen beschreibt das Wiki so (mod.json): None, wenn Hinzufügen oder Entfernen nichts schadet, etwa bei einer rein optischen Änderung. Warning, wenn der Spielstand etwas verliert, zum Beispiel Fahrzeugmodelle, aber stabil bleibt. Critical, wenn er kaputtgehen kann, etwa weil Gütertypen fehlen. Die technischen Anforderungen verlangen ein korrekt gesetztes severityRemove.
Laut Wiki hat die modId anders als in den früheren Transport-Fever-Spielen kein Suffix für die Hauptversion wie _3. Die ausgelieferten Skript-Mods enden trotzdem auf _1 (urbangames_no_costs_1, urbangames_sandbox_1, urbangames_tycoon_1, urbangames_vehicles_no_end_year_1), ihre Ordner, die Kampagne und die DLCs dagegen haben kein Suffix. Für ein Update, das Spielstände unbrauchbar macht, empfiehlt das Wiki eine neue modId und einen eigenen Mod statt einer höheren revision.
Das Spiel findet einen Mod über seine modId, egal wo er installiert ist. Ist dieselbe ID zweimal installiert, gewinnt der Staging-Bereich vor einer manuellen Installation und diese vor einem Abo über mod.io.
Die zitierten Beschreibungen stammen aus Mod.ModDesc in api/tealdef/api/type/mod.d.tl. Diesen Typ verwendet das Spiel für eine geladene Mod-Beschreibung. In ModDesc sind severityAdd und severityRemove Ganzzahlen („0: None, 1: Warning, 2:Critical“), in den JSON-Dateien stehen die Namen. Die installierten Mods liest das Spiel über ModRep (app.getUserProfile():getModRep() in base/content/gui/gui/menu/mod_util.tl), dessen getModDesc diesen Typ zurückgibt.
Abhängigkeiten¶
Kein ausgelieferter Mod gibt eine Abhängigkeit an. Das Wiki (dependencies and incompatibilities) zeigt die JSON-Form. Ein Repaint, der den Mod mit dem Originalfahrzeug braucht, würde Folgendes angeben:
{
"dependencies": [
{
"mod": { "modId": "jdoe_base_vehicles", "revisionMin": 2, "revisionMax": -1 },
"modInfo": { "displayName": "Base Vehicles", "url": "https://example.com/base-vehicles" },
"loadBefore": true,
"optional": false
}
],
"incompatibilities": [
{ "mod": { "modId": "jdoe_hard_mode" } }
]
}
| Feld | Bedeutung | Standard |
|---|---|---|
mod.modId |
modId aus der mod.json des anderen Mods |
Pflicht |
mod.revisionMin |
niedrigste akzeptierte Revision | keine (-1 oder weggelassen) |
mod.revisionMax |
höchste akzeptierte Revision; selten nötig, weil Updates kompatibel bleiben sollten | keine (-1 oder weggelassen) |
modInfo.displayName |
Name im Fenster „Mod-Abhängigkeiten nicht erfüllt“ (Mod Dependencies Not Satisfied), wenn die Abhängigkeit nicht installiert ist | |
modInfo.url |
optionaler Download-Link, zum Beispiel auf einer fremden Seite | |
loadBefore |
die Abhängigkeit vor diesem Mod laden | true |
optional |
empfohlen, nicht erforderlich | false |
Eine Inkompatibilität besteht nur aus dem Teil mod. Beides zeigt das Spiel an, während der Spieler Mods aktiviert. Es aktiviert Abhängigkeiten zuerst, bietet fehlende zum Herunterladen an und warnt, bevor ein Spiel mit fehlender Abhängigkeit oder mit einem aktiven inkompatiblen Mod startet.
Die Typen passen dazu: Mod.ModDependency hat mod, optional, loadBefore und modInfo. Mod.ModRef enthält modId, revisionMin und revisionMax, Mod.ModRefInfo enthält displayName und url. Eine Inkompatibilität (Mod.ModIncompatibility) ist nur ein ModRef. Die Mod-Auswahl in base/content/gui/gui/menu/mod_util.tl prüft diese Felder: Sie meldet Abhängigkeiten als fehlend mit dem Grund "NotFound" oder "IncorrectVersion", berücksichtigt optional und loadBefore und listet aktive inkompatible Mods auf.
_metadata/modinfo.json¶
Was der Spieler über den Mod zu sehen bekommt, steht in einer eigenen Datei. Aus mods/release/urbangames_campaign_mission_01/_metadata/modinfo.json:
{
"authors": [
{
"name": "Urban Games",
"role": "CREATOR"
}
],
"description": "...",
"name": "Campaign Mission 01",
"tags": [
"Campaign"
],
"url": ""
}
Die Skript-Mods und DLCs tragen als Werte Übersetzungsschlüssel wie "MOD_NO_COSTS_NAME" ein. Deren Übersetzungen kommen mit den Texten des Spiels (siehe Übersetzte Texte). Ein eigener Mod bringt seine Übersetzungen in derselben Datei mit, wie der Abschnitt zu modinfo.json im Wiki zeigt:
{
"name": "Soft Brakes",
"summary": "Trains brake more gently.",
"description": "Lowers the global train brake deceleration.",
"localization": {
"de": {
"name": "Sanfte Bremsen",
"summary": "Züge bremsen sanfter.",
"description": "Senkt die globale Bremsverzögerung von Zügen."
}
},
"authors": [ { "name": "jdoe", "role": "CREATOR" } ],
"tags": [ "Script Mod" ],
"dependencies": [ "5499706" ],
"url": "https://example.com/soft-brakes"
}
| Schlüssel | Regeln |
|---|---|
name |
Anzeigename, höchstens 32 Zeichen, ohne Zeilenumbruch. Die Richtlinien verlangen englische Titelschreibung (Title Case). |
summary |
Tooltip auf der Kachel des Mods im „Mod-Hub“ (Mod Hub), höchstens 100 Zeichen, ohne Zeilenumbruch |
description |
Langer Text mit Zeilenumbrüchen. Wird ignoriert, wenn _metadata/description.html existiert. Muss auf Englisch sein. |
localization |
name, summary und description pro Sprachcode; die Werte auf oberster Ebene gelten für alle anderen Sprachen |
authors |
Liste aus name und role; die ausgelieferten Dateien verwenden nur "CREATOR" |
tags |
offiziell unterstützte Tags, im „Mod-Hub“ als Filter genutzt ("Script Mod", "Campaign", "Town Building", "Depot" kommen in den Dateien und im Wiki vor) |
dependencies |
mod.io-IDs (keine modIds) der Mods, die dieser Mod braucht, damit mod.io sie verknüpft. Ein hier entfernter Eintrag bleibt auf mod.io bestehen; dort musst du ihn auf der Website löschen. |
url |
Link zu weiteren Informationen. Das Spiel zeigt ihn nicht an, und Konsolen können ihn nicht öffnen. Alles, was ein Spieler wissen muss, gehört deshalb in die Beschreibung. |
Beim Hochladen bekommt mod.io den englischen Namen und eine Beschreibung, in der alle Sprachen hintereinanderstehen, weil mod.io noch keine Texte pro Sprache kennt. Das Wiki erwartet eine Änderung bis zum 2. Quartal 2027. Jedes Hochladen überschreibt Name und Beschreibung auf mod.io mit den Werten aus modinfo.json.
Für den Mod-Hub gibt es einen eigenen Record, Modhub.ModInfo, mit title, summary, description, tags, logoImage, Zählern für Downloads und Bewertungen und Angaben zur Veröffentlichung. Das Wiki ordnet die Felder nicht einzeln zu. Es sagt nur, dass der englische Name als Titel hochgeladen wird und dass die Dateien _metadata/<Nummer>.png das Titelbild (0.png) und die Galeriebilder sind.
Mod-Skript-Hooks¶
Die Mod-Skript-Hooks sind die drei Schlüssel preRunScript, runScript und postRunScript in mod.json. Jeder nennt genau eine Funktion. Basisspiel und offizielle Mods setzen sie für unterschiedliche Aufgaben ein:
| Hook | Wann er läuft (Wiki) | Signatur in den ausgelieferten Skripten | Was die ausgelieferten Mods dort tun |
|---|---|---|---|
preRunScript |
bevor die Ressourcen von Spiel und Mods geladen werden | preRunFn(captureParams, configDict, allModParams, baseConfig) |
Die Basiskonfiguration (BaseConfig) ändern. urbangames_no_costs setzt baseConfig.noCosts = true; das Basisspiel füllt hier fast die ganze Konfiguration. |
runScript |
wenn der Mod geladen wird; registrierte Modifier und Filter wirken beim Laden der Ressourcen, nachdem alle runFn gelaufen sind |
runFn(captureParams, configDict, allModParams) (base/content/base/base/base_mod.lua; urbangames_vehicles_no_end_year nennt das zweite Argument settings und lässt das dritte weg, Mission 01 nimmt nur captureParams) |
Ressourcen-Modifier mit addModifier und Dateifilter mit addFileFilter registrieren. |
postRunScript |
einmal, nachdem alle Ressourcen geladen sind | postRunFn(captureParams, configDict, allModParams) |
Ressourcen über api.res bearbeiten, hinzufügen oder ausblenden. |
Jeder Hook läuft zuerst für das Basisspiel und dann für die Mods in der Reihenfolge ihrer Aktivierung (script functions). Abhängigkeiten mit loadBefore werden vor den Mods aktiviert, die sie brauchen. Das Wiki rät, Filter und Änderungen möglichst in postRunFn zu erledigen und Modifier in runFn nur für das zu verwenden, was dort nicht geht.
Die Argumente:
| Argument | Inhalt |
|---|---|
captureParams |
die Tabelle params des Skriptverweises (resource types); die Hook-Verweise in mod.json haben keine |
configDict |
das gewählte Klima, die Wirtschaft und die Namensliste |
allModParams |
Parameterwerte aller Mods, nach Mod-ID geordnet; allModParams[getCurrentModId()] liefert die des eigenen Mods (getCurrentModId) |
baseConfig |
die Basiskonfiguration, nur bei preRunFn |
Das Wiki führt die Hook-Parameter ohne captureParams auf. Jede ausgelieferte Funktion in mod.script nimmt es aber als ersten Parameter, und das entspricht der Regel des Wikis für Skriptverweise. Schreib deine Funktionen also mit diesem Parameter. Das Basisspiel liest seine eigenen Parameter aus allModParams[""] (base/content/base/base/base_mod.lua).
Ein Teal-Modul gibt eine Tabelle mit Funktionen zurück (urbangames_no_costs, zu sehen unter Erste Schritte). Eine Lua-Datei definiert data() und gibt die Tabelle daraus zurück (mods/release/urbangames_vehicles_no_end_year/content/mod.script.lua). Wie runFn Ressourcen-Modifier und Dateifilter registriert, samt der Liste der Kategorien, steht unter Ressourcen-Modifier und Dateifilter. Code zur Ladezeit zeigt, wofür die einzelnen Phasen da sind.
Eine postRunFn arbeitet mit den Repositorys in api.res. Aus mods/release/urbangames_campaign_mission_01/content/mod.script.tl:
mod.postRunFn = function(captureParams)
local baseConfig = api.res.getBaseConfig()
baseConfig.advancedOptions.subventionMode = 0
local model = api.res.modelRep.get(api.res.modelRep.find("::/vehicle/truck/horse_cart_medium/horse_cart_medium.mdl"))
local tvMetadata = model.metadata["transportVehicle"] as ModelMetadata.TransportVehicle
tvMetadata.loadSpeed = tvMetadata.loadSpeed * 10
-- ...
local allBriges = api.res.bridgeTypeRep.getAll()
for id, name in pairs(allBriges) do
if name ~= "::/infrastructure/bridge/trestle.bridge" then
api.res.bridgeTypeRep.setVisible(id, false)
end
end
end
ResTypeRep ist das Interface aller Repositorys: find(resName) gibt eine ID zurück (-1, wenn es die Ressource nicht gibt), get(id) die Ressource und getAll() eine Zuordnung von IDs zu Namen. setVisible blendet eine Ressource für den Spieler aus, add registriert eine neue. Die postRunFn des Basisspiels legt mit api.res.moduleRep.add für jeden Gleistyp ein Bahnhofsmodul an.
Zum Bearbeiten nehmen die Beispiele im Wiki die Tabellenform: getAsTable(id) gibt eine Lua-Tabelle zurück, die du beliebig ändern kannst, setAsTable(id, data) schreibt sie zurück, und addAsTable(name, data) fügt sie als neue Ressource hinzu. modelRep.forEachModelWithMetadata durchläuft alle Modelle mit einem bestimmten Metadaten-Schlüssel. Ein Kapazitätsfaktor aus einem Mod-Parameter:
mod.postRunFn = function(captureParams, configDict : {{string, string}}, allModParams : {string : {string : integer}})
local factor = allModParams[getCurrentModId()].capacityFactor
api.res.modelRep.forEachModelWithMetadata("transportVehicle", function(name : string)
local id = api.res.modelRep.find(name)
local model = api.res.modelRep.getAsTable(id)
local tv = model.metadata["transportVehicle"] as ModelMetadata.TransportVehicle
for _, compartment in ipairs(tv.compartments) do
for _, loadConfig in ipairs(compartment.loadConfigs) do
if loadConfig.cargoEntry and loadConfig.cargoEntry.capacity then
loadConfig.cargoEntry.capacity = loadConfig.cargoEntry.capacity * factor
end
end
end
api.res.modelRep.setAsTable(id, model)
end)
end
Das folgt dem Beispiel zur Ressourcenänderung im Wiki. Neue 3D-Daten können Skripte nicht anlegen: Ein so hinzugefügtes Modell kann über seinen modelPath nur auf vorhandene .mdl-Dateien zeigen.
Mod-Parameter¶
base/mod.json legt die Einstellungen für ein neues Spiel als params fest. Ein Eintrag:
{
"defaultIndex": 1,
"key": "advancedOptions.reforestation",
"name": "Reforestation",
"tooltip": "Enable or disable reforestation.\n\nIf reforestation is on, bald spaces after removing constructions will regrow their initial trees.",
"uiType": "Button",
"values": [
"Off",
"On"
],
"yearFrom": 0,
"yearTo": 0
}
Die Felder entsprechen ScriptParam und dem Abschnitt zu Mod-Parametern im Wiki:
| Feld | Bedeutung |
|---|---|
key |
Name, unter dem Skripte den Wert lesen |
name |
Beschriftung in der Oberfläche; ein Übersetzungsschlüssel, siehe Übersetzungen |
tooltip |
optionaler Text beim Darüberfahren mit der Maus; ein Übersetzungsschlüssel, siehe Übersetzungen |
uiType |
Button, Slider, ComboBox, IconButton oder CheckBox |
values |
Beschriftungen, bei IconButton Bildpfade; Beschriftungen sind Übersetzungsschlüssel, siehe Übersetzungen |
numbers |
optionale Werte, die die Skripte pro Option erhalten, einer pro Eintrag in values (script_param_util.tl verwirft die Liste, wenn die Anzahl nicht passt); Standard 1, 2, 3 usw. ScriptParam typisiert die Liste als {number}, die Hook-Signaturen typisieren die empfangenen Werte als integer, und kein ausgelieferter Parameter nutzt Kommazahlen. Prüf einen gebrochenen Wert also im Spiel. |
defaultIndex |
voreingestellte Option, ab 0 gezählt (anders als Lua-Indizes); Standard ist die erste Option |
"defaultIndex": 1 im Eintrag zur Wiederaufforstung wählt also "On". Ohne numbers bekommt ein Skript 1 für die erste Option, 2 für die zweite und so weiter. Der Spieler stellt die Werte in der Mod-Auswahl ein, wenn er ein neues Spiel startet oder einen Spielstand lädt. Konstruktionen haben eine ähnliche Parameterliste in Lua, dort beginnt defaultIndex bei 1 (siehe Parameter). Manche Einträge haben tags wie "desktop", "console" oder "experimentalMapFeatures", und derselbe Schlüssel kommt dann mehrmals mit unterschiedlichen Wertelisten je Tag vor. Die Skriptfunktionen bekommen die gewählten Werte in allModParams, einer Tabelle mit der Mod-ID als Schlüssel; das Basisspiel liest seine eigenen aus allModParams[""]. Zur Laufzeit lesen Game-Scripts sie mit api.engine.config.getModParams(), zum Beispiel api.engine.config.getModParams()[""]["isMapEditor"] in base/content/game_mechanics/game_mechanics/towns/towns.script.tl. Kein ausgeliefertes Game-Script liest die Parameter seines eigenen Mods. getOwningModId(), in der Game-Script-Datei selbst aufgerufen, müsste den Schlüssel für getModParams() liefern, aber keine ausgelieferte Datei bestätigt das.
Wie Inhalte verpackt sind¶
Zuordnung der Zip-Archive¶
Das Spiel liefert content/ als Satz von Zip-Archiven und losen Dateien aus. _content.json listet beides auf. Aus mods/release/urbangames_campaign_mission_01/_content.json (gekürzt):
{
"archives": [
{ "fileName": "animal.zip", "entries": [ { "fileName": "animal/alligator/alligator.mdl", ... } ] },
{ "fileName": "mission.zip", "entries": [ { "fileName": "mission/mission_story.tl", ... } ] }
],
"files": [ "info.mission.lua", "mod.script.tl", "mission01conlua.txt" ]
}
Die Namen der Einträge gelten relativ zu dem Ordner, in dem das Archiv liegt. mission.zip liegt direkt in content/ und enthält mission/mission_story.tl; das Spiel spricht die Datei als urbangames_campaign_mission_01::/mission/mission_story.tl an. Archivnamen können auch Ordner enthalten: Im Basisspiel hat assets/buildings/ind.zip den Eintrag ind/mat/assets_ind_1_body.mtl, der Pfad der Datei ist also /assets/buildings/ind/mat/assets_ind_1_body.mtl. Das Basisspiel hat 1.092 Archive, darunter assets/stations.zip und buildings/a/c1/1x1_01.zip, und lose Dateien wie industries/industry_workers.gs.lua. Im entpackten Verzeichnisbaum, aus dem diese Seiten zitieren, wurde jedes Archiv zu einem Ordner mit dem Namen der Zip-Datei. Daher kommen doppelte Pfade wie content/mission/mission/mission_story.tl.
Ein selbst geschriebener Mod braucht keine Archive: Im Wiki liegen die Dateien eines Mods lose in content/. Ob das Spiel die Archive und _content.json erzeugt, wenn es einen Mod aufbereitet („cooking“), steht weiter unten.
Dateiverweise im Inhalt¶
Die Regeln stehen im Wiki im Abschnitt zu Dateinamen und Verweisen. Ein Pfad mit / am Anfang ist absolut und beginnt im content/-Ordner des aktuellen Mods (bei Dateien des Basisspiels im Basisspiel). Ein Pfad ohne / am Anfang gilt relativ zur verweisenden Datei. Ein Präfix wählt einen anderen Mod:
| Form | Zeigt auf | Beispiel aus den Spieldateien |
|---|---|---|
/path |
absolut, im aktuellen Mod | ug_require "/mission/mission_params_01.tl" in Mission 01; ug_require "/scripts/vec3.tl" im Basisspiel |
path |
relativ zur aktuellen Datei, im aktuellen Mod | "small_stops.script@updateFn", resolve("small_new.mdl") |
::/path |
absolut, im Basisspiel | "::/gui/construction/construction_desc_hud_icons.script@configureStationHudIconsFn" |
::path |
relativ zur aktuellen Datei, aber in den Ordnern des Basisspiels | "::buildings/ind/mat/assets_ind_1_body.mtl" in /assets/instrument_box.mdl von Mission 04, also die Basisdatei /assets/buildings/ind/mat/assets_ind_1_body.mtl |
<modId>::/path |
absolut, im Mod <modId> |
"urbangames_campaign_mission_01::/mission/hud_replacement.script" |
<modId>::path |
relativ zur aktuellen Datei, im Mod <modId> |
"ug_wiki_example_mod::mod.script@preRunFn" aus dem Wiki, in mod.json |
Dateien des Basisspiels laden andere Basisdateien oft mit /..., weil / für sie das Basisspiel bedeutet. Wenn du eine solche Zeile in einen Mod übernimmst, schreib ::/...: Aus require "/scripts/mat4.tl" in einer Basisdatei wird in deinem Mod require "::/scripts/mat4.tl".
../ ist nicht erlaubt, eine Datei kann also nicht auf ihren übergeordneten Ordner verweisen. resolve(relativePath): „Resolves a file paths relative to the current file“.
Die ausgelieferten mod.json-Dateien schreiben die Hook-Verweise mit Schrägstrich (urbangames_no_costs_1::/mod.script@preRunFn), die Beispiele im Wiki ohne. Da mod.json im Wurzelordner des Mods liegt und mod.script direkt in content/, zeigen beide Formen auf dieselbe Datei.
Datei- und Ordnernamen dürfen nur die Kleinbuchstaben a-z, die Ziffern 0-9, _ und - enthalten, keine Leerzeichen, und sollten kurz bleiben. Textdateien (.lua, .tl, .json, .mdl, .msh usw.) sind in UTF-8 ohne Byte Order Mark gespeichert.
Skriptverweise zeigen auf eine Funktion: file.script@function. Die Endung .lua oder .tl fällt weg, und der Teil nach @ kann ein Pfad mit Punkten in die zurückgegebene Tabelle sein ("modules.script@bulk.updateFn" in base/content/warehouses/warehouses/wh_bulk.module.lua). ScriptRef beschreibt dasselbe Prinzip für eine ältere Schreibweise der Form models/model/animal/animal.flock.crane.
Code wird mit ug_require geladen: „Used instead of the regular lua require to include game related script“. Die Basisskripte casten das Ergebnis auf seinen Typ: local mathutil = ug_require "::/scripts/mathutil.lua" as MathUtil.
Die Lua-Dateien des Basisspiels laden Module mit einfachem require und denselben Pfaden, zum Beispiel require "/scripts/soundsetutil.lua" oder require "::/scripts/construction/constructionutil.lua". Beide Wege funktionieren, denn base/content/base/base/init.lua ersetzt require, sodass ein Pfad mit der Endung .lua oder .tl wie jeder andere Dateiverweis aufgelöst wird, und ug_require ruft nur dieses require auf. In Teal-Dateien nimmst du ug_require ... as Type: Nur ug_require ist in main.d.tl deklariert, und der Cast mit as ist Teal-Syntax, die in einer .lua-Datei fehlschlägt. Für Pfade ohne diese Endungen behält init.lua das ursprüngliche require.
Die Ordner in base/content¶
Die obersten Ordner von base/content und die Dateitypen, die dort üblich sind. Die Tabelle zur Ordnerstruktur im Wiki beschreibt dieselben Ordner, die Tabelle der Dateiendungen listet jeden Ressourcentyp nach Endung auf.
| Ordner | Typische Dateien |
|---|---|
animal, vehicle, characters |
.mdl-Modelle mit .mtl-Materialien, Animationen (.ani) |
assets, buildings |
Modelle für Assets und Stadtgebäude; Gebäude haben zusätzlich .con.lua |
stations, depots, industries, landmarks, warehouses |
Konstruktionen (.con.lua), Module (.module.lua), Metakonstruktionen (.metacon.tl) und ihre .script.lua/.script.tl |
infrastructure |
Straßen (.street.lua, .street_template.lua), Gleise, Brücken, Tunnel, Signale |
game_mechanics |
Game-Scripts (.gs.lua) für Städte, Unternehmen, Finanzen, Benachrichtigungen usw. (siehe Spielmechaniken) |
gui |
die Benutzeroberfläche in Teal, Stylesheets (.css.lua), GUI-Ressourcen (.gres.lua) |
mission |
Missions-Framework, Aufgaben, Spielführer |
scripts |
gemeinsame Bibliotheken (vec3, mat4, util, Hilfsfunktionen für Konstruktionen, react.d.tl) |
rendering |
Materialtypen (.mat.lua), Shader-Programme (.prog.lua), Techniken, Eigenschaften |
climates, terrain, economy, cargos, names, music, locale |
Klimas (.clima.lua), Geländematerialien, Wirtschaften (.eco.lua), Gütertypen, Namenslisten, Playlists, Sprachen |
Binärdateien (Meshes als .msh, Texturen als .dds und .tga, Sounds) stehen in _content.json, gehören aber nicht zu den entpackten Quellen, aus denen diese Anleitungen entstanden sind.
Generische Ressourcen¶
Konfiguration, die weder Modell noch Konstruktion ist, steckt oft in einer .res.lua-Datei mit type und einer Tabelle data. Aus base/content/game_mechanics/game_mechanics/game_time/game_time_config.res.lua:
Skripte lesen sie über api.res.genericRep, entweder nach Name (api.res.genericRep.find("::/game_mechanics/game_time/game_time_config.res")) oder nach Typ (api.res.genericRep.getAllOfType("notification")). Die 295 .res.lua-Dateien in den Spieldateien verwenden diese Typen (Pfade unterhalb von base/content/, sofern kein Mod genannt ist):
| Typ | Dateien | Beispiel | Was er hinzufügt | Siehe |
|---|---|---|---|---|
menu_filter_category |
75 | gui/gui/construction/menu/filter_categories/gravel.res.lua |
einen Filter im Baumenü | Das Baumenü |
menu_category |
49 | gui/gui/construction/menu/menu_categories/category_road_buildings.res.lua |
einen Reiter im Baumenü oder bei den Modulen | Das Baumenü |
react-plugin ::<ExtensionPoint> |
44 | gui/gui/entity_window/warehouse/warehouse_eow_stocks.res.lua |
ein GUI-Plugin an einem Erweiterungspunkt | GUI |
notification |
33 | game_mechanics/game_mechanics/notifications/types/stuck_vehicle.res.lua; Mission 05 und das Deluxe-DLC bringen eigene mit |
einen Benachrichtigungstyp | Benachrichtigungen |
construction_tool |
22 | gui/gui/construction/tools/asset_eraser_tool.res.lua |
ein Werkzeug im Baumenü | Werkzeuge im Baumenü |
company_exploration |
12 | game_mechanics/game_mechanics/company/explorations/exploration_coal.res.lua |
eine Prospektion als Bonus | Unternehmensboni |
stamp |
11 | gui/gui/construction/shared/tex/stamps/desert_hill1.res.lua |
eine Heightmap (.png plus Icon) für das Werkzeug „Gelände zerklüften“ im Baumenü, das nur der Karten-Editor und der Sandkastenmodus anbieten |
Geländestempel |
missiontask_boot |
8 | mission/mission/mission.res.lua in jeder Kampagnenmission |
startet eine Mission | Wie eine Mission startet |
guide_system_boot |
6 | mission/mission/guide_system/guide_system.res.lua; Missionen 01, 02, 03 und 07 |
startet einen Spielführer | Wie eine Mission startet |
game_script_config |
6 | game_mechanics/game_mechanics/game_time/game_time_config.res.lua |
Einstellungen, die ein Game-Script des Basisspiels nach Name liest | Die Game-Scripts des Basisspiels |
subvention |
5 | game_mechanics/game_mechanics/subventions/deliver_cargo/deliver_cargo.res.lua; Mission 06 fügt deliver_visitors hinzu |
einen Subventionstyp | Subventionen |
rename_scheme_component |
5 | gui/gui/line_vehicle_mgmt/auto_rename/scheme_components/scheme_component_town.res.lua |
einen Bestandteil eines automatisch erzeugten Namens, mit seiner renameFn |
keine |
react-replacement-config |
4 | mission/mission/hud_replacement.res.lua in Mission 01 |
ersetzt ein Recipe der Basis-GUI | Ein Basis-Recipe ersetzen |
firstStopToSend_scheme |
4 | gui/gui/line_vehicle_mgmt/first_stop_to_send/scheme_firststop.res.lua |
eine Regel, welche Haltestelle ein neues Linienfahrzeug zuerst anfährt | keine |
rename_scheme |
3 | gui/gui/line_vehicle_mgmt/auto_rename/schemes/scheme_firststop_laststop.res.lua |
ein Schema für automatische Namen (formatString = "{key1} - {key2}") |
keine |
drag_and_drop |
3 | gui/gui/line_vehicle_mgmt/dnd_train_composition.res.lua |
eine leere Markierung für etwas, das die GUI ziehen kann, wird nach Name gelesen | keine |
company_permit_key |
3 | game_mechanics/game_mechanics/company/permitKeys/hq.res.lua |
einen Genehmigungsschlüssel als Bonus | Unternehmensboni |
tutorial_boot |
1 | mission/mission/tutorial/mission.res.lua |
startet das Tutorial | Wie eine Mission startet |
game_config |
1 | infrastructure/traffic_light/traffic_light/traffic_light_config.res.lua |
trafficLightYearFrom, wird nach Name gelesen |
keine |
Nur notification, subvention, missiontask_boot, guide_system_boot und react-replacement-config kommen in einem ausgelieferten Mod oder DLC vor. Die übrigen Typen sammelt das Basisspiel mit getAllOfType ein (menu_category, menu_filter_category, react-plugin, construction_tool, company_exploration, company_permit_key, stamp, rename_scheme, rename_scheme_component, firstStopToSend_scheme, tutorial_boot). Eine Datei desselben Typs aus einem Mod müsste deshalb neben den Basisdateien auftauchen, aber kein ausgelieferter Mod bestätigt das. Ressourcen vom Typ game_script_config, game_config und drag_and_drop liest das Spiel über feste Namen, eine neue Datei dieser Typen wird also nicht berücksichtigt.
Platzhalter für fehlende Dateien¶
base/content/placeholders/placeholders/ enthält Ersatz für fehlende Inhalte: generic.con.lua (Name MISSING_CONSTRUCTION, dargestellt als Ausrufezeichen-Markierung /assets/markers/marker_exclamation.mdl), missing_signal.con.lua, missing_street_terminal.con.lua, missing_gtex.gtex.lua und je ein missing_*.mdl pro Modellart (missing_rail_vehicle.mdl, missing_road_vehicle.mdl, missing_animal.mdl, missing_person.mdl usw.). Verweist ein Spielstand oder eine Konstruktion auf eine Ressource, die es nicht mehr gibt, etwa weil ein Mod entfernt wurde, müsste das Spiel an ihrer Stelle einen dieser Platzhalter zeigen. Darauf deuten nur die Dateinamen und MISSING_CONSTRUCTION hin; im Spiel ist es nicht bestätigt. Die Basisskripte verwenden die Platzhalter auch direkt: base/content/scripts/scripts/construction/constructionutil.lua greift auf "::/placeholders/missing_generic.mdl" zurück, wenn eine Modellgruppe leer ist, und 48 Dateien nutzen /placeholders/generic.tga als Icon.
Übersetzte Texte¶
Texte in Skripten stehen in _(). Ein Mod übersetzt seine Schlüssel in strings.json im Wurzelordner des Mods, mit einer Tabelle pro Sprachcode. Die Funktionen für Übersetzungsschlüssel, das Format von strings.json, die Sprachcodes und die .lang.lua-Dateien erklärt Übersetzungen. Name, Kurzbeschreibung und Beschreibung des Mods werden dagegen in modinfo.json übersetzt (siehe oben).
Vom Spiel erzeugte Dateien¶
_content.json und texture_metadata_cache.lua (Texturgrößen und Anzahl der Mipmaps pro .dds) liegen in jedem ausgelieferten Mod. Die Modhub-Typen erwähnen das Aufbereiten („cooking“): Modhub.ModValidatorResult hat cookedPcPath und cookedConsolePath, und zu ModPublishHelper.publish heißt es „Validates and cooks the mod once more first and uploads the cooked folders“. Weder die Dateien noch das Wiki sagen, ob beim Aufbereiten diese beiden Dateien entstehen oder ob ein von Hand gebauter Mod sie braucht. Die Mod-Struktur im Wiki führt sie nicht auf. Ein Mod ohne sie ist also der dokumentierte Ausgangspunkt.
Die einzige Datei, die das Wiki als erzeugt bezeichnet, ist _metadata/mod.io_fileid.txt. Sie entsteht nach dem ersten Hochladen auf mod.io, enthält die mod.io-ID und verknüpft spätere Uploads mit demselben Eintrag (siehe Veröffentlichen).
Technische Anforderungen¶
Jeder Upload durchläuft eine automatische Prüfung für PC und Konsolen, und nur ein Mod, der sie besteht, wird veröffentlicht. Die technical requirements im Wiki:
| Bereich | Anforderung |
|---|---|
| Integrität | alle Ressourcen sind syntaktisch korrekt und laden ohne Fehler und Warnungen |
| Sicherheit | keine Batch-Dateien, Shell-Skripte oder andere ausführbare Dateien |
| Kodierung | Textdateien in UTF-8 ohne BOM |
| Größe | höchstens 500 MB entpackt pro Mod |
| Namen | Dateien und Ordner kleingeschrieben, ohne Leer- und Sonderzeichen |
| Ballast | keine Quelldateien (.psd, .fbx) und keine temporären Dateien |
| Meshes | Mesh-Blobs der niedrigsten LOD-Stufe höchstens 256 KB |
| Texturen | DDS mit Mipmaps, Größen als Zweierpotenz (512, 1024, 2048 usw.) |
| Skripte | nur die offizielle Skript-API |
| Audio | .wav mit 16-Bit-Samples bei 48.000 Hz |
| Spielstände | severityRemove korrekt gesetzt |
| Nur Konsolen | keine eigenen Shader, keine Eingriffe ins Kern-Gameplay, die die Leistung verschlechtern |
Die Richtlinien nennen außerdem Zielwerte für Inhalte, zum Beispiel 20.000 bis 50.000 Dreiecke für ein typisches Fahrzeug (die größten haben bis zu 100.000), eine Texeldichte von 128 bis 170 Pixeln pro Meter und 1 bis 3 LOD-Stufen. Diese Werte gehören auf die Seite Modelle und Assets.
Offizielles Wiki: Mod definition, Mod parameters and scripts, Resource types and structure, Syntax, Resource modifiers and file filters, Localizations, Guidelines and requirements, Best practices, Publish a mod.