1 Einleitung
Mit einem Game Script können Mods im laufenden Spiel regelmäßig Funktionen ausführen und auf verschiedene Ingame Events reagieren.
2 Speicherort
Man erstellt dazu im Mod-Ordner einen Ordner res/config/game_script. Dort legt man dann eine Datei modname.lua an.
Auch im Vanilla-Ordner gibt es einige GameScript-Dateien, die zB das Guidesystem, die ToolTips oder die Anzeige für die transportierten Personen/Güter (unten in der Leiste) beeinhalten. Diese kann man ebenfalls als Beispiel nehmen und sich daran orientieren.
3 Inhalt / Grundgerüst
Im Folgenden ein Beispiel für ein GameScript mit allen grundlegenden Funktionen. Dieses könnt ihr als Grundlage für eine Modentwicklung oder zum weiteren Testen verwenden.
Zunächst werden die 8 (so viele habe ich zumindest gefunden) GameScript-Funktionen definiert und unten in der function data() zusammengefasst zurückgegeben. Es empfiehlt sich dabei, beim return in der Tabelle immer nur die Funktionen nicht auskommentiert zu lassen, die gerade wirklich benötigt werden.
In game.res sind alle GameScript-Funktionen (Vanilla und andere Mods) des aktiven Spiels enthalten.
local function getGameTimeStr()
local gametime = game.interface.getGameTime()
local day = gametime.date.day
local month = gametime.date.month
local year = gametime.date.year
local ttime = gametime.time
return string.format("%.2d.%.2d.%d\t(%s)", day, month, year, ttime )
end
local function getRealTimeStr()
return os.date("%Y-%m-%d %H:%M:%S") .."\t".. string.format("(%s)", os.clock() )
end
local function init() -- called once at startup (only new game!)
print("------------INIT------------")
--commonapi.dmp(game.res) -- all gameScript functions
end
local function update() -- called every 0.2 seconds (Game Time)
print("update", getGameTimeStr() )
end
local function handleEvent(src, id, name, param)
if src=="guidesystem.lua" then return end
print("handleEvent:", src, id, name, param )
--commonapi.dmp(param)
end
local function save() -- called as often as update
print("save")
local state=42
return state -- this return is the parameter in load
end
local function load(loadedState)
print("load")
--commonapi.dmp(loadedState)
end
local function guiInit() -- called once at startup
print("------------GUI INIT------------")
end
local function guiUpdate() -- called every GUI frame
print("guiUpdate", getRealTimeStr() )
end
local function guiHandleEvent(id, name, param)
--if name=="visibilityChange" then return end
print("guiHandleEvent:", id, name, param )
end
function data()
return {
init = init,
--update = update,
handleEvent = handleEvent,
--save = save,
--load = load,
guiInit = guiInit,
--guiUpdate = guiUpdate,
guiHandleEvent = guiHandleEvent,
}
end
Display More
4 Funktionen
Die init Funktion wird einmal während des Ladens aufgerufen, aber nur bei einem neuen Spiel.
guiInit wird einmalig nach dem Laden eines Spiels/Spielstands aufgerufen.
Die update Funktion wird alle 0.2 Sekunden aufgerufen. Das hängt zwar mit der internen Spielzeit zusammen (beim Spiel-Geschwindigkeit erhöhen wird sie öfter aufgerufen), aber auch wenn das Spiel pausiert ist, wird sie regelmäßig aufgerufen.
guiUpdate wird mit jedem Frame aufgerufen.
Es gibt verschiedene Script Events im Spiel, auf die mit handleEvent reagiert werden kann. Wenn ihr die entsprechende Zeile oben auskommentiert, werdet ihr merken, dass das Log direkt von Events des Guidesystems geflutet wird, komischerweise auch wenn dieses gar nicht aktiviert ist.
Auch relativ viele Events gibt es bei guiHandleEvent. Fast jeder Klick oder jede Bewegung in der UI wird hier registriert.
Zu save und load siehe unten.
5 Script- und GUI-Thread
Wie ihr vielleicht schon wisst, gibt es seit Transport Fever 2 in LUA verschiedene Threads, die voneinander getrennt sind und unabhängig laufen. Im Script-Thread (Game Thread) werden die grundlegenden Berechnungen und Script-Funktionen ausgeführt, im Gui-Thread alle Benutzerinteraktionen und grafischen Funktionen.
init, update, handleEvent und save laufen im Script Thread.
guiInit, guiUpdate, guiHandleEvent und load laufen im Gui Thread.
Man kann Daten von der Gui zum Script schicken mit game.interface.sendScriptEvent(id, name, param)
Dabei dran denken, dass dieses Event alle anderen aktiven GameScripte auch erhalten können.
6 GameScript State Handling (Eigenarten)
save() und load() werden während eines Fames mehrmals (bei mir 3 mal) abwechselnd aufgerufen.
Wenn ein Spieler sein Spiel speichert, wird zusätzlich save() mehrmals aufgerufen.
Und wenn der Spieler ein Spiel startet, wird load() mehrmals ausgelöst, aber nur für die Game-Threads.
Anzumerken ist auch, das beim laden eines Spielstandes die load() und save() ebenfalls mehrmals ausgeführt werden. Außerdem überprüft TPF2 dabei, ob save() in jedem Fall den gleichen Zustand zurück liefert aus Game-Thread und GUI-Thread. Das ist natürlich meistens der Fall, wenn aber z.B. Zeitstempel hier mit gespeichert werden, werden diese sich in beiden Threads unterscheiden und TPF2 wird mit einer kryptischen Fehlermeldung abbrechen. Empfehlen kann ich dafür nur, Zeitstempel erst im guiUpdate() Funktion zu erstellen und per sendScriptEvent() an den Game-Thread zu schicken.
Der Zustand innerhalb eines GameScripts kann nur durch die Wechselaufrufe von save() und load() vom Game-Thread zum GUI-Thread gelangen.
Anders rum muss game.interface.sendScriptEvent() im GUI-Thread aufgerufen werden und dieser in handleEvent() dann im Game-Thread verarbeitet.
7 Interface und Gui Funktionen
In den jeweiligen GameScript-Funktionen können dann zum Beispiel game.interface oder game.gui Funktionen aufgerufen werden.
Für Transport Fever (1) gibt es eine Dokumentation dieser Funktionen, die einem vielleicht weiterhelfen kann. Für TPF2 gibt es mittlerweile wesentlich mehr Funktionen, die man nun verwenden kann, bei der Anwendung muss man manchmal etwas raten oder ausprobieren, was nicht selten zum Crash führt.
game.gui ist nur im GUI Thread vorhanden und enthält 43 Funktionen:
absoluteLayout_addItem
absoluteLayout_deleteAll
absoluteLayout_setPosition
addTask
boxLayout_addItem
boxLayout_create
button_create
calcMinimumSize
component_create
component_setLayout
component_setStyleClassList
component_setToolTip
component_setTransparent
getCamera
getContentRect
getMousePos
getTerrainPos
imageView_create
imageView_setImage
isEditor
isGuideSystemActive
openWindow
playCutscene
playSoundEffect
playTrack
setAutoCamera
setCamera
setConstructionAngle
setEnabled
setHighlighted
setMedalsCompletion
setMissionComplete
setTaskProgress
setVisible
showTask
stopAction
textView_create
textView_setText
window_close
window_create
window_setIcon
window_setPosition
window_setTitle
game.interface ist in beiden Threads verfügbar, allerdings sind im Gui Thread davon nur 42 Funktionen und im Script Thread 60 enthalten.
Einen Sonderfall stellt dabei, wie schon beschrieben, sendScriptEvent dar.
addPlayer
book
buildConstruction
bulldoze
clearJournal
findPath
getBuildingType
getBuildingTypes
getCargoType
getCargoTypes
getCompanyScore
getConstructionEntity
getDateFromNowPlusOffsetDays
getDepots
getDestinationDataPerson
getEntities
getEntity
getGameDifficulty
getGameSpeed
getGameTime
getHeight
getIndustryProduction
getIndustryProductionLimit
getIndustryShipping
getIndustryTransportRating
getLines
getLog
getMillisPerDay
getName
getPlayer
getPlayerJournal
getStationTransportSamples
getStations
getTownCapacities
getTownCargoSupplyAndLimit
getTownEmission
getTownReachability
getTownTrafficRating
getTownTransportSamples
getTowns
getVehicles
getWorld
replaceVehicle
setBuildInPauseModeAllowed
setBulldozeable
setDate
setGameSpeed
setMarker
setMaximumLoan
setMillisPerDay
setMinimumLoan
setMissionState
setName
setPlayer
setTownCapacities
setTownDevelopmentActive
setZone
spawnAnimal
upgradeConstruction
startEvent -- deprecated
sendScriptEvent(id, name, param) -- nur im GUI Thread verfügbar
Mehr Infos auch hier: Data structures returned by game.interface
Comments 5
Newly created comments need to be manually approved before publication, other users cannot see this comment until it has been approved.
Newly created comments need to be manually approved before publication, other users cannot see this comment until it has been approved.
brunna
Kann mir jemand ein Beispiel für diese Funktion schreiben "replaceVehicle"? Ich habe es mit CommonAPI2 getestet, aber ich konnte die Parameter nicht richtig angeben.
VacuumTube
Habe den Eintrag mal umfassend überarbeitet, sortiert und einige Informationen hinzugefügt.
Danke an alle für die bisherigen Infos
Was save und load angeht, so bin ich mir mit der Angabe, dass diese 3 mal in einem Frame aufgerufen werden, unsicher. Ich hatte den Eindruck, dass save und load ähnlich wie update aufgerufen werden.
melectro
Hat jemand startEvent getestet? Wenn ich dieselbe Zeile verwende, die in TpF1 funktioniert, erhalte ich jetzt eine CTD in TpF2.
Has anyone tested startEvent? When I use the same line that works in TpF1 I now get a CTD in TpF2.
stdout.txt
c:\build\tpf2_steam\src\game\scripting\interface.cpp:2320: auto __cdecl scripting::SetupInterface::<lambda_d632bb7827acf6d5147b617fa6d1eaa7>::operator ()(class lua::State &) const: Assertion `false' failed.
melectro
I have got a quick answer from UG and "startEvent" function is not supported for the moment.
PMV
War mal so frei und hab ein bischen was ergänzt
Verständlichere Formulierungen und noch mehr Infos wären aber sich wünschenswert, damit kann ich aber leider erst mal nicht dienen.
MFG PMV