Difference between revisions of "Boolean constants"

From Dragon Age Toolset Wiki
Jump to: navigation, search
(spelling)
m (Remove unnecessary whitespace)
 
(11 intermediate revisions by 3 users not shown)
Line 1: Line 1:
{{Generated}}
 
 
{{Constant table start
 
{{Constant table start
 
|sourcefile=script.ldf
 
|sourcefile=script.ldf
Line 24: Line 23:
 
== Remarks ==
 
== Remarks ==
 
<!-- This section contains additional comments, observations and known issues. -->
 
<!-- This section contains additional comments, observations and known issues. -->
If you test for a variable's truth value by testing if it is equal to TRUE you may not get the correct result since TRUE == x is a true expression only when x is 1, yet all non-zero values are also not FALSE; FALSE != x is true when x is not zero. There is an ambiguity whenever x is not 1 even though all non-zero values are treated logically as true. When x is 2, for instance, both TRUE == x and FALSE == x are false statements, leading one to conclude that x is neither TRUE nor FALSE when its value is 2 (or any other value besides 0 and 1). But this code snippet
+
In [[dascript]] there is no Boolean type. Instead [[int|integer]] values are used to represent the Boolean states: zero represents the '''false''' state and any non-zero integer represents the '''true''' state. For convenience the [[boolean constants]] have been defined in [[script.ldf]] to represent the '''true''' and '''false''' states.
<pre>
+
int b_X_is_True = FALSE;
+
if( x ) b_X_is_True = TRUE;
+
</pre>
+
will set the variable b_X_is_True to TRUE whenever the value of x is not zero. Clearly when x is 2 the language treats x as a true value logically.
+
  
The problem is that the identifier TRUE must be given a value, and that value must represent all non-zero values to be accurate. But a single value cannot represent many different values so there is ambiguity everywhere except at the value the constant is actually assigned. FALSE on the other hand, also needs to be given a value, but it only has to represent one integer value...zero, because that is the only integer value treated logically as false. Any integer value tested against FALSE will be unambiguously resolved. Because of this ambiguity with the TRUE identifier, and the correspondingly unambiguous nature of the FALSE identifier, you may wish to instead test if your variable is not equal to FALSE.
+
These constants are used in the following ways in '''script.ldf''', the [[Core Game Resources]] and the [[Single Player Campaign]]:
 +
<dascript>
 +
// Assigning a value to a integer variable.
 +
int bStillLooking = TRUE;
  
Note also that it is never necessary, and furthermore wasteful, to explicitly "test for TRUE" or "test for NOT FALSE" in a boolean expression anyway. One would never write, for example:
+
// Setting the a value of an integer argument in a function call.
<pre>
+
SetItemDroppable(oItem, TRUE);
if( (x == y) == TRUE ) ...  <-- redundant comparison with TRUE
+
if( (x == y) != FALSE ) ...  <-- redundant comparison with FALSE
+
</pre>
+
because the sub-expression (x == y) will evaluate to TRUE or FALSE already. Additionally comparing the result to TRUE (or NOT FALSE) gives the exact same result for the entire expression as leaving the extraneous comparison out completely does. Thus the comparison with TRUE (or NOT FALSE) is redundant, unnecessary and a wasteful computation.
+
  
The same logic applies to expressions involving function calls. If a function returns a TRUE/FALSE value, it would be a redundant waste of CPU cycles to test if the returned value was TRUE.
+
// Setting the default value of an integer argument in a function's declaration or definition.
<pre>
+
void AddAbility(object oObject, int nAbility, int bSendNotification = FALSE);
if( IsObjectValid( oObject) == TRUE) ... <-- this is redundant as well
+
if( IsObjectValid( oObject) ) ... <-- same thing, one less comparison
+
</pre>
+
In this case the return value of the function (TRUE/FALSE) has the same logic characteristics as the sub-expression (x == y) in the example above. So you can treat the function call as a boolean sub-expression just like (x == y) and therefore the same logic leads to the same conclusion concerning the overall expression.
+
  
However, testing for FALSE or NOT TRUE is perfectly acceptable:
+
// Setting a return value from an integer function.
<pre>
+
return TRUE;
if( IsObjectValid( oObject) == FALSE) ... <-- this is not redundant
+
</dascript>
if( IsObjectValid( oObject) != TRUE) ... <-- this is not redundant either
+
if( !IsObjectValid( oObject) ) ... <-- but this might be the best method
+
</pre>
+
  
The identifier TRUE is only really useful for defining default values of boolean type parameters in a function definition, passing a true value to a function with a boolean parameter when you call it, or setting the value of a boolean type variable. There is no other place where you need it.
+
The constants are also referenced in the comments describing a function's return values, for example, as seen is [[IsObjectValid]], to indicate that the function is constrained to return 0 or 1. This is probably reliable for engine functions (i.e. those in '''script.ldf''') which are presumably converting from a native C++ '''bool''' type but should be treated with scepticism for scripted functions.
<pre>
+
void Dance( int bChaCha = TRUE)... <-- default value in function def
+
SetItemDroppable( oItem, TRUE); <-- boolean parameter in function call
+
int bStillLooking = TRUE; <-- set a variable value
+
</pre>
+
  
Some will probably take the position that having the extra comparison in there makes the code more readable and obvious. It would be difficult to argue that point especially in some more complex expression situations. But it is functionally unnecessary and adding it does waste CPU cycles in exchange for that marginal increase in readability. Learning to read experessions without it having to be there would benefit anybody.
+
Finally the constants are also used in boolean expressions, for example, in an '''if''' statement:
  
<!-- == Examples == -->
+
<dascript>
<!-- This section contains examples transcluded from the snippet library. -->
+
if(<expression> == TRUE)
 +
{
 +
    ...
 +
}
 +
</dascript>
  
<!-- == See also == -->
+
Typically this will be seen in connection with the various '''Is*''' and '''Has*''' functions, for example, as seen with the use of '''IsObjectValid''' in the '''item_singletarget''' script:
 +
 
 +
<dascript>
 +
// make sure there is a location, just in case
 +
if(IsObjectValid(stEvent.oTarget) == TRUE)
 +
{
 +
    stEvent.lTarget = GetLocation(stEvent.oTarget);
 +
}
 +
</dascript>
 +
 
 +
While not technically wrong - provided the function returns 0 or 1 as it does here - the comparison with the boolean constant is redundant and is not considered best practice. The above example should have been simplified to:
 +
 
 +
<dascript>
 +
// make sure there is a location, just in case
 +
if(IsObjectValid(stEvent.oTarget))
 +
{
 +
    stEvent.lTarget = GetLocation(stEvent.oTarget);
 +
}
 +
</dascript>
 +
 
 +
The rationale for avoiding comparisons with the boolean constants, specifically with the '''TRUE''' constant, is because the '''TRUE''' constant is only one of many values that can represent the '''true''' state and therefore can introduce ambiguity or unintended behaviour (i.e. a bug).
 +
 
 +
An example of this might be where an include file contains the following function:
 +
 
 +
<dascript>
 +
int HasSuperValuableGems(object oCreature)
 +
{
 +
    return CountItemsByTag(oCreature, "SuperValuableGem");
 +
}
 +
</dascript>
 +
 
 +
And the action script or plot script contains the following:
 +
 
 +
<dascript>
 +
// The PC needs a "super valuable gem" in order to bribe the noble!
 +
if(HasSuperValuableGems(oHero) == TRUE)
 +
{
 +
    ...
 +
}
 +
</dascript>
 +
 
 +
The logic may appear correct however because the function is not constrained to return a boolean constant but is being compared against one the result will be that the PC can only bribe the noble when they have EXACTLY one gem which is probably not the intended behaviour. Removing the comparison against the '''TRUE''' constant would resolve this (although it could also be argued that the function should also be renamed to better indicate its behaviour).
 +
 
 +
Note that while comparisons against the '''FALSE''' constant do not suffer from the same ambiguity (as both it and the '''false''' state only ever equate to zero) they are nonetheless redundant and should be simplified using the [[logical negation operator]]:
 +
 
 +
<dascript>
 +
// Replace this:
 +
if(IsObjectValid(oTarget) == FALSE)
 +
{
 +
    ...
 +
}
 +
 
 +
// With this:
 +
if(!IsObjectValid(oTarget))
 +
{
 +
    ...
 +
}
 +
</dascript>
 +
 
 +
In conclusion it is best to only use the boolean constants for the following purposes:
 +
# Assigning a value to a integer variable.
 +
# Setting the a value of an integer argument in a function call.
 +
# Setting the default value of an integer argument in a function's declaration or definition.
 +
# Setting a return value from an integer function.
 +
 
 +
== See also ==
 
<!-- This section contains links to articles, functions or constant groups. -->
 
<!-- This section contains links to articles, functions or constant groups. -->
 +
*[[bool (2da type)]] for boolean values in [[2DA]]s
  
 
[[Category:Constants]]
 
[[Category:Constants]]

Latest revision as of 18:30, 13 April 2020

Source: script.ldf
Constant name Type Value Description Source
FALSE int 0 script.ldf
TRUE int 1 script.ldf

Remarks

In dascript there is no Boolean type. Instead integer values are used to represent the Boolean states: zero represents the false state and any non-zero integer represents the true state. For convenience the boolean constants have been defined in script.ldf to represent the true and false states.

These constants are used in the following ways in script.ldf, the Core Game Resources and the Single Player Campaign:

// Assigning a value to a integer variable.
int bStillLooking = TRUE;
 
// Setting the a value of an integer argument in a function call.
SetItemDroppable(oItem, TRUE);
 
// Setting the default value of an integer argument in a function's declaration or definition.
void AddAbility(object oObject, int nAbility, int bSendNotification = FALSE);
 
// Setting a return value from an integer function.
return TRUE;

The constants are also referenced in the comments describing a function's return values, for example, as seen is IsObjectValid, to indicate that the function is constrained to return 0 or 1. This is probably reliable for engine functions (i.e. those in script.ldf) which are presumably converting from a native C++ bool type but should be treated with scepticism for scripted functions.

Finally the constants are also used in boolean expressions, for example, in an if statement:

if(<expression> == TRUE)
{
    ...
}

Typically this will be seen in connection with the various Is* and Has* functions, for example, as seen with the use of IsObjectValid in the item_singletarget script:

// make sure there is a location, just in case
if(IsObjectValid(stEvent.oTarget) == TRUE)
{
    stEvent.lTarget = GetLocation(stEvent.oTarget);
}

While not technically wrong - provided the function returns 0 or 1 as it does here - the comparison with the boolean constant is redundant and is not considered best practice. The above example should have been simplified to:

// make sure there is a location, just in case
if(IsObjectValid(stEvent.oTarget))
{
    stEvent.lTarget = GetLocation(stEvent.oTarget);
}

The rationale for avoiding comparisons with the boolean constants, specifically with the TRUE constant, is because the TRUE constant is only one of many values that can represent the true state and therefore can introduce ambiguity or unintended behaviour (i.e. a bug).

An example of this might be where an include file contains the following function:

int HasSuperValuableGems(object oCreature)
{
    return CountItemsByTag(oCreature, "SuperValuableGem");
}

And the action script or plot script contains the following:

// The PC needs a "super valuable gem" in order to bribe the noble!
if(HasSuperValuableGems(oHero) == TRUE)
{
    ...
}

The logic may appear correct however because the function is not constrained to return a boolean constant but is being compared against one the result will be that the PC can only bribe the noble when they have EXACTLY one gem which is probably not the intended behaviour. Removing the comparison against the TRUE constant would resolve this (although it could also be argued that the function should also be renamed to better indicate its behaviour).

Note that while comparisons against the FALSE constant do not suffer from the same ambiguity (as both it and the false state only ever equate to zero) they are nonetheless redundant and should be simplified using the logical negation operator:

// Replace this:
if(IsObjectValid(oTarget) == FALSE)
{
    ...
}
 
// With this:
if(!IsObjectValid(oTarget))
{
    ...
}

In conclusion it is best to only use the boolean constants for the following purposes:

  1. Assigning a value to a integer variable.
  2. Setting the a value of an integer argument in a function call.
  3. Setting the default value of an integer argument in a function's declaration or definition.
  4. Setting a return value from an integer function.

See also