The Map.lua file defines all runtime logic for a map in SubwaySim 2.
It is responsible for:
This page explains the structure and purpose of the Map.lua file and how it interacts with elements placed in the Unreal Editor (tracks, signals, Station Definitions).
The Map.lua acts as the central configuration and logic file for a map.
While the Unreal level defines the physical layout (tracks, stations, signals), the Map.lua defines the operational behavior:
Without a Map.lua, a map:
Before working with the Map.lua, make sure that the following steps are completed:
→ Railtool
(these values are referenced directly by the Map.lua and must not change later)
The Map.lua relies on these elements being set up correctly and consistently in the level.
This page explains the structure of a Map.lua file for SubwaySim 2. It follows the file from top to bottom and explains each section in detail.
The Map.lua consists of two major parts:
-- -- -- SubwaySim2 -- Module TestMap.lua -- -- Map file for SDK TestMap -- -- -- Author: SDK User -- Date: __/__/____ -- ---@class TestMap : TestMap, BaseMap TestMap = Class("TestMap", TestMap, BaseMap); ---@type SSB_Map_DataTable local TestMap_DataTable = { contentType = "map", contentName = "TestMap", class = TestMap, levelName = "Testmap", author = "$GameDeveloper", title = "SDK TestMap", subtitle = "This could be your text", description = "This is a test map that demonstrates how to use the Modding SDK. You can also use it for testing your own vehicles.", previewFilename = "/SubwaySim2_Core/UI/MainMenu/Backgrounds/CityBerlin.CityBerlin", }; g_contentManager:addContent(TestMap_DataTable); --- Creates a new instance of this class ---@return TestMap function TestMap:new() self = TestMap:emptyNew(); self.levelName = "Testmap"; self.displayName = "SDK TestMap"; -- latitude and longitude of Berlin's city center self.latitude = 52.518611; self.longitude = 13.408333; -- UTC+1 self.timezone = 1; self:loadStations(); -- Timetables define AI service patterns. -- Full guide: manual:subwaysim:map_construction:create_timetable self:loadTimetables(); self:loadCareerMode(); EventManager.callModListeners("onMapCreated", self); return self; end; --- Event to load any additionally required level instances function TestMap:loadLevelInstances() assert(GameplayStatics.loadLevelInstance("SubwaySim2_Environment", Vector3.zero, Vector3.zero), "Failed to load a part of the level"); end; --- Loads the station definitions for this map function TestMap:loadStations() ---@type table<string, Station> self.stations = {} self.stations.TS = Station:new("TS", "TestStation") :addSpawnPlatform("1", 115, 2) :addSpawnPlatform("2", 115, 2) self.stations.TSD = Station:new("TSD", "TestStation Depot") self.stations.TSA = Station:new("TSA", "TestStation Anfang") :addSpawnPlatform("1", 110, 1) :addSpawnPlatform("2", 110, 1) self.stations.DP = Station:new("DP", "Depot") :addSpawnPlatform("51", 220, 2) :addSpawnPlatform("52", 220, 2) :addSpawnPlatform("53", 220, 2) :addSpawnPlatform("54", 220, 2) :addSpawnPlatform("60", 110, 2) end --- Loads the default timetables for this map --- Full guide: manual:subwaysim:map_construction:create_timetable function TestMap:loadTimetables() -- (Implementation depends on your map and service patterns) end --- Initializes data for career mode function TestMap:loadCareerMode() end; --- Registers all valid timetables to the given `controlCenter` instance ---@param controlCenter ControlCenter function TestMap:registerTimetables(controlCenter) controlCenter:setStationList(self.stations); controlCenter:setTimetableList(self.timetables, self.dispatchingStrategies, self.depots); end;
The DataTable registers the map in the ContentManager. Without it the map will not appear in the map selection menu and cannot be loaded.
| Field | Description |
|---|---|
| contentType | Defines the type of content. Must be `“map”`. |
| contentName | Unique internal identifier for this map across all mods. |
| class | Reference to the Lua map class that provides runtime logic. |
| levelName | Unreal level (.umap) name that will be loaded. Must match exactly. |
| author | Author metadata (UI / debugging). |
| title | Map title shown in the selection menu. |
| subtitle | Optional subtitle below the title. |
| description | Longer description shown in UI. |
| previewFilename | Path to the preview image used in the main menu. |
After the table is defined, it must be registered:
g_contentManager:addContent(TestMap_DataTable);
If this call is missing, the map is not registered and will never load.
The runtime class is responsible for everything that happens when the map is loaded:
TestMap = Class("TestMap", TestMap, BaseMap);
This creates a new map class inheriting from `BaseMap`.
The constructor creates the map instance and prepares the runtime data.
| Property | Description |
|---|---|
| self.levelName | The Unreal level to load (must match DataTable `levelName`). |
| self.displayName | Internal display name used at runtime. |
| self.latitude | Used for sun position and environment lighting. |
| self.longitude | Used for sun position and environment lighting. |
| self.timezone | Timezone offset for day/time simulation (UTC+1 = 1). |
The order matters:
EventManager.callModListeners("onMapCreated", self);
This allows other mods or systems to react when the map instance is created.
GameplayStatics.loadLevelInstance("SubwaySim2_Environment", Vector3.zero, Vector3.zero)
This call loads the shared environment level used by SubwaySim 2.
It provides:
By loading this prepared level, mod maps can use the full weather and lighting system without accessing license-protected core code.
⚠️ Current limitation: Loading multiple levels that contain Railtool Blueprints can cause issues. At the moment, only one loaded level should contain Railtool infrastructure. This limitation will be resolved in future updates.
Stations defined in `loadStations()` must match BP_StationDefinition actors placed in the Unreal Editor.
The connection is made via the station short name:
If the short name does not match exactly:
Stations are stored as a keyed table:
---@type table<string, Station> self.stations = {}
The key is usually identical to the short name:
Creating a station:
Station:new("TS", "TestStation")
| Parameter | Description |
|---|---|
| `“TS”` | Short name (station code). Must match BP_StationDefinition Name Short. |
| `“TestStation”` | Display name shown in UI. |
Spawn platforms define where trains may spawn for player spawning (depending on map setup)
Example:
:addSpawnPlatform("1", 115, 2)
| Parameter | Description |
|---|---|
| `“1”` | Platform number as defined in BP_StationDefinition platform array. |
| `115` | Max allowed train length in meters for spawning at this platform. |
| `2` | Spawn direction on the track (orientation). Must match your platform Begin/End marker direction logic. |
⚠️ Important Platform numbers are not “free”. They must match exactly the platform configuration inside BP_StationDefinition.
Timetables define the AI service pattern of a map.
They control:
In Map.lua, timetables are created inside:
This function acts as the integration point for all timetable-related data. It typically references or initializes:
The complete timetable workflow — including templates, stops, composition weights, service repetition, depot handling, and dispatching logic — is documented separately:
This page intentionally does not describe timetable creation in detail, to keep the Map.lua documentation focused on structure and integration.
Career Mode configuration is optional.
If your map does not support career mode, `loadCareerMode()` may remain empty.
If implemented, this function defines:
All career mode logic is map-specific and independent from timetable creation.
After stations and timetables are defined, they must be registered with the ControlCenter:
controlCenter:setStationList(self.stations); controlCenter:setTimetableList(self.timetables, self.dispatchingStrategies, self.depots);
| Call | Description |
|---|---|
| setStationList | Registers all stations for routing, UI, and spawning logic. |
| setTimetableList | Registers AI services together with dispatching and depot logic. |
If this step is missing or incomplete: