Zum Inhalt

Güter, Namen, Personen und Sounds

Manche Inhaltstypen sind kleine Konfigurationsdateien ohne eigenes Modell und ohne eigene Konstruktion: Güterarten, Sprachen, Namenslisten, Figurenmodelle, Wiedergabelisten für Musik und Soundsets. Diese Seite zeigt zu jedem Typ eine Datei aus dem Basisspiel und verlinkt die offizielle Wiki-Seite dazu.

Die Pfade unten stammen aus dem entpackten Basisspiel. Dort wurde jedes Inhaltsarchiv zu einem gleichnamigen Ordner, deshalb kommen Ordnernamen doppelt vor, etwa base/content/cargos/iron_ore/iron_ore/iron_ore.cargo.lua. Das Spiel selbst spricht diese Datei als /cargos/iron_ore/iron_ore.cargo an (siehe Aufbau eines Mods). Ein Mod legt dieselben Dateien in seinen eigenen content/-Ordner.

Inhalt Dateityp Ordner im Basisspiel Wiki-Seite
Güterarten .cargo.lua content/cargos/<cargo>/ Cargos
Güterklassen .cargoclass.lua content/cargos/classes/ Cargos
Gütermodellformate .cmf.lua content/cargos/formats/ Cargos
Sprachen .lang.lua content/locale/ Localizations
Übersetzungen eines Mods strings.json neben mod.json Mod definition
Namenssets .names.lua content/names/<set>/ Names
Figuren .mdl mit metadata.person content/characters/ People
Wiedergabelisten .plist.lua content/music/<playlist>/ Playlists
Soundsets .snd.lua sound/-Ordner neben den Modellen Sound sets

Wie andere Ressourcendateien ist jede davon eine Lua-Datei mit einer Funktion data(), die eine Tabelle zurückgibt. Anzeigenamen stehen in _(), damit sie übersetzt werden können (siehe Übersetzungen).

Güter

Güter sind in drei Ebenen aufgebaut. Eine Güterart ist eine einzelne Ware, zum Beispiel Eisenerz. Güterklassen fassen Arten mit gemeinsamen Eigenschaften zusammen, etwa BULK. Gütermodellformate sind genormte Begrenzungsquader. Mit ihnen zeigt ein Fahrzeug oder Lager für jede Ladung das passende Modell.

Güterarten

Das Basisspiel hat 37 Güterarten, jede in einem eigenen Ordner unter base/content/cargos/. Aus base/content/cargos/iron_ore/iron_ore/iron_ore.cargo.lua:

function data()
return
    {
        name = _("Iron Ore"),
        weightFactor = 1.0,
        order = 210,
        icon = "iron_ore.tga",
        cargoClasses = { "UNIVERSAL", "BULK" },
        categoryList = {
            categories = {
                "temperate.eco",
                "dry.eco",
                "tropic.eco",
                "subarctic.eco"
            }
        },
        scriptRef = {
            fileName = "::/economy/cargotype.script@incomeFactors.constantFn",
            params = {
                initialPrice = 4,
                finalPrice = 3.2,
                decayRate = 0.005,
                decayMod = 1,
                decayStart = 200,
            }
        },
        color = { 0.94, 0.53, 0.31},
        loadSpeedFactor = 0.0625,
        timeToDeliverInSeconds = 4500,
    }
end
Feld Bedeutung (Wiki)
name Anzeigename, übersetzbar.
weightFactor Dichte. 1,0 für schwere Güter, weniger für leichte; Passagiere haben 1/6.
order Position in Güterlisten.
icon Gütersymbol. Symbole sind in doppelter Auflösung (@2x.tga) 50 px hoch, flach und schwarz umrandet.
cargoClasses Klassen-Tags, zu denen die Art gehört.
categoryList.categories Wirtschaften (.eco), in denen es die Art gibt, siehe Wirtschaften.
scriptRef Einnahmefunktion und ihre params (siehe unten).
color RGB-Farbe (0 bis 1) für Diagramme mit mehreren Güterarten.
loadSpeedFactor Ladegeschwindigkeit pro Einheit. Passagiere haben 1, andere Güter 0,0625.
timeToDeliverInSeconds Wie lange die Lieferung dauern darf, bevor die Kunden unzufrieden werden.

Das Eisenerz-Beispiel im Wiki hat initialPrice = 3.5 und finalPrice = 2.9, die mitgelieferte Datei oben 4 und 3,2. Die Wiki-Zahlen sind also ein älterer Stand der Balance. Die Basisdateien verwenden außerdem zwei Felder, die das Wiki nicht beschreibt: category (ein kleingeschriebener Schlüssel wie "coal" oder "passengers", in 27 der 37 Arten) und id (nur in passengers.cargo.lua mit "PASSENGERS" und in rubber.cargo.lua mit "RUBBER"). Was sie bewirken, ist nicht dokumentiert. Die Wirtschaften und Klimazonen hinter categoryList beschreibt ModelMetadata.CategoryList, den Inhalt einer Wirtschaftsdatei die Seite Spielmechaniken.

