Difference between revisions of "If"

From the Oblivion ConstructionSet Wiki
Jump to navigation Jump to search
imported>JOG
(gave it a more professional look)
 
(38 intermediate revisions by 15 users not shown)
Line 4: Line 4:
== Overview ==
== Overview ==


An if statement uses the following syntax:
An "if" statement uses the following syntax:


<pre>
<pre>
Line 21: Line 21:
== Comparison Operators ==
== Comparison Operators ==


An if statement may contain one or more comparison operators. Below is a table of valid comparison operators:
An "if" statement may contain one or more comparison operators. Below is a table of valid comparison operators:


{|border="1" cellpadding="5" cellspacing="0"
{|border="1" cellpadding="5" cellspacing="0"
Line 49: Line 49:
It is important to note that there are no bitwise comparisons available in Oblivion's scripting language.
It is important to note that there are no bitwise comparisons available in Oblivion's scripting language.


NOTE: <nowiki><></nowiki> and <nowiki>><</nowiki> are used often in other programming languages, but not in Oblivion's scripting language.  While they do not register as errors when saved in a script in the CS, they will not work in-game.


== Combining Comparisons ==
== Combining Comparisons ==
Line 62: Line 63:
| <nowiki>&&</nowiki>
| <nowiki>&&</nowiki>
| Logical AND
| Logical AND
| <nowiki>if x == 1 && y == 1 ; considered true only if both x and y equal 1.</nowiki>
| <nowiki>if x == 1 && y == 1 ;</nowiki> considered true ''only if'' both x ''and'' y equal 1.
|-
|-
| <nowiki>||</nowiki>
| <nowiki>||</nowiki>
| Logical OR
| Logical OR
| <nowiki>if x == 1 || y == 1 ; considered true unless both x and y equal 0.</nowiki>
| <nowiki>if x == 1 || y == 1 ;</nowiki> considered true ''unless'' both x ''and'' y equal 0.
|}
|}




