Automation (scripting) - ihsoft/TimberbornMods GitHub Wiki
NOTE. This document is updated occasionally as the new features are being introduced into the mod. However, the documentation is always a bit behind and the newest features may not be captured yet. To learn all the freshest features, try applying all the templates in the game and check the created rules via the rules editor dialog.
This document was last updated for version 2.5.8
on 6/19/2025
.
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! This document gives the scripting concept and explains the available operators.
- There are "signals" that provide string or numeric values. They're 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 (for example, the current weather season change), bound to a specific building (e.g. inventory changes), or be limited to a district scope (for example, 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. For example, 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. For example, the weather season signal will only trigger when the season changes. The conditions that depend on it will not be checked regularly!
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 the arguments can 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. For example
'MapleSyrup'
. -
A number constant. 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 value operator. The most common value operator is "signal" – it provides either a string or a numeric value. You can use it everywhere unless it is declared the constant is required. There are more value operators that can also be used as arguments (see below).
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 is 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 1 1) (eq 2 2) (eq 3 4))
. Three conditions, checked for "and" term:1 = 1 and 2 = 2 and 3 = 4
. The result is:false
. -
(and (eq 1 1) (or (eq 2 2) (eq 3 4)))
. Three conditions, checked for a mix of terms:1 = 1 and (2 = 2 or 3 = 4)
. The result is:true
.
These operators accept and return the number 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.
Examples
-
(round (sub 100 40))
. Subtracts0.4
from1.0
, which gives the result0.6
. Then, the result is rounded to the nearest integer:1
. Thus, the result of this statement is value100
. -
(round 40)
. Rounds0.4
to the nearest integer:0
. Thus, the result of this statement is value0
. -
(min 0 (sub 0 100) 200)
. Gives value0
:min(0, -1.0, 2.0)
. -
(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>)
. Signals never have arguments, but some variable information may be present in the signal name.
Signals can return either a number or a string value, but the same signal never deals with both types. The values are enforced at the compilation time and checked during the execution.
- If the result is string, then the set of possible values is predetermined. And it is usually not too big.
- If the result is number, then the allowed range of values may be applied.
Normally, signals are only executed on the finished buildings (the construction has completed). The conditions and actions can be assigned to an unfinished building, but the condition will not start executing until the state changes. However, there are some signals that are designed to trigger while the building is not yet finished. Such signals have string OnUnfinished
in the name. Be careful when using them, since the action will be executed at an unfinished building, which may result in an unexpected behavior. For example, the Pause
action on an unfinished building will pause the construction site, not the building.
Global signals happen at the level of the whole settlement. They don't depend on a building or a district.
Signal name | Description |
---|---|
Signals.<CustomSignalName> |
Triggers when action Signals.Set is executed (see below). Returns the value of custom signal <CustomSignalName> . Only numbers can be returned and the value range checking is not applied to such values. |
Weather.Season |
Triggers when the weather season changes and returns a string that represents the current season. The allowed values are: "drought", "badtide", and "temperate". |
Examples
-
(eq (sig Weather.Season) 'drought')
. Triggers when the weather season changes to Drought. -
(ne (sig Weather.Season) 'badtide')
. Triggers when the weather season changes and the new season is not Badtide.
Custom signals are a way to pass data between buildings to construct rules that depend on different buildings. In difference to the regular signals, the script triggers the custom signals via action Signals.Set
. You can create a custom signal on any building (or multiple!), and then use it in the conditions and actions of the same or other buildings.
Example:
-
On a stream gauge you can create a custom signal
WaterLevel
that will be set to the current water level:If
(ge (sig StreamGauge.Depth) 0)
, then(act Signals.Set 'WaterLevel' (sig StreamGauge.Depth))
-
On a pausable building (just an example), you can pause it if the water level is too high:
If
(ge (sig Signals.WaterLevel) 100)
, then(act Pausable.Pause)
To learn how it may work in the game, try the "Signals templates."
The custom signals can be aggregated via name suffixes: .Min
, .Max
, .Sum
, .Avg
, and .Count
. The aggregation is done over all the buildings that have the signal with the same name. The aggregation is only available for numeric values. The same building can emit the same signal multiple times, but for the aggregation only the last value is used.
Aggregator suffix | Description |
---|---|
Signals.<CustomSignalName>.Min |
Returns the minimum value of the custom signal <CustomSignalName> among all the buildings that set this signal. If no building has the signal, then the result is 0 . |
Sginals.<CustomSignalName>.Max |
Returns the maximum value of the custom signal <CustomSignalName> among all the buildings that set this signal. If no building has the signal, then the result is 0 . |
Signals.<CustomSignalName>.Sum |
Returns the sum of all the custom signal <CustomSignalName> values among all the buildings that set this signal. If no building has the signal, then the result is 0 . |
Signals.<CustomSignalName>.Avg |
Returns the average value of the custom signal <CustomSignalName> among all the buildings that set this signal. If no building has the signal, then the result is 0 . |
Signals.<CustomSignalName>.Count |
Returns the number of buildings that set the custom signal <CustomSignalName> . The value is a positive integer. If no building has the signal, then the result is 0 . |
Examples
-
(eq (sig Signals.MyCustomSignal.Min) 100)
. Triggers when the minimum value of the custom signalMyCustomSignal
is exactly1
. -
(gt (sig Signals.MyCustomSignal.Max) 1000)
. Triggers when the maximum value of the custom signalMyCustomSignal
is greater than10
. -
(eq (sig Signals.MyCustomSignal.Count) 200)
. Triggers if there are two buildings that can set signalMyCustomSignal
. The rules on those buildings don't need to actually set the signal, but they must have the actionSignals.Set
in their script. If no building can set the signal, then the result is0
, and the condition will not trigger.
District signals are only available to the district buildings. That is, the building that require a connection to the district center (with the road).
Signal name | Description |
---|---|
District.Beavers |
Triggers when the number of beavers, adults and kittens, changes. Returns the current number of the beavers as a positive integer. If the building is not connected to any district, then the result is 0 . |
District.Bots |
Triggers when the number of bots in the district changes. Returns the current number of the bots as a positive integer. If the building is not connected to any district, then the result is 0 . |
District.NumberOfBeds |
Triggers when the number of sleeping places (beds) for the beavers changes. Returns the total number of the beds as a positive integer. If the building is not connected to any district, then the result is 0 . The returned value is a positive integer. |
Examples
-
(ge (sig District.Beaver) 0)
. Triggers each time a new beaver is born. -
(ge (sig District.Bots) 500)
. Starts triggering when the number of bots in the district exceeds 5. -
(ge (sig District.Bots) 501)
. Fails to compile, since the argument is required to be an integer value, and value5.01
is not.
Signal name | Description |
---|---|
Constructable.OnUnfinished.Progress |
Triggers when the construction progress advances, and returns the current progress as percentile: 0.0 - 1.0 . Once the maximum value reached, this signal stops triggering. |
Constructable.OnUnfinished.State |
Triggers when the building construction has complete. While the construction is in progress, the value of this signal is an empty string, and it's not triggering (obviously). When the construction is done, the value becomes "finished". This transition can happen only in in lifetime of a building. |
Floodgate.Height |
Triggers when floodgate changes its height, and returns the current height setting. The value ranges from 0.0 up to maximum height of the floodgate. |
Inventory.InputGood.<GoodId> |
Returns the good amount stored as the building's inventory input. "InputGood" means it is the good that haulers and workers can bring into the building. It's usually an ingredient of a recipe or fuel. The GoodId part specifies which good is being checked. The goods must be defined in the building inventory at the moment of the script compilation or execution. Some buildings can have different goods in the inventory, based on the selection (e.g. tanks), so a successfully compiled script can fail in the future if the good selection has changed. The returned value ranges for 0 up to the maximum inventory capacity for the good. Note that for the same good the capacity can change based on the building's selection (e.g. different recipes in a workshop). |
Inventory.OutputGood.<GoodId> |
Essentially, the same as Inventory.InputGood , but returns the good amount stored as the building's inventory output (the product, if it is a workshop). In the stockpiles, all goods are treated as "output" even though they can also be brought in by the haulers. |
StreamGauge.Depth , StreamGauge.Current , StreamGauge.Contamination
|
Trigger when the relevant stream gauge stat changes. Contamination is a percentile value that ranges 0.0 - 1.0 . The other stats are represented with positive floats: 0.0 and above. |
Examples
-
(gt (sig Inventory.InputGood.Plank) 10000)
. Triggers each time when the inventory changes and has more than 100 items of Planks. -
(eq (sig Inventory.OutputGood.Plank) 10000)
. Triggers when the amount changes and the new value is exactly 100 items. -
(ge (sig StreamGauge.Contamination) 0)
. Triggers on every contamination change. -
(lt (sig StreamGauge.Contamination) 101)
. Fails to compile due to the maximum allowed value is1.0
.
Action operator does the actual effect on the building. Its syntax: (act <NameOfTheAction> arg1 arg2 ...)
. Some actions have arguments, and some don't. The action argument must be a value operator (for example, a constant or a signal).
Normally, the action needs to be executed as many times as the condition gets evaluated to true. However, in some scenario the action needs to be executed only once, and stop affected the building after that. It can be achieved by making the condition more complex, or a special mechanism can be used: "run once". To use it, add suffix .Once
to the action name. The action will be executed, and then the whole rule will be deleted.
Action name | Description |
---|---|
Debug.LogStr ,Debug.LogNum
|
Accepts one argument of type number or string, and writes it into the game logs. The string in the log will have a reference to the building type and location. This is a good way to test rules conditions without actual effects on the building. |
Dynamite.DetonateAndRepeat |
Same as above, but once the dynamite exploded, a new dynamite is placed at the same location. The only argument of this action is an integer number that tells how many times to repeat the place operation. |
Dynamite.Detonate |
Detonates the dynamite. If will ensure there are no beavers or bots in 5x5 area around the dynamite. However, the nearby dynamites can automatically trigger, and for them no checking will be done. |
Floodgate.SetHeight |
Sets the floodgate to the specified value. The only argument of the action is the desired floodgate height. It must be a positive number that's is less or equal to the maximum flood gate height. |
FlowControl.Open , FlowControl.Close
|
Opens or closes the water flow on the buildings that support it. As of v2.1.1 only two buildings do: Sluice and Badwater Dome. |
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 support 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. |
Pausable.Pause , Pausable.Unpause
|
Pauses or resumes the building. If applied on an unfinished building, then the constructions site will be paused/resumed. |
Prioritizable.SetHaulers , Prioritizable.ResetHaulers
|
Sets or resets the building's mode "Prioritize building by haulers". This action will successfully set the mode even if there is no Hauling Post in the district yet. The setting will take effect when the post is built. |
Signals.Set |
Sets a custom signal value. The signal with relevant name will trigger and conditions on the other buildings will be able to react. It's a way how one building can execute its rules based on the values in another building. The action accepts two arguments: a string that is the name of the signal; and a number which should become the new value. The signal will only trigger if the new value is different from the one assigned previously. WARNING. This mechanism is an easy way to create circular signals that will trigger each other indefinitely. The scripting engine can detect such cases and disable the bad rules, but troubleshooting the case may be a pain. The suggested way is enabling "verbose logging" in the mod, reproducing the problem, and then reading the logs. |
Workplace.SetWorkers , Workplace.RemoveWorkers
|
Sets the number of workers in the workshop or removes them altogether. The only argument in SetWorkers is a number that is the new number of workers. The value must be between 1 and the maximum number of workers allowed in the workshop.Note that if all workers removed, then only the haulers will be able to bring ingredients to the workshop. |
Examples
-
(act Debug.LogStr 'Hello!')
. Will make a record like "[DistrictCenter.Folktails@(131, 138, 3)] [Debug action]: Hello!" -
(act Debug.LogNum (sig Floodgate.Height))
. Will log the current floodgate height. -
(act Floodgate.SetHeight 123)
. Sets floodgate height to1.23
, but only if its maximum height is 2 or higher. Otherwise, an error will be thrown. -
(act Floodgate.SetHeight (sig Signals.MyCustomSignal))
. Sets floodgate height to the current value of the custom signalMyCustomSignal
. In this case, the range check can't be done at parsing time, so the script will always compile, but it can fail in runtime. -
(act Pausable.Pause.Once)
. Will pause the building and remove the rule.
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're 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 third element of the collection (zero-based indexes). If there aren't 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 the template blueprints — 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 200)
on a floodgate of height 2.00
.
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) 200) %}(act Floodgate.SetHeight 200)
will be rejected on floodgates that have max height less than 2.