Skip to content

Laufzeitdaten lesen

Achimobil edited this page Oct 7, 2026 · 1 revision

Diese Seite richtet sich an Mods, die Revamps Daten nur lesen — HUD-Anzeigen, Info-Erweiterungen, Statistik- und Planungswerkzeuge. Wer eigene Produktionen, Gebäude oder Waren mitbringen will, ist in der XML-Referenz und unter Fremde Mods anbinden richtig; hier geht es ausschließlich darum, was zur Laufzeit im Speicher steht und wie man es richtig liest.

Der Grundsatz dabei: Revamp verändert die Bedeutung von Vanilla-Daten, nicht nur ihren Inhalt. Ein Werkzeug, das die Rezepte eines Produktionspunktes so auswertet wie im Vanilla-Spiel, rechnet an drei Stellen zuverlässig falsch, sobald eine Revamp-Produktion darunter ist — bei Mix-Gruppen, bei Boostern und beim geteilten Lagerplatz. Die Abschnitte dazu stehen weiter unten.

Woran man Revamp erkennt

Ob Revamp überhaupt geladen ist, steht unter Kompatibilität — der ModName lautet fest FS25_0_ProductionRevamp.

Alles, was auf Vanilla-Klassen liegt (ProductionPoint, Storage, die Einträge in productions, inputs, outputs), ist ohne weiteres Zutun lesbar: das sind dieselben Tabellen, die das Spiel selbst benutzt, Revamp hängt nur eigene Felder daran. Für die Abschnitte unten braucht ein fremder Mod deshalb in aller Regel keinen Zugriff auf Revamps eigenen Code.

Revamps eigene Globals (Revamp.*) liegen dagegen in Revamps Mod-Umgebung und sind von außen nicht direkt sichtbar. Das Spiel legt jede Mod-Umgebung unter ihrem ModNamen in _G ab, der Weg dorthin ist also:

local revampEnv = _G["FS25_0_ProductionRevamp"]
local revamp = revampEnv ~= nil and revampEnv.Revamp or nil
if revamp ~= nil then
    -- z.B. revamp.MIX_BOOST, revamp.getIsSharedStorageFillType(storage, fillType)
end

Nötig ist das selten. Die wenigen Zahlenwerte, die man wirklich braucht, stehen unten ausgeschrieben — sie sind Teil des gespeicherten Datenmodells und ändern sich nicht mehr.

Rezeptlinien

Eine Rezeptlinie ist unverändert ein Eintrag aus productionPoint.productions (bzw. productionPoint.productionsIdToObj[id]), mit Vanillas inputs, outputs, cyclesPerMinute, status und id. Revamp hängt an jede Linie zusätzliche Felder:

Feld Bedeutung
master Die Linie hat mindestens einen Master-Booster
limitBoosters Nur ein einziger Booster zählt, nicht alle gleichzeitig
mixMode / boostMode Auswahlverfahren ASC/DESC/MOST/LEAST — zur Laufzeit vom Spieler änderbar
hasMixChoice / hasBoostChoice Ob diese Auswahl bei dieser Linie überhaupt etwas bewirkt
hideFromMenu / autoStart Versteckte bzw. selbst startende Linie
sharedThroughputCapacity Ob die Linie an der geteilten Durchsatzkapazität teilnimmt
modes, hoursTable, seasonsList, monthsList Wetter- und Zeitvorgaben, siehe Wetter und Uhrzeit
hoursText, seasonText, monthsText Dieselben Vorgaben als fertiger Anzeigetext
weatherFactor, minPower, maxPower Statische Wetter-Kennwerte aus der XML
weatherFactorCurrent Aktueller Wetteranteil, nil wenn die Linie keine Wetter-Modi hat
modeStatus nil wenn die Linie laufen darf, sonst der Status, mit dem sie gerade angehalten ist
boosterFactor, masterFactor Ergebnis des letzten Taktes, siehe unten
mixGroupSatisfied, mixGroupWinnerType Je Mix-Gruppe: ob sie bedient ist und welche Sorte gerade gewinnt
mixGroupTitles Eigene Überschrift einer Mix-Gruppe, falls die XML eine vergibt
primaryProductFillType Vom Rezept erzwungenes Haupterzeugnis

productionPoint.openingHoursTable fasst die Öffnungszeiten aller Linien zusammen: der Betrieb gilt als offen, solange mindestens eine Linie laufen darf.

Mix-Gruppen

Alternative Zutaten liegen nicht verschachtelt, sondern als ganz gewöhnliche Einträge in production.inputs, verbunden über ein gemeinsames Feld mix:

input.mix Bedeutung
0 oder nil Zwingende Zutat, genau wie in Vanilla
1 bis 97 Mitglied der Mix-Gruppe mit dieser Nummer — pro Takt wird eine davon gebraucht und abgebucht
98 Booster (optional)
99 Master-Booster (optional)

