NMLTutorial/NML Syntax
This page will be a theoretical introduction to the NML syntax. It is very well possible that you do not understand anything of what is written here. If that is the case, please read the page carefully once and then just continue with the next page which will take you on an example journey through the world of coding in NML.
If you do understand this page completely, then it is very likely that you do not need the rest of this NML tutorial and just can look up what you need in the NML Documentation. At your option, you can browse through the tutorial quickly or just start making your NewGRF and figure things out as you go.
NML is a programming language
NFO is not a programming language: it is hex editing. NML on the other hand is a human-readable programming language and contains things like if/else statements, operators such as &&
, ||
, !=
, >=
etc. and curly brackets to enclose groups of statements.
If you already know a programming language such as C++ or PHP, it is probably very easy to understand and adapt to the NML syntax. If you are new to programming, you should understand a few concepts:
If you are new to programming
- Name things the way you want
- If you define something, it is you who gives it a name. There is no set of rules how things should be called. If you want to name everything foo0001 through foo9999, that is up to you. That does not make it very easy to remember what is what, but it is not wrong per se. However, do not use any fancy symbols in names: you may use (capital) letters a through z, numbers 0 through 9 and an underscore (_). Numbers furthermore cannot be at the beginning of the name of an identifier.
- When referencing something, it needs to exist
- If you want to use a string, the string needs to exists. That probably makes as much sense to you as that a circle is round, right? But it goes further than that: if you define a vehicle and want it to use this and that graphics, the graphics need to be defined before you define the vehicle that uses it. This applies to everything; if your reference something somewhere, that something needs to exists and essentially needs to be before the reference.
- Compilers do not understand typos
- If you make a typo, the compiler (in this case nmlc) will not understand what you mean. This especially applies to the names of things we discussed earlier. If you name your vehicle definition KirbyPaulTank, then you must use the exact capitalization everywhere. For a compiler, Kirbypaultank, kirbypaultank, kirbypaulTank etc. are all completely different things. You need to pay special attention to this if you come from Windows. In Windows, there is no difference if you give a file or directory a name with or without captials; in a programming language, with or without capitals is a completely different thing and essentially a typo when not used consistently.
- Add more comments than you think is necessary
- When working on a piece of code, you easily remember what does what and why you made it the way you did. However, if you do not, look at a piece of code for a couple of months, chances are you have no clue what it does if you have not commented it. Using decent names for definitions helps a great deal towards reducing the amount of comments, but ideally every code block should have a line stating what it is for and more complicated things also get comments as you go. If you do certain things differently for a reason, state why you did that. If you work with more people on one project, comments are vital in helping fellow developers understand why you did what.
NML structure: blocks
NML files are mainly composed from blocks. A block starts with the type of the block, optional arguments and then the contents enclosed by curly braces. Some blocks are supplied with an identifier (a name) as argument, some with even more arguments and some are only referenced by their type. The basic block syntax is as follows:
type { <block_contents> }
or
type <identifier> { <block_contents> }
or
type (<identifier>, <argument1>, [<argument2> [,...]]) { <block_contents> }
Note: the identifier is not necessarily the first argument of a block in case of multiple arguments.
When a block syntax is explained in the tutorial, some arguments may be optional. These are written between [ straight/square brackets ]. These brackets indicate that something is optional; the brackets are not part of the actual NML code. [,...]
means that you can have any number of arguments of the same type as the previous argument. Things between < pointy/angular brackets > must be replaced with actual content. What this content must be is explained directly after the introduction of the block's syntax. Also, pointy brackets are not part of actual NML code. Round and curly brackets are part of the NML code and must be written as indicated. Words or text not between pointy brackets is also part of the NML code and must appear as indicated.
Currently, the following block types exist:
- grf
- item
- recolour_sprite
- template
- spriteset
- spritegroup
- spritelayout
- tilelayout
- switch
- produce
- random_switch
- cargotable
- railtypetable
- error
- disable_item
- deactivate
- engine_override
- replace
- replacenew
- font_glyph
- alt_sprites
- town_names
- snowline
- basecost
Some special "blocks", which are not really blocks, but language constructs, exist as well:
- if
- else
Furthermore, there are too many functions to list here. For now, you can forget these if you want, but it is good to have seen their names.
The blocks define things that can be referenced or make new things available in the game. Some blocks can contain sub-blocks. The grf
block can contain a param
subblock to add parameter settings. The item
block can contain property
, graphics
and livery_override
subblocks.
If you need to do some calculations, you do those outside the blocks, or in some special cases, in a block's expression argument, but never inside a block. if
s and else
s also have no place inside blocks and can only be used outside them.
Identifiers
As you have read earlier, some blocks need to have an identifier. You can see this identifier as a name for the block and you choose this identifier yourself.
All identifiers need to be unique throughout you NML file! This even applies for identifiers of totally different blocks. An easy method to avoid duplicate identifiers is to precede the identifier name with the name of the block type. While this is not foolproof, it simply reduces the chances for duplicates. It is also a good idea to add the name of the item (vehicle, object, etc.) an identifier belongs to to the name of the identifier.
With both the block name and item name in the identifier, you can also see quickly from the identifier what this is for, without having to write that down somewhere seperately.
Features
Some blocks can do multiple things. For example, the item
can define a train, but also a road vehicle, ship, or aircraft. In case of such a block, you need to set which feature the block is for and supply this to the block as an argument. Below is a table of available features:
Name | Description |
---|---|
FEAT_TRAINS | Trains |
FEAT_ROADVEHS | Road vehicles |
FEAT_SHIPS | Ships |
FEAT_AIRCRAFT | Aircraft |
FEAT_STATIONS | Train stations |
FEAT_CANALS | Canals |
FEAT_BRIDGES | Bridges |
FEAT_HOUSES | Town houses |
FEAT_GLOBALVARS | Various global variables |
FEAT_INDUSTRYTILES | Industry tiles (visible part of industries) |
FEAT_INDUSTRIES | Industries |
FEAT_CARGOS | Cargo types |
FEAT_SOUNDEFFECTS | Sound effects |
FEAT_AIRPORTS | Airports |
FEAT_SIGNALS | Train signals |
FEAT_OBJECTS | Non-interactive objects (example: lighthouse) |
FEAT_RAILTYPES | Rail types |
FEAT_AIRPORTTILES | Airport tiles (visible part of airports) |
Comments
As said before, it is useful to add comments to your NML code to indicate what things are for.
Comments can be written as comment block or as inline comment. Comment blocks go between different lines of NML code, while inline comments can also go there as well as at the end of each line.
Comment block example:
/* This is a comment block, it starts with a slash and an asterisk. It can span multiple lines Before it ends with an asterisk and a slash */
Inline comment example:
// An inline comment is preceded by two slashes // If you need multiple lines, each line gets two slashes in front grf { name: string(STR_GRF_NAME); // it can also go at the end of a line }
Conclusion
Yes, that is a bunch of stuff with little hands-on examples, but I warned you about that. You will find (most of) these different blocks explained in the remainder of this tutorial. And, of course, how every block should be written is clearly documented in the block syntax chapter of the NML Documentation.
NML Tutorial: NML Syntax