Automation (scripting) - ihsoft/TimberbornMods GitHub Wiki
THIS IS A DRAFT DOCUMENT! Come later to see the final version.
Starting from version 2.0.0
, the mod supports a new format of the rules: scripted rules. That is, you can set up conditions and actions via a simple scripting language. And these actions can be changed during the game!
- There are "signals" that provide string or numeric values. They are primarily used in conditions but can also be used as value parameters in actions. When something happens in the Timberborn universe, a signal can trigger. If there is a condition for this signal, it will be handled, and the action will execute (if the condition evaluates to
true
). - Signals can be global (e.g. the current weather season change), bound to a specific building (e.g. inventory changes), or be limited to a district scope (e.g., population changes).
-
Actions are always bound to a specific building. They execute only in the same building to which the rule belongs. Different buildings can have different actions. E.g. a floodgate can have an action
SetHeight
, but it won't make any sense for an inventory. - The conditions that depend on a signal are only checked when the signal changes its value. E.g. the weather season signal will only trigger when the season changes. The conditions that depend on it, will not be checked on a regular basis!
When you manually create scripts, you need to understand which building they will be working on. Else, the script won't compile.
You can add your scripts via the new Rules Editor dialog. Simple scripts can be made via the visual constructor, but if you need complex conditions, you need to write them as a script.
The script follows the Lisp syntax convention. Both the condition and action are operators:
- The condition must be a logical operator.
- The action must be an operator
act
.
Operator is recorded as (<OperatorName> arg1 arg2 ...)
. It depends on the specific operator how many arguments it has. There are also restrictions to what values can the arguments have. Sometimes, another operator can be a value, but in the other cases a constant value may be required. Checkout the guide below to learn which operator needs what.
The arguments can be:
-
A string constant. They must be quoted with a single quote symbol. E.g.
'MapleSyrup'
. -
A number. All numbers in the scripts are real numbers, written with a 2-digits fixed precision. That is, the value
1.50
will be recorded as150
. And the value1.555
cannot be written in the script, you have to round it up to the last two digits -1.56
. Examples:- Script value
100
means1
in the game. - Script value
123
means1.23
in the game. - Script value
1234
means12.34
in the game.
- Script value
- A signal. Signal provides either a string or a numeric value. You can use it everywhere unless it's declared the constant is required.
This is a set of operators that are made for condition checking.
As the name assumes, there are always exactly two arguments. Any values are accepted, but keep in mind that the proper condition should have at least one signal referred. Or else, it will never be called: no signal - no callback on change.
Binary operators accept value operads and return a boolean result.
The available binary operators are:
-
eq
- "is equal to". -
ne
- "is not equal to". -
gt
- "is greater than". Only works for the numeric values. -
ge
- "is greater than or equal to". Only works for the numeric values. -
lt
- "is less than". Only works for the numeric values. -
le
- "is less than and equal to". Only works for the numeric values.
Examples:
-
(eq (sig Wather.Season) 'drought')
- a correct condition that will evaluate totrue
when the weather season changes to Drought. -
(eq 'drought' (sig Wather.Season))
- the same as above, it's still correct. -
(eq 'drought' 'drought')
- syntactically correct... error! Such a statement won't parse in the rule condition definition since it doesn't contain any signal. However, it will work fine in the preprocessor: it will always evaluate totrue
. -
(eq (sig Wather.Season) (sig Wather.Season))
- an absurd example that always evaluates totrue
, but it illustrates that the right value is not required to be a constant.
Only two are supported: and
and or
. These operators can accept any number of arguments, but not less than two. Every argument is required to be a logical operator.
(and arg1 arg2 ...)
(or arg1 arg2 ...)
Examples:
-
(and (eq ...) (eq ...) (eq ...))
. Three conditions, checked for "and" term:<arg1> and <arg2> and <arg3>
. -
(and (eq ...) (or (eq ...) (eq ...)))
. Three conditions, checked for a mix of terms:<arg1> and (<arg2> or <arg3>)
.
These operators accept and return the numbers values.
-
(add arg1 arg2)
. Addition. -
(sub arg1 arg2)
. Substraction. -
(mul arg1 arg2)
. Multiplication. -
(div arg1 arg2)
. Division. -
(min arg1 arg2 ...)
. Pick the minimum value in the set. -
(max arg1 arg2 ...)
. Pick the maximum value in the set. -
(round arg1)
- rounds the value to the nearest int value. E.g.(round (sub 100 40))
will give value100
(1.0f
).
Examples:
-
(round (sub 100 40))
. Subtracts0.4f
from1.0f
, which gives the result0.6f
. Then, the result is rounded to the nearest integer:1.0f
. Thus, the result of this statement is value100
. -
(min 0 (sub 0 100) 200)
. Gives value0
:min(0, -1.0f, 2.0f)
. -
(div 1 0)
. Results in a script executing error: division by zero. The game won't crash, but the rule will get permanently disabled.
Signal operator is written as: (sig <NameOfTheSignal> arg1 arg2 ...)
. At this point, there are no signals that accept arguments, but they can be added later. Signals can return either a number or a string, but the same signal cannot deal with the both types.
-
Weather.Season
- returns the current weather season as a string. The possible values are: "drought", "badtide", and "temperate". -
Inventory.InputGood.<good_id>
- returns the good amount stored as the building's inventory input. Thegood_id
part specifies which good is being checked. -
Inventory.OutputGood.<good_id>
- returns the good amount stored as the building's inventory output (the product, if it's a workshop). In the stockpiles, all goods are checked for "output". -
District.Bots
- returns the number of bots in the district to which the building is connected. If the building is not connected to any district, then the result is0
. -
District.Beavers
- returns the number of beavers, adults and kittens, in the district to which the building is connected. If the building is not connected to any district, then the result is0
. -
District.NumberOfBeds
- returns number of sleeping places (beds) for the beavers, in the district to which the building is connected. If the building is not connected to any district, then the result is0
.
Usage examples:
-
(eq (sig Weather.Season) 'drought')
. Triggers when the weather season changes to Drought. -
(gt (sig Inventory.InputGood.Planks) 10000)
. Triggers when inventory gets more than 100 items of Planks (10000
in the script is100
integer number).
Action operator does the actual effect on the building. It's syntax: (act <NameOfTheAction> arg1 arg2 ...)
. Some actions have arguments, and some don't. The action argument can be a constant or a signal. If the building doesn't have a relevant component(s), the parsing error is raised.
-
Debug.LogStr
. Accepts exactly only string value argument. It will be written to the game's logs. E.g.(act Debug.LogStr 'Hello!')
. -
Debug.LogNum
. The same as above, but it expects the number argument. E.g.(act Debug.LogNum 123)
will print value1.23
. -
Pausable.Pause
/Pausable.Unpause
. Pauses or resumes the building. -
Inventory.StartEmptying
/Inventory.StopEmptying
. Enables or disables "emptying" mode on the storage. In the game, this mode is intended to be used on the stockpiles only. However, many other buildings have it, and the mode can be activated on them.- Be careful! This mode instructs the workers to remove all goods from the building, even if they are ingredients of a workshop recipe.
-
Floodgate.SetHeight
. Sets the floodgate to the specified value. The game properly handles the boundaries (negative values and a too large height value), but the Automation mod shows the values "as-is" in the rules descriptions. If you don't want to see awkward text in the UI, consider using min/max functions to limit the value.
Usage examples:
-
(act Floodgate.SetHeight 100)
. Sets floodgate height to1
(read above to learn why100
turns into1
). -
(act Floodgate.SetHeight (sig Signals.MyCustomSignal))
. Sets floodgate height to the current value of the custom signalMyCustomSignal
- Custom signals are not implemented as of 3/9/2025. It's a future feature.
WARNING. If you use a signal value as an argument, keep in mind that the action is bound to the condition. If the condition didn't trigger, the action will not execute. That being said, if you use a signal as a value to the action and need the action re-executed on the signal change, this signal must also be a part of the condition of the rule.
These operators get data from the actual game components, assigned to the building. They are for advanced scripting, so you need to know what you're doing and which property of the game component means what. The component must exist at the building, or else a parsing error will rise.
-
(getnum ComponentName.PropertyName)
. Returns a numeric value of the property. Supported types: integer, float, and boolean. In the case of boolean,true
is100
value, andfalse
is0
. -
(getstr ComponentName.PropertyName)
. Returns a property of type string. If the property has a different type, then error is raised.
The both operators have an extended meaning and calling syntax to access collections:
-
(getnum ComponentName.ListPropertyName)
returns the number of elements in the collection. -
(getnum ComponentName.ListPropertyName 2)
returns the 3rd element of the collection (zero based indexes). If there are not enough elements, then error is raised.
Note that in difference to signals, the access operators will not notify the related condition about the property value change.
The scripts can have statements that will be executed during parsing. The result of preprocessor statements will substitute the statement text. Then, the resulting expression will be parsed as usual. This semantics only makes sense in templates - the preprocessor statements are executed when the template is being applied to a building, so the building gets a customized script.
For example, (act Floodgate.SetHeight {% (getnum Floodgate.MaxHeight) %})
will get transformed to (act Floodgate.SetHeight 2)
on a floodgate of height 2.
Preprocessor statements can also be logical operators. In this case, the result of preprocessing is always an empty string, but the expression is required to evaluate to true
. If not, then an error will be raised, and the rule will not be allowed to be applied to the building. For example, {% (ge (getnum Floodgate.MaxHeight) 2) %}(act Floodgate.SetHeight 2)
will be rejected on floodgates that has max height less than 2.