-
Notifications
You must be signed in to change notification settings - Fork 11
Expressions
This page may explain things that are not implemented yet and are set to be upcoming in the future versions of the engine. Implementation details may change.
Expressions are powerful data units used in configuration files. They can convey very complex logic without bloating the configuration file definitions, but they are also tricky to master due to a complex system of limitations imposed due to their nature.
You can define an Expression only where it's explicitly allowed by the configuration file. Always read the field description carefully for a list of contexts you can have in your expression.
There is no expression validation built in the schema - if they return a wrong type of value, errors will happen!
Expressions have a few flavors, depending on what kind of value they return. Currently supported return values are numbers, integers, booleans, strings and Vector2's.
Each of such fields can have either a constant (a value of an appropriate type) or an expression of a given flavor.
Since implementing new value types is against the JSON spirit, Expressions utilize strings instead, formatted in the following way:
"${<expression>}"
Under the hood, in Config Classes, Expressions are stored in the form of an Expression
class instance.
The syntax of an Expression is quite simple. There are no functions, if
blocks, or for
/while
loops. However, with what you have, you can most likely still achieve the results you are hoping for.
You can use the following variable types:
- numbers (e.g.
0
,5
,-1
,-3.5
), - booleans (
false
,true
), - strings (e.g.
"hello"
,""
,'Apostrophes can be used too!'
). - There is also a limited support for Vector2's. You can only construct them with a
vec2
function (see below), and cannot obtain the X or Y coordinates back.
The following operators are available:
- addition (
+
), e.g."${2+2}"
will resolve to4
, - subtraction (
-
), e.g."${2-4}"
will resolve to-2
, - unary minus (
-
), e.g."${-4}"
will resolve to-4
, - multiplication (
*
), e.g."${2*4}"
will resolve to8
, - division (
/
), e.g."${2/3}"
will resolve to0.66666666667
, - power (
^
), e.g."${3^4}"
will resolve to81
, - modulo (
%
), e.g."${18%5}"
will resolve to3
, - string concatenation (
..
), e.g."${'hello' .. 'world'}"
will resolve to"helloworld"
, - equal (
==
), e.g."${2==3}"
will resolve tofalse
, - not equal (
!=
), e.g."${2!=3}"
will resolve totrue
, - greater than (
>
), e.g."${2>3}"
will resolve tofalse
, - lower than (
<
), e.g."${2<3}"
will resolve totrue
, - greater or equal to (
>=
), e.g."${2>=2}"
will resolve totrue
, - lower or equal to (
<=
), e.g."${2<=2}"
will resolve totrue
, - logic OR (
||
), e.g."${true||false}"
will resolve totrue
, - logic AND (
&&
), e.g."${true&&false}"
will resolve tofalse
, - logic NOT (
!
), e.g."${!true}"
will resolve tofalse
, - ternary if (
?
:
), e.g."${false ? 2 : 3}"
will resolve to3
. - You can also use round brackets in order to manipulate the order of operations.
You can also use the following functions:
-
floor(n)
- rounds a number down, e.g."${floor(2.5)}"
will resolve to2
, -
ceil(n)
- rounds a number up, e.g."${ceil(2.5)}"
will resolve to3
, -
round(n)
- rounds to a nearest integer, e.g."${round(2.45)}"
will resolve to2
, -
random()
- returns a random number in range[0, 1)
, e.g."${random()}"
will resolve to0.48267382917
, -
randomf(a, b)
- returns a random number in range[a, b)
, including non-integer numbers, e.g."${randomf(1, 5)}"
will resolve to2.09681385721
, -
vec2(x, y)
- returns a Vector2 with defined X and Y axes, e.g."${vec2(5, 2)}"
will resolve to(5, 2)
, -
sin(theta)
- returns a sine of an angle (in radians), e.g."${sin(0)}"
will resolve to0
, -
cos(theta)
- returns a cosine of an angle (in radians), e.g."${cos(0)}"
will resolve to1
, -
tan(theta)
- returns a tangent of an angle (in radians), e.g."${tan(0)}"
will resolve to0
, -
max(a, b)
- returns the bigger of two values, e.g."${max(2, 5)}"
will resolve to5
, -
min(a, b)
- returns the lower of two values, e.g."${min(2, 5)}"
will resolve to2
, -
clamp(a, min, max)
- returns the value ofa
clamped to the given minimum and maximum, e.g."${clamp(5, 2, 3)}"
will resolve to3
, -
get(name)
- returns a value of a particular Expression Variable. Crashes the game if that variable doesn't exist.-
The
"${get('value')}"
syntax is discouraged! Write this instead:"${[value]}"
.
-
The
-
getd(name, default)
- returns a value of a particular Expression Variable. If that variable doesn't exist, returnsdefault
instead.-
The
"${getd('value', 0)}"
syntax is discouraged! Write this instead:"${[value|0]}"
.
-
The
Expressions' source file is located at src/Expression.lua
.
Expressions live in Config Classes, where they are constructed from data provided by the JSON file, as long as it has appropriate syntax (see above).
During construction, two procedures happen in order to prepare the expression to be evaluated as fast as it's possible during the gameplay.
These are tokenization and compilation.
Tokenization is the act of dividing one big expression string into atomic chunks, so-called tokens. Tokens can be numbers, strings, literals, operators or brackets. The detailed procedure of obtaining tokens is provided in the Expression:tokenize()
and Expression:getToken()
functions.
For example, the string "-2 + (3 - randomf(1, 5))"
will be divided into the following tokens:
Token |Type
----------|-----------
-u |operator
2 |number
+ |operator
( |bracket
3 |number
- |operator
randomf |function
( |bracket
1 |number
, |operator
5 |number
) |bracket
) |bracket
You can use the ex
command to show the breakdown of an expression of choice.
This intermediate form is then rearranged and interpreted into a Reverse Polish Notation (RPN), which is the way the Expressions are actually stored. This process is called compilation. Look at the Expression:compile()
function to learn how this process works.
The result of this process is the final form of the Expression, which is a list of steps, each of them being either an operator or a value. For example, the above intermediate form translates to the following list of RPN steps:
2 (-u) 3 1 5 (,) (randomf) (-) (+)
You can use the exprt
command to show the RPN steps of an expression of choice. The words in round brackets are operators.
This process happens every time we want to get a value from the Expression. The process is extremely simple: RPN steps are evaluated one after another. When a value is encountered, it gets added onto the stack. When an operator is encountered, it consumes some of the most recent items from the stack and adds the result of an operation as an another value.
In principle, all we should have at the end is just one item on the stack - that's our result. However, the code does not check for this - it returns the bottommost item.
Evaluation code is located in the Expression:evaluate()
function.
Example processing for the above example:
Step |Stack
----------|-------------------------
2 |2
(-u) |-2
3 |-2 3
1 |-2 3 1
5 |-2 3 1 5
(,) |-2 3 1 5
(randomf) |-2 3 3.281984
(-) |-2 -0.281984
(+) |-2.281984
Thus, the result of evaluating this Expression is -2.281984
.
Note that the randomf
step is kept intact within the Expression's data - every time the Expression is evaluated, the result will be different.
The same principle is used in random
, get
and getd
tokens, which are not guaranteed to return the same value each time.
The tokens are nearly always matching the operator/function names, with a key exception of the ternary operator a ? b : c
. This translates to c b a ?
- the ?
token takes three arguments here. The colon is effectively ignored.
Although you can exploit this behavior, we cannot guarantee this will work in the future - so please stand by the list of expressions available here!
The parameter count is also the same - although, in the future there's planned support for operands with a variable number of parameters. For example:
Expression |RPN Notation
--------------|--------------
test() |0 (test)
test(a) |a 1 (test)
test(a, b, c) |c b a 3 (test)
The principle is simple - take a number, and that number is the number of arguments to be taken. This can be useful for multi-purpose functions, for example getd
and randomf
can be removed this way.
You can use the following console commands in order to test the expression system:
-
expr <expression>
- Evaluates the value of an expression. -
exprt <expression>
- Shows the list of RPN steps of an expression. -
ex <expression>
- Shows the detailed lists of both the tokens an expression has been broken into, and the list of RPN steps generated from it. It also shows the final value of an expression.
The entered expression will be evaluated with no contexts. Double quotes which are delimiting strings don't need to be escaped.
For high flexibility, Expressions can use parameters specific to what's currently going on in the game. However, a certain variable will sometimes not make sense (like match-related values in bonus scarab parameters). As such, some variables are only available in some cases. To help with what's available in each case, the variables are grouped in what's called Expression Contexts. In some cases, an expression will have a well defined list of parameters, but in other cases (like sound events) the context will depend on where the sound event is used.
If you're not sure whether a variable can exist at all times, it may be worth it to prepend a default value by using the [name|defaultValue]
syntax.
To access a variable from an expression context, for the variable name, use the context.variable
notation.
Examples of expression contexts are listed below:
The Session context is available for all Expressions, and it exists most of the time. This Context will not exist if one of the following is true:
- No Profile is selected at that moment.
- The currently active Profile has no active session (no game in progress, i.e. they must start a new game).
Variable | Type | Description |
---|---|---|
session.score |
number | The current player's score. |
session.lives |
number | The current player's life count. If the current life system does not have lives, this value does not exist. |
session.coins |
number | The current player's coin count. If the current life system does not have coins, this value does not exist. |
The Match context is available in Score Events, Sound Events and Collectible Generators associated with the Sphere Effect's config of which the spheres are being destroyed.
Reminder: regular matches also utilize sphere effects.
Variable | Type | Description |
---|---|---|
match.length |
number | How many spheres are being destroyed at once with this match. |
match.streak |
number | The amount of consecutive matches successfully performed prior to the match. |
match.streakBoost |
boolean | Whether this match has contributed to incrementing the consecutive match counter. For example, in cascades, only the first match will have this value guaranteed to be set to true . |
match.cascade |
number | The cascade (Luxor's "Chain", Zuma's "Combo") level that is applied to this match. |
match.gapCount |
number | The number of gaps traversed by the sphere that has caused the match. |
match.smallestGapSize |
number |
|
match.color |
number | The color ID of the sphere that has caused this match. |
The Sphere context is available in a few places.
Sphere Selectors provide the following context variables that can be used only in itself.
Alongside Sphere Selectors, a scoreEventPerSphere
field can be provided, which executes one Score Event per each selected sphere.
Note: Sphere Selectors referenced in Collectible Effects have a limited set of parameters, because they don't have a reference position. Parameters which need this position will be absent.
Variable | Type | Description |
---|---|---|
sphere.object |
Sphere | The Sphere object in itself. Since Expressions cannot interact with them, they are only useful in equality checks. For example, in Sphere Selectors, use the condition: ${[sphere.object] == [hitSphere.object]} in order to select only the hit sphere, like daggers do. |
sphere.color |
number | The sphere's color ID. |
sphere.isOffscreen |
boolean | True if the sphere is less than 16 pixels ahead of the spawn point. |
sphere.distance |
number | The distance in pixels from the Sphere Selector's reference position. May not exist. |
sphere.distanceX |
number | The distance in pixels from the selector's reference position, but only along the X axis. May not exist. |
This is defined in the scoreEvent
field in the lightningStorm
section of the config/gameplay.json
file.
Variable | Type | Description |
---|---|---|
sphere.object |
Sphere | The Sphere object in itself. Since Expressions cannot interact with them, they are only useful in equality checks. |
sphere.color |
number | The sphere's color ID. |
sphere.isOffscreen |
boolean | True if the sphere is less than 16 pixels ahead of the spawn point. |
Each Sphere config can have a destroyCollectible
and/or a destroySound
field. The Collectible Generators and Sound Events which are linked to in such config will have the following variables:
Variable | Type | Description |
---|---|---|
sphere.object |
Sphere | The Sphere object in itself. Since Expressions cannot interact with them, they are only useful in equality checks. |
sphere.color |
number | The sphere's color ID. |
sphere.isOffscreen |
boolean | True if the sphere is less than 16 pixels ahead of the spawn point. |
sphere.offset |
number | The distance of this Sphere from the spawn point, in pixels. Negative if the Sphere is behind its path's spawn point. |
sphere.offsetE |
number | The distance of this Sphere from the end point, in pixels. Negative if the Sphere is past its path's end point. |
sphere.distance |
number | The distance of this Sphere from the spawn point to the end point, as a percentage value. 0 means the Sphere is at its spawn point. 1 means the Sphere is at its end point. Can be less than 0 and/or bigger than 1 ! |
sphere.crushed |
boolean | Exclusive to Scarabs (color=0). Set to true whenever this Sphere has been destroyed in a merger of two Sphere Chains. |
This is a context that's exclusive to Score Events and can be only used in the text
field.
Variable | Type | Description |
---|---|---|
event.score |
number | The score that has been calculated for this Score Event, including any difficulty multipliers, if applicable. Use this for dynamic score labels. |
entity
selector
hitSphere
... To be continued ...