Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

If your library is supposed to provide a result to the library user, it is recommended to store a predefined variable in the library cue with a standardised name, e.g. $result. The user will be able to read it via CueName.$result. This variable does not have to be defined as a parameter but should be documented in the library.


 

Instantiation

 

One of the possible cue attributes is instantiate. If you set it to true, this changes what happens when a cue's conditions are met. Normally, if a cue is not instantiated, the cue's actions are run (taking a delay node into account) and the cue is marked as completed. But with instantiate, a copy of the cue (and all its sub-cues) is made when the conditions are met, and it is this copy in which the actions are performed and it is the copy whose status is set to complete when they are finished - this means that the original cue (the so-called static cue) remains in the waiting state, and if the conditions are met again then the whole thing happens all over again.

...

Cleaning up instances explicitly

...

Cancelling a cue with <cancel_cue> also cancels all its sub-cues, and cancelling a static cue stops it from instantiating more cues - but it does not cancel its instances. Resetting a cue with <reset_cue> resets both sub-cues and instantiated cues, but has the (desired) side effect that condition checks will start again if the parent cue’s state allows it. Even a sub-instance that has been reset can return to the waiting state. Resetting an instantiated cue will stop it forever, because it is not supposed to be in the waiting state (only its static cue is). Resetting will also induce the clean-up reliably, but keep in mind that this is not the case for instance sub-cues.

Info

<cancel_cue> and <reset_cue> only take effect after all remaining actions of the current cue are performed. So you can even safely cancel the cue that you are currently in (keyword “this”) or any ancestor cue, and still perform more actions afterwards.


Access to instances

...

Note

This sub-section requires basic knowledge of script expressions, see the section below.

...

You can store cue references in variables. But when storing an instance cue in a variable, and later accessing that variable, be aware that the instance may not exist any more. Use the property exists to check if an instance is still alive. (In contrast, non-instance cues always exist, but may be in the disabled or cancelled state.)


Pitfalls

...

Some additional common pitfalls with respect to instantiation are listed here. There may be more.

  • 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)

Note

Since octal numbers are hardly ever used (usually unknowingly), the parser is will produce a warning if an octal number is encountered.

You can write string literals by putting the string in single quotes:

 

  • 'Hello world'

  • '' (empty string)

  • 'String with a line break\n'

Note

Since expressions are written in XML attribute values, you have to use the single quotes inside the double quotes for the actual attribute value. To write characters like < > " & in an expression string (or anywhere else in an XML attribute value), you’ll have to escape them as &lt; &gt; &quot; &amp; respectively. The backslash \ can be used in strings for escape characters like in C/C++. Most important are \' for a single quote as part of the string, and \\ for the backslash itself.


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)

A space between number and suffix is allowed.

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)
Cr

200Cr
50ct

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
(Note: Braces are also used for property lookups, see below)

+

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
42.0 / 10.0

4
4.2

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

&lt; (<)

binary

1 lt 3

1 &lt; 3

true

Less than

le

&lt;=

binary

1 le 3

1 &lt;= 3

true

Less than or equal to

gt

&gt; (>)

binary

1 gt 3

1 &gt; 3

false

Greater than

ge

&gt;=

binary

1 ge 3

1 &gt;= 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.

For multiplication and division, this may not be intuitive in all cases: Dividing a length by another length results in a length - so if you want to have a simple float as a result, you will have to convert it manually.

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

When converting to a non-default unit type, this means you interpret the number as in the given units: “(1km + 500m) h” means that you interpret 1500m as 1500 hours, so the resulting value will be 1500x3600 seconds. (As stated above, the default unit for a length is metres.)

The division operation will be an integer division (rounding towards zero) if both operands are integers (see the example in the table above). So if you want to get a floating point result, you have to make sure that at least one of the operands is a floating point type.

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.

Instead of ‘%1 %2 %3’, you can also use ‘%s %s %s’ - however, this should not be used for texts that require localisation. Translators should be aware that it is possible to change the order of the parameters.

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.

A list can contain values of arbitrary data types, even mixed in the same list - so a list can actually contain other lists. However, some of the things that you can do with lists require that all contained elements are of a certain type. The contents of a list can be accessed via properties, see the next section. Lists can be empty, they are written as “[ ]”.

Note

When accessing a list’s elements, the numbering is 1-based, so the first element has number 1. This is intuitive but different from 0-based numbering in most programming languages.

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.

Numbers don’t have any properties. Lists, for example, have quite a few of them: You can access the number of elements; and each element is also a property of the list. A ship can have properties like its name, the ship class, its position etc.

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

As you can see, a property key can be a number or a string. Actually there is no restriction regarding the data type of the key.

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]

Note

The string formatting syntax that you have seen above is also based on the property system. You basically pass a list as property key to a string. Braces around the brackets are not required, so 'foo'.[...] is just a convenient alternative notation for 'foo'.{[...]}.

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.

Here are a few enumeration classes and corresponding example lookup values:

 

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.