InformationYou can write string literals by putting the string in single quotes:
- 'Hello world'
- '' (empty string)
- 'String with a line break\n'
InformationSince 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. |
InformationAll unit data types are floating point types, except for money, which is an integer data type.
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 |
table[] | delimiter | table[$foo='bar', {1+1}=40+2] | table[$foo='bar', {2}=42] | Table of values |
{} | delimiter | {101, 3} | 'Some text' | Text lookup (page ID and text ID) from TextDB
(Note: Braces are also used for property lookups) |
+ | 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 / 1042.0 / 10.0 | 44.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
< (<) | binary | 1 lt 3
1 &lt; 3 | true | Less than |
le
<= | binary | 1 le 3
1 &lt;= 3 | true | Less than or equal to |
gt
> (>) | binary | 1 gt 3
1 &gt; 3 | false | Greater than |
ge
>= | 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 (short-circuit semantics) |
or | binary | true or false | true | Logical OR (short-circuit semantics) |
if ... then ...
if ... then ... else ... | ternary | if 1 == 2 then 'F'
if 1 == 2 then 'F' else 'T' | null
'T' | Conditional operator ("inline if") |
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
- if/then/else (lowest precedence)
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.
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 short-circuit semantics: The right side of the operation can be skipped if the left side already determines the outcome of the operation
- Example: false and $foo ⟹ false (the value of $foo is not checked at all)
- 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.
== Strings and formatting==
==
You can concatenate string literals using the + operator, but there is also a printf-like formatting 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.
Instead of ΓÇÿ%1 %2 %3ΓÇÖ, you can also use ΓÇÿ%s %s %sΓÇÖ, which is also compatible with Lua string formatting in the UI system. However, this should only be used if you are sure that the order is the same in all supported languages. If you want to make translators aware that they can change the order of parameters, you should prefer '%1 %2 %3'.
To get a percent character in the result string, use '%%' in the format string.
If you need a more sophisticated method for text substitution, try <substitute_text>. See the XML schema documentation for this script action.
[New as of X Rebirth 4.0]
With the formatting syntax above, it is even possible to control how the parameter is formatted, using modifiers between "%" and the parameter specifier ("s" or the parameter number):
- '%,s'.[12345678] ⟹ '12,345,678' (the "," modifier shows a number with thousands separators, correctly localised)
- '%.3s'.[123.4] ⟹ '123.400' (show 3 fractional digits, rounding half away from zero - decimal point correctly localised)
- '%,.1s'.[12345.67]' ⟹ '12,345.7' (combination of the above)
Additional remarks:
- The "," and "." formatting modifiers only apply to numbers. They are ignored if used on values of other types.
-  If "," is used without "." then any fractional digits are discarded.
- "." must be followed by a single digit (0-9). In case of ".0" any fractional digits are discarded (rounding towards zero, not half away from zero).
Failed to execute the [info] macro. Cause: [The required content is missing.]. Click on this message for details.
org.xwiki.rendering.macro.MacroExecutionException: The required content is missing.
at org.xwiki.rendering.macro.box.AbstractBoxMacro$BoxBlockBuilder.build(AbstractBoxMacro.java:255)
at org.xwiki.rendering.macro.box.AbstractBoxMacro.execute(AbstractBoxMacro.java:181)
at org.xwiki.rendering.internal.macro.message.AbstractMessageMacro.execute(AbstractMessageMacro.java:123)
at org.xwiki.rendering.internal.macro.message.AbstractMessageMacro.execute(AbstractMessageMacro.java:53)
at org.xwiki.rendering.internal.transformation.macro.MacroTransformation.transform(MacroTransformation.java:311)
at org.xwiki.rendering.internal.transformation.DefaultRenderingContext.transformInContext(DefaultRenderingContext.java:183)
at org.xwiki.rendering.internal.transformation.DefaultTransformationManager.performTransformations(DefaultTransformationManager.java:88)
at org.xwiki.display.internal.DocumentContentAsyncExecutor.executeInCurrentExecutionContext(DocumentContentAsyncExecutor.java:397)
at org.xwiki.display.internal.DocumentContentAsyncExecutor.execute(DocumentContentAsyncExecutor.java:269)
at org.xwiki.display.internal.DocumentContentAsyncRenderer.execute(DocumentContentAsyncRenderer.java:112)
at org.xwiki.rendering.async.internal.block.AbstractBlockAsyncRenderer.render(AbstractBlockAsyncRenderer.java:157)
at org.xwiki.rendering.async.internal.block.AbstractBlockAsyncRenderer.render(AbstractBlockAsyncRenderer.java:54)
at org.xwiki.rendering.async.internal.DefaultAsyncRendererExecutor.syncRender(DefaultAsyncRendererExecutor.java:290)
at org.xwiki.rendering.async.internal.DefaultAsyncRendererExecutor.render(DefaultAsyncRendererExecutor.java:267)
at org.xwiki.rendering.async.internal.block.DefaultBlockAsyncRendererExecutor.execute(DefaultBlockAsyncRendererExecutor.java:125)
at org.xwiki.display.internal.DocumentContentDisplayer.display(DocumentContentDisplayer.java:67)
at org.xwiki.display.internal.DocumentContentDisplayer.display(DocumentContentDisplayer.java:43)
at org.xwiki.display.internal.DefaultDocumentDisplayer.display(DefaultDocumentDisplayer.java:96)
at org.xwiki.display.internal.DefaultDocumentDisplayer.display(DefaultDocumentDisplayer.java:39)
at org.xwiki.sheet.internal.SheetDocumentDisplayer.display(SheetDocumentDisplayer.java:123)
at org.xwiki.sheet.internal.SheetDocumentDisplayer.display(SheetDocumentDisplayer.java:52)
at org.xwiki.display.internal.ConfiguredDocumentDisplayer.display(ConfiguredDocumentDisplayer.java:68)
at org.xwiki.display.internal.ConfiguredDocumentDisplayer.display(ConfiguredDocumentDisplayer.java:42)
at com.xpn.xwiki.doc.XWikiDocument.display(XWikiDocument.java:1429)
at com.xpn.xwiki.doc.XWikiDocument.getRenderedContent(XWikiDocument.java:1565)
at com.xpn.xwiki.doc.XWikiDocument.displayDocument(XWikiDocument.java:1515)
at com.xpn.xwiki.doc.XWikiDocument.displayDocument(XWikiDocument.java:1484)
at com.xpn.xwiki.api.Document.displayDocument(Document.java:821)
at jdk.internal.reflect.GeneratedMethodAccessor621.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:569)
at org.apache.velocity.util.introspection.UberspectImpl$VelMethodImpl.doInvoke(UberspectImpl.java:571)
at org.apache.velocity.util.introspection.UberspectImpl$VelMethodImpl.invoke(UberspectImpl.java:554)
at org.apache.velocity.runtime.parser.node.ASTMethod.execute(ASTMethod.java:221)
at org.apache.velocity.runtime.parser.node.ASTReference.execute(ASTReference.java:368)
at org.apache.velocity.runtime.parser.node.ASTReference.value(ASTReference.java:704)
at org.apache.velocity.runtime.parser.node.ASTExpression.value(ASTExpression.java:75)
at org.apache.velocity.runtime.parser.node.ASTSetDirective.render(ASTSetDirective.java:242)
at org.apache.velocity.runtime.parser.node.ASTBlock.render(ASTBlock.java:147)
at org.apache.velocity.runtime.parser.node.SimpleNode.render(SimpleNode.java:439)
at org.apache.velocity.runtime.parser.node.ASTIfStatement.render(ASTIfStatement.java:190)
at org.apache.velocity.runtime.parser.node.ASTBlock.render(ASTBlock.java:147)
at org.xwiki.velocity.internal.directive.TryCatchDirective.render(TryCatchDirective.java:86)
at org.apache.velocity.runtime.parser.node.ASTDirective.render(ASTDirective.java:304)
at org.apache.velocity.runtime.parser.node.SimpleNode.render(SimpleNode.java:439)
at org.apache.velocity.Template.merge(Template.java:358)
at org.apache.velocity.Template.merge(Template.java:262)
at org.xwiki.velocity.internal.InternalVelocityEngine.evaluate(InternalVelocityEngine.java:233)
at com.xpn.xwiki.internal.template.VelocityTemplateEvaluator.evaluateContent(VelocityTemplateEvaluator.java:107)
at com.xpn.xwiki.internal.template.TemplateAsyncRenderer.evaluateContent(TemplateAsyncRenderer.java:219)
at com.xpn.xwiki.internal.template.TemplateAsyncRenderer.renderVelocity(TemplateAsyncRenderer.java:174)
at com.xpn.xwiki.internal.template.TemplateAsyncRenderer.render(TemplateAsyncRenderer.java:135)
at com.xpn.xwiki.internal.template.TemplateAsyncRenderer.render(TemplateAsyncRenderer.java:54)
at org.xwiki.rendering.async.internal.DefaultAsyncRendererExecutor.lambda$syncRender$0(DefaultAsyncRendererExecutor.java:284)
at com.xpn.xwiki.internal.security.authorization.DefaultAuthorExecutor.call(DefaultAuthorExecutor.java:98)
at org.xwiki.rendering.async.internal.DefaultAsyncRendererExecutor.syncRender(DefaultAsyncRendererExecutor.java:284)
at org.xwiki.rendering.async.internal.DefaultAsyncRendererExecutor.render(DefaultAsyncRendererExecutor.java:267)
at org.xwiki.rendering.async.internal.block.DefaultBlockAsyncRendererExecutor.render(DefaultBlockAsyncRendererExecutor.java:154)
at com.xpn.xwiki.internal.template.InternalTemplateManager.render(InternalTemplateManager.java:904)
at com.xpn.xwiki.internal.template.InternalTemplateManager.renderFromSkin(InternalTemplateManager.java:866)
at com.xpn.xwiki.internal.template.InternalTemplateManager.render(InternalTemplateManager.java:853)
at com.xpn.xwiki.internal.template.InternalTemplateManager.renderNoException(InternalTemplateManager.java:808)
at com.xpn.xwiki.internal.template.InternalTemplateManager.renderNoException(InternalTemplateManager.java:800)
at com.xpn.xwiki.internal.template.DefaultTemplateManager.renderNoException(DefaultTemplateManager.java:79)
at com.xpn.xwiki.internal.template.DefaultTemplateManager.renderNoException(DefaultTemplateManager.java:73)
at org.xwiki.template.script.TemplateScriptService.render(TemplateScriptService.java:54)
at jdk.internal.reflect.GeneratedMethodAccessor255.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:569)
at org.apache.velocity.util.introspection.UberspectImpl$VelMethodImpl.doInvoke(UberspectImpl.java:571)
at org.apache.velocity.util.introspection.UberspectImpl$VelMethodImpl.invoke(UberspectImpl.java:554)
at org.apache.velocity.runtime.parser.node.ASTMethod.execute(ASTMethod.java:221)
at org.apache.velocity.runtime.parser.node.ASTReference.execute(ASTReference.java:368)
at org.apache.velocity.runtime.parser.node.ASTReference.render(ASTReference.java:492)
at org.apache.velocity.runtime.parser.node.ASTBlock.render(ASTBlock.java:147)
at org.apache.velocity.runtime.directive.VelocimacroProxy.render(VelocimacroProxy.java:217)
at org.apache.velocity.runtime.directive.RuntimeMacro.render(RuntimeMacro.java:331)
at org.apache.velocity.runtime.directive.RuntimeMacro.render(RuntimeMacro.java:261)
at org.apache.velocity.runtime.parser.node.ASTDirective.render(ASTDirective.java:304)
at org.apache.velocity.runtime.parser.node.SimpleNode.render(SimpleNode.java:439)
at org.apache.velocity.Template.merge(Template.java:358)
at org.apache.velocity.Template.merge(Template.java:262)
at org.xwiki.velocity.internal.InternalVelocityEngine.evaluate(InternalVelocityEngine.java:233)
at com.xpn.xwiki.internal.template.VelocityTemplateEvaluator.evaluateContent(VelocityTemplateEvaluator.java:107)
at com.xpn.xwiki.internal.template.TemplateAsyncRenderer.evaluateContent(TemplateAsyncRenderer.java:219)
at com.xpn.xwiki.internal.template.TemplateAsyncRenderer.renderVelocity(TemplateAsyncRenderer.java:174)
at com.xpn.xwiki.internal.template.TemplateAsyncRenderer.render(TemplateAsyncRenderer.java:135)
at com.xpn.xwiki.internal.template.TemplateAsyncRenderer.render(TemplateAsyncRenderer.java:54)
at org.xwiki.rendering.async.internal.DefaultAsyncRendererExecutor.lambda$syncRender$0(DefaultAsyncRendererExecutor.java:284)
at com.xpn.xwiki.internal.security.authorization.DefaultAuthorExecutor.call(DefaultAuthorExecutor.java:98)
at org.xwiki.rendering.async.internal.DefaultAsyncRendererExecutor.syncRender(DefaultAsyncRendererExecutor.java:284)
at org.xwiki.rendering.async.internal.DefaultAsyncRendererExecutor.render(DefaultAsyncRendererExecutor.java:267)
at org.xwiki.rendering.async.internal.block.DefaultBlockAsyncRendererExecutor.render(DefaultBlockAsyncRendererExecutor.java:154)
at com.xpn.xwiki.internal.template.InternalTemplateManager.render(InternalTemplateManager.java:904)
at com.xpn.xwiki.internal.template.InternalTemplateManager.renderFromSkin(InternalTemplateManager.java:866)
at com.xpn.xwiki.internal.template.InternalTemplateManager.render(InternalTemplateManager.java:853)
at com.xpn.xwiki.internal.template.InternalTemplateManager.renderNoException(InternalTemplateManager.java:808)
at com.xpn.xwiki.internal.template.InternalTemplateManager.renderNoException(InternalTemplateManager.java:800)
at com.xpn.xwiki.internal.template.DefaultTemplateManager.renderNoException(DefaultTemplateManager.java:79)
at com.xpn.xwiki.internal.template.DefaultTemplateManager.renderNoException(DefaultTemplateManager.java:73)
at org.xwiki.template.script.TemplateScriptService.render(TemplateScriptService.java:54)
at jdk.internal.reflect.GeneratedMethodAccessor255.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:569)
at org.apache.velocity.util.introspection.UberspectImpl$VelMethodImpl.doInvoke(UberspectImpl.java:571)
at org.apache.velocity.util.introspection.UberspectImpl$VelMethodImpl.invoke(UberspectImpl.java:554)
at org.apache.velocity.runtime.parser.node.ASTMethod.execute(ASTMethod.java:221)
at org.apache.velocity.runtime.parser.node.ASTReference.execute(ASTReference.java:368)
at org.apache.velocity.runtime.parser.node.ASTReference.render(ASTReference.java:492)
at org.apache.velocity.runtime.parser.node.ASTBlock.render(ASTBlock.java:147)
at org.apache.velocity.runtime.directive.VelocimacroProxy.render(VelocimacroProxy.java:217)
at org.apache.velocity.runtime.directive.RuntimeMacro.render(RuntimeMacro.java:331)
at org.apache.velocity.runtime.directive.RuntimeMacro.render(RuntimeMacro.java:261)
at org.apache.velocity.runtime.parser.node.ASTDirective.render(ASTDirective.java:304)
at org.apache.velocity.runtime.parser.node.ASTBlock.render(ASTBlock.java:147)
at org.apache.velocity.runtime.parser.node.ASTIfStatement.render(ASTIfStatement.java:171)
at org.apache.velocity.runtime.parser.node.ASTBlock.render(ASTBlock.java:147)
at org.apache.velocity.runtime.parser.node.SimpleNode.render(SimpleNode.java:439)
at org.apache.velocity.runtime.parser.node.ASTIfStatement.render(ASTIfStatement.java:190)
at org.apache.velocity.runtime.parser.node.SimpleNode.render(SimpleNode.java:439)
at org.apache.velocity.Template.merge(Template.java:358)
at org.apache.velocity.Template.merge(Template.java:262)
at org.xwiki.velocity.internal.InternalVelocityEngine.evaluate(InternalVelocityEngine.java:233)
at com.xpn.xwiki.internal.template.VelocityTemplateEvaluator.evaluateContent(VelocityTemplateEvaluator.java:107)
at com.xpn.xwiki.internal.template.TemplateAsyncRenderer.evaluateContent(TemplateAsyncRenderer.java:219)
at com.xpn.xwiki.internal.template.TemplateAsyncRenderer.renderVelocity(TemplateAsyncRenderer.java:174)
at com.xpn.xwiki.internal.template.TemplateAsyncRenderer.render(TemplateAsyncRenderer.java:135)
at com.xpn.xwiki.internal.template.TemplateAsyncRenderer.render(TemplateAsyncRenderer.java:54)
at org.xwiki.rendering.async.internal.DefaultAsyncRendererExecutor.lambda$syncRender$0(DefaultAsyncRendererExecutor.java:284)
at com.xpn.xwiki.internal.security.authorization.DefaultAuthorExecutor.call(DefaultAuthorExecutor.java:98)
at org.xwiki.rendering.async.internal.DefaultAsyncRendererExecutor.syncRender(DefaultAsyncRendererExecutor.java:284)
at org.xwiki.rendering.async.internal.DefaultAsyncRendererExecutor.render(DefaultAsyncRendererExecutor.java:267)
at org.xwiki.rendering.async.internal.block.DefaultBlockAsyncRendererExecutor.render(DefaultBlockAsyncRendererExecutor.java:154)
at com.xpn.xwiki.internal.template.InternalTemplateManager.render(InternalTemplateManager.java:904)
at com.xpn.xwiki.internal.template.InternalTemplateManager.renderFromSkin(InternalTemplateManager.java:866)
at com.xpn.xwiki.internal.template.InternalTemplateManager.renderFromSkin(InternalTemplateManager.java:846)
at com.xpn.xwiki.internal.template.InternalTemplateManager.render(InternalTemplateManager.java:832)
at com.xpn.xwiki.internal.template.DefaultTemplateManager.render(DefaultTemplateManager.java:91)
at com.xpn.xwiki.internal.template.DefaultTemplateManager.render(DefaultTemplateManager.java:85)
at com.xpn.xwiki.XWiki.evaluateTemplate(XWiki.java:2570)
at com.xpn.xwiki.web.Utils.parseTemplate(Utils.java:180)
at com.xpn.xwiki.web.XWikiAction.execute(XWikiAction.java:650)
at com.xpn.xwiki.web.XWikiAction.execute(XWikiAction.java:338)
at com.xpn.xwiki.web.LegacyActionServlet.service(LegacyActionServlet.java:111)
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:614)
at org.eclipse.jetty.ee10.servlet.ServletHolder$NotAsync.service(ServletHolder.java:1379)
at org.eclipse.jetty.ee10.servlet.ServletHolder.handle(ServletHolder.java:736)
at org.eclipse.jetty.ee10.servlet.ServletHandler$ChainEnd.doFilter(ServletHandler.java:1621)
at com.xpn.xwiki.web.ActionFilter.doFilter(ActionFilter.java:123)
at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205)
at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1593)
at org.xwiki.wysiwyg.filter.ConversionFilter.doFilter(ConversionFilter.java:65)
at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205)
at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1593)
at org.xwiki.resource.servlet.RoutingFilter.doFilter(RoutingFilter.java:135)
at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205)
at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1593)
at org.xwiki.container.servlet.filters.internal.SavedRequestRestorerFilter.doFilter(SavedRequestRestorerFilter.java:211)
at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205)
at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1593)
at org.xwiki.container.servlet.filters.internal.SetCharacterEncodingFilter.doFilter(SetCharacterEncodingFilter.java:120)
at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205)
at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1593)
at org.tuckey.web.filters.urlrewrite.RuleChain.handleRewrite(RuleChain.java:176)
at org.tuckey.web.filters.urlrewrite.RuleChain.doRules(RuleChain.java:145)
at org.tuckey.web.filters.urlrewrite.UrlRewriter.processRequest(UrlRewriter.java:92)
at org.tuckey.web.filters.urlrewrite.UrlRewriteFilter.doFilter(UrlRewriteFilter.java:405)
at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205)
at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1593)
at org.eclipse.jetty.ee10.servlet.ServletHandler$MappedServlet.handle(ServletHandler.java:1554)
at org.eclipse.jetty.ee10.servlet.Dispatcher.forward(Dispatcher.java:126)
at org.tuckey.web.filters.urlrewrite.NormalRewrittenUrl.doRewrite(NormalRewrittenUrl.java:233)
at org.tuckey.web.filters.urlrewrite.RuleChain.handleRewrite(RuleChain.java:171)
at org.tuckey.web.filters.urlrewrite.RuleChain.doRules(RuleChain.java:145)
at org.tuckey.web.filters.urlrewrite.UrlRewriter.processRequest(UrlRewriter.java:92)
at org.tuckey.web.filters.urlrewrite.UrlRewriteFilter.doFilter(UrlRewriteFilter.java:405)
at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:208)
at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1593)
at org.eclipse.jetty.ee10.websocket.servlet.WebSocketUpgradeFilter.doFilter(WebSocketUpgradeFilter.java:195)
at org.eclipse.jetty.ee10.servlet.FilterHolder.doFilter(FilterHolder.java:205)
at org.eclipse.jetty.ee10.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1593)
at org.eclipse.jetty.ee10.servlet.ServletHandler$MappedServlet.handle(ServletHandler.java:1554)
at org.eclipse.jetty.ee10.servlet.ServletChannel.dispatch(ServletChannel.java:819)
at org.eclipse.jetty.ee10.servlet.ServletChannel.handle(ServletChannel.java:436)
at org.eclipse.jetty.ee10.servlet.ServletHandler.handle(ServletHandler.java:469)
at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:575)
at org.eclipse.jetty.ee10.servlet.SessionHandler.handle(SessionHandler.java:717)
at org.eclipse.jetty.server.handler.ContextHandler.handle(ContextHandler.java:1064)
at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:181)
at org.eclipse.jetty.server.Server.handle(Server.java:182)
at org.eclipse.jetty.server.internal.HttpChannelState$HandlerInvoker.run(HttpChannelState.java:662)
at org.eclipse.jetty.server.internal.HttpConnection.onFillable(HttpConnection.java:416)
at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:322)
at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:99)
at org.eclipse.jetty.io.ssl.SslConnection$SslEndPoint.onFillable(SslConnection.java:575)
at org.eclipse.jetty.io.ssl.SslConnection.onFillable(SslConnection.java:390)
at org.eclipse.jetty.io.ssl.SslConnection$2.succeeded(SslConnection.java:150)
at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:99)
at org.eclipse.jetty.io.SelectableChannelEndPoint$1.run(SelectableChannelEndPoint.java:53)
at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.runTask(AdaptiveExecutionStrategy.java:480)
at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.consumeTask(AdaptiveExecutionStrategy.java:443)
at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.tryProduce(AdaptiveExecutionStrategy.java:293)
at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.run(AdaptiveExecutionStrategy.java:201)
at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:311)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:979)
at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.doRunJob(QueuedThreadPool.java:1209)
at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1164)
at java.base/java.lang.Thread.run(Thread.java:840)
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. 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 section about value properties. Lists can be empty, these are written as ΓÇ£[ ]ΓÇ¥.
InformationWhen 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.
InformationWhen using <remove_from_list/>, be aware that all elements are checked and potentially removed during the action. Do not provide this action with a index lookup of that list as it may become out of bounds.
Bad usage attempting to remove the last element of the list: <remove_from_list name="$List" exact="$List.{$List.count}"/>
If you know the index, simply use <remove_value/> e.g. <remove_value name="$List.{$List.count}"/>"
Tables are associative arrays - they are like lists, but you can assign values to (almost) arbitrary keys, not just to index numbers. A table is constructed within an expression using the table[] syntax. See the section about value properties for how to access the contents of a table. Creating and removing entries works similarly to lists, but instead of inserting, you simply assign a value to a table key. If the key does not exist yet, it will be created.
Almost all values are allowed as table keys, but there are a few exceptions:
- Strings must start with '$', like variables
- null cannot be used as table key (but the number 0 is valid)
- Lists, tables, groups and buildplans cannot be used as table keys
These restrictions only apply to the keys, there are no restrictions for values that you assign to them. For example:
- table[] ⟹ creates an empty table
- table[{0} = null] ⟹ creates a table that maps the number 0 to null
- table[{'$foo'} = 'bar'] ⟹ a table that maps the string '$foo' to the string 'bar'
- table[$foo = 'bar'] ⟹ exactly the same, just a shorter notation for string keys
- table[foo = 'bar'] ⟹ error, 'foo' does not start with a '$'
- table[{1} = [], {2} = table[]] ⟹ a table that maps 1 to an empty list and 2 to an empty table
Just like lists, tables are stored as references, so it's possible that multiple variables reference the same table (see above).
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
- table[{21} = 42].{21} ⟹ 42
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'}
- table[$foo='bar'].{'$foo'}
But it is easier just to write the property key without braces, which is equivalent:
- [0].count
- $ship.name
- $ship.class
- table[$foo='bar'].$foo
(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 (which requires that the list is non-empty)
min' and 'max' return the minimum or maximum (all elements have to be numeric)
average' returns the average (but all element types have to be compatible)
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]
A table has different properties:
- 'clone' creates a shallow copy of the table
- 'keys' allows you to access data about the table's keys
However, 'keys' alone will not give you a result. 'keys' must be followed by another keyword to retrieve the desired information, for example:
- $table.keys.list: Yields a list of all keys in the table (reliably sorted by key if all keys are numeric)
- $table.keys.sorted: Yields a list of all keys in the table, sorted by their associated values (which requires that all values are numeric)
- $table.keys.random: A randomly chosen key (which requires that the table is non-empty)
InformationThe string formatting syntax that you have seen NULL|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'.{[...]}.
=== Lookup tests and suppressing errors
===
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 of a list - however, if $list has less than 5 elements (and if it's also not a table with the key 5), there will be an error
- $list.{5}? ⟹ true if $list exists and has the property 5, false otherwise
- $table.$key? ⟹ Analogously, true if $table exists and has the string property '$key'
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 result of the $list lookup if $list exists and has the property 5, otherwise null (without error message)
- @$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.
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 |
profile | profile.flat
profile.increasing
profile.bell | Probability distribution profile (see random ranges) |
cuestate | cuestate.waiting
cuestate.active
cuestate.complete | Cue states |
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 |
InformationWith the ''typeof'' operator you can get the datatype of any expression and compare it with what you expect, for example:
<code>typeof $value == datatype.faction</code>
However, you should not compare the type to datatype.string because there are strings that have different data types. To check for a string you should use the datatype's property "'''isstring'''" instead. For example, to check if the variable $value is a string, use the following term:
<code>(typeof $value).isstring</code>"
InformationThere 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."
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 currently on (not necessarily the player's ship), or null if the player is on a station
- player.primaryship: The player's own ship (but the player is not necessarily on board)
- player.entity: The actual player object
- player.zone, player.sector, player.cluster, player.galaxy: Location of the player entity
- player.copilot: The co-pilot NPC
The game consists of objects of different classes (zones, ships, stations, NPCs). They have the common datatype "component", however, they have different properties, e.g. NPCs have the property "race", but ships don't.
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.
=== Money and time formatting
===
[New as of X Rebirth 4.0]
Numbers don't have any properties, except for money and time: They have a "formatted" property, which allows you to get a custom string representation with more advanced options than the generic formatting method for numbers.
- $money.formatted.{'formatstring'}
- $money.formatted.default (using default format string '%s')
- $time.formatted.{'formatstring'}
- $time.formatted.default (using default format string '%T')
In scripts, money is stored in cents, not Credits. The formatted representation always shows the value in Credits, including thousands separators.
When formatting the money value, any specifier (such as '%s') in the format string is replaced by the money value, so usually the format string only consists of this one specifier. The following modifiers can be used between '%' and the specifier character, to enable formatting options:
1-9 | Truncation | To enable truncation, specify the number of relevant digits that should be displayed. If the money string is too long, it can be truncated and a metric unit prefix (e.g. k = kilo) is appended. (All digits are shown unless truncation is enabled.) |
c | Colouring | If truncation is enabled, the metric unit prefixes (e.g. k, M, G) can be coloured when displayed on the screen, using the escape sequence '\033C'. |
. | Cents | Usually money values have no cent part, since cents are not used in accounts or trades. However, single ware prices can have a non-zero cent part. (Cents are not displayed if money is truncated) |
_ | Spaces | An underscore adds trailing spaces to the result string for better right-aligned display in a tabular layout. |
By default, these options are disabled.
More available specifiers (in addition to %s):
- %k: Credits (truncated) in kilo format
- %M: Credits (truncated) in Mega format
- %G: Credits (truncated) in Giga format
- %T: Credits (truncated) in Tera format
- %Cr: Localised "Cr" string
- %%: A % sign
Examples:
- (1234Cr).formatted.{'%s'}⟹'1,234'
- (1234Cr).formatted.default⟹'1,234' (same as {'%s'})
- (1234Cr).formatted.{'%.s %Cr'}⟹'1,234.00 Cr'
- (1234Cr).formatted.{'%1s'}⟹'1 k' (rounding towards zero)
- (1234Cr).formatted.{'%cM'}⟹'0 M'
For documentation of time format strings, see the Lua function ConvertTimeString() in the XRWIKIModding_supportUI_Modding_supportLua_function_overview.
Examples:
- (151s).formatted.{'%T'} ⟹ '00:02:31'
- (151s).formatted.default ⟹ '00:02:31' (same as {'%T'})
- (151s).formatted.{'%.3T'} ⟹ '00:02:31.000'
- (151s).formatted.{'%h:%M'} ⟹ '0:02'
To access the script property documentation that is included in the game, you can extract the required files from the game's catalog files using the X Catalog Tool. Extract the HTML file scriptproperties.html in the game's root folder, and all files in the "libraries" sub-folder. For resolving text references in the browser automatically, also extract 0001-L044.xml in the "t" sub-folder.
The raw documentation data is located in libraries/scriptproperties.xml, but it is recommended to open scriptproperties.html in a browser.
Informationscriptproperties.html has to load files from different folders, which modern browsers do not allow by default for security reasons. In order to open scriptproperties.html, the following is required:
- Firefox: On the about:config page, the value of "security.fileuri.strict_origin_policy" has to be changed to "false".
- Chrome: The Chrome launcher has to be started with the command-line parameter
allow-file-access-from-files
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
InformationThe 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.
MD refreshing and patching
When a saved game is loaded, the saved MD state is restored, but also all MD files are reloaded and changes in them are applied to the MD state. This is called ΓÇ£refreshΓÇ¥. It is also possible to refresh the MD at run-time using the command ΓÇ£refreshmdΓÇ¥ on the in-game command line. This is a convenient way to update MD scripts while the game is already running.
Details and restrictions
Here are some noteworthy facts about refreshing scripts and cues, and the restrictions:
- MD scripts and cues are identified by their names. So a script can only be refreshed if it has the same script name as before (file name is irrelevant).
- If there are new script files or new cue nodes (i.e. scripts/cues with new names) they are created and added properly. If you remove script files or cue nodes, the corresponding scripts/cues are removed from the game, including instances.
- As a consequence, you CANNOT rename scripts or cues if you want to refresh them. Doing so would remove the old script or cue and add a new one with the new name.
- You CANNOT change a <cue> to a <library> or vice versa.
- You CANNOT add, remove, or change the "ref" attribute of a cue. But it is possible to remove the whole cue. (If all references to a library are removed you can also remove the library itself.)
- You CANNOT change the cue tree structure, i.e. if you move a cue out of its <cues> node, you also have to change its name (see above). Changing the order of cues within the same <cues> node is possible, however, the order of execution is not reliable anyway.
- You CAN change a library and change/add/remove its sub-cues. This automatically updates all cues that use the library.
- You CAN change library parameters (both in libraries and in referencing cues). However, this does not change the variables of a referencing cue if it is already enabled.
- You CAN change conditions without restrictions. You can even change between event and non-event conditions. If a cue has enabled condition checks, they are aborted and restarted (even if there is no change).
- Adding root cues enables their condition checks immediately (if the module attribute allows it).
- Adding sub-cues to active or complete cues enables their condition checks immediately.
- You CAN change/add/remove <actions>, <force>, <delay>, and all attributes without restrictions, except for the "ref" attribute (see above). You can even change the <delay> while the cue is already active and the timer is running.
- Changing instantiate="false" to "true" turns the cue into "waiting" state if it was active or complete before.
- Changing instantiate="true" to "false" removes all instantiated cues and their descendants.
WarningBe aware that completed instances can be auto-deleted, and so added sub-cues will not become active in such a case.
WarningWhen adding a variable in a new MD script version and using that variable in multiple places, be aware that the variable doesn't exist yet in older savegames. You may have to check the existence of the variable before accessing it, or add some patch logic that initiailses the variable after loading the savegame, if necessary.
Cues can have <patch> elements with actions that will be performed when an old savegame is loaded. To control which savegames should be affected, you can add a version attribute to the <cue> node and a sinceversion attribute in the patch. When a cue is loaded from a savegame that has an older version than sinceversion, the <patch> actions will be performed immediately after loading.
<cue [...] version="42"> <conditions> [...] </conditions> <actions> [...] </actions> <patch sinceversion="42">    [patch actions] </patch></cue>
The patch actions are only performed if the cue is in a certain state, ΓÇ£completeΓÇ¥ by default. Use the state attribute to change this requirement. For more information, see the XML schema documentation of the <patch> element.
A sequence of multiple <patch> elements is possible. They will be performed in order of appearance, checking the sinceversion and state attributes in each case. Patches are also applied to all users of a library and to instances.
InformationThe <patch> elements will be ignored when refreshing the MD at run-time. They only affect loaded savegames."
There are many commonly used actions and conditions which share groups of attributes. The most important ones are explained here.
There are many conditions and conditional actions that require a value comparison, for example the condition <check_value>:
<check_value value="$ware == ware.silicon and $amount != 0"/>
In the value attribute you specify a boolean expression, and if it is true (that is, not equal to zero), the condition is met. This is a special case: This condition and all other nodes that support a value comparison allows you to specify an upper limit, a lower limit, a number range, or a list of allowed values. Examples:
<check_value value="FooCue.state" exact="cuestate.complete"/><check_value value="$foo.count" min="5"/><check_value value="$foo" max="player.age + 1min"/><check_value value="player.money" min="300Cr" max="600Cr"/><check_value value="$method" list="[killmethod.hitbymissile, killmethod.collected]"/><check_value value="$attention" min="attention.visible"/>
InformationValues of most enumeration types cannot be compared via ''min'' or ''max'' (also not via lt, gt, etc.). The only data types that can be used with ''min'' and ''max'' are numbers and the enumeration types ''level'' and ''attention'' (see Boolean operators). The ''exact'' attribute can be used with any type, and is equivalent to using the == operator."
Random ranges
If an action requires a value, e.g. when you set a variable to a value, you can have some randomisation. To specify an exact value, e.g. in <set_value>, you can write this:
<set_value name="$race" exact="race.teladi"/>
To select a random element from a list, this syntax can be used:
<set_value name="$prime" list="[2, 3, 5, 7, 11]"/>
To get a random number within a given range, you can use min/max:
<set_value name="$foo" min="-20" max="20"/><set_value name="$timeout" max="20s"/>
min and max have to be compatible number types. Enumeration types are not allowed, not even level and attention. The min attribute is optional and defaults to 0 (of the number type used in max).
You can select one of 5 different probability distribution profiles for the random range, ΓÇ£flatΓÇ¥ being the default (all values in the range are equally likely). If you select another profile, e.g. ΓÇ£increasingΓÇ¥ to make higher numbers more likely, you also have to specify a scale value (integer) that is greater or equal to 2. Higher scale values result in higher peaks in the distribution profiles (probable values become even more probable).
<set_value name="$foo" min="-20" max="20" profile="profile.increasing" scale="4"/>
Variables and namespaces
As you have seen above, you can easily access variables by writing their name (including $ prefix) in an expression. Namespaces define in which cue the variables are actually stored (and from which cue they are read).
Creating and removing variables
You can create variables with certain actions and conditions, such as the <set_value> action:
<set_value name="$foo" exact="$bar + 1" />
<set_value> also exists as a ΓÇ£conditionΓÇ¥, which can be useful if you want to pass information about the conditions to the actions, that would otherwise be lost - like in a complex <check_any> event condition, where you want to create a variable only if you are in a certain check branch. (Other pseudo-conditions are <remove_value> and <debug_text>.)
The default operation of <set_value> is ΓÇ£setΓÇ¥, but there are more: ΓÇ£addΓÇ¥, ΓÇ£subtractΓÇ¥, and ΓÇ£insertΓÇ¥. add and subtract change the value of an existing variable, which is created as 0 if it didnΓÇÖt exist before. If neither min, max nor exact attribute is provided, an exact value of 1 is assumed.
<set_value name="$foo" operation="add" />
The trick is that <set_value> not only works on variables, but also on list elements and table keys:
<set_value name="$list.{1}" exact="42" /><set_value name="$table.$foo" exact="42" />
The operation insert is special, and it only works on lists. It inserts the value at the specified position (note that the position beyond the last element is also valid here):
<set_value name="$list.{1}" exact="42" operation="insert" />
This shifts the positions of all following elements up by one. If min/max/exact are missing, the default value is null for insertions, not 1 like in other cases.
Appending is easier than that. The following actions are equivalent:
<set_value name="$list.{$list.count + 1}" exact="42" operation="insert" /><append_to_list name="$list" exact="42" />
Inserting at a position below 1 or above $list.count + 1 is not possible.
To remove variables or list/table entries, use <remove_value>:
<remove_value name="$foo" /><remove_value name="$list.{1}" /><remove_value name="$table.$foo" />
Removing an entry from a list shifts all following elements down by one. If you want to clear an entry without removing it from the list, just use <set_value> instead.
You can also read and write variables in other cues by using the variable name as property key:
<set_value name="OtherCue.$foo" min="0.0" max="1.0" /><set_value name="md.OtherScript.YetAnotherCue.$bar" exact="OtherCue.$foo" />
Instead of referencing a cue by name, you could also reference it via a keyword or another variable:
<set_value name="static.$counter" operation="add" /><set_value name="parent.$foo" exact="42" /><set_value name="this.$bar" exact="parent" /><set_value name="$baz" exact="this.$bar.$foo" />
In the examples above, a variable was written to and read from the ΓÇ£thisΓÇ¥ cue. This can be necessary: the expression ΓÇ£$fooΓÇ¥ may be different from the expression ΓÇ£this.$fooΓÇ¥. The reason for that are namespaces.
Consider this case:
<cue name="Root"> <actions> <set_value name="$foo" /> </actions> <cues> <cue name="SubCue"> [...] </cue> </cues></cue>
When the root cue creates $foo, the variable is stored in the Root cue directly. But SubCue and its descendants will also need access to $foo. Of course they could write ΓÇ£parent.$fooΓÇ¥ or ΓÇ£Root.$fooΓÇ¥, but since itΓÇÖs very common to have a single location for most variables in the whole cue tree, the easy solution is to write just ΓÇ£$fooΓÇ¥ - because variable names are looked up in the namespace cue, which is the root by default. Also newly created variables end up in the namespace, and not in ΓÇ£thisΓÇ¥ cue.
You can also use the keyword ΓÇ£namespaceΓÇ¥ in expressions to get the namespace cue.
When writing a cue, you can specify what the namespace of the cue should be, by adding the namespace attribute. The following values are possible:
- this: Use ΓÇ£thisΓÇ¥ cue as namespace, even for instances: $foo == this.$foo
- static: Same as ΓÇ£thisΓÇ¥, but when instantiated, use the static cue: $foo == static.$foo
- default: The namespace is inherited from the parent cue. The default for root cues and for libraries is the same as ΓÇ£staticΓÇ¥.
WarningAlthough in general the expression ΓÇ£$foo == namespace.$fooΓÇ¥ is true, there is one exception: When library parameters are evaluated in the referencing cue, variables are resolved using the parentΓÇÖs namespace. However, the referencing cue creates a new namespace, so the namespace keyword already points to the library, not to the parentΓÇÖs namespace. Example:
<code><cue name="LibRef" ref="Lib"> <param name="Param1" value="$foo" /> <! $foo from parent namespace > <param name="Param2" value="namespace.$foo" /> <! LibRef.$foo (error) ></cue></code>