Tech Tree API
Versioning
The const trap, what a Tech Tree update can and cannot break, and how to change your own numbers without hurting players.
The const trap
This is the single most common way a mod that depends on another mod breaks, and it is worth understanding before anything else on this page.
In C#, a const is inlined into your assembly at compile
time. If Tech Tree exposed its API version as a const and you wrote a check against it,
your compiled dll would carry the number that was current on your machine the day you built - and
would keep comparing against that number forever, on every player's install, no matter which Tech
Tree they actually have.
// If ApiVersion were a const, this is what YOU write...
if (TechTreeApi.ApiVersion < 2)
Log.LogWarning("Tech Tree too old");So TechTreeApi.ApiVersion is deliberately declared
public static readonly int, not const. A
readonly field is read from the installed dll at runtime, which is the
only thing that can answer "what does this player actually have".
If you expose constants to other
mods, apply the rule to yourself. const is right for something that can
never change, like ModGuid. Anything a future version might bump should
be static readonly.
Checking the version
public override void Load()
{
if (TechTreeApi.ApiVersion < 1)
{
Log.LogWarning($"Tech Tree API {TechTreeApi.ApiVersion} is older than this mod needs.");
return;
}
RegisterUpgrades();
}| ApiVersion | Tech Tree | What it means |
|---|---|---|
| (does not exist) | 1.2.0 and earlier | No API. Your mod fails to load with a TypeLoadException. |
1 | 1.3.0 | Everything documented here. |
1 | 1.3.1 | Added SellBackRate and LegacyIds. Not a breaking change, so the number stays. |
The number only goes up on a breaking change. New optional fields, new events and new built-in icons do not bump it - code written against version 1 keeps compiling and keeps working.
Using a field that arrived in a later version
ApiVersion answers "is the contract still the same". It does
not answer "does this specific field exist". A mod compiled against 1.3.1 that
sets SellBackRate will throw
MissingFieldException on a player who still has 1.3.0 - and a runtime
check cannot save you, because the failure happens as the method is compiled, not where the line
sits.
Declare the dependency with the guid alone:
// This is the one to use. Any Tech Tree satisfies it.
[BepInDependency(TechTreeApi.ModGuid)]The second argument reads
like "minimum version". It does not behave that way here. A plugin declaring
[BepInDependency(TechTreeApi.ModGuid, "1.3.1")] was refused with Tech
Tree 1.3.2 installed and loaded:
Could not load [Your Mod] because it has missing dependencies: TechTree
(1.3.1) - printed on the line right after Loading [Tech Tree
1.3.2].
So pinning a version does not protect you from an older Tech Tree. It breaks your mod the day Tech Tree updates. This page used to recommend it, and that was wrong.
Say it in your mod page description, and use the field. There is no attribute that reliably expresses "1.3.1 or newer".
To reach every player instead, stick to what 1.3.0 offers - the API itself:
ForPlugin, Register, the events, icons and
translations. SellBackRate and LegacyIds
arrived in 1.3.1.
When Tech Tree updates
| Change | Effect on your mod |
|---|---|
New optional field on UpgradeDefinition | None. Old code compiles and runs. |
| New built-in icon | None. |
| New event | None. |
| Built-in node prices rebalanced | None - yours are yours. |
| A built-in id renamed | Only if you gated on it. Prefer gating on your own nodes. |
ApiVersion bumped to 2 | Read the changelog. Your version check catches it. |
Changing your own numbers
Levels are saved by id, and nothing else about the node is. That makes most updates free:
| You change | Player who already owns levels |
|---|---|
| Price | Keeps their levels; the new price applies to levels not yet bought. |
| Icon | Just changes. |
| Display name / translations | Just changes. |
| Effect strength | Applies immediately at their current level. |
MaxLevel up | New levels appear, buyable in order. Free win. |
MaxLevel down | See below. |
| The id | Loses everything. Do not do this. |
Lowering MaxLevel
Say a player owns level 5 and your update drops MaxLevel to 3. The
raw number 5 stays in the save file; only what is displayed and applied is clamped to 3. If you
change your mind and put it back to 5 in the next version, their level 5 returns.
Nothing is destroyed, but the player did pay for two levels they no longer have. If you must narrow a line, say so in your changelog.
Removing a node for good
If a feature is gone, simply stop registering the id. The player's level stays in their save
file under the "kept for mods" block indefinitely; it costs a few bytes and it means a rollback to
your older version restores everything. Unregister does the same thing
at runtime.
There is no API to erase a level, on purpose - deleting something a player spent in-game money on should not be a side effect of a mod update.
Semantic versioning for your mod
| Bump | When |
|---|---|
| Patch | Bug fix, wording, icon. |
| Minor | New node, more levels on an existing one, rebalance. |
| Major | A node removed, MaxLevel lowered, or a new minimum Tech Tree version. |