S9SourceNine Labs / CMAPI Docs 1.0
CMAPI / Guides

Content and lifecycle

Recipes, unlocks, inventory snapshots, save events, and world lifecycle.

View source ↗
CMAPI 1.0.0Standalone runtimeWindows x64Tested with Planet Crafter 2.008

Content and lifecycle API

CMAPI 1.0.0 exposes Planet Crafter recipes, unlock rules, local-player inventories, and lifecycle boundaries through the Unity-free CMAPI.API.dll. These surfaces are read-only. They do not craft, force unlocks, travel between planets, or mutate arbitrary inventories.

World lifecycle

Subscribe through helper.Events.World:

CSHARP
helper.Events.World.WorldEntered += world =>
    Monitor.Info($"Entered {world.PlanetId} in {world.SaveName ?? "unknown save"}.");

helper.Events.World.PlanetChanged += change =>
    Monitor.Info($"Traveled {change.PreviousPlanetId} -> {change.CurrentPlanetId}.");

helper.Events.World.Saved += world =>
    Monitor.Info($"Save completed: {world.SaveName ?? "unknown"}.");

helper.Events.World.WeatherStarted += weather =>
    Monitor.Info($"Started {weather.ActiveEventId}.");

helper.Events.World.WeatherEnded += weather =>
    Monitor.Info($"Ended {weather.ActiveEventId}.");

helper.Events.World.WorldExited += world =>
    Monitor.Info($"Exited {world.PlanetId}.");

Handlers run on Unity's main thread and should return quickly. Events are owner-scoped: if a mod fails during entry, CMAPI removes all its subscriptions. One handler throwing is logged under that mod and does not stop other handlers.

Event semantics and order:

  1. WorldEntered fires after both the local player and a loaded planet exist.
  2. WeatherStarted follows if CMAPI observes active synchronized weather.
  3. Completed travel raises WeatherEnded for the old observation, then PlanetChanged, then WeatherStarted if the new planet has active weather.
  4. Saved comes directly from Planet Crafter's SavedDataHandler.OnSaved, so it means the game reported completion rather than merely receiving a request.
  5. World teardown raises WeatherEnded before WorldExited.

WeatherEnded includes the last active snapshot. Its elapsed/remaining values are the last values CMAPI observed, not a synthetic zero-duration snapshot.

Recipes

helper.Recipes.AllRecipes returns a fresh ordered snapshot. Query again after content initialization instead of permanently caching an empty main-menu list.

CSHARP
foreach (IRecipeDefinition recipe in helper.Recipes.FindRecipes("uranium"))
{
    Monitor.Info($"{recipe.OutputDisplayName} ({recipe.OutputId})");

    foreach (IRecipeIngredient ingredient in recipe.Ingredients)
        Monitor.Info($"  {ingredient.Amount} x {ingredient.Id}");

    Monitor.Info("Stations: " + string.Join(", ", recipe.CraftingStations));
}

Planet Crafter represents quantities by repeating an ingredient group. CMAPI collapses those repetitions into distinct IRecipeIngredient.Amount values. CraftingStations contains stable game enum IDs such as CraftStationT2 or CraftBioLab; keeping those IDs as strings lets CMAPI report future stations without adding a new API enum. Constructible recipes normally have no station because the construction interface owns them.

Unlocks

helper.Unlocks returns static requirements and a live state captured at query time:

CSHARP
if (helper.Unlocks.TryGetUnlock("SomeGroupId", out IUnlockDefinition? entry))
{
    if (!entry.IsStateAvailable)
        Monitor.Info("Load a world before trusting live unlock state.");
    else if (entry.IsUnlocked && entry.IsAvailableOnCurrentPlanet)
        Monitor.Info($"{entry.DisplayName} is usable here.");

    if (entry.RequiredWorldUnit is WorldUnitKind unit)
        Monitor.Info($"Requires {entry.RequiredValue} {unit}.");
}

Always check IsStateAvailable before treating IsUnlocked or IsAvailableOnCurrentPlanet as authoritative. Static fields—including IsBlueprintRequired, RequiredWorldUnit, RequiredValue, and PlanetIds—are still useful before the world finishes initializing. Query again after saves, blueprints, progression, or planet travel; snapshots do not update themselves.

An empty PlanetIds list means Planet Crafter applies its default/start-planet rule, not that CMAPI has proven the content usable everywhere.

Local player inventory

Backpack and equipment snapshots aggregate slots by stable group ID:

CSHARP
PlayerInventorySnapshot backpack = helper.PlayerInventory.Backpack;
if (backpack.IsAvailable)
{
    Monitor.Info($"Slots: {backpack.OccupiedSlots}/{backpack.Capacity}");
    foreach (InventoryItemSnapshot item in backpack.Items)
        Monitor.Info($"{item.Amount} x {item.DisplayName} ({item.Id})");
}

helper.PlayerInventory.Changed += change =>
{
    string verb = change.WasAdded ? "added" : "removed";
    Monitor.Info($"{change.Kind}: {verb} {change.ItemId}");
};

Amount counts occupied slots of the same base group. It intentionally does not expose mutable game objects, genetic traits, custom text, growth, or other instance data in this first read-only contract. InventoryId belongs to the current save and should not be treated as a permanent cross-save identifier.

The Changed event is raised from Planet Crafter's native inventory callback after an add/remove and includes a fresh post-change snapshot. Availability changes by themselves do not raise it; use world/player lifecycle events and then read the snapshot.

Console equivalents

Players and authors can inspect the same information without code:

inventory
inventory equipment
inventory backpack uranium
recipes super alloy
recipe Super Alloy
unlocks available
unlocks blueprint chip
unlock_info SomeGroupId

List commands show at most 100 entries and ask for narrower search text when more exist. Exact IDs are preferred for scripts because localized display names change with the player's language.

Compatibility behavior

gameinfo and health_check report the Content API separately. On a future game build missing required content, inventory, save, or lifecycle hooks, CMAPI keeps the loader and console alive but returns empty/unavailable snapshots and does not bind lifecycle callbacks. It never guesses a replacement mutation.