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:
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:
WorldEnteredfires after both the local player and a loaded planet exist.WeatherStartedfollows if CMAPI observes active synchronized weather.- Completed travel raises
WeatherEndedfor the old observation, thenPlanetChanged, thenWeatherStartedif the new planet has active weather. Savedcomes directly from Planet Crafter'sSavedDataHandler.OnSaved, so it means the game reported completion rather than merely receiving a request.- World teardown raises
WeatherEndedbeforeWorldExited.
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.
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:
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:
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.