# Planets

`PM.Planets` patches celestial bodies and the things attached to them: the body's own properties, the galaxies that place it in orbit, and the atmosphere and cloud visuals layered on top.

## Patching a body

`PM.Planets:Patch(name)` patches a celestial body, targeted by body name (`Kerbin`, `Mun`, `Eve`, and so on). Inside the callback you get the body's properties and set fields directly. This real patch points Eve's reentry effects at a custom gradient:

```lua
PM.Planets:Patch("EveReentryGradient")
          :Named("Eve")
          :Do(function(body)
              body.atmosphericReentryVFXGradient =
                  "Assets/ReduxAssets/Definitions/CelestialBodies/KerbolSystem/Eve/EveReentry.png"
          end)
```

Vector and color fields are tables. This patch sets each body's dust color, sharing one helper across many bodies:

```lua
local function RGB(r, g, b)
    return function(body) body.MineDustColor = { x = r, y = g, z = b, w = 1.0 } end
end

PM.Planets:Patch("MunDustColor"):Named("Mun"):Do(RGB(1.0, 0.99, 0.87))
PM.Planets:Patch("EveDustColor"):Named("Eve"):Do(RGB(0.69, 0.57, 0.43))
```

## Changing a body for some campaigns only

To change a body only in campaigns that have one of your [features](/doc/features-geboWdIiDN) on, patch the body in place. Gate the patch on the feature. Campaigns without the feature keep the original body.

```lua
PM:WithFeature("Rescale", function()
    PM.Planets:Patch("RescaleDrast"):Late():Named("Drast"):Do(function(body)
        body.radius = body.radius * 2.5
        body.TerrainHeightMultiplier = 2.5
        body.MinTerrainHeight = body.MinTerrainHeight * 2.5
        body.MaxTerrainHeight = body.MaxTerrainHeight * 2.5
        body.oceanAltitude = body.oceanAltitude * 2.5
    end)
end)
```

With `:Late()`, the patch runs after the changes that Redux and other mods make to the body. `PM.Planets:Duplicate(source, newName)` copies the data of a body. The copy keeps the name of the body, so the copy is a second definition of the same body. Use `Duplicate` only to start a new body. Give the copy a new name.

### Terrain height

A body's mountains come from its PQS data, not its JSON, so scaling `radius` alone flattens the terrain relative to the bigger body. `TerrainHeightMultiplier` scales every terrain height when the body's surface loads. It defaults to `1`. Scale `MinTerrainHeight` and `MaxTerrainHeight` by the same amount, since the camera and the orbit checks read those.

Scale `oceanAltitude` by it too. The terrain is built that far below sea level, so leaving it at the stock value lifts the seabed out of the ocean and drains most of it.

Surface decals, such as the flattened ground under the space center, scale by the same factor in both height and size, so they keep their stock slopes and stay level with the terrain around them. The multiplier is best set to the same factor as the radius, so the whole surface is the stock one, only bigger.

## Adding a body to the galaxy

A new body is added to the galaxy definition, not created as a loose asset. `PM.Planets:PatchDefaultGalaxy(name)` patches the galaxy, and `galaxy:Add(guid, callback)` adds a body. This is how Redux adds the moon Drast:

```lua
PM.Planets:PatchDefaultGalaxy("AddDrast"):Do(function(galaxy)
    galaxy:Add("Drast", function(body)
        body.PrefabKey = "Celestial.Drast.Local"
        body.referenceBodyGuid = "Dres"
        body.OrbitProperties = {
            referenceBodyGuid = "Dres",
            inclination = 0,
            eccentricity = 0,
            semiMajorAxis = 434400,
            longitudeOfAscendingNode = 0,
            argumentOfPeriapsis = 0,
            meanAnomalyAtEpoch = 0,
            epoch = 0,
        }
    end)
end)
```

Adding a new fully working body is a large undertaking (it needs a prefab, orbit, properties, and usually science and atmosphere data). The galaxy `Add` call is only the placement step. Everything else should be done using the planet editing tools in the SDK, as there is a lot more than just the JSON data itself that goes into the body

## Galaxies

A galaxy definition lists the bodies a campaign plays with and where they orbit. Redux's is `GalaxyDefinition_Default`. A mod can add its own, and a [campaign pack](/doc/campaign-packs-5HBzAytKyU) names the galaxy it plays in.

* `PM.Planets:PatchDefaultGalaxy(name)` patches Redux's galaxy only.
* `PM.Planets:PatchGalaxy(key, name)` patches the galaxy with the given key.
* `PM.Planets:CreateGalaxy(key, callback)` creates an empty galaxy. You add its bodies with `galaxy:Add`.
* `PM.Planets:DuplicateGalaxy(key, newKey)` copies a galaxy under a new key. Order it after anything that adds bodies to the source, such as Redux's Drast and Beyl, so the copy has them.

