NIMBY Rails

NIMBY Rails

63 ratings
Mod development guide
By Weird and Wry
How to create mods for NIMBY Rails.
4
   
Award
Favorite
Favorited
Unfavorite
Introduction
At this time NIMBY Rails supports adding new train models to the game, new track kinds, and translation mods. Other kinds of mods may be added in the future. In this guide I will describe how these mods are structured, how to develop and test them, and how to upload them to Steamworks.

NOTE TO MOD AUTHORS FROM THE DEV: all the features described in this guide are correctly loaded and interpreted by game version 1.4 an later, but not all of them are displayed in the UI. In particular some tag sets and train information has no UI yet. Work is ongoing to enable them, and once they are are enabled, any mod that defined them will automatically display the data.
Changelog
Version 1.12
New mod: POIs and demand curves

Version 1.11
StyleLabelDefaults section in map style mods to change map label colors.

Version 1.10
  • New mod: map style mods
  • The game now renders textures using premultiplied alpha. You don't need to do anything to your mod textures, it is handled automatically. If you observed darkening or black borders in your textures when the game rendered them when zoomed out, it should now be corrected in 1.10.

Version 1.8
New TrainUnit properties: max_acceleration, max_regular_braking, max_emergency_braking, max_tractive_effort

Version 1.7
  • All tags are now allowed to appear in TrainUnit, including role tags
  • TrainUnit supports the year_introduced and year_retired properties
  • New cost_per_km property for TrainUnit
  • New allow_player_composition property for TrainUnit, to disable player custom trains using the TrainUnit
  • New front_coupler, front_coupler_mandatory, back_coupler, back_coupler_mandatory properties for TrainUnit
  • New category and description fields for TrainUnit
Concepts
Rules
In the game systems, a rule is an object that contains constant, immutable data that controls parameters of the simulation. Mods are just a collection of rules objects written in an easy to edit text format, plus any required data in separate files, like textures.

IDs
Rules objects usually have an ID field, and often make reference to other rules objects by using their ID. When creating your own mods you must pick unique IDs for your rules objects, not used by the game or by other mods. IDs are arbitrarily long strings so it should be easy to come up with both unique and readable IDs. Do not use spaces or special characters when picking up IDs. Latin characters, digits and underscores are OK.

If you reuse or reference IDs from the base game, the example mod, or from other Workshop mods, players using your mod can experience broken train textures and wrong train parameters. The game does not support sharing IDs between mods, even if they are your own mods.

schema
All the objects declared in the mod have a schema field. This declares the version of the mod structure, and allows the game to load older mods. Use the schema value indicated in the rules tables in the next section.

Train models
The game supports the concept of train models. One or more train models can be defined in a mod, in the sections called "TrainMultipleUnit", which despite the name, are not limited to MUs at all. Then each model can have one or more compositions, and the mod author is free to combine any TrainUnits defined inside their mod in any number in any combinations.
TrainUnit rules
TrainUnit
The rules object TrainUnit represents the combined concept of a train car and/or locomotive. The game makes no distinction, and a TrainUnit may act as both concepts at the same time, depending on how it is configured. It is of course possible to declare a TrainUnit with no power if unpowered cars are desired.

Field
Type
Range
Description
schema
integer
2
This guide describes schema version 2
id
string
any string
Global, unique ID that identifies this TrainUnit
name_loc
string
any string
ID used to localize name_en
name_en
string
any string
English name of the carriage
category
string
any string
English category name to group cars in the train editor car shop listing. Defaults to the mod name if not present.
description
string
any string
English short description of the car. Please respect the little space available for this in the UI.. Use <br> for line breaks.
tags
tag list
one or more tags
Tags that represent this unit. See the Tags section for more info.
year_introduced
integer
any integer
The year this model was introduced
year_retired
integer
any integer
The year this model was retired
length
meters
1 to 30
Length of the carriage
width
meters
1 to 3.75
Width of the carriage
max_speed
km/h
10 to 10,000
Max allowed speed
max_acceleration
m/s/s
0.1 to 10
Max allowed acceleration. If missing the default value is 1 m/s/s
max_regular_braking
m/s/s
0.1 to 10
Max allowed braking in regular operation, like station stops. If missing the default value is 1 m/s/s
max_emergency_braking
m/s/s
0.1 to 10
Max allowed braking in emergencies. If missing the default value is 2.5 m/s/s
max_tractive_effort
kN
0.0 to 10,000
Max allowed force generated by the engine in the TrainUnit. This value is ignored if power is 0. This value is auto generated if missing and the power is non 0. See formulas for more details.
power
kW
0 to 100,000
See the description in formulas
empty_mass
kg
1,000 to 100,000,000
Empty mass of the carriage
price
"cash"
0 to 1,000,000,000
Price for one carriage unit
max_pax
passengers
0 to 1,000
Max allowed passengers
standing_pax
passengers
0 to 1,000
How many of the max_pax are assumed to be standing. This is an optional value, if unknown or 0, do not declare it.
pax_doors_per_side
integer
0 to 100
How many doors in the TrainUnit for pax to enter or exit, per side. A "door" is defined as space for a single pax to enter/exit, so for example a wide door should add 2 to this number. This is an optional value, if unknown or 0, do not declare it.
allow_player_composition
boolean
true/false (default is false)
Allow the player to use this TrainUnit in their own train compositions.
cost_per_km
"cash"
0 to 1,000
Price to pay for every km
cost_per_km_per_pax
"cash"
0 to 1,000
Price to pay for every passenger in this carriage over 1km
cost_per_day
"cash"
0 to 100,000
Price to pay for running this carriage for 1 day
tex_base
relative path
any valid relative path
Base, colorizable texture
tex_top
relative path
any valid relative path
Top texture
tex_decors
relative path list
a list of valid relative paths
List to decorative colorizable textures
tex_decors_titles
list of strings
a list of decor title strings
List an UI title for each of the colorizable textures. This is optional.
tex_m_width
meters
30
This field must be always 30
tex_m_height
meters
3.75
This field must be always 3.75
recolor_base
boolean
true or false
Allow or disallow the player to recolor the base texture
recolor_decor
boolean
true or false
Allow or disallow the player to recolor the decor texture

