NMLTutorial/Starting an NML file
It's time to create your first NML file! All this reading must have made you hungry by now, so let's continue right quick.
Where to put the NML file
From the Language files section you may remember this folder structure:
mygrf |- lang | |- english.lng | |- other_language.lng |- custom_tags.txt |- mygrf.nml
This is exactly where the NML file should go: one folder above the language files, and in the same folder as custom_tags.txt. You can name the file any way you want, but try to avoid spaces. Create it either from the file explorer or from your text editor.
The grf block
Nearly all NML files will start with a grf block. The grf block takes no parameters and is one of the simplest blocks there is. Your NML file also has no more than one grf block. NML files without a grf block are only useful for base graphic files such as the grf files from OpenGFX. So chances are that you need a grf block.
The general syntax of the grf block is:
grf { grfid: <literal-string>; name: <string>; desc: <string>; version: <expression>; min_compatible_version: <expression>; }
We'll be looking at every line of this grf block and give you an example how to fill it in.
Line 1: grf {
This indicates the start of the grf block and the opening curly bracket defines the start of it's contents. Just type it in your editor:
grf {
Line 2: grfid: <literal-string>;
This defines the GRFID of your NewGRF. A GRFID is a four-byte string that should be unique. If two NewGRFs have the same GRFID, only one can be used at a time and in OpenTTD chances are only one shows up in the NewGRF list. It's a convention to use the first two bytes for the creator's initials. The last two bytes should be numbers, typically the first number identifying which of the author's sets this is, and the second number being a version number.
The GRFID must be written as a quoted string, but now comes the complicated part: one character is one byte, but one byte is two hexadecimal characters. The first two bytes are best written as a normal quoted string. The last two are best written as escaped bytes, as this allows for 256 sets/versions rather than just 10.
So if your name is Simon Foster and you're starting the first version of your first set, your GRFID can be "SF\01\01"
. This way, your GRFID is exactly four bytes: two normal characters S and F, and two escaped bytes 01. The backslash indicates that the next two characters together make one byte. When using escaped bytes, remember that they are in hex and as such can only consist the numbers 0-9 and letters A-F. See Wikipedia for more details on the hexadecimal numeral system.
Now your second line of the nml file will be (but then with your own GRFID):
grfid: "SF\01\01";
Line 3: name: <string>;
This is the name of your NewGRF, as displayed ingame. NML expects a reference to a string in your language file here. Luckily, we already put a name for our NewGRF in there. Open the language file and check what name you attached to the string. In our example that would be STR_GRF_NAME
.
To tell NML to use a string from the language file, you have to use the function string(<string-name>)
, replacing <string-name>
with the actual name of the string. This gives us string(STR_GRF_NAME)
.
Put this in the third line:
name: string(STR_GRF_NAME);
Line 4: desc: <string>;
The description is similar to the name of the NewGRF. Again look up the name of the string in the language file and use the string()
function to put it in the grf block. Line four:
desc: string(STR_GRF_DESCRIPTION);
Line 5: version: <expression>;
Another version? Yes. If your NewGRF is aimed at (but not limited to) OpenTTD you should use this to indicate the actual version and leave the GRFID constant. If your NewGRF is aimed at (but not limited to) TTDPatch you still should provide this version number, but bump the GRFID whenever a new release is not compatible with the previous release. More on this at the end of this page.
The first release of your NewGRF will start with version 0 and every subsequent release you will increase this value. In case you use a version control system, set this to the revision number.
This gives line five:
version: 0;
Line 6: min_compatible_version: <expression>;
For the first release, the minimal compatible version is also 0. Leave it at 0 for as long as subsequent releases are compatible with all previous releases. Whenever you need to break compatibility, you set this to the version number (from above) in which you have broken the compatibility.
Line six:
min_compatible_version: 0;
Line 7: }
The closing curly bracket defines the end of the grf block; it closes it if you will.
Just type a curly bracket on line seven:
}
Summarizing
Already? You haven't told us about the semicolons at the end of each line in the grf block!
I haven't? Well, let's do that now then...
Semicolons
Like in a lot of other programming languages, a semicolon defines the end of an instruction. You must use these at the end of every instruction inside and outside a block, but not on lines that open or close a block (with a curly bracket).
The usage of semicolons in NML is identical (with the exception of templates) to that in Java or C-family (or PHP) languages. It allows to have multiple instructions on one line, which is useful in some cases.
Summarizing
This completes our grf block. All lines together, the example looks like this:
grf { grfid: "SF\01\01"; name: string(STR_GRF_NAME); desc: string(STR_GRF_DESCRIPTION); version: 0; min_compatible_version: 0; }
If you like, you can encode this into a grf to see if it works. The grf will not do anything, but it should show up in the game if you add it.
You still remember how to use nmlc' on the command line, right? If not, check the Installation page with the details for your operating system, or maybe the following pointer is enough to get you going:
nmlc --grf mygrf.grf mygrf.nml
NML Tutorial: Starting an NML file
Breaking compatibility
Sometimes it happens that you have to break compatibility between different releases of your NewGRF. The NML Documentation has a separate chapter on NewGRF compatibility and what actions break compatibility and how to avoid breaking it in the first place.
However, if you must break compatibility, here's how to handle the version information in your NML file.
OpenTTD targeted NewGRFs
- Keep the GRFID you have;
- Don't forget to update
grf { version }
; - Set
grf { min_compatible_version }
to the value ofgrf { version }
.
This way, OpenTTD will hide the old release from the NewGRF window while keeping it available for older savegames to use (provided the user doesn't delete the old release). Old releases can still be downloaded from BaNaNaS for old savegames.
TTDPatch targeted NewGRFs
TTDPatch doesn't have a concept for grf { min_compatible_version }
(or grf { version }
for that matter). The only way to indicate broken compatibility in TTDPatch is to bump the GRFID. This means that you increase the last byte of the GRFID by one. So if your initial GRFID is "SF\01\01"
, change it to "SF\01\02"
for the next release.
As a consequence, both NewGRFs can be used separate from each other and in OpenTTD both will show up in the NewGRF window.
In order to avoid loading both the old version and the new version in the same game, you have to add a version check to the NewGRF in order to disable itself (or the older version).
NML Tutorial: Starting an NML file