Skripte finden geladene Güterarten in api.res.cargoTypeRep (siehe Repositorys). find erwartet den Ressourcennamen, also den Spielpfad ohne .lua, wie in api.res.cargoTypeRep.find("::/cargos/fish/fish.cargo") aus base/content/mission/mission/tutorial/tutorial_story_branch_b.tl. Für einen unbekannten Namen kommt eine negative ID zurück. getName macht aus einer ID wieder diesen Namen, und getPassengerCargoTypeId() liefert die ID der Passagiere.

Die Einnahmefunktion

scriptRef.fileName zeigt in der Form file@path.to.function auf eine Funktion. Laut Wiki ruft das Spiel sie mit der Tabelle params und mit timeInMs auf, der Zeit zwischen Abholung und Abgabe. Den zurückgegebenen Faktor multipliziert es mit der Luftlinie zwischen Abhol- und Abgabeort. Liegt der Abgabeort höher, kommt das Achtfache des Höhenunterschieds zur Strecke hinzu, und der Schwierigkeitsgrad skaliert das Ergebnis.

Alle Güterarten des Basisspiels außer den Passagieren verwenden incomeFactors.constantFn aus base/content/economy/economy/cargotype.script.lua. Diese Funktion beachtet die Zeit gar nicht:

constantFn = function(captureParams, timeInMs)
    if captureParams.initialPrice and captureParams.finalPrice then
        return captureParams.initialPrice + (captureParams.finalPrice - captureParams.initialPrice) * 0.25
    else
        return 6
    end
end,

Für Eisenerz ergibt das 4 + (3,2 − 4) × 0,25 = 3,8 pro Entfernungseinheit, egal wie lange die Fahrt dauert. decayRate, decayMod und decayStart wirken nur in logisticDecayFn aus derselben Datei, und die nutzt keine Güterart des Basisspiels. Passagiere verwenden passengersFn, die ebenfalls einen festen Faktor liefert. In der Datei stehen noch stoneFn, constructionMaterialsFn und baseFactorFn. Soll eine Güterart mit der Zeit an Wert verlieren, lass scriptRef auf logisticDecayFn zeigen oder auf eine eigene Funktion mit denselben zwei Parametern.

Güterklassen

Die sechs Klassen liegen in base/content/cargos/classes/classes/. Aus bulk.cargoclass.lua:

function data()
    return {
        tag = "BULK",
        name = _("Bulk"),
        description = "",
        icon = "bulk.tga",
        order = 3,
        color = { 254/255, 202/255, 23/255 },
    }
end

tag ist der Schlüssel, mit dem andere Dateien die Klasse ansprechen (nur Großbuchstaben A-Z). color färbt die Klassen-Labels im „Fahrzeugladen“ (Vehicle Store) und im Baumenü. Laut Wiki werden description und icon bisher nirgends angezeigt. Die Basisdatei lässt description leer, wo das Wiki-Beispiel einen übersetzten Text hat.

Klasse Verwendung (Wiki)
UNIVERSAL Jede normale Güterart. Arten dieser Klasse brauchen Modelle für die Formate BIG und SMALL.
PASSENGERS Nur Passagiere.
GOODS Stückgut, das in gedeckten Güterwagen fährt.
FLATBED Große, schwere Ladung auf flachen Fahrzeugen.
BULK Geschüttete Ladung wie Stein, Erz und Kohle in offenen Fahrzeugen mit hohen Bordwänden. Braucht die Bulk-Formate.
LIQUID Flüssigkeiten in Kesselwagen und Tanklastwagen. Braucht die Formate CIRCLE_*.

Mods dürfen Klassen hinzufügen. Urban Games bittet aber darum, vorher Kontakt aufzunehmen, damit die Mods untereinander kompatibel bleiben.

Gütermodellformate

Ein Format besteht aus einem tag und einer size in Metern, gemessen vom Wurzelknoten des Modells nach außen und nach oben. Aus base/content/cargos/formats/formats/rect_20x5.cmf.lua:

function data()
    return
        {
            tag = "RECT_20x5",
            size = { 20, 5, .3 }
        }
    end

Das Wiki hat eine Tabelle der Standardformate und der Klassen, die sie brauchen. Der Ordner im Basisspiel enthält 28 Formate und weicht an zwei Stellen von dieser Tabelle ab:

  • Das Wiki führt MEDIUM2x1 und MEDIUM4x1 mit 11,0 × 21,0 × 5,0 (genau wie BUNKER_11x21). In den Basisdateien steht size = { 3.37, 2.2, .3 } für medium2x1.cmf.lua und { 8.34, 2.8, .3 } für medium4x1.cmf.lua.
  • Fünf Formate des Basisspiels fehlen im Wiki: PILE_15x15, PILE_30x30, PILE_50x20, SQUARE_1_2 und SQUARE_1_7. Eisenerz bringt Modelle für SQUARE_1_2 und SQUARE_1_7 mit, Schüttgüter verwenden sie also.

