Weather, world state, planets, and environment
CMAPI 1.0.0 exposes Planet Crafter's environment through CMAPI-owned interfaces and immutable values. Ordinary mods reference only CMAPI.API.dll; they do not need Unity, Assembly-CSharp, BepInEx, or Unity Netcode references.
Service map
| Helper property | Safe reads | Mutations |
|---|---|---|
helper.Weather |
Current-planet definitions and synchronized status | Host-only start; never replaces active weather |
helper.WorldState |
World-unit totals/rates and current/next stage | None |
helper.Environment |
Day/night snapshot and underwater queries | Host-only reset to full day |
helper.Planets |
Current and installed/purchased planet definitions | None; forced travel is intentionally excluded |
Snapshots are point-in-time values. Read them again when current state matters. Empty catalogs and unavailable snapshots are normal at the title screen, during planet travel, and before world managers finish initializing. Do not permanently cache an empty startup result.
Weather discovery
foreach (IWeatherEventDefinition weather in helper.Weather.Events)
{
helper.Monitor.Log(
$"{weather.DisplayName} [{weather.Id}] | " +
$"{weather.DurationSeconds:0}s | meteor: {weather.HasAsteroids}"
);
}Id is the exact current-build game asset ID accepted by StartAsync. DisplayName is a readable form intended for menus and logs. Definitions also expose rain emission, terrain wetness, and optional lower/upper terraformation stage IDs.
Use exact ID lookup when saving a choice:
if (helper.Weather.TryGetEvent(savedEventId, out IWeatherEventDefinition? weather))
helper.Monitor.Log($"Found {weather.DisplayName}.");Use FindEvents for a player-facing search box. It matches partial IDs and display names without case sensitivity.
Weather status
WeatherSnapshot weather = helper.Weather.Status;
if (!weather.IsAvailable)
{
helper.Monitor.Log(weather.UnavailableReason, LogLevel.Debug);
}
else if (!weather.IsActive)
{
helper.Monitor.Log("No synchronized weather event is active.");
}
else
{
helper.Monitor.Log(
$"{weather.ActiveDisplayName}: {weather.Progress:P0}, " +
$"{weather.RemainingSeconds:0}s remaining"
);
}Status is derived from the game's synchronized selected index, server start time, and live event list. It therefore reports the same event on host and clients rather than a local-only visual guess.
Starting weather safely
EnvironmentActionResult result = await helper.Weather.StartAsync(eventId);
if (!result.Succeeded)
helper.Monitor.Log($"Weather start: {result.Status} — {result.Message}");Meteor events may optionally use a same-planet reference position:
WorldPosition impactReference = helper.Players.LocalPlayer!.Position;
EnvironmentActionResult result = await helper.Weather.StartAsync(
eventId,
impactReference
);The optional position is a meteor reference, not a promised impact coordinate. Planet Crafter still owns event spawning and targeting.
StartAsync may be called from any thread. CMAPI schedules game access on the main thread and returns a structured status:
| Status | Meaning |
|---|---|
Success |
The host submitted the native synchronized start. |
InvalidArgument |
ID or optional coordinates are empty, non-finite, too long, or outside CMAPI bounds. |
UnsupportedGameBuild |
The installed game is missing one or more hooks required by this API; inspect gameinfo. |
WorldNotReady |
Player, planet, weather state, or host manager is unavailable. |
HostRequired |
A joining client attempted the mutation. |
NotFound |
The exact event ID is not in the current planet's natural catalog. |
AlreadyActive |
Another event is active and was deliberately left untouched. |
RequirementsNotMet |
Current terraformation is outside the event's configured stage range. |
OwnerInactive |
The requesting mod did not finish loading or is no longer active. |
GameRejected |
Reserved for a normal game rejection path. |
Failed |
An unexpected compatibility or game exception occurred. |
CMAPI does not expose forced weather stopping. Planet Crafter performs cleanup only when the synchronized selected index transitions from an event to -1. Replacing one non-negative event with another skips that cleanup branch and can leave asteroid, audio, sky, wetness, or particle state behind. Refusing overlap is therefore a deliberate correctness guarantee.
Terraformation and world units
WorldStateSnapshot world = helper.WorldState.GetSnapshot();
if (!world.IsAvailable)
return;
foreach (WorldUnitSnapshot unit in world.Units)
{
helper.Monitor.Log(
$"{unit.Kind}: {unit.DisplayValue} | " +
$"gross +{unit.IncreasePerSecond:0.##}/s | " +
$"gross {unit.DecreasePerSecond:0.##}/s | " +
$"net {unit.NetPerSecond:0.##}/s"
);
}
helper.Monitor.Log(
$"Stage {world.CurrentStage?.Id ?? "unknown"} → " +
$"{world.NextStage?.Id ?? "final"} " +
$"({world.NextStageCompletionPercent:0.0}%)"
);Value and rates are raw Planet Crafter values. DisplayValue uses the game's current formatted unit. WorldUnitKind avoids leaking the game's enum and includes Unknown so a later game update can fail safely.
CMAPI intentionally exposes no setters or force-reset methods for these values. They are progression and save state.
Day/night and water
EnvironmentSnapshot environment = helper.Environment.GetSnapshot();
if (environment.IsAvailable)
{
helper.Monitor.Log(
$"{environment.DayNightPhase} at " +
$"{environment.DayNightValue:0.0}/100; " +
$"underwater: {environment.IsLocalPlayerUnderwater}"
);
}Planet Crafter uses 0 for full day and 100 for full night. DayNightPhase.Transition deliberately does not claim dusk versus dawn because the synchronized value alone does not contain direction.
Query another current-planet position without referencing Vector3:
bool submerged = helper.Environment.IsUnderwater(
new WorldPosition(559f, 2f, 600f)
);The method returns false when the water manager is unavailable or water has not begun rising. It throws only for programmer errors involving NaN or infinite coordinates.
Reset to full day with a structured host-only request:
EnvironmentActionResult result =
await helper.Environment.ResetToDayAsync();This resets the live synchronized cycle. It does not edit terraformation or expose raw fog, light, skybox, material, or post-processing overrides.
Planets
IPlanetDefinition? current = helper.Planets.CurrentPlanet;
foreach (IPlanetDefinition planet in helper.Planets.AvailablePlanets)
{
helper.Monitor.Log(
$"{planet.Id} | current: {planet.IsCurrent} | " +
$"weather: {planet.WeatherEventCount}"
);
}The catalog includes planets available to the current installation and purchased DLC set. It exposes stable ID, scene name, starting/current flags, purification requirement, and natural weather count.
CMAPI does not expose forced planet travel. The native travel operation moves players and inventories, creates and destroys world objects, chooses landing positions, and mutates synchronized/save state. A future travel API needs its own readiness, authority, lifecycle, multiplayer, and recovery contract.
Console equivalents
weather_events [search]
weather_status
weather_start <event ID or name>
world_state [unit]
environment
day_reset
planets
weather, weather_list, world, and env are aliases. Mutation commands are shown only when cheat commands are enabled. Public mutations remain host-only regardless of RequireHostForCheatCommands.
Lifecycle checklist for mods
- Read catalogs after world entry; retry after planet travel.
- Treat
IsAvailable == falseas temporary unless compatibility diagnostics say the API is disabled. - Store weather and planet IDs, never display names.
- Handle every
EnvironmentActionStatus; do not infer success from task completion alone. - Treat
UnsupportedGameBuildas a feature-disable result. CMAPI also returns unavailable/empty read-only environment snapshots on that build instead of invoking a missing game method. - Never block the game thread with
.Resultor.Wait(); useawait. - Re-read snapshots rather than retaining them as live objects.
- Test title screen, world entry, world exit, save switching, planet travel, host, joining client, active-weather overlap, and game shutdown.