front_coupler
string list
one or more coupler names
Front coupler(s) of the car. When front_coupler is not present it assumed to be of type "universal"
front_coupler_mandatory
boolean
true or false
True means this car must be always coupled to another car at the front. Default value is false, making it optional
back_coupler
string list
one or more coupler names
Back coupler(s) of the car. When back_coupler is not present it assumed to be of type "universal"
back_coupler_mandatory
boolean
true or false
True means this car must be always coupled to another car at the back. Default value is false, making it optional
Formulas
power, max_tractive_effort and max_acceleration
The train tractive effort is dynamic and depends on the current speed of the train, and thus a dynamic acceleration ramp. It is a bit sped up compared to real life, but not too much. It's also a simplified linear function compared to the more complex curves of real motors.

// tractive effort is expressed in N // K = tuning constant, currently 0.75 // P = total train power in W // V = speed in m/s tractive effort = min(K * P / V, max_tractive_effort) acceleration = min(tractive effort / current train mass, max_acceleration)

The max_tractive_effort of a train is the sum of all the independent max_tractive_effort of each TrainUnit in the train which have power above 0. If this sum is 0, max_tractive_effort = max_acceleration * empty mass.

The max_acceleration of a train is the minimum of all max_acceleration values of the TrainUnit in the train.

If you are in doubt about the values of max_tractive_effort and max_acceleration, I recommend you don't set max_tractive_effort in your mods, and test values for max_acceleration which look good to you. In case of doubt, do not set any of them, and let the game use its default values.
TrainUnit coupling
Starting with game version 1.7 it is possible for players to purchase and edit custom composed trains, not just those defined in TrainMultipleUnit compositions. TrainUnit(s) defined in mods are automatically made available in the custom train editor, but only if they are explicitly enabled by using the allow_player_composition property. This property defaults to false, so modders must opt-in into this new system if they wish their TrainUnit(s) to be usable in the custom train editor.

To give modders finer control over how their TrainUnits are used, 1.7 introduces the concept of couplers in the train editor. When the player is composing a custom train, the couplers declared in the TrainUnit are checked for compatibility, and the game won't allow to purchase or reconfigure a train if one or more of the couplers between two cars are not compatible.

The properties controlling the set of car couplers are front_coupler and back_coupler. These properties must either not be present, which defaults them to universal, or if present, they must list one or more coupler names, separated by a coma. front_coupler_mandatory and back_coupler_mandatory indicate if a car must always be coupled in the front or back.

universal coupler

The universal coupler is the default coupler. It is able to couple with any other universal coupler and with the predefined couplers.

none coupler

The none coupler does not allow coupling with any kind of coupler, not even to the universal coupler. The none coupler overrides the mandatory flag, always behaving as false.

Predefined couplers

The following couplers are officially proposed for interoperability between mods, to avoid making every car universal. Predefined couplers can couple with themselves and with the universal coupler.

buffer-chain
aar
scharfenberg
sa3
dellner
ward

These are modern coupler or coupler families, some of them quite large, like Scharfenberg or AAR.

Custom couplers

TrainUnits can invent couplers which are only used within a mod or even within a specific train, for example for modelling MU trains with custom coupling not compatible with any other train. These couplers won't couple with the universal coupler.

For example if your mod is the usual mass transit EMU which can couple with other full EMUs, you can do this for the head/tail cars:

front_coupler=scharfenberg
back_coupler=my_custom_proprietary_coupler_123

And do this for the inner cars:
front_coupler=my_custom_proprietary_coupler_123
back_coupler=my_custom_proprietary_coupler_123

But what if you wanted to restrict the coupling to just compositions of the same EMU? head/tail cars:
front_coupler=my_custom_outer_coupler_123
back_coupler=my_custom_inner_coupler_123

Inner cars:
front_coupler=my_custom_inner_coupler_123
back_coupler=my_custom_inner_coupler_123

front_coupling_mandatory documents if it is mandatory to have anything coupled to that side of the car. For the previous example EMU head/tail:
front_coupling_mandatory=false
back_coupling_mandatory=true

And for the inner cars:
front_coupling_mandatory=true
back_coupling_mandatory=true

Suggested properties for mod compatibility