Welche Formate ein Schüttgut in der Praxis braucht, zeigt ein Ordner aus dem Basisspiel: In base/content/cargos/iron_ore/iron_ore/ liegen iron_ore_big.mdl, iron_ore_small.mdl, iron_ore_bunker_11x21.mdl, elf Dateien iron_ore_rect_*.mdl und die beiden square-Modelle.

Güterart-Sets

Fahrzeuge, Stationen und Gütermodelle wählen ihre Güter mit einem CargoTypeSet. Das Wiki nennt die Reihenfolge, in der die vier Listen wirken: Die Menge beginnt leer, dann kommen alle Arten aus cargoClassesIncluded hinzu, die aus cargoClassesExcluded fallen weg, cargoTypesIncluded kommt hinzu und cargoTypesExcluded fällt weg. Klassen stehen als Tag in der Liste, Arten als Dateiname ohne .lua:

cargoTypeSet = {
    cargoClassesIncluded = { "PASSENGERS", },
    cargoClassesExcluded = { },
    cargoTypesIncluded = { },
    cargoTypesExcluded = { },
},

Gütermodelle

Ein Gütermodell ist ein gewöhnliches Asset-Modell mit einem Block cargoModel in den Metadaten (siehe Modelle und Assets). Aus base/content/cargos/iron_ore/iron_ore/iron_ore_big.mdl:

cargoModel = {
    cargoTypeSet = {
        cargoClassesExcluded = { },
        cargoClassesIncluded = { },
        cargoTypesExcluded = { },
        cargoTypesIncluded = { "iron_ore.cargo", },
    },
    formats = { "BIG", },
},

formats nennt die Formate, die das Modell ausfüllt. Ein optionales availability mit yearFrom und yearTo (ModelMetadata.Availability) begrenzt das Modell auf einen Zeitraum. Ladeanzeigen, die zufällige Gütermodelle auswählen, halten sich daran.

Übersetzungen

Übersetzter Text besteht aus zwei Teilen: den Schlüsseln in den Dateien und den Tabellen oder Katalogen, die aus Schlüsseln Text machen.

Schlüssel in Skriptdateien

Jeder Text, den der Spieler sieht, steht in _(): name = _("Iron Ore"). Das Argument ist entweder englischer Text (_("Iron Ore"), _("English")) oder ein Schlüssel (_("MISSION_08_NAME") in mods/release/urbangames_campaign_mission_08/content/info.mission.lua). Gibt es keine Übersetzung, zeigt das Spiel das Argument selbst an. Englischer Text als Schlüssel braucht deshalb keinen englischen Eintrag.

Für Pluralformen und Kontext gibt es nGetText, pGetText und npGetText. Kontext-Strings trennt <|ctx|>, wie in "map-size<|ctx|>Small" in base/mod.json. Zahlen und Prozentwerte formatieren LangUtil und api.util.lang passend zur eingestellten Sprache.

Übersetzungen in einem Mod: strings.json

Ein Mod übersetzt seine Schlüssel in einer strings.json neben der mod.json, mit einem Objekt pro Sprachcode. Das Beispiel aus dem Wiki:

{
    "en" : {
        "brake_intensity_name" : "Brake Intensity",
        "brake_intensity_tooltip" : "Adjust the global brake intensity."
    },
    "de" : {
        "brake_intensity_name" : "Bremsintensität",
        "brake_intensity_tooltip" : "Passen Sie die globale Bremsintensität an."
    }
}

Die Schlüssel im Wiki-Beispiel sind Name und Tooltip eines Parameters. Die strings.json ist also für name, tooltip und values deiner Mod-Parameter (params in mod.json) gedacht und ebenso für die _("...")-Schlüssel in deinen Modellen und Skripten. Kein mitgelieferter Mod hat eine strings.json, mit der sich das bestätigen ließe. Fehlt ein Schlüssel in der eingestellten Sprache, nimmt das Spiel den englischen Text, und fehlt auch der, zeigt es den Schlüssel. Weil keiner der offiziellen Mods in mods/release/ oder dlcs/ eine strings.json mitbringt, stehen ihre Texte MISSION_* und M8_* nicht in den Spieldateien, aus denen diese Dokumentation entsteht.

Zum Sprachcode: Laut Wiki legst du ein Objekt pro locale-Kennung an, also <language>_<region> wie de_CH, und das einfache de greift für alle deutschen Varianten ohne eigenes Objekt. Sein strings.json-Beispiel verwendet die Kurzcodes en und de, die mitgelieferten .lang.lua-Dateien dagegen regionale Codes wie de_DE und en_US (Tabelle unten). Für einen Mod sind Kurzcodes die sichere Wahl. Ein regionales Objekt brauchst du nur für Text, der abweicht, etwa für Schweizerdeutsch.

