Repository navigation
Laufzeitdaten lesen
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.
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)
endNö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.
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.
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.
endZwei 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 inproduction.mixGroupWinnerType[mix]. - Welches Mitglied gewinnt, entscheidet
production.mixMode(ASCdas erste,DESCdas letzte,MOSTdas bestbevorratete,LEASTdas 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.
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. OhnelimitBoostersträgt jeder ausreichend bevorratete Booster seinen Faktor bei und mehrere multiplizieren sich; mitlimitBoosterszählt nur einer, ausgewählt nachboostMode. Das Ergebnis steht inproduction.boosterFactor. -
Master-Booster (
mix == 99) wirken auf die gesamte Linie — Verbrauch und Erzeugung, nur nicht auf den eigenen Verbrauch. Das Ergebnis steht inproduction.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).
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.
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.capacityist die Größe des gemeinsamen Topfes, nicht die Kapazität einer einzelnen Sorte. -
storage.capacities[fillType]istnilfür jede Sorte, die sich den Topf teilt — diesesnilbedeutet „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.
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.
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.
Ü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.
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
endStand: 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.