Wer inputs naiv durchläuft, hält jede Alternative für einzeln zwingend erforderlich und meldet einen Mangel, obwohl die Gruppe längst bedient ist. Richtig ist eine Gruppierung:

local MIX_BOOST, MIX_MASTER = 98, 99
local required, groups = {}, {}

for _, input in ipairs(production.inputs) do
    local mix = input.mix or 0
    if mix == 0 then
        table.insert(required, input)
    elseif mix ~= MIX_BOOST and mix ~= MIX_MASTER then
        groups[mix] = groups[mix] or {}
        table.insert(groups[mix], input)
    end
    -- Booster und Master-Booster bleiben hier absichtlich außen vor: fehlender Bestand
    -- hält die Produktion nie an, er lässt nur den Bonus ausfallen.
end

Zwei Dinge, die dabei leicht übersehen werden:

  • Eine Gruppe blockiert die Linie, wenn keines ihrer Mitglieder ausreichend bevorratet ist — sie verhält sich also wie eine zwingende Zutat, nur eben mit Wahlmöglichkeit. Ob sie gerade bedient ist, steht in production.mixGroupSatisfied[mix], die aktuell gewählte Sorte in production.mixGroupWinnerType[mix].
  • Welches Mitglied gewinnt, entscheidet production.mixMode (ASC das erste, DESC das letzte, MOST das bestbevorratete, LEAST das schwächstbevorratete). Der Wert ist kein statischer XML-Wert: der Spieler stellt ihn im Verwalten-Dialog um, er kann sich also im laufenden Spiel ändern.

Eine Zutat, die in der XML über fillTypeCategory angegeben wurde, ist zur Laufzeit nicht mehr als solche erkennbar — Revamp löst sie schon beim Laden in gewöhnliche Einträge derselben Mix-Gruppe auf. Für einen lesenden Mod ist das der angenehme Fall: er sieht immer nur echte Fülltypen.

Booster und Master-Booster

Beide sind optionale Zutaten, die den Durchsatz verändern. Ihr input.boostFactor ist ein reiner Multiplikator (Neutralwert 1, deshalb als XML-Angabe unzulässig); input.boostSatisfied sagt, ob der Bestand im letzten Takt für ihn gereicht hat.

  • Booster (mix == 98) wirken auf die Erzeugnisse. Ohne limitBoosters trägt jeder ausreichend bevorratete Booster seinen Faktor bei und mehrere multiplizieren sich; mit limitBoosters zählt nur einer, ausgewählt nach boostMode. Das Ergebnis steht in production.boosterFactor.
  • Master-Booster (mix == 99) wirken auf die gesamte Linie — Verbrauch und Erzeugung, nur nicht auf den eigenen Verbrauch. Das Ergebnis steht in production.masterFactor.

Am einzelnen Erzeugnis entscheidet output.boost, ob der Booster überhaupt greift: true multipliziert mit boosterFactor, false lässt die Menge unberührt, "reverse" nutzt den Kehrwert (ein Booster verringert diese Ausgabe dann).

Was pro Takt tatsächlich fließt

Die Zahlen in input.amount und output.amount sind nominale Rezeptwerte und nicht das, was ein laufender Betrieb wirklich umsetzt:

takt = cyclesPerMinute
     * (weatherFactorCurrent or 1)
     * masterFactor
     / (Anzahl Linien, die sich die Durchsatzkapazität gerade teilen)

Verbrauch je Zutat   = input.amount  * takt          (der Master-Input selbst ohne masterFactor)
Erzeugung je Produkt = output.amount * takt * Booster-Anteil dieses Outputs

Der letzte Teiler greift nur, wenn productionPoint.sharedThroughputCapacity und production.sharedThroughputCapacity gesetzt sind. rngAffected an einer Zutat oder einem Erzeugnis multipliziert zusätzlich mit einem Zufallswert zwischen 0 und 1 — eine exakte Vorhersage ist dann grundsätzlich nicht möglich, ein Mittelwert von 50 % kommt der Sache am nächsten.

boosterFactor, masterFactor und weatherFactorCurrent stammen aus dem jeweils letzten Produktionstakt. Sie sind vor dem ersten Takt gar nicht gesetzt und bleiben auf ihrem alten Stand stehen, solange eine Linie über modeStatus angehalten ist — also immer mit or 1 absichern und bei gesetztem modeStatus nicht als aktuell behandeln.

Lager: der gemeinsame Topf

Ein Lager kann eine Gesamtkapazität haben, aus der sich alle Fülltypen ohne eigene Kapazitätszeile bedienen. Ein Getreidelager über 60.000 Liter fasst 60.000 Liter, gleich über wie viele Sorten sie sich verteilen.