Sprachdateien

Eine Sprache selbst ist eine .lang.lua-Datei. Aus base/content/locale/locale/de.lang.lua:

function data()
return {
    name = "Deutsch",
    locale = "de_DE",
    fontMap = {
        regular = "Lato2OFL/Lato-Regular.ttf",
        bold = "Lato2OFL/Lato-Bold.ttf",
        light = "Lato2OFL/Lato-Light.ttf",
        medium = "Lato2OFL/Lato-Medium.ttf",
        monoRegular = "Noto/NotoSansMono-Regular.ttf",
        monoBold = "Noto/NotoSansMono-Bold.ttf",
    },
    unicodeFontMap = {
        regular = "Noto/NotoSansCJKsc-Regular.otf",
        bold = "Noto/NotoSansCJKsc-Bold.otf",
        monoRegular = "Noto/NotoSansMonoCJKsc-Regular.otf",
        monoBold = "Noto/NotoSansMonoCJKsc-Bold.otf",
        scaling = 0.9,
    },
}
end

name ist der Eintrag in der Einstellung „Sprache“ (Language) unter „Einstellungen“ > „Oberfläche“. locale ist der Code, mit dem Mods ihre Übersetzungen abgleichen und der das Zahlenformat bestimmt. Die beiden Font-Tabellen wählen die Schriften aus, die in base/content/locale/locale/Lato2OFL/ und Noto/ liegen.

Datei name locale
de.lang.lua Deutsch de_DE
en.lang.lua English en_US
es.lang.lua Español es_ES
fr.lang.lua Français fr_FR
it.lang.lua Italiano it
ja.lang.lua 日本語 ja_JP
ko.lang.lua 한국어 ko
nl.lang.lua Nederlands nl_NL
pl.lang.lua Polski pl_PL
pt_BR.lang.lua Português pt_BR
ru.lang.lua Pусский ru_RU
zh_CN.lang.lua 简体中文 zh_CN
zh_TW.lang.lua 繁體中文 zh_TW

Die .lang.lua-Datei enthält keinen Text. Laut Wiki liegen die Texte einer Sprache als kompilierte gettext-Kataloge in strings/<locale>/LC_MESSAGES/base.mo. Diese Dateien gehören nicht zu den hier verwendeten Quellen des Basisspiels. Für eine Übersetzung des Spiels in eine neue Sprache beschreibt das Wiki den üblichen gettext-Ablauf:

  1. Eine vorhandene base.mo in eine bearbeitbare .po-Datei umwandeln, mit msgunfmt (msgunfmt "xyz.mo" -o "xyz.po") oder der Python-Bibliothek polib.
  2. Die .po in einem Texteditor oder in Poedit übersetzen, nachdem du unter Catalogue > Properties die Zielsprache eingestellt hast. Platzhalter wie %1% oder {townName} bleiben stehen, und jede Pluralform muss ausgefüllt sein.
  3. Mit File > Compile to MO in Poedit wieder zu .mo kompilieren. (Das Wiki nennt auch für diesen Schritt msgunfmt, das gettext-Werkzeug zum Kompilieren heißt aber msgfmt.)
  4. Nach einem Spiel-Update neue Schlüssel mit Catalog > Update from POT file übernehmen.

Die Anleitung Missionen beschreibt Keyframe-Dateien pro Sprache für Untertitel und Sprachausgabe.

Namen

Ein Namensset liefert Personen-, Stadt- und Straßennamen, und der Spieler wählt eines unter „Namen“ (Names) in den Einstellungen für ein neues Spiel aus. Das Basisspiel hat 18 Sets in base/content/names/names/. Aus england/england.names.lua:

function data()
return
    {
        name = _("English"),
        personNamesScript = {
            fileName = "/names/names.script@personNameScriptFn",
            params = {
                path = "unitedKingdom"
            }
        },
        townNamesScript = {
            fileName = "/names/names.script@townsNameScriptFn",
            params = {
                languages = {
                    en = "en",
                    fallback = "en",
                },
                path = "england"
            }
        },
        streetNamesScript = {
            fileName = "/names/names.script@streetsNameScriptFn",
            params = {
                languages = {
                    en = "en",
                    fallback = "en",
                },
                path = "england"
            }
        },
    }
end

Jeder Eintrag ...Script zeigt auf eine Funktion. Das Spiel ruft sie mit den hier angegebenen params auf (das Wiki nennt sie captureParams) und mit einer zweiten Tabelle:

