NMLTutorial/Version check

From TTWiki
< NMLTutorial
Revision as of 19:45, 2 August 2014 by FooBar (talk | contribs) (→‎If block: is possible inside item block)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigationJump to search

Version checks are useful if you use certain features in your NewGRF that are not available in older versions of the game (or only available in one game and not the other, both TTDPatch and OpenTTD have some features that are not available in the other). If your NewGRF relies on a certain feature, you must have your NewGRF disable itself and issue an error message. If your NewGRF can work without a certain feature, you can use a version check to disable that part of your NewGRF while keeping the rest.


If block

The basis of every version check is an if block[1]. The if blocks allows to check if a certain condition (for instance based on a general variable or a GRF parameter) is true. If the condition is true, the contents inside the switch block is executed, otherwise it's skipped.

An if block looks like this:

if (<condition>) {
	<code_executed_when_condition_true>
}

The <condition> usually is a general variable or GRF parameter compared against a static value, a different variable or a different parameter. The <code_executed_when_condition_true> can be anything you would normally write in an NML file, so you can have one or more blocks inside an if block. However, you cannot have if blocks inside other blocks (except other if or else blocks and item blocks). The code inside the if block is only executed when the <condition> is true.

<condition>

The comparison itself is made using a comparison operator which you may already know from different programming languages. Here's a table with the comparison operators available in NML:

Symbol Description
== Is equal to
!= Is not equal to
< Is less than
<= Is less than or equal to
> Is bigger than
>= Is bigger than or equal to


So for example if you want to check if A is bigger than or equal to B:

if (A >= B) {
	//whatever happens when A is bigger than or equal to B goes here
}


In case you make multiple comparisons, you can either nest multiple if blocks or combine those comparisons in a single <condition> using the so-called logical operators. The logical operators can work with boolean values (true or false) to combine multiple comparisons. Here's a table with the logical operators available in NML:

Symbol Description
&& Logical AND. True if both sides are true.
|| Logical OR. True if either side (or both) are true.


So for instance if you want to check if A is bigger than or equal to B while at the same time C is not equal to D:

if ((A >= B) && (C != D)) {
	//whatever happens when A is bigger than or equal to B and C is not equal to D goes here
}


Things between brackets is evaluated first before combining it with things outside these brackets this makes sure that a certain sub-condition is evaluated to true/false before logically combining it (the comparison operators can compare just about anything, but the logical operators can only combine boolean values). In some cases you do not need all brackets, which has something to do with operator precedence; we will not bother you with that right now. When in doubt, it doesn't hurt to use more brackets than actually needed (as long as they are in the correct places of course!) and they work very similar to brackets in mathematics.

else

If besides something you want to do when the condition is true, you also want to do something else when the condition is not true, you can add an else block behind the if block:

if (<condition>) {
	<code_executed_when_condition_true>
}
else {
	<code_executed_when_condition_false>
}

The code in the if block will only be executed when the condition is true. The code in the else block is only executed when the condition (for the if block) is not true. An else block must be written directly behind the closing curly bracket of the if block, with no other code inbetween.


You can also combine an else and if block:

if (<condition>) {
	<code_executed_when_condition_true>
}
else if (<other_condition>) {
	<code_executed_when_other_condition_true>
}
else {
	<code_executed_when_both_conditions_false>
}

With this, if the first comparison is false, you make a different comparison and when even that is false you get the else. You can have as many else if blocks between an if block and an else block as you want. Again no other code may appear inbetween those blocks (but of course you can have any code you like inside these blocks).

Error function

The error function allows to issue notices, warnings, errors and fatal error messages. The fatal type also disables the NewGRF immediately. The general syntax of an error block is:

error(<level>, <message> [, <extra_text> [, <parameter1> [, <parameter2> ]]])

The error function may appear outside blocks, inside if or else blocks, but not inside other blocks.

The error <level> can be any of:

Constant Description
NOTICE Add a notice in the NewGRF status window, continue loading the grf.
WARNING Add a warning in the NewGRF status window, prefixed with "Warning: ", continue loading the grf.
ERROR Add a error message in the NewGRF status window, prefixed with "Error: ", continue loading the grf.
FATAL Add a error message in the NewGRF status window, prefixed with "Error: ", abort loading the grf, also give the error message in a red popup box ingame.

<message> can be one of seven predefined messages (see the NML Documentation) or a custom string (using the string() function). <extra_text>, <parameter1> and <parameter2> are optional. <extra_text> must be used when one of the predefined messages is used and can either be a string from the language file or a literal quoted string in the NML code. <parameter1> and <parameter2> can be used in custom strings and when defined must be the identifier of a GRF parameter.


So if you want to disable your NewGRF with an error message, you use the FATAL error level combined with either a predefined string or a error message of your own. If you just want a message without disabling your NewGRF, use one of the other error levels.


Examples

Version checks in NML are actually pretty straight forward.

NML provides the general variables openttd_version, ttdpatch_version and ttd_platform to do version and game platform checks. Concerning openttd_version checks the built-in function version_openttd() comes in very handy as it provides an easy means to specify the version instead of manually constructing the version number corresponding to a specific version.

Check game platform

Checking for the OpenTTD platform is simply done by an if block:

if (ttd_platform != PLATFORM_OPENTTD) {
	error(FATAL, string(STR_REQUIRES_OPENTTD));
}

The string STR_REQUIRES_OPENTTD will hold an error message telling the (TTDPatch) user that OpenTTD is needed for this NewGRF. Note that you cannot use the predefined REQUIRES_OPENTTD as this string doesn't exist in TTDPatch!

Similarly, if you want to check for the TTDPatch platform:

if (ttd_platform != PLATFORM_TTDPATCH) {
	error(FATAL, REQUIRES_TTDPATCH, string(STR_VERSION_TTDPATCH));
}

This time you can use the predefined REQUIRES_TTDPATCH string, as this string does exist in OpenTTD.

Check game version and die

If you NewGRF requires a specific feature to work, disable it if a game version is used that doesn't have that version.

This is a check to disable the NewGRF if a certain OpenTTD feature is not found:

//parameterized spritelayout is only supported since OpenTTD 1.2.0 r22723
if (version_openttd(1,2,0,22723) > openttd_version) {
	error(FATAL, REQUIRES_OPENTTD, "1.2.0 (r22723)");
}

Check game version and conditionally use more advanced features

If your NewGRF doesn't need a specific feature to work, you can disable that version for older versions.

This is a check for a specific OpenTTD (and similarily TTDPatch) version:

/* Only define cargo_age_period when that property is available */
if (openttd_version > version_openttd(1, 2, 0, 22713)) {
	item (FEAT_TRAINS, my_engine) {
		property {
			cargo_age_period: 200;
		}
	}
}

Combining platform and version checks

You must not check the OpenTTD version in TTDPatch or the TTDPatch version in OpenTTD. TTDPatch doesn't have any useful information for the OpenTTD version and similarly OpenTTD doesn't have useful information for the TTDPatch version.

You can nest both checks above using multiple if blocks. It's also possible to combine these checks into a single block:

/* Long date string codes require OpenTTD > 1.2.0 or r22780 */
if (ttd_platform != PLATFORM_OPENTTD || openttd_version < version_openttd(1, 2, 0, 22780)) {
	error(FATAL, REQUIRES_OPENTTD, string(STR_MIN_OPENTTD_VERSION));
}


NML Tutorial: Version check


  1. Actually if is not a block but a language construct, but for the purpose of understanding it you can consider it a block.