Revamp korrigiert dafür Storage:getCapacity und Storage:getFreeCapacity (Vanilla widerspricht sich hier selbst und meldet je nach Fall zu wenig freien Platz oder für jede Sorte den vollen Topf). Wer diese beiden Methoden aufruft, bekommt automatisch die richtigen Werte und muss sonst nichts tun.

Falsch wird es, sobald ein Mod stattdessen die Rohfelder liest:

  • storage.capacity ist die Größe des gemeinsamen Topfes, nicht die Kapazität einer einzelnen Sorte.
  • storage.capacities[fillType] ist nil für jede Sorte, die sich den Topf teilt — dieses nil bedeutet „gemeinsamer Topf", nicht „kein Platz".

Daraus folgt für jede Restlaufzeit- oder Auslastungsrechnung: Der Platz für ein Erzeugnis hängt vom Füllstand anderer Sorten ab und ändert sich, ohne dass am Erzeugnis selbst etwas passiert. Ein Balken, der fillLevel / storage.capacity rechnet, zeigt eine Auslastung, die es so nicht gibt; storage:getCapacity(fillType) liefert dagegen das, was für diese Sorte gerade tatsächlich erreichbar ist.

Ob eine Sorte betroffen ist, lässt sich ohne Revamp-Zugriff prüfen: storage.supportsMultipleFillTypes == true and storage.fillLevels[fillType] ~= nil and storage.capacities[fillType] == nil.

Ausgangsmodus und Verteilziele

Revamp ergänzt Vanillas Ausgangsmodi (KEEP, DIRECT_SELL, AUTO_DELIVER) um einen vierten:

ProductionPoint.OUTPUT_MODE.STORE = 3

„Einlagern" unterdrückt den automatischen Paletten- und Ballenspawn für einen Fülltyp. productionPoint:getOutputDistributionMode(fillType) liefert ihn wie gewohnt mit, die betroffenen Fülltypen stehen zusätzlich in productionPoint.outputFillTypeIdsStorage. Ein Werkzeug, das die Modi in Text übersetzt, braucht also einen vierten Fall — sonst zeigt es für eingelagerte Waren nichts oder etwas Falsches.

Die Verteilziele einer Produktionsstätte liegen unter productionPoint.distributionTargets[fillTypeIndex][uniqueId] = {level = "PRIMARY"|"SECONDARY"|"REST"|"NEVER", limit = <Liter oder nil>}. Der Schlüssel ist die uniqueId des Ziel-Placeables; die Stufe REST ohne Obergrenze ist der Normalfall und wird gar nicht erst gespeichert. Was die Stufen bedeuten, steht unter Verteilen.

Erweiterte Vanilla-Konstanten

Revamp verändert einige Vanilla-Konstanten für den ganzen Spielstand. Ein lesender Mod, der eigene Werte über das Netz schickt oder Statuswerte in Text übersetzt, muss das kennen:

Konstante Vanilla Mit Revamp
ProductionPoint.PROD_STATUS INACTIVE, RUNNING, MISSING_INPUTS, NO_OUTPUT_SPACE zusätzlich OUTSIDE_OF_SEASONS (4), OUTSIDE_OF_MONTHS (5), OUTSIDE_OF_HOURS (6), NO_WEATHER (7)
ProductionPoint.PROD_STATUS_NUM_BITS 2 3
FillTypeManager.SEND_NUM_BITS 8 (255 Fülltypen) 10 (1024 Fülltypen)
ProductionChainManager.NUM_MAX_PRODUCTION_POINTS Vanilla-Grenze 360
SlotSystem.NUM_OBJECT_LIMITS Ballen und Paletten begrenzt auf PC unbegrenzt, Konsolen unverändert begrenzt

Die neuen Status haben passende Texte in ProductionPoint.PROD_STATUS_TO_L10N — wer diese Tabelle benutzt statt einer eigenen Zuordnung, bekommt sie ohne Zutun mit.

Von Revamp überschriebene Vanilla-Funktionen

Überschreibt ein anderer Mod dieselbe Funktion, müssen beide Seiten superFunc aufrufen, sonst verdrängt eine die andere. Bei Utils.prependedFunction und Utils.appendedFunction stellt sich die Frage nicht, sie sind von sich aus verträglich — die kritische Zeile ist jedes „überschrieben".