Note that "||" is evaluated before "&&", just like "*" is evaluated before "+" in normal algebra.
Note that "||" is evaluated before "&&": '''"||" has precedence over "&&"''', just like "*" is evaluated before "+" in normal algebra. Which also shows an opposite behaviour from the standard operator notations for scripting conditional expressions (IF statements), and needs to be clarified, as it impacts the design of '''boolean expressions''': in arithmetic and algebra, from the earliest use of mathematical notation, multiplication took precedence over addition, and the standard order of operators is: 1-exponents and roots; 2-multiplication and division; 3-addition and subtraction;
* In terms of computing, we're talking about a '''precedence number order''', and operator precedence is usually ordered with the corresponding number order. For expressions where two operators of different precedences compete for the same operand, the operator with the ''higher precedence'' wins.
* '''functions have precedence over comparison and arithmetic operators, which themselves always have precedence over logical operators ("||" and "&&").'''
* In ''Common operator notation'' involving "normal" algebra or boolean algebra, "*" ("&&") is always evaluated before "+" ("||"), it has a higher precedence number than the "+" operator. For example, 3×4+5 = ((3×4)+5), not (3×(4+5)). Oblivion's scripting language keeps the normal precedence for arithmetic operations, '''but OR ("||") has a higher precedence than AND ("&&")''':<pre>if myVar1 == 1 && myVar2 == 1 || myVar2 == 5</pre>is equivalent to<pre>if myVar1 == 1 && (myVar2 == 1 || myVar2 == 5)</pre>This is true when MyVar1 = 1 AND myVar2 is either 1 or 5.<BR><BR>If you need the "&&" comparison operator to be evaluated before the "||" (OR) operator, you must include its part in-between parentheses. In this case: <pre>if (myVar1 == 1 && myVar2 == 1) || myVar2 == 5</pre>is true when either myVar2 is 5 OR both, myVar1 and myVar2 are 1.


If you want the && to be evaluated first, you have to include that part in parentheses. For example:
The later also explains why one has to be very careful in positioning conditions in a [[Logical Conditions#The_Condition_List|condition list]] of an editor item: for the CS/OB's engine, OR has order preference, ''has precedence'' over AND. For example, the condition items (A AND B OR C AND D) are evaluated as (A AND (B OR C) AND D), and not (( A AND B) OR (C AND D)), as opposed to common operator notation for most languages. In general, we call this an ''inversed'' or ''negative'' notation. '''Always keep the later in mind when [http://en.wikipedia.org/wiki/Boolean_algebra_%28logic%29#Basic_operations applying boolean algebra] for evaluating some given expression when scripting''', as standard operator notations will lead you to errors.


<nowiki>if myVar1 == 1 && myVar2 == 1 || myVar2 == 5</nowiki>
=== Oblivion evaluates entire If statement ===


This is true when MyVar1 = 1 AND myVar2 is either 1 or 5.
Oblivion evaluates all the conditions for an If statement. For example, when you combine expressions with "&&", if the first expression is false, later expressions will still be evaluated, even though they are irrelevant (false && anything = always false, true || anything = always true). This results in more code being processed than necessary.


<nowiki>if (myVar1 == 1 && myVar2 == 1) || myVar2 == 5</nowiki>
This can also result in unexpected errors, such as below:


This is true when either myVar2 is 5 OR both, myVar1 and myVar2 are 1
<pre>If (ReferenceVariable != 0) && (ReferenceVariable.Getav Health < 30)
    (code...)
endif</pre>


This crashes Oblivion if the reference variable is undefined ("0"), because the second part is still evaluated. Instead, use one If within another ("nested If statements"):
<pre>If ReferenceVariable != 0
    If ReferenceVariable.Getav Health < 30
        (code...)
    endif
endif</pre>
'''Since boolean operators (&& ||) are implemented poorly in Oblivion, avoid them whenever possible.'''
In addition, the script engine looks at every line inside an If block, even when the condition was false, until an exit point is found. (Note that the code is only looked at, not evaluated, thus the example above works correctly.) An exit point is an accessible RETURN call, or the end of the script.
This:
<pre>;; unoptimized
begin GameMode
    If (somecondition != 0)
        (...some inefficient/complex algorithm...)
    endif
end</pre>
Is more expensive than this:
<pre>;; optimized
begin GameMode
    If (somecondition == 0) ;; logical negation
        RETURN
    endif
    (...some inefficient/complex algorithm...)
end</pre>
In short, best practice is to call RETURN early and often, when possible. For large scripts with complex routines (such as looking for an item in inventory or a container, sorting a list, searching a list, etc.) this subtle difference can yield dramatic performance gains.


== Comparisons and Expressions ==
== Comparisons and Expressions ==
Line 112: Line 152:
  IF IsActor != 0 && Flag != 0
  IF IsActor != 0 && Flag != 0


== Comparisons and Reference Variables ==
In general it is better to use GetIsReference than using the logical operators with references (see the talk page).
So instead of:
<pre>if ( refVar == player )    ; especially in the case of the player this is unreliable</pre>
use
<pre>if ( refVar.getIsReference player )</pre>
Here are some equivalent ways to check if a refVar is a Welkynd stone :
<pre>
if refVar.GetIsID WelkyndStone
if refVar.GetIsID "00000191" ; valid but avoid it - makes code less readable
set refVarBase to WelkyndStone
if refVar.GetIsID refVarBase
set refVarBase to "00000191" ; valid but avoid it - makes code less readable
if refVar.GetIsID refVarBase
</pre>
== Assignment ==
The result of logical expressions can be saved to variables just like the results of mathematical expressions. E.g. you can do this:
set bResult to c == -3 && b == 20
set bTest to c == -3 && b == 20 && a == 17
You can use this to '''invert a logical value''':
set bValue to ( bValue == 0 )
If bValue is intially 1 (True), then it will be reset to 0 (False). Or if initially 0 (False), it will be reset to 1 (True).
== Use of Else and Elseif ==
Although Elseif and Else are not required for an If/Endif block, they can be very useful.  Here are a few basics to remember:
=== Else ===
- There can only be one Else in an If/Endif block and it must follow after any and all other Elseifs
- An Else should only be used when, in the absence of all previous If/Elseif criteria, something should definitely happen.
=== Elseif ===
- Elseifs are useful if you want to make sure that a certain thing only happens once, even though there are potentially multiple sets of criteria that could lead to the same result.
For example, with the following code:
if a && b
  call FunctionScriptA
endif
if c && d
  call FunctionScriptA
endif
If by chance a && b is true, as well as c && d, then FunctionScriptA will get called twice in a row.  If you only want it to be called once, then you would use elseif as follows:
if a && b
  call FunctionScriptA
elseif c && d
  call FunctionScriptA
endif
- Elseifs are also very helpful when there are a variety of different events that can happen under different conditions, but only one of the events should occur:
if aInt == 23
  set myVariable to 1
elseif bRef == PlayerRef
  set myVariable to 2
elseif cString == "Lich"
  set myVariable to 3
endif
- You can also potentially use Elseifs to break up a complex If statement that uses lots of logical ORs.  Instead of:
if (a && b) || (h && k) || (x && y)
  do stuff
else
  do other stuff
endif
you could do:
if a && b
  do stuff
elseif h && k
  do same stuff as above
elseif x && y
  do same stuff as above
else
  do other stuff
endif
As mentioned earlier in this page, Oblivion will evaluate an entire If line, even when it technically would not need to.  This technique avoids that issue by breaking the OR criteria into separate chunks.  So even though there are more lines of code, performance-wise it may be more efficient, depending on what the criteria is.
'''Tip:''' When deciding which order to do your Ifs/Elseifs, go in ascending order of simplicity/complexity.  The first If statement should be very easy for the Oblivion engine to calculate.  The next should be the second easiest, and so on.
'''Disclaimer:''' You may want to ignore the priority of simplicity/complexity depending on the importance of the If/Elseif criteria.  If there are two sets of criteria of equal value, then put the simpler to evaluate criteria first.  If one set of criteria has to be checked first before processing the rest, then that becomes the priority, regardless of complexity.


== Notes ==
== Notes ==
Line 127: Line 266:
  endif
  endif


(it's better readable too...)
(it's more readable too...)
 
 
After the condition or the "else" statement the rest of the line is ignored. There aren't any error messages to warn you, so (since the line appears to be correct) you might spend a lot of time locating the source of the problem.
 
=== Spaces and Tabs===
When using Tabs or Spaces to separate operators/expressions you need to use the same separator on each side. If you use Space on one side and Tab on the other, the script might be permanently stopped when the line is executed.
 
  if SomeVar>=1          ;GOOD
  if (SomeVar>=1)        ;GOOD
  if ( SomeVar >= 1 )    ;GOOD
  if (SomeVar___>=___1)  ;GOOD ( "___" = Tab)
  if___(SomeVar >= 1)    ;GOOD ( Tab before "'''('''" and ''nothing'' after it is okay)
  if ( SomeVar ___>=___1) ;GOOD ( Spaces around "'''SomeVar'''"), tabs around "'''>='''")  


  if ( SomeVar___>=___1);BAD  ( Space and Tab around "'''SomeVar'''" causes problems)
  if___SomeVar >= 1    ;BAD  ( Tab and Space around "'''SomeVar'''")
  if (___SomeVar >= 1)  ;BAD  ( Space and Tab around "'''('''") 
  if___( SomeVar >= 1)  ;BAD  ( Tab and Space around "'''('''")
  if SomeVar >=___1    ;BAD  ( Space and Tab around "'''>='''") 
  if (SomeVar___>= 1)  ;BAD  ( Tab and Space around "'''>='''") 


After the condition or the "else" statement the rest of the line is ignored, and there aren't any error messages to warn you, so (since the line appears to be correct) you might spend a lot of time locating the source of the problem.  
Note that you will not receive any compiler error or warnings. You only notice the script not working correctly. You can determine if the problem is caused by this by examining the script in an external text-editor or by looking directly at the compiled script data.  


[[Category:Commands]]
[[Category:Commands]]

Latest revision as of 17:54, 30 September 2024

The if statement allows you to execute (or not execute) a block of script commands based on one or more comparisons that you specify. Oblivion's if command is very powerful and comparable to "real" programming languages.


Overview[edit | edit source]

An "if" statement uses the following syntax:

if expressionA [comparison] expressionB
; test "expressionA [comparison] expressionB" passed
elseif expressionB [comparison] expressionC
; test "expressionB [comparison] expressionC" passed
else
; none of the above tests passed
endif

The else and elseif statements are optional.


Comparison Operators[edit | edit source]

An "if" statement may contain one or more comparison operators. Below is a table of valid comparison operators:

Operator Description
== Exactly equal to
!= Not equal to
> Greater than
>= Greater than or equal to
< Less than
<= Less than or equal to

It is important to note that there are no bitwise comparisons available in Oblivion's scripting language.

NOTE: <> and >< are used often in other programming languages, but not in Oblivion's scripting language. While they do not register as errors when saved in a script in the CS, they will not work in-game.

Combining Comparisons[edit | edit source]

Comparisons can be linked together using the following logical operators:

Operator Description Example
&& Logical AND if x == 1 && y == 1 ; considered true only if both x and y equal 1.
|| Logical OR if x == 1 || y == 1 ; considered true unless both x and y equal 0.


Note that "||" is evaluated before "&&": "||" has precedence over "&&", just like "*" is evaluated before "+" in normal algebra. Which also shows an opposite behaviour from the standard operator notations for scripting conditional expressions (IF statements), and needs to be clarified, as it impacts the design of boolean expressions: in arithmetic and algebra, from the earliest use of mathematical notation, multiplication took precedence over addition, and the standard order of operators is: 1-exponents and roots; 2-multiplication and division; 3-addition and subtraction;

  • In terms of computing, we're talking about a precedence number order, and operator precedence is usually ordered with the corresponding number order. For expressions where two operators of different precedences compete for the same operand, the operator with the higher precedence wins.
  • functions have precedence over comparison and arithmetic operators, which themselves always have precedence over logical operators ("||" and "&&").
  • In Common operator notation involving "normal" algebra or boolean algebra, "*" ("&&") is always evaluated before "+" ("||"), it has a higher precedence number than the "+" operator. For example, 3×4+5 = ((3×4)+5), not (3×(4+5)). Oblivion's scripting language keeps the normal precedence for arithmetic operations, but OR ("||") has a higher precedence than AND ("&&"):
    if myVar1 == 1 && myVar2 == 1 || myVar2 == 5
    is equivalent to
    if myVar1 == 1 && (myVar2 == 1 || myVar2 == 5)
    This is true when MyVar1 = 1 AND myVar2 is either 1 or 5.

    If you need the "&&" comparison operator to be evaluated before the "||" (OR) operator, you must include its part in-between parentheses. In this case:
    if (myVar1 == 1 && myVar2 == 1) || myVar2 == 5
    is true when either myVar2 is 5 OR both, myVar1 and myVar2 are 1.

The later also explains why one has to be very careful in positioning conditions in a condition list of an editor item: for the CS/OB's engine, OR has order preference, has precedence over AND. For example, the condition items (A AND B OR C AND D) are evaluated as (A AND (B OR C) AND D), and not (( A AND B) OR (C AND D)), as opposed to common operator notation for most languages. In general, we call this an inversed or negative notation. Always keep the later in mind when applying boolean algebra for evaluating some given expression when scripting, as standard operator notations will lead you to errors.

Oblivion evaluates entire If statement[edit | edit source]

Oblivion evaluates all the conditions for an If statement. For example, when you combine expressions with "&&", if the first expression is false, later expressions will still be evaluated, even though they are irrelevant (false && anything = always false, true || anything = always true). This results in more code being processed than necessary.

This can also result in unexpected errors, such as below:

If (ReferenceVariable != 0) && (ReferenceVariable.Getav Health < 30)
    (code...)
endif

This crashes Oblivion if the reference variable is undefined ("0"), because the second part is still evaluated. Instead, use one If within another ("nested If statements"):

If ReferenceVariable != 0
    If ReferenceVariable.Getav Health < 30
        (code...)
    endif
endif

Since boolean operators (&& ||) are implemented poorly in Oblivion, avoid them whenever possible.

In addition, the script engine looks at every line inside an If block, even when the condition was false, until an exit point is found. (Note that the code is only looked at, not evaluated, thus the example above works correctly.) An exit point is an accessible RETURN call, or the end of the script.

This:

;; unoptimized

begin GameMode
    If (somecondition != 0)
        (...some inefficient/complex algorithm...)
    endif
end

Is more expensive than this:

;; optimized

begin GameMode
    If (somecondition == 0) ;; logical negation
        RETURN
    endif
    (...some inefficient/complex algorithm...)
end

In short, best practice is to call RETURN early and often, when possible. For large scripts with complex routines (such as looking for an item in inventory or a container, sorting a list, searching a list, etc.) this subtle difference can yield dramatic performance gains.

Comparisons and Expressions[edit | edit source]

The comparison operators can be used with any expression that can be evaluated into a number. Assuming "a = 17", "b = 20" and "c = a - b", all of the following expressions work as expected. Parentheses are only needed when they're necessary for mathematical reasons.

IF c == -3 && b == 20
IF c == -3 && b == 20 && a == 17
IF c - 1 == -4 && b == 20 && a == 17
IF a - 20 == 17 - b
IF a - 20 == 17 - b && c + 3 == 0
IF a + 3 == b
IF a - b == c
IF a *4 - b * 4 == c * 4
IF a * ( 5 + c ) - 14 == b
IF 2*(a*(5+c)-14)==b - -b

If a variable or the result of a function returns 1 or 0, or you're just interested in whether the result is 0 or not you don't need to test on "== 1" or "!=0"

IF Done
IF Getisid MyNPC
IF Getitemcount Lockpick
IF IsActor && Flag

Do the same as

IF Done != 0
IF Getisid MyNPC != 0
IF Getitemcount Lockpick != 0
IF IsActor != 0 && Flag != 0


Comparisons and Reference Variables[edit | edit source]

In general it is better to use GetIsReference than using the logical operators with references (see the talk page).

So instead of:

if ( refVar == player )    ; especially in the case of the player this is unreliable

use

if ( refVar.getIsReference player )

Here are some equivalent ways to check if a refVar is a Welkynd stone :

if refVar.GetIsID WelkyndStone

if refVar.GetIsID "00000191"	; valid but avoid it - makes code less readable
	
set refVarBase to WelkyndStone
if refVar.GetIsID refVarBase

set refVarBase to "00000191"	; valid but avoid it - makes code less readable
if refVar.GetIsID refVarBase

Assignment[edit | edit source]

The result of logical expressions can be saved to variables just like the results of mathematical expressions. E.g. you can do this:

set bResult to c == -3 && b == 20
set bTest to c == -3 && b == 20 && a == 17

You can use this to invert a logical value:

set bValue to ( bValue == 0 )

If bValue is intially 1 (True), then it will be reset to 0 (False). Or if initially 0 (False), it will be reset to 1 (True).

Use of Else and Elseif[edit | edit source]

Although Elseif and Else are not required for an If/Endif block, they can be very useful. Here are a few basics to remember:

Else[edit | edit source]

- There can only be one Else in an If/Endif block and it must follow after any and all other Elseifs

- An Else should only be used when, in the absence of all previous If/Elseif criteria, something should definitely happen.

Elseif[edit | edit source]

- Elseifs are useful if you want to make sure that a certain thing only happens once, even though there are potentially multiple sets of criteria that could lead to the same result.

For example, with the following code:

if a && b
 call FunctionScriptA
endif
if c && d
 call FunctionScriptA
endif

If by chance a && b is true, as well as c && d, then FunctionScriptA will get called twice in a row. If you only want it to be called once, then you would use elseif as follows:

if a && b
 call FunctionScriptA
elseif c && d
 call FunctionScriptA
endif

- Elseifs are also very helpful when there are a variety of different events that can happen under different conditions, but only one of the events should occur:

if aInt == 23
 set myVariable to 1
elseif bRef == PlayerRef
 set myVariable to 2
elseif cString == "Lich"
 set myVariable to 3
endif

- You can also potentially use Elseifs to break up a complex If statement that uses lots of logical ORs. Instead of:

if (a && b) || (h && k) || (x && y)
 do stuff
else
 do other stuff
endif

you could do:

if a && b
 do stuff
elseif h && k
 do same stuff as above
elseif x && y
 do same stuff as above
else
 do other stuff
endif

As mentioned earlier in this page, Oblivion will evaluate an entire If line, even when it technically would not need to. This technique avoids that issue by breaking the OR criteria into separate chunks. So even though there are more lines of code, performance-wise it may be more efficient, depending on what the criteria is.

Tip: When deciding which order to do your Ifs/Elseifs, go in ascending order of simplicity/complexity. The first If statement should be very easy for the Oblivion engine to calculate. The next should be the second easiest, and so on.

Disclaimer: You may want to ignore the priority of simplicity/complexity depending on the importance of the If/Elseif criteria. If there are two sets of criteria of equal value, then put the simpler to evaluate criteria first. If one set of criteria has to be checked first before processing the rest, then that becomes the priority, regardless of complexity.

Notes[edit | edit source]

Keep in mind that the script parser doesn't accept one-lined statments. While

if condition==1 set varname to 1
else set varname to 2

works in other programming languages, you need to split this up for Oblivion.

if condition==1
  set varname to 1
else
  set varname to 2
endif

(it's more readable too...)


After the condition or the "else" statement the rest of the line is ignored. There aren't any error messages to warn you, so (since the line appears to be correct) you might spend a lot of time locating the source of the problem.

Spaces and Tabs[edit | edit source]

When using Tabs or Spaces to separate operators/expressions you need to use the same separator on each side. If you use Space on one side and Tab on the other, the script might be permanently stopped when the line is executed.

  if SomeVar>=1           ;GOOD
  if (SomeVar>=1)         ;GOOD
  if ( SomeVar >= 1 )     ;GOOD
  if (SomeVar___>=___1)   ;GOOD ( "___" = Tab) 
  if___(SomeVar >= 1)     ;GOOD ( Tab before "(" and nothing after it is okay) 
  if ( SomeVar ___>=___1) ;GOOD ( Spaces around "SomeVar"), tabs around ">=") 
  if ( SomeVar___>=___1);BAD  ( Space and Tab around "SomeVar" causes problems)
  if___SomeVar >= 1     ;BAD  ( Tab and Space around "SomeVar")
  if (___SomeVar >= 1)  ;BAD  ( Space and Tab around "(")  
  if___( SomeVar >= 1)  ;BAD  ( Tab and Space around "(") 
  if SomeVar >=___1     ;BAD  ( Space and Tab around ">=")  
  if (SomeVar___>= 1)   ;BAD  ( Tab and Space around ">=")  

Note that you will not receive any compiler error or warnings. You only notice the script not working correctly. You can determine if the problem is caused by this by examining the script in an external text-editor or by looking directly at the compiled script data.