Cloud Layer Types

A cloud layer type decides where a cloud layer has cloud. Your plugin can add its own, see API for setup.

How a layer finds its type

A layer is a file in plugins/MythicSeasons/cloud-layers/. Its file name without .yml is the layer id, and its type key is the type id.

type: dust-storm
forms-at: 3.0
full-at: 7.0

The layer asks the type dust-storm.

  • Type ids ignore letter case, and the type value also ignores surrounding spaces.
  • A layer file without type is skipped with cloud-layers/<file>.yml has no type, skipping it.
  • A layer is drawn only while its id is in a clouds.layers list.
  • A layer with an unknown type is skipped with cloud layer '<layer>' has type '<type>', which is not a known cloud layer type; skipping it, printed once until the next /ms reload.

Clouds must be on in the world, see Clouds. Changing clouds.enabled needs a restart.

CloudLayerRegistry

Get it with SeasonsAPI.getInstance().getCloudLayerRegistry(). It is a final class in net.taiyou.mythicseasons.api.clouds, safe to use from any thread.

Method Returns Description
register(String id, CloudLayerType type) boolean Registers the type under the id, stored in lower case. false when the id is taken, built-in ids included. Throws a NullPointerException for a null id or type.
unregister(String id, CloudLayerType type) boolean Removes the type only when the id holds this same object. true when removed.
get(String id) CloudLayerType The type for the id, compared in lower case, or null.
version() int Goes up by one each time a type is registered or unregistered. You do not need it.

MythicSeasons never reads a registry made with the public constructor CloudLayerRegistry(), which holds only the built-in types.

Register in your onEnable. Later registry changes take effect on the next server tick.

Built-in ids

These ids are taken, see Layer Types.

Id Returns
simulated weather.cloudWater(x, y, z)
surface-humidity weather.surfaceHumidity(x, z)
humidity weather.humidity(x, y, z)
surface-wind weather.surfaceWind(x, z)
noise The constant 0.25.
none The constant 0.
outflow weather.outflow(x, z)

CloudLayerType

CloudLayerType is a functional interface in net.taiyou.mythicseasons.api.clouds, so a lambda or a class works.

float density(CloudSample weather, double x, double y, double z)
Argument Description
weather The weather to read, see CloudSample.
x, z The position in world block coordinates.
y The absolute block height.

It returns a raw reading with no fixed range. The layer's forms-at (default 0) and full-at (default 0.5), both -1000 to 1000 (Layer Options), turn it into t = (value - forms-at) / (full-at - forms-at), limited to 0 to 1:

  • t of 0 draws no cloud, and the first wisps appear a little above it.
  • t of 1 draws solid cloud, and so does everything above full-at.
  • With full-at at or below forms-at, the layer is fully on from forms-at and draws nothing below it.
  • NaN draws nothing.

A season, world or atmosphere profile can change forms-at and full-at under cloud-layers.<layer>. Do not rely on the exact points asked, which depend on voxel-size, voxel-height and the distance from the player. The call carries no world: limit a layer to a world with that world's clouds.layers list.

CloudSample

CloudSample is an interface in net.taiyou.mythicseasons.api.clouds that MythicSeasons passes to density. Implement it only to test your type.

Method Returns Unit and range
cloudWater(double x, double y, double z) float Simulated cloud water in grams per kilogram, interpolated between cells: 0 to 12.75, 0 where nothing is simulated. What simulated returns.
humidity(double x, double y, double z) float Relative humidity, interpolated between cells: 0 to 1 (1 is saturated), 0 where nothing is simulated.
surfaceHumidity(double x, double z) float Relative humidity at the ground: 0 to 1 (1 is saturated), 0 where there is no weather.
surfaceWind(double x, double z) float Wind speed at the ground in metres per second: 0 to 25.5, 0 where there is no weather.
outflow(double x, double z) float Strength of the high cloud spreading downwind from tall storm clouds: 0 to 1 (1 is fresh). The default method returns 0f.

x, y and z are world coordinates, and the values are smooth between cells. Use a sample only inside the call and do not keep it.

Contract

  • density runs on several threads, possibly on the same object at once, so keep no unsynchronised state in it. Besides the cloud mesh thread, for the first solid layer of clouds.layers (the first with opacity: 1.0) it runs on the thread that asks whether a location is in a cloud: the incloud test of the Atmosphere condition, the cloud enter and exit triggers, the in-cloud, cloud base and cloud top placeholders, and /ms atmosphere. Triggers and commands ask on the main thread.
  • Never call the Bukkit API from density. Read only the CloudSample and your own data that is safe from any thread.
  • density must be pure: no side effects, and the same value for the same arguments and weather. Otherwise incloud disagrees with what players see.
  • density must be fast, a few arithmetic operations and CloudSample reads. It runs at every point the renderer tests, even when it ignores y, and on the main thread for in-cloud checks.
  • MythicSeasons catches a RuntimeException or a LinkageError from density, no other errors. The layer then reads 0 everywhere and the console prints cloud layer '<layer>' (type <type>) failed and draws nothing: <error> once until the next /ms reload. A 0 still draws cloud with a negative forms-at, or with a full-at at or below a forms-at of 0 or less. The layer is tried again after /ms reload, after a type is registered or unregistered, and when the season progress changes.

Keep your type's settings in your own plugin's config: a layer file reports any key that is not a layer setting, type, color or shape as unknown cloud layer setting and ignores it.

Example

A type that makes dust where the ground wind is strong and the air is dry:

import net.taiyou.mythicseasons.api.SeasonsAPI;
import net.taiyou.mythicseasons.api.clouds.CloudLayerRegistry;
import net.taiyou.mythicseasons.api.clouds.CloudLayerType;

public final class DustType {

    private static final String ID = "dust-storm";

    private final CloudLayerType type = (weather, x, y, z) ->
            weather.surfaceHumidity(x, z) < 0.4f ? weather.surfaceWind(x, z) : 0f;

    public void register() {
        CloudLayerRegistry registry = SeasonsAPI.getInstance().getCloudLayerRegistry();
        if (!registry.register(ID, type)) {
            throw new IllegalStateException("The cloud layer type " + ID + " is already registered");
        }
    }

    public void unregister() {
        SeasonsAPI.getInstance().getCloudLayerRegistry().unregister(ID, type);
    }
}

Call register() from onEnable and unregister() from onDisable.

The layer file plugins/MythicSeasons/cloud-layers/dust.yml uses it:

type: dust-storm
forms-at: 3.0
full-at: 7.0
opacity: 0.4
color: "C8A46E"
voxel-size: 16
voxel-height: 8
max-distance: 1024
thickness: 48
ground-clearance: 0

Dust starts a little above 3 metres per second of wind and is thickest from 7. The floor follows the highest ground in each weather cell, so the band floats over lower land. Add ground-grid: 64 to make this see-through band follow the local ground.

List the layer to draw it, in atmosphere.yml:

clouds:
  layers: [cumulus, dust]

Edit the layers line already under clouds (do not add a second clouds section), then run /ms reload.

To draw it in one world only, name the world under worlds and give it its own list:

worlds:
  red_planet:
    clouds:
      layers: [cumulus, dust]

Replaces the worlds: {} line of atmosphere.yml. Other worlds keep the list under clouds.

Updated Nov 11, 2026