Funktion Zweite Tabelle Rückgabewert
Personennamen lang, isMale ein String, z. B. "Madeline Lyla"
Stadtnamen lang, num eine Liste mit num Namen; alle Namen bei num = -1
Straßennamen lang, num eine Liste mit num Namen; alle Namen bei num = -1

Das Wiki verlangt, dass Funktionen für Stadtnamen keine Dubletten liefern und genug Namen für eine volle Karte haben. Im Beispielcode des Wikis heißen die Funktionen townsNameScriptFn und streetNameScriptFn, im Basisskript townsNameScriptFn und streetsNameScriptFn (mit s).

So arbeiten die gemeinsamen Funktionen in base/content/names/names/names.script.lua:

  • personNameScriptFn nimmt Vor- und Nachnamen aus personnameutil.lua im selben Ordner, aus names[path][lang] oder names[path].en. Sie wählt einen männlichen oder weiblichen Vornamen und einen Nachnamen. Mit lastNameFirst = true wie in china.names.lua steht der Nachname vorn. Statt path kann ein Set eine Liste paths mit einer passenden Liste lastNameFirst angeben, dann wird pro Person einer der Einträge gewählt.
  • townsNameScriptFn und streetsNameScriptFn laden <modId>::/names/<path>/<language>/towns.lua bzw. streets.lua. Die Sprache kommt aus der Tabelle languages: das aktuelle lang, wenn es dafür einen Eintrag gibt, sonst fallback. Jede dieser Dateien gibt eine einfache Liste von Strings zurück. england/en/towns.lua beginnt mit "Aberdeen", "Belfast", "Birmingham".

Die Tabelle languages in china.names.lua hat die Schlüssel de, en, zh_CN, zh_TW und fallback. lang kann also ein regionaler Code sein, das Wiki erwähnt nur Kurzcodes wie en.

Damit kommt ein Mod einfach zu neuen Stadt- und Straßennamen: Er verwendet das Basisskript, übergibt seine Mod-ID und bringt die Listen mit. Das Basis-Set oben darf /names/... schreiben, weil es zum Basisspiel gehört. Ein Mod verweist mit ::/names/... auf das Basisskript.

-- content/names/mynames/mynames.names.lua im Mod "author_mynames"
function data()
return {
    name = _("My names"),
    personNamesScript = {
        fileName = "::/names/names.script@personNameScriptFn",
        params = { path = "unitedKingdom" },
    },
    townNamesScript = {
        fileName = "::/names/names.script@townsNameScriptFn",
        params = { modId = "author_mynames", path = "mynames", languages = { fallback = "en" } },
    },
    streetNamesScript = {
        fileName = "::/names/names.script@streetsNameScriptFn",
        params = { modId = "author_mynames", path = "mynames", languages = { fallback = "en" } },
    },
}
end

Die Listen kommen dann nach content/names/mynames/en/towns.lua und streets.lua. Das Basisskript liest den Parameter modId, aber keines der Basis-Sets nutzt ihn. Genau dieser Aufbau ist deshalb nicht erprobt. Personennamen lassen sich so nicht erweitern, weil die Personenfunktion nur die Tabellen in der personnameutil.lua des Basisspiels liest. Für neue Personennamen brauchst du eine eigene Funktion.

Figuren

Fußgänger, Passagiere und Fahrzeugbesatzungen sind Figurenmodelle: normale .mdl-Dateien mit einem Block person in den Metadaten. Das Basisspiel hat 37 davon in base/content/characters/characters/, zum Beispiel era_a_wom_01 oder era_b_driver_rail. Aus era_a_driver_water/era_a_driver_water.mdl (gekürzt):

metadata = {
    availability = {
        yearFrom = 1850,
        yearTo = 1935,
    },
    colorConfig = {
        configs = {
            {
                { 0.360784, 0.357884, 0.352295, },
                { 0.266667, 0.266667, 0.266667, },
                { 0.596078, 0.596078, 0.596078, },
                { 0.623529, 0.554269, 0.542837, },
            },
            -- weitere Farbsets
        },
    },
    person = {
        drivingLicenses = { "WATER", },
        gender = "MALE",
    },
    soundConfig = {
        effects = {
            select = {
                "::/characters/shared/sound/selected_male_0.wav",
                -- vier weitere
            }
        }
    },
},
  • availability begrenzt die Figur auf eine Epoche. yearFrom und yearTo können fehlen oder 0 sein, dann gilt keine Grenze.
  • colorConfig.configs ist eine Liste von Farbsets zum Umfärben. Jedes Set hat vier RGB-Farben, eine pro Kanal der Color-Blend-Map des Materials.
  • person kennzeichnet das Modell als Figur. Ist drivingLicenses leer, ist sie Fußgänger oder Passagier. Mit Führerscheinen gehört sie zur Besatzung der entsprechenden Fahrzeuge. gender ist "FEMALE" oder "MALE" (Gender) und ordnet der Figur weibliche oder männliche Einwohnernamen zu.

