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!

Main Concepts of the Scripting Approach

  • 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.

image

Script Syntax

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.

Operators

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 as 150. And the value 1.555 cannot be written in the script, you have to round it up to the last two digits - 1.56. Examples:
    • Script value 100 means 1 in the game.
    • Script value 123 means 1.23 in the game.
    • Script value 1234 means 12.34 in the game.
  • A signal. Signal provides either a string or a numeric value. You can use it everywhere unless it's declared the constant is required.

Logical operators

This is a set of operators that are made for condition checking.

Binary operators

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 to true 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 to true.
  • (eq (sig Wather.Season) (sig Wather.Season)) - an absurd example that always evaluates to true, but it illustrates that the right value is not required to be a constant.

Boolean operators

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>).

Mathematical operators

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 value 100 (1.0f).

Examples:

  • (round (sub 100 40)). Subtracts 0.4f from 1.0f, which gives the result 0.6f. Then, the result is rounded to the nearest integer: 1.0f. Thus, the result of this statement is value 100.
  • (min 0 (sub 0 100) 200). Gives value 0: 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.

Signals

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. The good_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 is 0.
  • 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 is 0.
  • 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 is 0.

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 is 100 integer number).

Actions

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 value 1.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 to 1 (read above to learn why 100 turns into 1).
  • (act Floodgate.SetHeight (sig Signals.MyCustomSignal)). Sets floodgate height to the current value of the custom signal MyCustomSignal
    • 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.

Access operators

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 is 100 value, and false is 0.
  • (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.

Preprocessor

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.

⚠️ **GitHub.com Fallback** ⚠️