In order to make your cars as compatible as possible with other mods, I suggest you always use universal couplers or predefined couplers, none couplers, and the use of the mandatory flag. Using custom couplers to keep MU compositions from being too crazy is also not too bad for compatibility, since MUs cannot be freely mixed and matched.

TrainMultipleUnit rules
TrainMultipleUnit
A TrainMultipleUnit represents a train model and one or more compositions of said model. It's called "TrainMultipleUnit" but it can represent any kind of train, not just MUs. You must declare at least one TrainMultipleUnit containing at least one composition if you want to add new train models to the game

Field
Type
Range
Description
schema
integer
2
This guide describes schema version 2
id
string
any string
Global, unique ID that identifies this TrainMultipleUnit
name_loc
string
any string
ID used to localize name_en
name_en
string
any string
English name of the train model
tags
tag list
one or more tags
Tags that represent this train model. See the Tags section for more info.
description_loc
string
any string
ID used to localize description_en
description_en
string
any string
English long description of the train model. Use <br> for line breaks.
year_introduced
integer
any integer
The year this model was introduced
year_retired
integer
any integer
The year this model was retired
countries_operated
country code list
one or more country codes
A list of ISO 3166 alpha 2 codes[en.wikipedia.org]
countries_built
country code list
one or more country codes
A list of ISO 3166 alpha 2 codes[en.wikipedia.org]
photo
relative path
any valid relative path
A photo in JPG format of the train model, ideally square, but any ratio is valid. The UI will automatically scale and center the photo without cropping. Please keep this file at a reasonable resolution and size (1000px max on the side and less than 1MB, for example)
default_code
string
any string
Default train code. Use # as a digit placeholder.
default_name
string
any string
Default player train name. Use # as a digit placeholder.
composition
string
a valid composition syntax
A train composition. See the composition section.
Train compositions
In TrainMultipleUnit, it is possible to have one or more composition lines, with this syntax:

composition=translation_id,English name of composition,<unit spec 1>,<unit spec 2>,<unit spec 3>,...

Where a unit spec is defined as either just the TrainUnit ID:

unit_id1

Or a TrainUnit ID followed by a min, default and max number of duplications. 0 is allowed as the min and default number:

unit_id1 min default max

Both of the previous syntaxes can end with the word flip to flip the unit when drawing the train. For example:

composition=test1_compo1_name,Test Train,test1_head,test1_car 1 1 3,test1_head flip

Then, when the player goes to purchase a train and selects a "model" (a TrainMultipleUnit in the ini file), they are shown a listing of all the defined compositions for that TrainMultipleUnit, and the player can select one, then edit the counts of each variable unit.

TrainMultipleUnit compositions ignore all the coupling and authoring rules set in TrainUnit(s), since it is the modder themself who is adding them to the mod. TrainUnit coupling and restrictions only apply to players using the in-game custom train editor.

If you are porting a v1 mod, coming up with a compatible composition line is straightforward. This v1 spec:
head_id=test1_head car_id=test1_car tail_id=test1_head tail_flip=true min_cars=1 max_cars=3

Is equivalent to this v2 composition:
composition=test1_comp1_loc,TestTrain MU Compo 1,test1_head,test1_car1 1 2 3,test1_head flip

When porting a v1 mod to v2 with active users of the v1 mod, it's recommended to make the first composition line an equivalent composition to the old v1 mod, to make features like "clone train" work better with in-game.
Track rules
An example of a track mod can be found in github[github.com].

TrackKind
TrackKind represents a track kind in the track editor picker. It conceptually groups all the variations of a track (layer and mode), and it is the only named item. TrackLayer and TrackMode always reference a TrackKind.

Field
Type
Range
Description
schema
integer
1
This guide describes schema version 1
id
string
any string
Global, unique ID that identifies this TrackKind
name_loc
string
any string
ID used to localize name_en
name_en
string
any string
English name of the track kind
tags
tag list
one or more tags
Tags that represent this track. See the Tags section for more info.

TrackLayer
Track kinds always have 3 layers: viaduct, ground and tunnel. TrackLayer rules store the technical values of the track for those levels. They all have default values. When in doubt, don't set a TrackLayer value, and let the game apply its defaults. The exception is price and speed, which are also the visible values in the track UI, and virtually all mods will want to change.

Field
Type
Range
Description
schema
integer
1
This guide describes schema version 1
track_kind_id
string
any string
Global, unique ID that identifies the TrackKind this TrackLayer belongs to
layer
string
viaduct, ground or tunnel
The layer level to configure
max_speed
real
10 to 600
Max speed of the track in this layer, in km/h
price
real
1,000 to 10,000,000,000
Price per km of track in this layer
angle_speed_factor
real
0.1 to 100 (default is 5.21)
When track has curve, speed is capped at angle_speed_factor*sqrt(radius), with radius in meters
station_price_mul
real
1 to 1,000 (default is 10)
Multiply track price per km of station platform with this factor
depot_price_mul
real
1 to 1,000 (default is 25)
Multiply track price per km of depot with this factor
crossing_price
real
1,000 to 1,000,000,000 (default is 125,000)
Price of automatically inserted road overpasses, when those are relevant for the layer
collides_ways
boolean
true/false (default is true)
For the ground layer, specify the track collides with roads and streets. Tram tracks need this setting set to false. Ignored in the viaduct and tunnel layers.
conflicts_tracks
boolean
true/false (default is false)
Change diamonds into track collision conflicts. Useful for track kinds with no diamond intersections like maglevs. Ignored in infinite levels.