Das Wiki nennt die Führerscheine BUS, TRUCK, TRAM, RAIL, WATER, AIR und AIR_OUTDOOR. Das Basisspiel verwendet zusätzlich HELICOPTER, in era_c_driver_helicopter.mdl. Welchen Führerschein die Besatzung eines Fahrzeugs braucht, steht in dessen seatProvider.drivingLicense (siehe Sitze). Von den 37 Figuren des Basisspiels haben 24 keinen Führerschein. Fahrer von Straßenfahrzeugen haben BUS, TRUCK und TRAM zusammen. Die Typdefinition ModelMetadata.Person deklariert nur gender, nicht drivingLicenses.

Die Liste soundConfig.effects.select fehlt im Wiki. Sie enthält die Sprachclips, die zu hören sind, wenn der Spieler die Figur auswählt.

Figurenmodelle brauchen diese Animationen, pro Knochen angegeben als animations = { <name> = { type = "FILE_REF", params = { id = "/characters/shared/ani/..." } } }:

Animation Verwendung (Wiki)
driving Sitzen auf einem niedrigen Fahrersitz wie im Auto.
driving_upright Sitzen auf einem höheren Fahrersitz wie in den meisten anderen Fahrzeugen.
idle Stehen, z. B. beim Warten an Ampeln oder Stationen oder im Fahrzeug.
sitting Sitzen, meist als Passagier.
walk Gehen.

Alle fünf laufen in Schleife. Die Figuren des Basisspiels teilen sich ihre Animationsdateien unter /characters/shared/ani/.

Wiedergabelisten für Musik

Eine Wiedergabeliste ist eine Liste von Musikstücken, die der Spieler unter „Standard-Wiedergabeliste“ (Default Playlist) in den „Audio-Einstellungen“ auswählen kann. Aus base/content/music/music/default/default.plist.lua (gekürzt):

function data()
return {
    name = _("Transport Fever 3"),
    tracks = {
        {
            name = "J.J. Ipsen - A Promise Made Is a Promise Kept",
            fileName = "a_promise_made_is_a_promise_kept.ogg",
        },
        {
            name = "J.J. Ipsen - Cup of Joe",
            fileName = "cup_of_joe.ogg",
        },
        -- weitere Stücke
    },
    order = 1,
}
end

name ist der Name der Wiedergabeliste in der Liste „Standard-Wiedergabeliste“, order ihre Position dort. Jedes Stück hat einen fileName relativ zur .plist.lua-Datei (die .ogg-Dateien liegen im selben Ordner) und einen name für den „Musikplayer“ (Music Player). Das Deluxe Upgrade Pack bringt in dlcs/urbangames_deluxe_upgrade_pack/content/music/music/ vier weitere Wiedergabelisten mit. Sie haben order-Werte wie 300 und Stücke, die nur einen fileName haben, name darf also fehlen.

base/content/music/music/menu.plist.lua ist die Musik des Hauptmenüs und verwendet Felder, die das Wiki nicht behandelt: hidden = true hält sie aus der Auswahl „Standard-Wiedergabeliste“ heraus, die Stücke haben enableRepeat und instantTransition, fileName ist ein absoluter Pfad wie "::/music/tf3_theme_a_loop.ogg", und variants tauscht die Datei in einem bestimmten context aus (hier "Credits").

Soundsets

Ein Soundset (.snd.lua) fasst die Sounds eines Objekts zusammen: eines Fahrzeugs, einer Industrie, Station, eines Depots oder Stadtgebäudes. Das Basisspiel hat 100 davon. Ein Modell oder eine Konstruktion gibt das Soundset ohne .lua in soundConfig.soundSet.name an (ModelMetadata.SoundConfig). Aus base/content/vehicle/train/br75_4/br75_4/br75_4.mdl:

soundConfig = {
    soundSet = {
        name = "/vehicle/train/shared/sound/train_steam_modern.snd",
    },
},

base/content/industries/coal_mine/coal_mine/coal_mine.con.lua verwendet einen relativen Namen, "sound/coal_mine.snd".

Aufbau

Ein Soundset gibt tracks, events und ein updateScript zurück. Tracks laufen in Schleife, Events spielen bei jedem Auslösen einmal. In jedem Frame setzt das Update-Skript Lautstärke (Gain) und Tonhöhe (Pitch) pro Track und entscheidet, welche Events spielen.

Im Wiki steht refDist (die Entfernung, auf die die Lautstärke des Sounds normiert ist) direkt an jedem Track und Event. Die mitgelieferte soundsetutil.lua schreibt es stattdessen in eine Tabelle attrs: { name = name, attrs = { refDist = refDist } } für einen Track und { names = names, attrs = { refDist = refDist } } für ein Event. Wenn du soundsetutil verwendest, musst du dich nicht entscheiden.

