Tech Tree API
Registration
When to call, what comes back, the rules ids must follow, and what happens when two mods want the same one.
When to call
Register from your plugin's Load(). That is early - before any scene
exists - and it is fine, because registration only records data. Nothing touches Unity until the
player opens the tree window.
You can also register later: mid-game, after a config reload, after the player unlocks something. A node that appears while the window is open shows up on the next repaint, and if the player already owned levels on that id from a previous session, those levels come back with it (see Persistence).
The API never throws
Register is called from inside your
Load(). If it threw, the exception would abort your plugin, not Tech
Tree - which is a terrible failure mode for a mistake as small as a typo in an id. So it does
not throw. Every failure comes back as a result and is written to the log with your guid.
UpgradeHandle handle = tree.Register(new UpgradeDefinition { /* ... */ });
if (!handle.Success)
{
// handle.Result.Status -> RegisterStatus enum
// handle.Result.Message -> the same text that went to the log
Log.LogError($"Tech Tree refused the node: {handle.Result}");
return;
}
int owned = handle.Level; // 0 on a fresh saveRegisterStatus
| Status | Meaning |
|---|---|
Ok | The node was added. |
Replaced | You re-registered your own id. The definition was swapped and the owned level kept. |
MissingField | No DescribeLevel, no name, or a null definition. |
InvalidId | The id is not prefix.name, or uses characters outside a-z 0-9 _ - . |
ReservedId | The id starts with techtree. or tt. |
OwnedByAnother | Another plugin got there first. The first registration is untouched. |
InvalidLevels | MaxLevel is outside 1..20. |
InvalidCost | No positive BaseCost and no CostForLevel. |
InvalidModule | You passed an empty plugin guid to ForPlugin. |
Id rules
An id registered through the API must be in prefix.name
form:
- lower case only, plus digits,
_,-and dots - at least one dot, not at the start or the end, never two in a row
- 3 to 64 characters
- not starting with
techtree.ortt.
| Id | Verdict |
|---|---|
bigboxes.capacity | ok |
night_owl.open-late | ok |
capacity | no dot |
BigBoxes.Capacity | upper case |
bigboxes..capacity | double dot |
tt.capacity | reserved prefix |
cashier_speed | no dot (and it is a built-in id) |
None of Tech Tree's own ten ids contain a
dot - they are cashier_speed,
more_customers and so on. Requiring a dot from everyone else means a
third-party mod cannot collide with or hijack a built-in node by construction. No
renaming, no save migration, and the rule is one line to remember.
Renaming it normally costs your
players their levels, because the id is the save key. Since 1.3.1,
LegacyIds carries the level over to the new name.
How it works
Duplicate ids
| Situation | What happens |
|---|---|
| Same plugin, same id, twice | The definition is replaced and the owned level is kept. This is what makes hot-reload work. |
| Different plugin, same id | Rejected with OwnedByAnother, naming both guids in the log. The first registration is untouched and neither mod breaks. |
| Same prefix, different plugins | Allowed, with a warning. Mod suites legitimately share a prefix. |
Limits
| Limit | Value |
|---|---|
| Nodes per plugin | 64 |
| Levels per node | 1 to 20 |
| Sub-tab label | 18 characters |
| Icon PNG | 512 KB |
Removing a node
Unregister takes a node out of the tree. It does not
delete the player's level - that stays in the save file and comes back if you register the id
again. Use it for config toggles, not as a cleanup step on shutdown.
// Via the handle...
handle.Unregister();
// ...or by id, from the module.
tree.Unregister("bigboxes.capacity");A sub-tab with no nodes is not drawn, and if yours was the only mod registered, the whole Mods tab disappears too. If that happens while the window is open, the view falls back to the Staff tab instead of going blank.