TrackMode
Track layers have one of 6 modes. Modes represent the current state of the track, like built or blueprint. This rules object is purely for storing texture paths, and it is also fully pre-populated with default textures by the game. Modders are encouraged to reuse stock textures as much as possible, either by using the "@stock" syntax described later, or just by not setting the texture fields, as the example mod and the stock tracks do.

Field
Type
Range
Description
schema
integer
1
This guide describes schema version 1
track_kind_id
string
any string
Global, unique ID that identifies the TrackKind this TrackMode belongs to
layer
string
viaduct, ground or tunnel
The layer level this TrackMode belongs to
mode
string
built, built_branch, blueprint, blueprint_branch, built_diamond, blueprint_diamond
The mode being configured by this object
border
string
a valid relative path or a @stock path
Border of the track, painted first. Used to give very visual distinct shape for tunnel and viaduct layers.
base
string
a valid relative path or a @stock path
The base of the track, painted on top of the border. Ballast, concrete bases, etc.
sleepers
string
a valid relative path or a @stock path
The sleepers of the track, painted on top of the base.
rails
string
a valid relative path or a @stock path
The rails of the track, painted on top of the sleepers.
TrackMode textures
All textures for all track modes for all layers have default values, picked to look like the stock "High Speed" track. If you want to customise your mod textures, you are free to do so, but you are also encouraged to make heavy use of those defaults and the stock textures using the @stock syntax. The nr-example-tracks[github.com] mod only adds a single texture, yet it's very visually distincting from the stock track.

When authoring textures, keep in mind the transparent pixels must contain valid color values in order for the texture minification to work correctly. For example, by default GIMP discards color information for transparent pixels, and those are assumed to be black, resulting in ugly rendering of the texture when the game generates mipmaps for it. The reason for this effect is explained here:

https://substance3d.adobe.com/documentation/spdoc/padding-134643719.html

mode=built
layer
border
base
sleepers
rails
viaduct
@stock/viaduct/border.png
@stock/viaduct/base.png
@stock/common/sleepers.png
@stock/common/rails.png
ground
@stock/common/empty.png
@stock/ground/base.png
@stock/common/sleepers.png
@stock/common/rails.png
tunnel
@stock/tunnel/border.png
@stock/tunnel/base.png
@stock/common/sleepers.png
@stock/common/rails.png

mode=built_branch
layer
border
base
sleepers
rails
viaduct
@stock/viaduct/border.png
@stock/viaduct/base.png
@stock/common/sleepers_short.png
@stock/common/rails.png
ground
@stock/common/empty.png
@stock/ground/base.png
@stock/common/sleepers_short.png
@stock/common/rails.png
tunnel
@stock/tunnel/border.png
@stock/tunnel/base.png
@stock/common/sleepers_short.png
@stock/common/rails.png

mode=blueprint
layer
border
base
sleepers
rails
viaduct
@stock/viaduct/border.png
@stock/common/base_bp.png
@stock/common/sleepers_bp.png
@stock/common/rails_bp.png
ground
@stock/common/empty.png
@stock/common/base_bp.png
@stock/common/sleepers_bp.png
@stock/common/rails_bp.png
tunnel
@stock/tunnel/border.png
@stock/tunnel/base_bp.png
@stock/common/sleepers_bp.png
@stock/common/rails_bp.png

mode=blueprint_branch
layer
border
base
sleepers
rails
viaduct
@stock/viaduct/border.png
@stock/common/base_bp.png
@stock/common/sleepers_short_bp.png
@stock/common/rails_bp.png
ground
@stock/common/empty.png
@stock/common/base_bp.png
@stock/common/sleepers_short_bp.png
@stock/common/rails_bp.png
tunnel
@stock/tunnel/border.png
@stock/tunnel/base_bp.png
@stock/common/sleepers_short_bp.png
@stock/common/rails_bp.png

The diamond modes are special and ignore all textures except the base texture. The other textures will be picked from the actual mode of the track.

mode
base
built_diamond
@stock/common/base_diamond.png
blueprint_diamond
@stock/common/base_diamond_bp.png
Building rules
An example of a building mod can be found in github[github.com].

BuildingKind
BuildingKind represents a building kind in the track editor building picker. In-game buildings actually represent parts of buildings, like towers, awnings, roofs, floors, track trenches, etc. A BuildingKind is meant to display one of these parts, using a single texture. The way this texture is mapped into a player provided rectangle can be controlled via some attributes of BuildingKind.