Das Update-Skript bekommt die Parameter aus der .snd.lua, eine Tabelle params mit dem aktuellen Zustand (params.currentInfo) und ein Ausgabeobjekt, einen SoundTransfOutput. Das Wiki nennt dessen zwei Methoden addTrack (einmal pro Track, in der Reihenfolge der Tracks) und addEvent. Die Typdefinition in base/tealdef/scripts/soundset.d.tl hat eine dritte, triggerEvent, und das soundset_default.script.tl des Basisspiels ruft triggerEvent für eigene Events auf und addEvent für die übrigen.

soundsetutil

Alle Soundsets des Basisspiels entstehen mit /scripts/soundsetutil.lua, das das Standard-Update-Skript einträgt. Aus base/content/industries/coal_mine/coal_mine/sound/coal_mine.snd.lua:

local soundsetutil = require "/scripts/soundsetutil.lua"
-- in einem Mod: require "::/scripts/soundsetutil.lua"

function data()

local data = soundsetutil.makeSoundSet()

soundsetutil.addSimpleTrackParam01(data, "coal_mine_producing.wav", 50.0, {"industry", "production01"})
soundsetutil.addSimpleTrackParam01(data, "coal_mine_boosted.wav", 60.0, {"industry", "boosted01"})
soundsetutil.addSimpleTrackParam01(data, "coal_mine_double_boosted.wav", 70.0, {"industry", "doubleBoosted01"})

return data

end

Die Namen der Sounddateien sind relativ zur .snd.lua-Datei. Rufe zuerst makeSoundSet() auf, danach die anderen Funktionen:

Funktion Fügt hinzu
addEvent(data, key, names, refDist) Ein Event, das beim Auslösen immer spielt; bei mehreren Dateien wird eine zufällig gewählt.
addEventParam01(data, key, names, refDist, gainCurve, pitchCurve, infoGain, infoPitch) Ein Event, dessen Gain und Pitch Kurven folgen.
addEventCustom(..., customUpdateScript) Ein Event mit eigener Update-Funktion.
addTrackDefault(data, name, refDist) Einen Track mit festem Gain und Pitch (in der Datei, nicht im Wiki).
addTrackParam01(data, name, refDist, gainCurve, pitchCurve, infoGain, infoPitch) Einen Track, dessen Gain und Pitch Kurven folgen.
addSimpleTrackParam01(data, name, refDist, infoGain) Einen Track mit Pitch 1, dessen Gain der Eingabewert ist.
addTrackSqueal(data, name, refDist) Radquietschen aus Geschwindigkeit und Querkraft, z. B. über Weichen.
addTrackBrake(data, name, refDist, maxGain) Bremsgeräusch aus Geschwindigkeit und Bremsung; moderne Bremsen bekommen ein niedrigeres maxGain.
addEventClacks(data, names, refDist, axleRefWeight) Schienenstoß-Klacken aus Geschwindigkeit, Gewicht und Achszahl.
addTrackCustom(..., customUpdateScript) Einen Track mit eigener Update-Funktion.
makeRoadVehicle2(data, speeds, idle, idleSpeed, idleGain0, drive, driveSpeed, refDist, infoGain, infoPitch) Leerlauf- und Fahrgeräusche eines Straßenfahrzeugs.
makeSteamTrain(data, idle, fast, tracksRefDist, chuffNames, chuffsRefDist, chuffsFastFreq, refWeight) Leerlauf- und Fahrgeräusche sowie Auspuffschläge einer Dampflok.

Eine Kurve ist eine Liste von Punkten { input, value } (SampleCurveParams). Zwischenwerte werden interpoliert, Werte außerhalb auf den Rand begrenzt. Die Eingabe ist ein Paar von Schlüsseln in currentInfo. Diese Paare nutzen die Soundsets des Basisspiels am häufigsten:

Eingabe Verwendungen
{"vehicle", "speed01"} 81
{"industry", "production01"} 34
{"industry", "boosted01"} 34
{"vehicle", "power01"} 32
{"industry", "idle01"} 19
{"industry", "doubleBoosted01"} 15
{"station", "crowd01"} 12
{"station", "cargo01"} 10

Eine eigene Update-Funktion bekommt ein result mit gain und pitch (beide beginnen bei 1), deine Kurvenparameter und currentInfo, bei Events zusätzlich die Info des vorigen Frames. Sie setzt result.trigger = true, um das Event abzuspielen (CustomUpdateScript, CustomUpdateEventScript). Setze trigger nur für einen Frame, sonst wiederholt sich der Sound. base/content/vehicle/bus/shared/shared/sound/bus_electric.snd.lua verwendet addTrackCustom mit "::/vehicle/shared/sound/accelerationOnlyTrackUpdate.script@updateFn". Sein Leerlaufbrummen ist deshalb nur zu hören, während der Bus beschleunigt.

Event-Schlüssel