Funktion Art
FSBaseMission.onConnectionFinishedLoading angehängt
FSCareerMissionInfo.saveToXMLFile angehängt
FillTypeDesc.loadFromXMLFile überschrieben
FillTypeManager.loadModFillTypes angehängt
FinanceStats.new überschrieben
InGameMenuProductionFrame.getCellTypeForItemInSection überschrieben
InGameMenuProductionFrame.getNumberOfItemsInSection überschrieben
InGameMenuProductionFrame.getNumberOfSections überschrieben
InGameMenuProductionFrame.getTitleForSectionHeader überschrieben
InGameMenuProductionFrame.populateCellForItemInSection überschrieben und angehängt
InGameMenuProductionFrame.updateMenuButtons angehängt
InGameMenuSettingsFrame.onFrameOpen angehängt
InGameMenuSettingsFrame.updateGameSettings angehängt
Mission00.loadMission00Finished angehängt
PalletSpawner.getSupportedFillTypes überschrieben
PalletSpawner.update überschrieben
ProductionChainManager.distributeGoods überschrieben
ProductionPoint.delete vorangestellt
ProductionPoint.getOutputDistributionMode überschrieben
ProductionPoint.load überschrieben
ProductionPoint.loadFromXMLFile überschrieben
ProductionPoint.loadSpecValueInputFillTypes vollständig ersetzt, ohne Aufruf der ursprünglichen Fassung
ProductionPoint.readStream vorangestellt
ProductionPoint.registerSavegameXMLPaths vorangestellt
ProductionPoint.registerXMLPaths vorangestellt
ProductionPoint.saveToXMLFile vorangestellt
ProductionPoint.setOutputDistributionMode überschrieben
ProductionPoint.setProductionState überschrieben
ProductionPoint.toggleOutputDistributionMode überschrieben
ProductionPoint.updateProduction überschrieben
ProductionPoint.writeStream vorangestellt
ProductionPointActivatable.run überschrieben
SmoothListElement.updateView überschrieben
Storage.getCapacity überschrieben
Storage.getFreeCapacity überschrieben
Storage.load überschrieben
StorageSystem.addUnloadingStation überschrieben
StoreManager.loadMapData vorangestellt

ProductionPoint.load, ProductionPoint.loadFromXMLFile und ProductionPoint.updateProduction tragen dabei mehrere Revamp-Schichten übereinander — eine weitere Schicht eines fremden Mods ist damit problemlos möglich, solange sie superFunc aufruft.

Zusätzlich kommen auf ProductionPoint neue Methoden dazu (setDistributionTarget, setRecipeSelectionModes, transportAnimals, calculateAnimals, getIsSubTypeAllowedForTrigger, getPalletCallback) sowie ein neuer Geldtyp MoneyType.PRODUCTION_DELIVERY_COSTS für die Lieferkosten. Überschrieben wird davon nichts, ein Namenskonflikt entstünde nur mit einem Mod, der dieselben Namen benutzt.

Was ein Admin abgeschaltet haben kann

Mehrere Revamp-Bereiche lassen sich pro Spielstand abschalten (siehe Einstellungen). Ist das geschehen, gilt für den betroffenen Bereich wieder Vanilla-Verhalten, obwohl die Daten weiterhin gespeichert sind und im Speicher stehen — ein ausgelesener Verteil- oder Einlagern-Zustand kann also gerade wirkungslos sein. Betroffen sind Verteilen, Einlagern, Kaufen, Verkaufen, die Wetter- und Zeitmodi sowie die Auswahlmodi. Für den Ausgangsmodus fängt Revamp das selbst ab: bei abgeschalteter Lager-Erweiterung liefert getOutputDistributionMode wieder Vanillas Wert.

Woran man es erkennt: ProductionPoint.REVAMP_SETTINGS enthält den aktuellen Stand aller Einstellungen unter denselben Namen, die auch die Seite Einstellungen benutzt. Ohne Revamp gibt es das Feld nicht.

Feld Werte
distributionActive Verteilen an andere Stätten
distributionCostFactor Faktor auf die Lieferkosten, 1 = unverändert
storageActive Lager-Erweiterung samt Ausgangsmodus „Einlagern"
buyingActive Zutaten kaufen
sellingActive Erzeugnisse verkaufen
tradeRestriction "everyone", "farmManager" oder "productionAgent"
directSellingPriceFactor Faktor auf den Erlös beim Direktverkauf
weatherModesActive Wetter- und Zeitvorgaben der Rezeptlinien
recipeSettingsActive Auswahlmodi für Mix-Gruppen und Booster

Die Werte stehen auf dem Client genauso wie auf dem Server: bis die Einstellungen des Servers eintreffen, gelten die Startwerte, ein halb gefüllter Stand kommt nicht vor.

local revampSettings = ProductionPoint.REVAMP_SETTINGS
if revampSettings ~= nil and not revampSettings.weatherModesActive then
    -- hoursTable, seasonsList und monthsList stehen zwar da, halten aber gerade keine Linie an
end

Stand: Version 0.0.0.33. Diese Referenz beschreibt den gebauten Entwicklungsstand. Die Felder auf production, input, output und productionPoint sowie die Konstanten oben sind Teil des Datenmodells und werden absichtlich stabil gehalten; alles andere kann sich noch ändern.

Clone this wiki locally