Field
Type
Range
Description
schema
integer
1
This guide describes schema version 1
id
string
any string
Global, unique ID that identifies this BuildingKind
name_loc
string
any string
ID used to localize name_en
name_en
string
any string
English name of the building kind
tags
tag list
one or more tags
Tags that represent this building. See the Tags section for more info.
default_draw_layer
string
base, floor, roof
Correspond to the in-game settings of under, over, and roof. The player can change this value.
recolor
boolean
true/false (default is false)
Allow the player to recolor the texture.
rule_x, rule_y
string
repeat, stretch (default is repeat)
Repeat or stretch the texture along the x or y axis
partial_repeat_x, partial_repeat_y
boolean
true/false (default is true)
If repeat is enabled for a given axis, allow the mapped texture to have a final partial segment. In other words, when set to false, the texture will always be repeated whole, without a final partial segment, with some small stretching
border_x, border_y
integer
0 to 62 (default is 0)
If stretch mode is enable for a given axis, set appart some pixels border of the texture. This border won't be stretched in the given axis. This setting is not compatible with repeat mode.
pixel_m
real
1 to 100
Texture pixels per meter. Default is 25.6 pixels/m
price_m2
real
0 to 100000 (default is 1000)
Price per square meter. Setting this to 0 disables blueprint mode for the building.

default_w
real
0.1 to 1000 (default is 5)
Default building width, in meters
default_h
real
0.1 to 1000 (default is 5)
Default building height, in meters
default_attached_offset_bot
real
-100 to 100 (default is -2.5)
Default building track attachment bottom offset, in meters
default_attached_offset_top
real
-100 to 100 (default is 2.5)
Default building track attachment top offset, in meters
default_color
hex
000000 to ffffff(default is ffffff)
Default building color. Only 6 hex lowercase digits are allowed.
decals
IDs
list of building IDs
Only buildings defined in the same mod are allowed. These buildings will be removed from the building listing and instead offered as a decal choice in the building properties of the built building
tex
string
a valid relative path
The building texture. Only 128x128 PNGs images are allowed. Other formats or sizes might be subjected to forced resizing or replacement with a placeholder texture.
Tags
Concepts for tags

Tags are single word or hyphen separated short text strings that give a single fact of information about a game rules object like TrainUnit or TrackKind. Objects can have zero or more tags, and they can have multiple if it applies. For example an hybrid diesel-electric locomotive can have both the tag "electric" and "diesel". Or versatile train models can have both regional and commuter tags. The game uses tags to make it easier for players to find train models when they have many mods installed.

Tags for TrainUnit and TrainMultipleUnit

TrainMultipleUnit will also automatically inherit all the tags present in its TrainUnits.

Car kinds: coach, baggage, cable-car, end-of-train, railbus, locomotive, tank, tender, generator, brake, autorack, battery, control

Car interior: restaurant, bar, lounge, observation, sleeper, sitting, standing, compartments, open-coach, couchette, kitchen, vending, toilet

Role: metro, commuter, intercity, high-speed, tram, light-rail, regional, long-distance, shuttle, people-mover

Gauge: minimum-gauge, narrow, standard, broad, monorail, maglev, tyres

Power: steam, turbine, diesel, electric

Misc: linear-induction, third-rail, cable, heritage, prototype, fantasy, concept, private, hotel, tilting, mu, push-pull

Tags for TrackKind

Gauge: minimum-gauge, narrow, standard, broad, monorail, maglev, tyres

Misc: catenary, third-rail, cable, prototype, fantasy, concept

Tags for BuildingKind

Moddable buildings are as of v1.4.1 purely cosmetic, so the proposed tags only cover cosmetic concepts. These tags have no gameplay effect whatsoever.

Cosmetic: base, floor, roof, wall, decoration, furniture, technical, lighting, barrier, translucent, glass, metal, concrete, rough, smooth, shiny, worn, brick, pavement, shingle, tile, stone, wood, weathered, herringbone, framed, vaulted, domed, vegetation
Map styles
It is possible to change the way the game renders the OpenStreetMap geometry with map style mods. These mods control the way OSM objects are rendered by matching OSM tags to the OSM objects distributed with the game, and then apply some styles like colors, textures or line thickness.

OSM Tags[wiki.openstreetmap.org] express the purpose, role or usage of an object in the OSM database. There's thousands of tag keys and millions of tag-key combinations. For compression reasons NIMBY Rails only supports a subset of around 60 tag keys, and of those keys, only the top 254 values are stored. Despite this a wide range of combinations are possible and little information is lost for the purposes of the game.

Map inspector
To help learn what data is actually available in the game, you can use the new map inspector tool in the debug panel (key: F11). When enabled this tools allows you to position a small "sample rectangle" which then queries the game OSM database for any objects being touched by said rectangle, and displays a list of them. It is most usable when zoomed since, since when zoomed out it will either display too many objects or when above lod 0, it will display too little, due to the way the game starts discarding objects after a certain zoom level.

Tip: inline editing of colors
Map style mods use CSS color syntax, so if you use VSCode, you can install this plugin[marketplace.visualstudio.com] and enable plain text files (like mod.txt) to show interactive color swatches when it detects CSS color syntax.

Matching operators
Once you have an idea of what you want to mod by using the map inspector and by looking at the default map style mod.txt file, you need to come up with matching operators for these objects. Matching operators are simple combinations of a single level deep of boolean logic operators: or, and, and- and not.

Operators are written in the sections of the mod.txt file in this format
(operator kind) (tag key) = (tag value 1), (tag value 2), ...