The Redux galaxy, its bodies and the data attached to them belong to the `Ksp2Redux:Galaxy` feature. The Redux cloud layers, coloured reentry gradients and the scaled lighting of Bop belong to `Ksp2Redux:Visuals`. To change the galaxy for some campaigns only, patch the galaxy in place under one of your features. This patch makes the orbits of the Redux galaxy 2.5 times larger. It applies only in campaigns that have the `Rescale` feature on:

```lua
PM:WithFeature("Rescale", function()
    PM.Planets:PatchDefaultGalaxy("RescaleOrbits"):Late():Do(function(galaxy)
        for _, entry in ipairs(galaxy.CelestialBodies) do
            -- Kerbol has no orbit
            local orbit = entry.OrbitProperties
            if orbit ~= nil and orbit.semiMajorAxis ~= nil then
                orbit.semiMajorAxis = orbit.semiMajorAxis * 2.5
            end
        end
    end)
end)
```

For a different system, create your own galaxy with `CreateGalaxy`. Put its name in the `Galaxy` field of your campaign pack. If the features of that pack do not include `Ksp2Redux:Galaxy`, the Redux bodies do not load in its campaigns.

| Field | What it does |
|-------|--------------|
| `HomeWorld` | The home world and its space center, below. |
| `CelestialBodies` | The bodies, each with its orbit. |

Players do not select a galaxy directly. Every campaign, sandbox included, uses a campaign pack. The pack supplies its galaxy and the [features](/doc/campaign-features-9YIewcIArR) that a new campaign starts with.

### Home world


:::warning
Home world swapping is experimental, and the default space center is a placeholder. A galaxy's `HomeWorld` is ignored unless the player enables **Enable home world swapping** in the `Advanced` section of `Redux/config.json`.

:::

`HomeWorld` moves the space center to another body.

| Field | Default | What it does |
|-------|---------|--------------|
| `Body` | `Kerbin` | The GUID of the home world. |
| `RemoveOtherSpaceCenters` | `true`  | Removes a space center found on any other body when that body's surface loads. |
| `SpaceCenter` | none    | The space center to place. Without one, the home world keeps whatever space center its own surface already has. |

`SpaceCenter` places a prefab on the home world:

| Field | Default | What it does |
|-------|---------|--------------|
| `Prefab` | `DefaultSpaceCenter.prefab` | Addressable key of the space center prefab. |
| `Latitude`, `Longitude` | `0`     | Where the space center's origin goes, in degrees. |
| `Altitude` | `25`    | Height of the origin above the terrain drawn directly below it, in meters. |
| `Heading` | `0`     | Rotation clockwise from north, in degrees. At `0` the prefab's -Z axis points north. |

Pick a flat spot. The space center sits on its origin, so steep terrain under a wide prefab leaves parts of it floating or buried.

## Atmosphere overrides

An atmosphere override tunes a body's atmosphere visuals and physics without editing the body itself. Every field except the body name is optional, so you set only what you want to change.

* `PM.Planets:PatchAtmosphereOverride(name)` patches an existing override.
* `PM.Planets:CreateAtmosphereOverride(bodyName, callback)` creates a new override for a body.
* `PM.Planets:DuplicateAtmosphereOverride(source, newName)` copies an existing override.

## Cloud overrides

Cloud overrides work the same way for volumetric clouds:

* `PM.Planets:PatchCloudOverride(name)` patches an existing override.
* `PM.Planets:CreateCloudOverride(bodyName, callback)` creates a new one.
* `PM.Planets:DuplicateCloudOverride(source, newName)` copies an existing one.

A cloud override's `cumulusList` is a typed list keyed by layer name, so you reach a specific cloud layer by its `layerName` and edit it. The full set of atmosphere and cloud fields, which are extensive and rendering-specific, is in `pm-planets.d.lua`.

## Overrides for some campaigns only

`CreateAtmosphereOverride` and `CreateCloudOverride` return the queued override. As with all created assets, the override belongs to the features that gate its registration. Inside `PM:WithFeature`, or with `:Feature` added to the chain, the override applies only in campaigns that have that feature on:

```lua
PM:WithFeature("Rescale", function()
    PM.Planets:CreateAtmosphereOverride("kerbin", function(atmosphere)
        atmosphere.BottomRadius = 1500
        atmosphere.AtmosphereHeight = 92
    end)
end)
```

Some overrides already exist, for example the cloud overrides that Redux ships for Kerbin, Eve and Jool. Patch these in place under the feature with `PatchCloudOverride` or `PatchAtmosphereOverride`. Do not create a second override for the same body. The Redux cloud overrides belong to `Ksp2Redux:Visuals`, so they load only in campaigns that have that feature on.

## Which surface to use

* Changing a body's physical or surface properties: `Patch`.
* Changing a body for some campaigns only: `Patch` it under a [feature](/doc/features-geboWdIiDN).
* Placing a new body: `PatchDefaultGalaxy` and `galaxy:Add`.
* A rescaled system: patch the Redux bodies and galaxy in place under a feature, with a campaign pack that turns the feature on.
* A whole new system: `CreateGalaxy`, with a campaign pack that names it.
* Tuning how an atmosphere or clouds look: the override methods, so the body definition stays untouched and your visual changes are separable.