...
Conditions with results: If the instantiating cue has conditions with results, those results are stored in variables - but in the variables of the static cue, not of the instance! So in the <actions> you have to access the variables via the static keyword:
<
debug_text text="static.$foo"/>
It may even be necessary to copy the variables over to the instance because the static variables can be overwritten by the next condition check:<set_value name="$foo" exact="static.$foo"/>
Resetting completed/cancelled instances: As explained above, sub-instances are only created when needed (when going to the waiting state) and are destroyed when they are not needed any more (when they are completed or cancelled, including all sub-cues). There are cases in which you want to access cues that don’t exist any more - it simply doesn’t work. In some cases you are safe: You can be sure that all your ancestors exist, and instantiating cues won’t be removed until they are cancelled. In some other cases you simply don’t know and have to check if the instance is already (or still) there.
Lifetime of instances: Do not make assumptions about when an instance is removed! Just looking at it in the Debug Manager keeps it alive for the time being. So, sometimes you could still have a completed instance that wouldn’t exist under other circumstances.
Expressions
Most of the attribute values in actions and conditions are interpreted as script expressions and parsed accordingly. An expression is a phrase that can be evaluated to a single value. The simplest expressions are actual numeric values and strings, so called literals:
0 (integer number)
0772 (leading 0 means octal integer number)
3.14159 (floating point number)
5e12 (float in exponent notation, “times ten to the power of”)
0xCAFE (hexadecimal integer number)
...
You can write string literals by putting the string in single quotes:
'Hello world'
'' (empty string)
'String with a line break\n'
...
Numeric data types and suffixes
Numbers can have a suffix that determines their numeric type. There are also numerical data types like “money” or “time” which can only be expressed by using an appropriate unit suffix:
5000000000L (large integer)
1f (floating point number, same as 1.0, just 1 would be an integer)
1000Cr (Money in Credits, converted to 100000 cents automatically)
500m (Length in metres)
10s (Time in seconds)
1h (Time in hours, which is converted to 3600s automatically)
...
Here is the complete list of numeric data types and corresponding unit suffixes:
Data type | Suffix | Examples | Description |
---|---|---|---|
null | (none) | null | Converted to non-null data type of value 0 when needed. |
integer | i | 42 | 32-bit signed integer. Default for integer literals, so the suffix is not required for them. |
largeint | L | 0x1ffffffffL | Large 64-bit signed integer. |
float | f | 3.14 0x100f | 32-bit float (single precision). Default for floating point literals, so the suffix is not required for them. |
largefloat | LF | 1.5e300 LF | Large 64-bit floating point number (double precision). |
money | ct (default) | 200Cr | Money in Credits or cents, always stored in cents. Do not forget to write Cr when working with Credits. |
length | m (default) km | 500m 2.3km | Length in metres or kilometres, respectively. A length value is always stored in metres. |
angle | rad (default) deg | 90deg 3.14159rad | Angle in radians or degrees, respectively. An angle value is always stored in radians. |
hitpoints | hp | 100hp | Hit points |
time | ms s (default) min h | 800ms 1.5s 10min 24h | Time in milliseconds, seconds, minutes, or hours, respectively. A time value is always stored in seconds. |
Note |
---|
All unit data types are floating point types, except for money, which is an integer data type. |
Operators
You can build expressions by combining sub-expressions with operators. For Boolean operations, expressions are considered “false” if they are equal to zero, “true” otherwise. The following operators, delimiters, and constants are supported:
...
Operator / Delimiter / Constant | Type | Example | Result of example | Description |
---|---|---|---|---|
null | constant | null + 1 | 1 | Null value, see above |
false | constant | 1 == 0 | false | Integer value 0, useful in Boolean expressions |
true | constant | null == 0 | true | Integer value 1, useful in Boolean expressions |
pi | constant | 2 * pi | 6.2831853rad | π as an angle (same as 180deg) |
() | delimiter | (2 + 4) * (6 + 1) | 42 | Parentheses for arithmetic grouping |
[] | delimiter | [1, 2, 2+1, 'string'] | [1, 2, 3, 'string'] | List of values (see below) |
{} | delimiter | {101, 3} | 'Some text' | Text lookup (page ID and text ID) from TextDB |
+ | unary | +21 * (+2) | 42 | Denotes positive number (no effect) |
- | unary | -(21 * -2) | 42 | Negates the following number |
not | unary | not (21 == 42) | true | Yields true if the following expression is false (equal to zero), false otherwise |
typeof | unary | typeof null typeof 0 typeof 'Hello world' | datatype.null datatype.integer datatype.string | Yields the data type of the following sub-expression |
sin | unary | sin(30deg) sin(pi) | 0.5 1.0 | Sine (function-style, parentheses required) |
cos | unary | cos(60deg) cos(pi) | 0.5 0.0 | Cosine (function-style, parentheses required) |
sqrt | unary | sqrt(2) | 1.414213LF | Square root (function-style, parentheses required) |
exp | unary | exp(1) | 2.71828LF | Exponential function (function-style, parentheses required) |
log | unary | log(8) / log(2) | 3.0LF | Natural logarithm (function-style, parentheses required) |
^ | binary | 10 ^ 3 | 1000.0LF | Power |
* | binary | 21 * 2 | 42 | Multiplication |
/ | binary | 42 / 10 | 4 | Division |
% | binary | 42 % 10 | 2 | Modulus (remainder of integer division) |
+ | binary | 1 + 1 'Hello' + ' world' | 2 'Hello world' | Addition String concatenation |
- | binary | 1 - 1 | 0 | Subtraction |
lt < (<) | binary | 1 lt 3 1 < 3 | true | Less than |
le <= | binary | 1 le 3 1 <= 3 | true | Less than or equal to |
gt > (>) | binary | 1 gt 3 1 > 3 | false | Greater than |
ge >= | binary | 1 ge 3 1 >= 3 | false | Greater than or equal to |
== | binary | 1 + 1 == 2.0 | true | Equal to |
!= | binary | 1 + 1 != 2.0 | false | Not equal to |
and | binary | true and false | false | Logical AND (strict semantics) |
or | binary | true or false | true | Logical OR (strict semantics) |
Operator precedence rules
You can group sub-expressions using parentheses, but if you don’t, the following order of operations is applied, so that 5-1+2*3 == 10 as you would expect. The order is the same as in the table above, but there are operators with the same precedence - these are applied from left to right.
Unary operators: +, -, not, typeof, function-style operators (highest precedence)
Power operator: ^
Multiplicative: *, /, %
Additive: +, -
Comparison: lt, le, gt, ge
Equality: ==, !=
and
or (lowest precedence)
Type conversion
...
When a binary arithmetic operator is used on numbers of different types, they will be converted to a suitable output type. The resulting type depends on whether a unit data type is involved (types that are not plain integers or floats). The following cases may occur:
Null and something else: The null value will be interpreted as “0” of the other type.
Two non-unit integers: The result will be an integer of the largest involved type.
Two non-unit numbers, not all integers: The result will be the largest involved float type.
Non-unit and unit: The result will be the unit type.
Two different units: The types are incompatible. This is an error, the result is undefined.
...
There is a way to convert a number into a different type manually: You append the corresponding suffix to a sub-expression in parentheses, like this:
(1 + 1)f ⟹ 2f ⟹ 2.0
(1h) m / (180deg) i ⟹ (3600s) m / (3.14rad) i ⟹ 3600m / 3 ⟹ 1200m
...
Every data type can be combined with a string with the + operator, and will be converted to a string representation. That way you can also concatenate strings and numbers:
'One plus one is equal to ' + (1+1) + '.' ⟹ 'One plus one is equal to 2.'
'One plus one is not equal to ' + 1 + 1 + '.' ⟹ 'One plus one is not equal to 11.'
As you can see, operators of the same precedence (+ in this case) are always evaluated from left to right.
Boolean operators
Some additional notes on Boolean operators (such as and, or, not, ==):
Of course a Boolean operation always results in true or false (integer 1 or 0).
Values of any type can be used as Boolean operands, e.g. for “and”. They will be interpreted as “true” if they are non-zero or non-numeric.
!= and == can be used with any data types, even non-numeric ones. When comparing two numeric values, they are converted using the rules above. Values of non-numeric types are never equal to null, or to any other numbers.
“and” and “or” use strict semantics: Both operands (left and right side expressions) are evaluated and then the result is calculated. This is different from C/C++, in which short-circuit semantics are used and the right side expression can be ignored. If you need this, you can simply use nested XML nodes instead of a single expression, e.g. <check_all> and <check_any>.
Unlike != and ==, the comparison operators <, <=, >, >= are only supported for numeric values, difficulty levels, and attention levels. Comparing other non-numeric values will result in an error and an undefined result.
<, <=, >, >= cannot be used in XML directly, so lt, le, gt, ge are provided as alternatives. In some cases you won’t have to use them, though - using range checks with additional XML attributes can be more readable (see below).
Strings
...
You can concatenate string literals using the + operator, but there is also a printf-like syntax, which is easier to use than concatenating lots of small pieces:
'The %1 %2 %3 jumps over the %5 %4'.['quick', 'brown', 'fox', 'dog', 'lazy']
'%1 + %2 = %3'.[$a, $b, $a + $b]
...
See also the section about value properties below.
...
If you need a more sophisticated method for text substitution, try <substitute_text>. See the XML schema documentation for this script action.
Lists
...
Another example for a non-numeric value is a list: It is an ordered collection of other arbitrary values (called array or vector in other languages). It can be constructed within an expression using the [] syntax, see above. It may also be generated by special actions and conditions, and there are actions that can insert or remove values.
...
Lists are stored in variables as references, so multiple variables can refer to the same shared list: If you change a shared list through a variable, e.g. by changing the value of an element, you change it as well for all other variables. However, the operators == and != can also be used on two distinct lists to compare their elements.
Value properties
...
Properties are a crucial concept in script expressions. In the previous sections you have seen mostly constant expressions, which are already evaluated when they are parsed at game start. For reading and writing variables and evaluating the game’s state, properties are used.
...
You can imagine properties as key/value pairs in an associative mapping: You pass the key, and you get the value as result. For example, the list [42, null, 'text'] has the following mapping:
1 ⟹ 42
2 ⟹ null
3 ⟹ 'text'
'count' ⟹ 3
...
You can look up a property by appending a dot and the key in curly braces:
[100, 200, 300, 400].{1} ⟹ 100 (reading the first element)
[100, 200, ['Hello ', 'world']].{3}.{2} ⟹ 'world' (second element of the inner list, which is the third element of the outer list)
[ ].{'count'} ⟹ 0
In most cases the property key is a fixed string, like “name” or “class”. You can write this like above:
[42].{'count'}
$ship.{'name'}
$ship.{'class'}
But it is easier just to write the property key without braces, which is equivalent:
[0].count
$ship.name
$ship.class
...
(In this case, $ship is a variable. All variables start with a “$”, so they cannot be confused with keywords.)
A list has even more properties:
'random' returns a randomly chosen element, or null if the list is empty
'min' and 'max' return the minimum or maximum (all elements have to be numeric)
[1, 6, 8].min ⟹ 1
'average' returns the average (but all element types have to be compatible)
[1, 6, 8].average ⟹ 5
'indexof' is followed by another property, and the index of the first occurence of that key in the list is returned, or 0 if it’s not in the list
[1, 6, 8].indexof.{8} ⟹ 3
'clone' creates a shallow copy of the list (i.e. lists that are contained as elements in the list are not copied, only the reference to them)
[1, 6, 8].clone ⟹ [1, 6, 8]
...
If you look up a property that does not exist, there will be an error, and the result will be null. To test whether a property exists, you can append a question mark “?” to the lookup, which yields true or false:
$list.{5} ⟹ The fifth element, or error if the list has less than 5 elements (or if $list is not a list at all)
$list.{5}? ⟹ true if the list exists and has the property 5, false otherwise
The question mark can even be applied to variables:
$list ⟹ The value stored under the name $list, or an error if there is no such variable
$list? ⟹ true if the variable exists, false otherwise
To look up the value of a property although it may not exist, you can use the at-sign “@” as prefix:
@$list.{5} ⟹ The fifth element if the list exists and has a fifth element, null otherwise
@$list ⟹ The list if this variable exists, null otherwise
@$list.{5}.{1} ⟹ The first element of the fifth element of $list, if it exists, null otherwise
As you can see, an error is already prevented if any link in the property chain does not exist. But use the @ prefix with care, since error messages are really helpful for detecting problems in your scripts. The @ prefix only suppresses property-related error messages and does not change any in-game behaviour.
Static lookups
...
There are a few data types which are basically enumerations: They only consist of a set of named values, e.g. the “class” data type, which is used for the component classes that exist in the game. For all these static enumeration classes there is a lookup value of the same name, from which you can get the named values as properties by their name. So for the type “class”, there is a value “class” that can be used to access the classes.
...
Data type (= value name) | Examples | Description |
---|---|---|
class | class.ship class.ship_xl class.space class.weapon | Component classes |
purpose | purpose.combat purpose.transportation | Purposes |
killmethod | killmethod.hitbybullet killmethod.hitbymissile | Ways to die (already used before destruction) |
datatype | datatype.float datatype.component datatype.class datatype.datatype | Script value datatypes (see typeof operator above) |
profile | profile.flat profile.increasing profile.bell | Probability distribution profile (see random ranges below) |
cuestate | cuestate.waiting cuestate.active cuestate.complete | Cue states (see above) |
level | level.easy level.medium level.veryhard | Mission difficulty levels (comparable with each other using lt, gt, etc.) |
attention | attention.insector attention.visible attention.adjacentzone | Attention levels (comparable with each other using lt, gt, etc.) |
ware | ware.ore ware.silicon | Wares |
race | race.argon race.boron | Races |
faction | faction.player faction.argongovernment | Factions |
There is also the datatype “tag” with the lookup name “tag” - however, this is not an enumeration type. Looking up a value by name never fails, you actually create a tag value for a given name if it does not exist. For example, if you have a typo, like “tag.mision” instead of “tag.mission”, there won’t be an error because any name is valid for a tag, and the tag “mision” is created on its first use.
Player properties
You can access many player-related game properties via the keyword “player”:
player.name: The player’s name
player.age: The passed in-game time since game start
player.money: The money in the player’s account
player.ship: The ship the player is on (not necessarily the player's ship)
- player.primaryship: The ship that the player controls (but the player is not necessarily on board)
player.zone, player.sector, player.cluster, player.galaxy: Location of the player entity
player.copilot: The co-pilot NPC
Components, like ships, spaces, or NPCs, have their own properties. The available properties depend on the particular component type, even if there is only one script datatype for components. So ships may have the property “speed”, but stations don’t.
Safe properties
Most properties cause errors if you use them on non-existing objects, such as destroyed ships. There are a few exceptions:
exists
isoperational
iswreck
isconstruction
available
isclass.(...)
These properties will not cause errors when used on “null” or on a destroyed object (which may still be accessible from scripts in some cases), and produce null or false as results, respectively. (The keyword “available” is used for trades, not for objects. Trades can also become invalid.) However, when using such a property on a different data type like a number, there will still be an error.
Complete property documentation
...
The game files include the HTML file scriptproperties.html in the game’s root folder, and more scriptproperties.* files in the libraries folder. Open the HTML file in a browser (only Firefox is officially supported, there may be problems with Chrome). This provides you with a complete list of all supported “base keywords” and properties. To filter in this list, you can enter an expression in the text field:
Enter the beginning of a base keyword
Enter $ followed by the data type you are looking for (e.g. “$ship”), as if it were a variable
To see the properties of a base keyword or data type, enter a dot (“.”)
After the dot, you can enter a property name
You can also enter a dot (“.”) as first character to search globally for a property
...
Note |
---|
The documentation contains some data types that are no real script data types, but which are useful for documentation purposes. For example, ships and stations are both of datatype “component”, but have different properties based on their component class. |