or
The or matching operator enables a given rule when the given key has one of the listed values.
For example:
[StyleLine] or highway = primary, primary_link color = #ff00ffff
This will enable all OSM objects tagged highway=primary or highway=primary_link to be displayed as a line, and then use some ugly color to do so. Now imagine you also want to do this to rivers. You could write a second StyleLine rule just for rivers, but since or activates with just one match, you can combine both:
[StyleLine] or highway = primary, primary_link or waterway = river color = #ff00ffff
One or more or matches might fail, but as long as one or is accepted, the rule is accepted (as long as the and and and not operators match too). If at least one or operator is present in a rule, it means at least one or must match, otherwise the rule fails.

and
The and matching operator enables a given rule when the given key has one of the listed values. All and operators in a rule must be true for the rule to be accepted. A common use for this rule is to apply area styles to OSM objects which are usually line features:
[StyleMesh] or highway = pedestrian and area = yes color = #ff00ffff
This would make all closed pedestrian highways a filled area with some ugly color.

and not
The and not matching operator is the same as and, but it only accepts a rule if does not match any of the values in its list. A rule cannot be composed exclusively of and not operators. At least one or or one and operator must be present, otherwise the rule will always fail.
Palette style rules
StyleLandPalette

StyleLandPalette allows to change the color palette used by the map land texture. This is a table of 37 colors, each assigned to an index. Each index has a category of terrain assigned to it. To change one or more palette colors, use a section in your mod.txt file like:

[StyleLandPalette] index 4 = #e5e0d6 index 10 = #aac800 index 18 = #8ca000

For a reference of the meaning of every index, check the default game styles in the file <steamapps install folder>/resources/styles/mod.txt.

StyleLabelDefaults

StyleLabelDefaults allows to change the colors used by the map label text. To change one or more colors, use a section in your mod.txt file like:

[StyleLabelDefaults] region_color_fg = #aa33aaff region_color_bg = #ddddddff main_color_fg = #222222ff main_color_bg = #ddddddff poi_color_fg = #444444ff poi_color_bg = #ddddddff

StyleSpeedPalette

StyleSpeedPalette allows to change the color palette used by the track speed overlay. You must provide a single RGBA PNG file, sized up to 600x1, where each X coordinate point represents a km/h color. Add a StyleSpeedPalette section in your mod.txt file like:

[StyleSpeedPalette] texture = myspeed.png

StylePopulationPalette

StylePopulationPalette allows to change the color palette used by the population. You must provide a single RGBA PNG file, sized up to 2000x1, where each X coordinate represents an amount of people living in a given texture pixel at the game most detailed level (other levels use an extrapolation of this value). Add a StylePopulationPalette section in your mod.txt file like:

[StylePopulationPalette] texture = mypop.png
StyleMesh rules
OSM objects which form one or more closed shapes can be rendered as an area if they match one or more StyleMesh rules.

Field
Type
Range
Description
or, and, and not
match operator
any string
See the previous section on how to write matching operators
display
boolean
true or false
Allows to explicitly disable the display of the matching objects
texture
string
a valid relative path
Texture to use in this rule. 64x64 RGBA PNG files are recommended. Other resolutions will be resized with no guarantee of quality
color
RGBA CSS hex color
#00000000 to #ffffffff(default is #ffffffff)
Color to modulate the texture. If your texture does not need to be modulated you can leave this out
StyleLine rules
OSM objects with more than one node can be rendered as a line if they match one or more StyleLine rules.

The central concept of rendering lines is line thickness. There's two kinds of line thickness: pixel thickness and physical thickness. Pixel thickness is expressed in 1/10 pixel units and directly corresponds to the on-screen pixel thickness of a line (times the UI scaling factor). Physical thickness scales with the zoom level, just like it is done with tracks, and it can scale down to almost 0 if zoomed out enough. This is useful to hide spammy objects like waterways. But if you want to make sure some other objects remain visible, like highway=motorway, you can specify both thickness, and the game will pick whatever results in a thicker line for the current zoom level.

Lines are rendered twice. By giving different thickness values to each render pass, you can achieve border-like effects which fuse together between lines. Refer to the game built-in mod.txt styles for examples of both border and non-border lines.

Field
Type
Range
Description
or, and, and not
match operator
any string
See the previous section on how to write matching operators
display
boolean
true or false
Allows to explicitly disable the display of the matching objects
visual_layer_bias
integer
-5 to 5
Changes the visual OSM layer of the matching object by adding the value to the existing value in the database. This is only for the rendered value. Track vs map collision is not affected by this
pass1_texture
pass2_texture
string
a valid relative path
Texture to use in this rule. Internally stored at 128x64 resolution, but there's no need to match this. If your texture has a different size or ratio, feel free to use it, but it's not recommended to exceed either 128 width or 64 height
pass1_color
pass2_color
RGBA CSS hex color
#00000000 to #ffffffff(default is #ffffffff)
Color to modulate the texture. If your texture does not need to be modulated you can leave this out
pass1_half_stroke_phys_mm
pass2_half_stroke_phys_mm
integer
0 to 16000
Physical half thickness of the line, in millimeters
pass1_half_stroke_px_dec
pass2_half_stroke_px_dec
integer
0 to 120
Half thickness of the line, in 1/10 pixels
Mod structure and development
A mod is just a folder that contains a file named mod.txt. This file is formatted using the simple INI syntax[en.wikipedia.org]. Inside mod.txt you must declare at least one ModMeta section, and then one or more rules sections.

