S9SourceNine Labs / CMAPI Docs 1.0
CMAPI / Guides

Environment and weather

Read world state and safely control supported weather and daylight actions.

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

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

CSHARP
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:

CSHARP
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

CSHARP
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

CSHARP
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:

CSHARP
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

CSHARP
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

CSHARP
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:

CSHARP
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:

CSHARP
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

CSHARP
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 == false as 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 UnsupportedGameBuild as 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 .Result or .Wait(); use await.
  • 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.