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.

csharp
// If ApiVersion were a const, this is what YOU write...
if (TechTreeApi.ApiVersion < 2)
    Log.LogWarning("Tech Tree too old");
csharp
// ...and this is what the compiler bakes into your dll:
if (1 < 2)                      // the value from YOUR build machine, frozen
    Log.LogWarning("Tech Tree too old");

// The player upgrades Tech Tree to ApiVersion 5. Your check still says 1.

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

The same trap in your own code

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

csharp
public override void Load()
{
    if (TechTreeApi.ApiVersion < 1)
    {
        Log.LogWarning($"Tech Tree API {TechTreeApi.ApiVersion} is older than this mod needs.");
        return;
    }

    RegisterUpgrades();
}
ApiVersionTech TreeWhat it means
(does not exist)1.2.0 and earlierNo API. Your mod fails to load with a TypeLoadException.
11.3.0Everything documented here.
11.3.1Added 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:

csharp
// This is the one to use. Any Tech Tree satisfies it.
[BepInDependency(TechTreeApi.ModGuid)]
Do not put a version in BepInDependency

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.

Then how do I guard a newer field?

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

ChangeEffect on your mod
New optional field on UpgradeDefinitionNone. Old code compiles and runs.
New built-in iconNone.
New eventNone.
Built-in node prices rebalancedNone - yours are yours.
A built-in id renamedOnly if you gated on it. Prefer gating on your own nodes.
ApiVersion bumped to 2Read 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 changePlayer who already owns levels
PriceKeeps their levels; the new price applies to levels not yet bought.
IconJust changes.
Display name / translationsJust changes.
Effect strengthApplies immediately at their current level.
MaxLevel upNew levels appear, buyable in order. Free win.
MaxLevel downSee below.
The idLoses 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

BumpWhen
PatchBug fix, wording, icon.
MinorNew node, more levels on an existing one, rebalance.
MajorA node removed, MaxLevel lowered, or a new minimum Tech Tree version.