Folder structure
Here's how the structure of folders and files looks like when you are developing a mod in your local PC. If you put your mod in the "mods" folder of your local saved games folder, it will show up in game. In this example you are developing a mod inside the folder "nr-example-train".


The only mandatory file is "mod.txt". You can also create any other folders and files required to organize and bundle textures in your mod.

Example mod.txt

This mod.txt file can be found in the example mod github[github.com].

ModMeta
The ModMeta section gives some general information that is displayed to the user in the game UI.

Field
Type
Range
Description
schema
integer
1
This guide describes schema version 1
name
string
any string
Mod name used in the in-game UI
author
string
any string
Mod author name used in the in-game UI
desc
string
any string
Mod description used in the in-game UI
version
string
any string
Mod version used in the in-game UI

Steamworks uploader
In order to create and upload your mods to Steamworks, you first need to make sure your mod works properly as a local mod, and have it installed as such. So the first step is to develop your mod as a local mod, in your local mods folder as described in the first section of this chapter.

Once your local mod is ready, you can use the Steamworks mod uploader available in the Main Menu, Options dialog.


By default the mod uploader shows a listing of your authored mods already uploaded in Steamworks, and a series of options.
  • New mod: Create a new mod from an existing local mod
  • Update mod: Update the selected Steamworks mod from an existing local mod
  • View mod in workshop: view the selected Steamworks mod in the Steam overlay
  • Manage workshop: view your private Workshop in the Steam overlay
  • Mod development guide: opens a browser with this guide

When you select "New mod" or "Update mod" the mod editor is displayed:


You must fill all the displayed fields before you can create or update an existing Steamworks mod.
  • Title: mod title to display in the Workshop
  • Visibility: decide if you want to make your mod public or not. You can change this option later if you update your mod again
  • Local mod to upload: select an existing, local, valid mod as the contents for this Steamworks mod
  • Icon file: type a path or open a file picker to select an image to display in the Workshop as the mod icon
  • Description: mod description to display in the Workshop
  • Update note: mod change log to display in the Workshop (only for updates)

You may find it easier and more usable to edit the description and update note fields from the Workshop interface than from the uploader interface, after the uploader is done creating or updating your mod.

Textures for trains
Train units are drawn with 3 texture layers: base, decal and top. Every layer is a full RGBA 32bit PNG image of size 1024x128, which always corresponds to a real world size of 30m x 3.75m. Train units can be shorter and narrower than this size, in that case the unit must be aligned to the left of the image for the X axis, and centered in the Y axis. The real size of the train unit is then declared in the TrainUnit section of the mod.txt file (in meters, never in pixels). Use a conversion factor of 34.13 pixels per meter when drawing the textures.

You can store the PNG files in the mod folder with any name, and with any subfolder structure you wish to use, as long as you use correct relative paths to reference them from the mod.txt file.


The base layer is drawn first, and it can be colorized by the user in the train properties editor. For this reason it is recommended to make this layer grayscale. The colorization function is a simple component-wise color multiplication. Use this layer to represent the bulk of the train unit.


The decal layer is drawn on top of the base layer. It is also colorizable by the user, and uses the same multiplication function. It is possible to declare a series of decal layers in the same TrainUnit by separating them with commas. The user will then be capable of choosing one in the train properties editor. Use this layer as a decoration, to give the user more customization options. The user can also colorize this layer with the line colors.


The top layer is drawn on top of the decal layer, and it is not colorizable. Use this layer for final details and for parts of the train that should never be colorized, like windows.
Translation mods
Translation mods are structured in the same way as any other mod: a main mod.txt file which declares generic information about the mod, and then one or more sections declaring which language is being supported, and which dictionary files are to be loaded. Read the "Mod structure and development" section on how to create local mods for development, and how to upload your work to Steamworks.

Source translation
To make a new translation you will need to customize the official Spanish translation[github.com] to your language. This translation is kept up to date with the game, so on new versions you will be able to also track the changes in your own translations. Edit the mod.txt and game.po files as described in the rest of this guide.

mod.txt for translation mods
Make sure to read what is the mod.txt file and its basic ModMeta section as described earlier in this guide. This is a complete example of a mod.txt file for the Spanish translation:

[ModMeta] schema=1 name=Traducción al Español author=Weird and Wry version=1.0.0 [Language] code_iso_639_2=spa name=Español [LanguageDict] code_iso_639_2=spa po=game.po

There are 2 translation specific sections: Language and LanguageDict.

Language section
The language section identifies a language to be displayed in the options drop down in the game Options dialog. If no Language section is provided, the game will never display the option to select a language, even if a LanguageDict and a PO file is present.

Field
Type
Range
Description
code_iso_639_2
string
an ISO 639-2 code
Languages are identified by ISO 632-2 codes
name
string
any string
Native name of the language

Only use ISO 632-2, 3 letter codes to identify languages, do not make up any random code or use the 2 digit codes. You can look up the codes in this page[en.wikipedia.org]. Also always use UTF-8 file encoding, both in mod.txt files and PO files. No other encoding is supported.

LanguageDict section
The LanguageDict section declares a PO file as a translation dictionary for a given language ISO 632-2 code.

