Difference between revisions of "NMLTutorial/Version check"
Planetmaker (talk | contribs) m (use common style for <code>) |
(→If block: is possible inside item block) |
||
(3 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
+ | {{NMLTutorial}} |
||
+ | |||
+ | 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<ref>Actually ''if'' is not a block but a language construct, but for the purpose of understanding it you can consider it a block.</ref>. The if blocks allows to check if a certain condition (for instance based on a [http://newgrf-specs.tt-wiki.net/wiki/NML:General#General_variables 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: |
||
+ | |||
+ | <pre class="pseudo"> |
||
+ | if (<condition>) { |
||
+ | <code_executed_when_condition_true> |
||
+ | } |
||
+ | </pre> |
||
+ | |||
+ | The <code><condition></code> usually is a [http://newgrf-specs.tt-wiki.net/wiki/NML:General#General_variables general variable] or GRF parameter compared against a static value, a different variable or a different parameter. The <code><code_executed_when_condition_true></code> 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 <code><condition></code> 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: |
||
+ | |||
+ | <!-- Indeed, I cannot be bothered to remember the mediawiki table syntax while html tables work just fine. Live with it, or change it yourself --> |
||
+ | <table> |
||
+ | <tr><th> Symbol </th><th> Description</th></tr> |
||
+ | <tr><td><code> == </code></td><td> Is equal to </td></tr> |
||
+ | <tr><td> <code>!= </code></td><td> Is not equal to </td></tr> |
||
+ | <tr><td><code> < </code></td><td> Is less than </td></tr> |
||
+ | <tr><td><code> <= </code></td><td> Is less than or equal to </td></tr> |
||
+ | <tr><td><code> > </code></td><td> Is bigger than </td></tr> |
||
+ | <tr><td><code> >= </code></td><td> Is bigger than or equal to </td></tr> |
||
+ | </table> |
||
+ | |||
+ | |||
+ | So for example if you want to check if A is bigger than or equal to B: |
||
+ | |||
+ | <pre class="example"> |
||
+ | if (A >= B) { |
||
+ | //whatever happens when A is bigger than or equal to B goes here |
||
+ | } |
||
+ | </pre> |
||
+ | |||
+ | |||
+ | In case you make multiple comparisons, you can either nest multiple if blocks or combine those comparisons in a single <code><condition></code> 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: |
||
+ | |||
+ | <!-- Again with the html table --> |
||
+ | <table> |
||
+ | <tr><th> Symbol </th><th> Description</th></tr> |
||
+ | <tr><td><code> && </code></td><td> Logical AND. True if both sides are true. </td></tr> |
||
+ | <tr><td><code> || </code></td><td> Logical OR. True if either side (or both) are true. </td></tr> |
||
+ | </table> |
||
+ | |||
+ | |||
+ | 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: |
||
+ | |||
+ | <pre class="example"> |
||
+ | 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 |
||
+ | } |
||
+ | </pre> |
||
+ | |||
+ | |||
+ | 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: |
||
+ | |||
+ | <pre class="pseudo"> |
||
+ | if (<condition>) { |
||
+ | <code_executed_when_condition_true> |
||
+ | } |
||
+ | else { |
||
+ | <code_executed_when_condition_false> |
||
+ | } |
||
+ | </pre> |
||
+ | |||
+ | 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: |
||
+ | |||
+ | <pre class="pseudo"> |
||
+ | if (<condition>) { |
||
+ | <code_executed_when_condition_true> |
||
+ | } |
||
+ | else if (<other_condition>) { |
||
+ | <code_executed_when_other_condition_true> |
||
+ | } |
||
+ | else { |
||
+ | <code_executed_when_both_conditions_false> |
||
+ | } |
||
+ | </pre> |
||
+ | |||
+ | 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: |
||
+ | |||
+ | <pre class="pseudo"> |
||
+ | error(<level>, <message> [, <extra_text> [, <parameter1> [, <parameter2> ]]]) |
||
+ | </pre> |
||
+ | |||
+ | The error function may appear outside blocks, inside if or else blocks, but not inside other blocks. |
||
+ | |||
+ | The error <code><level></code> can be any of: |
||
+ | |||
+ | <table> |
||
+ | <tr><th> Constant </th><th> Description </th></tr> |
||
+ | <tr><td><code> NOTICE </code></td><td> Add a notice in the NewGRF status window, continue loading the grf. </td></tr> |
||
+ | <tr><td><code> WARNING </code></td><td> Add a warning in the NewGRF status window, prefixed with "Warning: ", continue loading the grf. </td></tr> |
||
+ | <tr><td><code> ERROR </code></td><td> Add a error message in the NewGRF status window, prefixed with "Error: ", continue loading the grf. </td></tr> |
||
+ | <tr><td><code> FATAL </code></td><td> 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. </td></tr> |
||
+ | </table> |
||
+ | |||
+ | <code><message></code> can be one of seven predefined messages (see the [http://newgrf-specs.tt-wiki.net/wiki/NML:Error NML Documentation]) or a custom string (using the <code>string()</code> function). <code><extra_text></code>, <code><parameter1></code> and <code><parameter2></code> are optional. <code><extra_text></code> 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. <code><parameter1></code> and <code><parameter2></code> 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 <code>FATAL</code> 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. |
Version checks in NML are actually pretty straight forward. |
||
− | NML provides the [http://newgrf-specs.tt-wiki.net/wiki/NML:General#General_variables |
+ | NML provides the [http://newgrf-specs.tt-wiki.net/wiki/NML:General#General_variables general variables] <code>openttd_version</code>, <code>ttdpatch_version</code> and <code>ttd_platform</code> to do version and game platform checks. Concerning <code>openttd_version</code> checks the built-in [http://newgrf-specs.tt-wiki.net/wiki/NML:Builtin_functions function] <code>version_openttd()</code> 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 === |
||
− | Thus checking for the TTD platform is simply done by an if block: |
||
+ | Checking for the OpenTTD platform is simply done by an if block: |
||
− | <pre> |
||
+ | |||
+ | <pre class="example"> |
||
if (ttd_platform != PLATFORM_OPENTTD) { |
if (ttd_platform != PLATFORM_OPENTTD) { |
||
− | error(FATAL |
+ | error(FATAL, string(STR_REQUIRES_OPENTTD)); |
− | exit; |
||
} |
} |
||
</pre> |
</pre> |
||
+ | The string <code>STR_REQUIRES_OPENTTD</code> will hold an error message telling the (TTDPatch) user that OpenTTD is needed for this NewGRF. Note that you cannot use the predefined <code>REQUIRES_OPENTTD</code> as this string doesn't exist in TTDPatch! |
||
− | as is the check for a specific OpenTTD (and similarily TTDPatch) version: |
||
+ | |||
− | <pre> |
||
+ | Similarly, if you want to check for the TTDPatch platform: |
||
+ | |||
+ | <pre class="example"> |
||
+ | if (ttd_platform != PLATFORM_TTDPATCH) { |
||
+ | error(FATAL, REQUIRES_TTDPATCH, string(STR_VERSION_TTDPATCH)); |
||
+ | } |
||
+ | </pre> |
||
+ | |||
+ | This time you ''can'' use the predefined <code>REQUIRES_TTDPATCH</code> 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: |
||
+ | |||
+ | <pre class="example"> |
||
+ | //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)"); |
||
+ | } |
||
+ | </pre> |
||
+ | |||
+ | === 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: |
||
+ | |||
+ | <pre class="example"> |
||
/* Only define cargo_age_period when that property is available */ |
/* Only define cargo_age_period when that property is available */ |
||
if (openttd_version > version_openttd(1, 2, 0, 22713)) { |
if (openttd_version > version_openttd(1, 2, 0, 22713)) { |
||
Line 23: | Line 173: | ||
</pre> |
</pre> |
||
+ | === Combining platform and version checks === |
||
− | It's also possible to combine these checks into a single block: |
||
+ | 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. |
||
− | <pre> |
||
+ | |||
+ | You can nest both checks above using multiple if blocks. It's also possible to combine these checks into a single block: |
||
+ | |||
+ | <pre class="example"> |
||
/* Long date string codes require OpenTTD > 1.2.0 or r22780 */ |
/* Long date string codes require OpenTTD > 1.2.0 or r22780 */ |
||
if (ttd_platform != PLATFORM_OPENTTD || openttd_version < version_openttd(1, 2, 0, 22780)) { |
if (ttd_platform != PLATFORM_OPENTTD || openttd_version < version_openttd(1, 2, 0, 22780)) { |
||
error(FATAL, REQUIRES_OPENTTD, string(STR_MIN_OPENTTD_VERSION)); |
error(FATAL, REQUIRES_OPENTTD, string(STR_MIN_OPENTTD_VERSION)); |
||
− | exit; |
||
} |
} |
||
</pre> |
</pre> |
||
+ | |||
+ | {{NMLTutorialNavbar|Object graphics|Object slopes}} |
||
+ | |||
+ | |||
+ | <references/> |
Latest revision as of 19:45, 2 August 2014
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
- ↑ Actually if is not a block but a language construct, but for the purpose of understanding it you can consider it a block.