Das Spiel löst diese Event-Schlüssel aus (laut Wiki):

Schlüssel Objekte Wann
random1 ... random64 (Zweierpotenzen) Stadtgebäude, Stationen, Industrien etwa alle n Sekunden
openDoors Straßen-, Schienen-, Wasser- und Luftfahrzeuge nach der Ankunft an einer Station
closeDoors Straßen-, Schienen-, Wasser- und Luftfahrzeuge vor der Abfahrt von einer Station
horn Schienen- und Wasserfahrzeuge bei der Abfahrt von einer Station
clacks Schienenfahrzeuge regelmäßig während der Fahrt (Achsklacken)
chuffs Schienenfahrzeuge regelmäßig während der Fahrt (Auspuffschläge der Dampflok)
sonicBoom Luftfahrzeuge beim Durchbrechen der Schallmauer
land Luftfahrzeuge wenn die Räder den Boden berühren

Die Soundsets des Basisspiels definieren horn für mehr Fahrzeuge, als das Wiki aufzählt: Busse, Autos, Lastwagen, Trams, Flugzeuge und Hubschrauber haben auch eines (zum Beispiel bus_electric.snd.lua oben). Die Feuerwerk-Soundsets in base/content/game_mechanics/game_mechanics/fun_elements/firework/ verwenden launch und bang, gesteuert über {"state", "launch"} und {"state", "explode"}. Laut Wiki lassen sich andere eigene Events nur von Kantenobjekten auslösen.

Bau-Audiosets

Ein Bau-Audioset (.builder_audio.lua) enthält die Sounds, die das Baumenü beim Bauen, Ziehen oder Abreißen spielt. Es ist kein Soundset: Es hat keine Tracks und kein Update-Skript, nur Listen von .wav-Dateien pro Aktion. Alle 29 Dateien des Basisspiels liegen in base/content/gui/gui/construction/sound/, die Clips in Unterordnern dort (build/, bulldoze/, terraform/, tool/), mit Pfaden relativ zur Datei. Aus buildoze_tree.builder_audio.lua (die Basisdateien schreiben tatsächlich „buildoze“):

function data()
    return {
        noMoney = {
            "build/no_money.wav",
        },
        build = {
            "build/asset.wav",
        },
        bulldoze = {
            "bulldoze/tree.wav",
        },
        terraform = {
            "terraform/brush_trees.wav",
        },
    }
end

Die Tabelle ist ein BuilderAudioSet. Die 29 Dateien verwenden die folgenden Schlüssel. Wofür jeder gedacht ist, ergibt sich aus dem Schlüsselnamen und den Dateien, die ihn setzen. Die Spieldateien beschreiben es nicht.

Schlüssel Wert Dateien Verwendet bei
build Liste von Clips 23 Platzieren einer Konstruktion, eines Moduls, Assets, einer Straße, eines Gleises oder einer Werkzeugaktion
bulldoze Liste von Clips 24 Abreißen; die Werkzeug-Sets verwenden tool/cancel.wav
noMoney Liste von Clips 20 Bauen ohne genug Geld
terraform Liste von Clips 9 Geländepinsel sowie Baum-, Fels- und Pflanzenpinsel
dragStart, dragEnd ein Clip 4 Anfang und Ende beim Ziehen einer Straße, eines Gleises oder einer Spur
drag Liste von Clips 4 während des Ziehens
dragBridge, dragTunnel Liste von Clips 3 beim Ziehen über eine Brücke oder durch einen Tunnel
dragOffset Zahl 4 in allen vier -0.2; die Wirkung ist nicht dokumentiert

Die vier Sets mit Zieh-Schlüsseln sind buildoze_street, buildoze_track, tool_lane_modifier und fallback. Der Typ hat außerdem ein Feld attrs (SoundAttributes), das keine Basisdatei setzt.

Inhalte verweisen mit builderAudioRes und dem Spielpfad ohne .lua auf ein Set: .con.lua-, .module.lua-, .street_template.lua- und .edge.lua-Dateien auf oberster Ebene, Modelle in metadata.soundConfig.builderAudioRes (ModelMetadata.SoundConfig, genutzt von Bäumen, Felsen und Pflanzen) und die Werkzeuge des Baumenüs in ihrer Definition. Die Module verwenden "::/gui/construction/sound/buildoze_module.builder_audio". In dieser Form schreibt auch ein Mod den Verweis auf ein Basis-Set. Ein Mod kann ein eigenes Set unter content/ mitbringen und genauso darauf verweisen. Geladene Sets stehen in api.res.builderAudioSetRep. Der Schlüssel für Ressourcen-Modifier ist loadBuilderAudioSet, die Kategorie für Dateifilter builderAudioSet (siehe Ressourcen-Modifier).

Offizielles Wiki: Cargos, Localizations, Mod definition (strings.json), Names, People, Playlists, Sound sets.