Field
Type
Range
Description
code_iso_639_2
string
an ISO 639-2 code
Languages are identified by ISO 632-2 codes
po
string
a relative path
Relative path to a PO file

The PO file
Translation dictionaries are a simple text file that repeatedly lists the strings to translate in the game. Edit PO files with any text editor that supports UTF-8 editing. For every string you will find 3 entries: msgctxt, msgid and msgstr. For example:

msgctxt "host_game" msgid "Host game" msgstr "Hospedar partida"

  • msgctxt: the internal identifier for the string. Do not edit this text. It often gives extra information about the context of the string.
  • msgid: the original english text of the string.
  • msgstr: the translated text of the string. This is the text you have to edit.

Sometimes strings contain arguments, indicated as {}, and your translated text MUST preserve these arguments. For example:

msgctxt "banner_join_friend" msgid "{} wants to join your game." msgstr "{} quiere entrar a tu partida."

You can locate the {} argument anywhere inside your string. Sometimes there's more than one argument, and this is indicated by a series of arguments like {0} and {1}:

msgctxt "banner_prompt_nogame_join_user" msgid "{0} proposes you to join a game hosted by {1}. Do you want to join?" msgstr "{1} hospeda una partida y {0} te propone entrar a jugar. Quieres jugar con ellos?"

Again, if an argument is present in msgid, it MUST also be present in msgstr, but it does not have to be in the same position. Reorder arguments as you see fit for your language. Occasionally you will also come across more complicated arguments like {2:+.2f}. Just locate them in any position you decide, but don't modify the contents. The game will actively reject your translated strings if their arguments don't match, and it may even crash if your translated string is too malformed.

To test your translation in-game, first make a local mod in your Saved Games folder as described earlier in this guide. If it's created properly it will show up in the Languages drop down in the Options dialog, and it can be immediately activated. Keep in mind a few strings are not properly updated when changing languages this way, like tab titles. Restarting the game will fix this.
POIs and demand curves
POIs and demand mods introduce immutable gameplay elements in the player save, under control of the mod author. POIs from these kinds of mods provide pax under a certain demand curve, and show up as labels in the map, but cannot be moved, edited or deleted. Demand curves show up in the curve editor but cannot be deleted or edited. Players can use these curves for their own buildings.

DemandCurve
The rules object DemandCurve gives name and ID to a given curve TSV file. This TSV format is identical to the one used by the in-game curve export feature.

Field
Type
Range
Description
id
string
any string
Global, unique ID that identifies this DemandCurve
name
string
any string
Name for the curve
tsv
string
a valid relative path
Path to the curve TSV file

POILayer
The rules object POILayer gives name and ID to a given TSV bulk listing of POIs. The ID is used for mod enabling/disabling. The name is used in the Company Options window for managing mod POI layers.

Field
Type
Range
Description
id
string
any string
Global, unique ID that identifies this POILayer
name
string
any string
Name for the POI layer
tsv
string
a valid relative path
Path to bulk TSV POI file

An example mod.txt for a POI/demand mod: https://gist.github.com/carloscm/5580b7c5b0b5a1013a3a13df4f3bc837

Bulk POI TSV files must contain a single table with the following columns:

Field
Type
Range
Description
lon
real
-180 to 180
Longitude of POI
lat
real
-90 to 90
Latitude of POI
color
hex
any valid 6 hex CSS color
Color of POI label
text
string
any string
Text of POI label
font_size
integer
0 to 2
Font size, numbered as the options of the in-game label font options
max_lod
integer
0 to 10
Maximum zoom level to display the label text. If the POI contains population it will be displayed as a low priority small icon over this zoom
transparent
boolean
0 or 1
Display text label as stroked or with a solid background
demand
string
any string
Demand ID as used in the DemandCurve id field. If it is "default" it will use the default game curve. If empty this POI is considered a purely cosmetic label, even if it has population.
population
integer
0 to 100000000
Population in this POI, if it has a demand curve

See this gist for an example: https://gist.github.com/carloscm/8f7f7250bb9228873c3ad5cbf54220eb

Mods cannot supply more than 10K POIs between all their POILayer-s, but the player can enable any number of POI mods, in excess of more than 10K POIs.

Demand curve formula

As of 1.12.6 this is the pax spawn formula used in the pax sim, code directly copy pasted:
// base demand is guessed to be 25% population as pax/day // why 50% in formula? it's aiming for 25%, but that would be for the final tally of the day, ignoring time demand fluctuations, // so it is doubled to compensate, as another guess. this is actually a bit low for the default curve. // population has already been time-modulated at this point double pax_s = population * 0.50 * (1.0 / 86'400); // world factor is a multiplier for pax_s // it is always at least one, and it can grow indefintely, but it has a logarithmic grow rate // world_weight has already been time-modulated at this point, and it is always distance modulated double world_f = std::max(1.0, (std::log(world_weight) * (1.0 / std::log(2.5))) * 0.1); // elapsed_time is the elapsed time for the current sim frame, in s // company_global_demand is the Company option, expressed as 100% = 1.0 // the amount of pax to spawn in this frame is pax_s * world_f * elapsed_time * company_global_demand // the decimals of this final result are added to the next frame