Automation (scripting) - ihsoft/TimberbornMods GitHub Wiki
NOTE. This document is updated occasionally as new features are introduced into the mod. However, the documentation is always a bit behind, and the newest features may not be captured yet.
To learn about the latest features, try applying all templates in the game and check the created rules via the Rules Editor dialog.
This document was last updated for version 3.2.3 on January 19th, 2026.
The mod supports two kinds of rules: pre-built and scripted.
The first kind is made for very specific purposes and should be considered "internal". Scripted rules are composed by users via a simple scripting language. These rules allow you to set up conditions and actions that can be changed during the game. This document explains the scripting concepts, operators, and available functions.
-
Signals: These provide string or numeric values. They are primarily used in conditions but can also serve as parameters in actions. When a signal value changes, any associated rule is re-evaluated; if the condition is
true, the action executes. - Scope: Signals can be global (e.g., weather changes), bound to a specific building (e.g., inventory levels), or limited to a district (e.g., population counts).
-
Actions: Actions are always bound to a specific building and execute only within that building. For example, a floodgate has a
SetHeightaction, which would not be applicable to a stockpile. - Execution: Conditions are checked only when a referenced signal changes. They are not checked on a regular timed interval.
- Custom Signals: Users can publish their own global signals, which support aggregation (min, max, sum, etc.) when multiple buildings emit the same signal name.
When creating scripts manually, ensure the script is compatible with the target building type, or it will fail to compile. Scripts can be created via the visual constructor or written manually for complex logic in the Rules Editor.
The mod originally used Lisp syntax, which is still used internally for templates. The UI currently defaults to Python syntax for a more human-friendly experience. You can switch the default in mod settings or override the syntax for a specific script using a prefix: #PY for Python or #LP for Lisp.
NOTE. Syntax overrides only work within the UI editor.
Examples
-
#LP (eq Weather.Season 'DroughtWeather')parses as Lisp regardless of global settings. -
#PY Weather.Season == 'DroughtWeather'parses as Python regardless of global settings.
Rule Structure:
- Condition: Must contain at least one signal. It consists of one or more logical operators.
-
Action: A single action statement executed when the condition evaluates to
true.
An argument is any element that returns a value.
-
String constants: Must be enclosed in single or double quotes (e.g.,
'MapleSyrup'). Use\"or\'to escape quotes. If you use double quotes for the literal, single quotes inside do not need escaping (e.g.,"test'test"). -
Number constants: Real numbers with two-digit fixed precision (e.g.,
1.23). Values are rounded (e.g.,1.234becomes1.23, while1.235becomes1.24). - Runtime values: Values returned by functions, operators, or signals.
NOTE. Every mathematical operation is rounded to two decimal places. Keep this in mind for complex calculations.
Examples
-
123/100results in1.23. -
1235/1000is rounded to1.24. - The constant
1.235is accepted but stored as1.24.
Operators manipulate the arguments. They define how to derive a value from the provided arguments. The absolute majority of operators have two arguments (binary operators). However, there are a few that have only one argument (unary operators).
This is a set of operators that are used for condition checking.
As the name implies, there are always exactly two arguments. Any values are accepted, but keep in mind that a proper condition must reference at least one signal. Otherwise, it will never be called: no signal means no callback on change.
Binary operators accept values (math operators or functions) and return a boolean result. The boolean result can be used either as the output for the rule condition or as input to a logical operator. It cannot be used for anything else.
This type of operator is binary. They require exactly two number or string arguments. The arguments can be any value type: a function, a signal, or another operator that returns a number or a string.
The following comparison operators are supported:
-
==— is equal to. Applies to numbers and strings. -
!=— is not equal to. Applies to numbers and strings. -
>— is greater than. Only works for numeric values. -
>=— is greater than or equal to. Only works for numeric values. -
<— is less than. Only works for numeric values. -
<=— is less than or equal to. Only works for numeric values.
Examples
NOTE. All numeric values in scripts are real numbers, but their precision is limited to two digits after the decimal point. This may have side effects that you do not expect.
-
1.23 == 123/100evaluates totrue. -
1.24 == 123/100evaluates tofalse. -
1.24 == 1235/1000evaluates totrue, because1.235is rounded to1.24before comparison. -
Weather.Season == 'DroughtWeather'evaluates totruewhen the season changes to DroughtWeather. -
'DroughtWeather' == 'DroughtWeather'is a syntactically valid but invalid rule condition, because it contains no signal. -
Weather.Season == Weather.Seasonalways evaluates totrueand can be used to react to any season change.
There are three supported boolean operators: and, or, and not. The first two are binary, while the last one is unary and negates its argument.
Operators have a precedence order, which affects how expressions are evaluated.
Python syntax supports parentheses. Lisp syntax does not require them due to its structure.
Examples
-
1 == 1 and 2 == 3 and 3 == 3evaluates tofalse. -
1 == 1 and (2 == 3 or 3 == 3)evaluates totrue. -
1 == 1 and 2 == 3 or 3 == 3evaluates totrue. -
2 > 1 and 3 >= 3evaluates totrue. -
2 == 2 and not (3 != 3)evaluates totrue.
All math operators work with real number arguments. Every argument and result is limited to at most two digits after the decimal point (fixed precision).
The supported operators are:
-
arg1 + arg2— addition. -
arg1 - arg2— subtraction. -
arg1 * arg2— multiplication. -
arg1 / arg2— division.
Operator precedence follows standard arithmetic rules. Parentheses are supported.
If you need aggregation logic, you can use functions:
-
min(arg1, arg2, ...)— returns the minimum value. -
max(arg1, arg2, ...)— returns the maximum value. -
round(arg1)— rounds to the nearest integer.
Examples
-
round(1 - 0.4). Subtracts0.4from1.0, which gives the result0.6. Then, the result is rounded to the nearest integer:1. Thus, the result of this statement is value1.0. -
round(0.4). Rounds0.4to the nearest integer:0. Thus, the result of this statement is value0. -
min(0, 0 - 1, 2). Gives value0:min(0, -1.0, 2.0). -
1 / 0. Results in a script execution error: division by zero. The game won't crash, but the rule will get permanently disabled.
The signals are represented as properties in Python. For example Weather.Season. They provide a value and also re-notify the rule when the value changes.
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: "DroughtWeather", "BadtideWeather", and "TemperateWeather". |
Examples
-
Weather.Season == 'DroughtWeather'. Triggers when the weather season changes to DroughtWeather. -
Signals.MySignal > 0. Triggers when custom signalMySignalchanges to a value more than 0.
Custom signals are a way to pass data between buildings to construct rules that depend on different buildings. Unlike 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
WaterLevelthat will be set to the current water level:If
StreamGauge.Depth > 0, thenSignals.Set('WaterLevel', StreamGauge.Depth) -
On a pausable building (just an example), you can pause it if the water level is too high:
If
Signals.WaterLevel >= 1.0, thenPausable.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. |
Signals.<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
-
Signals.MyCustomSignal.Min == 1.0. Triggers when the minimum value of the custom signalMyCustomSignalis exactly1. -
Signals.MyCustomSignal.Max > 10. Triggers when the maximum value of the custom signalMyCustomSignalis greater than10. -
Signals.MyCustomSignal.Count == 2. 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.Setin 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. |
District.ResourceCapacity.<GoodId> |
Triggers when the total capacity of the stock in the district changes. Capacity is only counted for the stockpiles. The output inventories of production won't be counted even though they can provide the stock. If you calculate ratio, be ready to see a value more than 100%. |
District.ResourceStock.<GoodId> |
Triggers when the total reserve of the stock in the district changes. The returned value includes reserves in all stockpiles and in the output inventories of the buildings. |
Examples
-
District.Beavers > 0. Triggers each time a new beaver is born. -
District.Bots > 5. Starts triggering when the number of bots in the district exceeds 5. -
District.Bots > 5.01. Fails to compile, since the argument is required to be an integer value, and value5.01is not an integer. -
District.ResourceStock.Gear / District.ResourceCapacity.Gear < 0.5. Triggers when district inventory fill ratio drops below 50%. Be careful! The capacity can be 0, it will raise an error. -
District.ResourceCapacity.Gear > 0 and District.ResourceStock.Gear / District.ResourceCapacity.Gear < 0.5. This bulky condition will handle the zero capacity case, but you will likely need to make a counterpart rule for the case of zero capacity.
| Signal name | Description |
|---|---|
Collectable.Ready |
Returns number of tiles that the building can gather resources from. |
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 completed. 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 once in the 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. |
Plantable.Ready |
Returns number of tiles that the building can plant to. |
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
-
Inventory.InputGood.Plank > 100.0. Triggers each time when the inventory changes and has more than100items of Planks. -
Inventory.OutputGood.Plank == 100. Triggers when the amount changes and the new value is exactly100items. -
StreamGauge.Contamination > 0. Triggers on every contamination change. -
StreamGauge.Contamination < 1.01. Fails to compile due to the maximum allowed value is1.0.
Action operator does the actual effect on the building. Its syntax is a Python method call: 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 affecting 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.
NOTE. The number arguments in the actions below are in Python format, if not stated otherwise. I.e. floodgate height of "1" means "1.00".
| Action name | Description |
|---|---|
Debug.LogStr(<StringArg>),Debug.LogNum(<NumberArg>)
|
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. The number value is logged in Lisp syntax (number 100 is a 1.0 value). |
Debug.Log(<Format>, ...) |
An advanced form of a logging action that is only available in the script editor mode. It allows formatting the arguments. The Format argument is a string literal and its syntax is equal to C# String.Format method. Number of placeholders in the format string must match number of arguments! This action logs numbers as floats (Python syntax). |
Dynamite.DetonateAndRepeat(<RepeatTimes>) |
Same as above, but once the dynamite exploded, a new dynamite is placed at the same location. <RepeatTimes> is an integer number that tells how many times to repeat the place operation. |
Dynamite.Detonate() |
Detonates the dynamite. It 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(<Height>) |
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 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 construction 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(<SignalName>, <Value>) |
Sets custom signal, specified by a string literal <SignalName> to float value <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 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(<NumWorkers>), Workplace.RemoveWorkers()
|
Sets the number of workers in the workshop or removes them altogether. The <NumWorkers> is an integer 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
-
Debug.LogStr('Hello!'). Will make a record like "[DistrictCenter.Folktails@(131, 138, 3)] [Debug Log]: Hello!" -
Debug.LogNum(Floodgate.Height). Will log the current floodgate height like "[Floodgate.Folktails@(131, 138, 3)] [Debug Log]: 100", where100would mean1.0(Lisp syntax!) -
Debug.Log('Floodgate height is: {0}', Floodgate.Height). Will log the current floodgate height like "[Floodgate.Folktails@(131, 138, 3)] [Debug Log]: Floodgate height is: 1.0". -
Floodgate.SetHeight(1.23). Sets floodgate height to1.23, but only if its maximum height is 2 or higher. Otherwise, an error will be thrown. -
Floodgate.SetHeight(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. -
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 (C# classes), 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 arise.
-
getvalue(<FullPropertyName>). Returns value of the property. TheFullPropertyNameargument is a string literal of formatClassName.PropertyName, whereClassNameis the name of the game of the component on the building, and thePropertyNameis the name of the property to read. Supported value types: string, integer, float, and boolean. In the case of boolean,trueis1value, andfalseis0. -
getlen(<FullPropertyName>)returns the number of elements in the collection, given the property is a collection (list, array ,etc.). It will fail to parse for a non-collection property. -
getelement(<FullPropertyName>, <Index>)returns the element of the collection at the provided<Index>(zero-based). If the property is not a collection or 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. Always use them in conjunction with signals.
Below are the typical components and their properties that you may want to access. It's not the complete list!
-
BatteryController.Charge- the current charge level of a gravity battery. -
Floodgate.MaxHeight- the maximum height of the floodgate. -
Inventory.Capacity- the total capacity of the building inventory. If inventory allows more than one good, the capacity will be a multiple of the number of the goods and their capacities. -
Inventory.InputGoods- the full list of good IDs that the inventory can take (input). -
Inventory.OutputGoods- the full list of good IDs that the inventory can give (output). -
SingleGoodAllower.AllowedGood- the single good that is allowed on the building. It's usually a stockpile building. -
SingleGoodAllower.HasAllowedGood- tells if the building has any good selected for the storage. In the component, the value is boolean, but in the script it is numeric: 0 =>false; anything else =>true. -
Workplace.MaxWorkers- the maximum number of workers that can work on the building.
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.
The preprocessor statements are wrapped in tags {% and %}. They cannot be nested. Anything inside the tags will be attempted to be parsed and executed as the Lisp script. Python syntax is not supported in preprocessor. Even with the syntax override prefix (it only works in UI editor).
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.
Examples
- Expression
(act Floodgate.SetHeight {% (getvalue Floodgate.MaxHeight) %})will get transformed to(act Floodgate.SetHeight 200)on a floodgate of height2.00. - Precondition
{% (ge (getvalue Floodgate.MaxHeight) 200) %}(act Floodgate.SetHeight 200)will be rejected on floodgates that have max height less than 2.