GUI Scripting: If Statements

From The DarkMod Wiki
Revision as of 19:50, 30 November 2022 by Geep (talk | contribs) (Rename "Multiple If Statements" to "Problems due to Early Expression Evaluation"; move, rewrite)
Jump to navigationJump to search

This page is part of a series. See GUI Scripting Language for overview.

Introduction

The term "if statement" here is an abbreviated name for "if ... else if ... else ... statement". Its nature in GUI scripting is just enough like that of C-family languages to get you into trouble. In a nutshell, here’s guidance on problem areas:

  • Curly brackets are not optional.
  • A closing curly bracket is never followed by ";"
  • Each commands within curly brackets should be terminated by ";"
  • In if-conditions, within the parentheses:
    • wrap variables in double-quotes.
    • preferably surround any #defined macros with spaces.
    • know that expressions are evaluated early, as the event block begins. This unusual treatment constrains the use of multiple-if logic.
    • be aware that non-numeric string comparison has an odd syntax.

Overall Structure and Syntax

GUI scripting "if statements" are found in an event block (i.e., body of an event handler like onTime). The general form is:

if (boolean condition)
{
  1 or more valid statements terminated by ";" and executed only if condition was true.
}
else if (boolean conditionN)
{
  1 or more valid statements, each terminated by ";" and executed if all preceding conditions were false and conditionN is true.
}
else
{
  1 or more valid statements, each terminated by ";" and executed if all conditions were false.
}

where the "else if" and "else" substructures are optional, and the "else if" substructure can be repeated any number of times.

The curly brackets can be on the same line as the "if", "else if", and "else", or separate. The degree of white space indentation doesn’t matter. It is a convention to use whitespace (i.e., space or newline) to separate the keywords ("if", "else if", "else) from the punctuation (parentheses and curly brackets).

There is NO semi-colon after any closing bracket "}".

CURLY BRACKETS ARE REQUIRED! So this is NOT OK:

if (boolean condition)
  command1;
else
  command2;

If-Condition

This must resolve to a boolean value. This could come directly from a variable that only has 0 or non-zero (interpreted as 1) as values, or by a comparison, or by a conjunction of these separated by "&&" (i.e., logical AND) or "||" (i.e., logical OR) as in C.

Common Cases

All variables names MUST be in double quotes, e.g.:

if ("gui::lang_danish") {....
if ("gui::lang_spanish" == 0) {....
if ("gui::av_screenshot_download_in_progress" == "0" && "exit" == 0) {....

Most if-conditions in TDM are used to test gui:: parameters, as shown. The last example, from mainmenu_download.gui, also tests a user variable defined locally in the guiDef.

Tested variables are routinely of type float (representing boolean as above, or integer or real), and comparisons use numeric operators, e.g.:

if ("gui::curPage" < "gui::numPages") {

By convention, numeric literal values are UNQUOTED, although quotes are tolerated. Unquoted examples with various operators:

if ("gui::diffSelect" != 2) {....
if ("gui::numPages" > 1) {....
if ("gui::flashbomb_halfblind_enum" >= 0.8) {....
if ("gui::player_health"  <= 0) {....

Problems due to Early Expression Evaluation

As detailed in GUI Scripting: Evaluating Expressions & Variables, for a particular event handler, the run-time values of all its if-conditions are calculated at the moment the handler starts to run, and not again until the handler ends. This "early evaluation" is uncommon among scripting languages. As a result, while you can write sequential ifs (perhaps interspersed with commands) and nested ifs within an event block, early evaluation will often limit their usefulness. See also: Just-Changed Value Not Immediately Known to "If" Expression.

String Comparison

There is no true capability to compare strings, but you can determine whether a string (like property "text") is empty or not. The preferred form is:

if(text) {/* do this if non-empty */} else {/* do this if empty */}

Since this form is not self-documenting, it is suggested you add a comment, indicating which are the non-empty and empty branches. Here's another example, referring to a property declared in a different guiDef from that of the if statement.

if ("LSGSaveGameNameEntry::text") {...

An older form, that worked by happenstance and is now deprecated and with a warning as of TDM 2.11, was:

if(text == "") {/* do this if empty */} else {/* do this if non-empty */}

This worked because, when used in an expression, a string is always treated as a float, with value 0.0 if empty and 1.0 if non-empty. So with the deprecated form, "text" becomes 0 if empty or 1 otherwise. "" is seen as a variable with empty name, so replaced by 0 and then compared using "==".

With #Defined Values

If you don’t see double quotes, e.g.,

if ( MM_BRIEFING_VIDEO_LENGTH_1 > 0 && MM_BRIEFING_VIDEO_LENGTH_2 == 0 )

...it means a #define statement either incorporates the quotes around a variable name, or uses a numeric literal, where quotes are optional, e.g.:

#define MM_BRIEFING_VIDEO_LENGTH_1 10000 // In mainmenu_custom_defaults.gui & mainmenu_custom_defs.gui

Also, if you have a #defined item in your condition, the preprocessor parser will probably thank you if you surround it with spaces, e.g.:

if ( MM_BRIEFING_VIDEO_LENGTH_1 > 0 && MM_BRIEFING_VIDEO_LENGTH_2 == 0 )

NOT:

if (MM_BRIEFING_VIDEO_LENGTH_1>0&&MM_BRIEFING_VIDEO_LENGTH_2==0)

Precedence & Arithmetic Operations?

No example of an if-condition in TDM using parenthetical grouping or simple arithmetic operators {+,-,*,/} was seen. It is presumed those are possible; such arithmetic is deployed elsewhere.

For example, arithmetic is used to initialize vector values of the "rect" property, to position a button. First, in tdm_gui01.pk4\guis\mainmenu_defs.gui, boolean MM_POS_HASMOD_SHIFT is #defined:

#define MM_POS_HASMOD_SHIFT ("gui::hasCurrentMod" || "gui::inGame")

This in turn affects where the "New Mission" button is positioned vertically, by calculation:

#define MM_POS_NEW_MISSION_BUTTON 	MM_POS_X, MM_POS_Y + MM_POS_Y_STEP*(0+2*MM_POS_HASMOD_SHIFT), MM_POS_W, MM_POS_H

Then in tdm_gui01\guis\mainmenu_main.gui:

rect MM_POS_NEW_MISSION_BUTTON