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
typevalue also ignores surrounding spaces. - A layer file without
typeis skipped withcloud-layers/<file>.yml has no type, skipping it. - A layer is drawn only while its id is in a
clouds.layerslist. - 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:
tof 0 draws no cloud, and the first wisps appear a little above it.tof 1 draws solid cloud, and so does everything abovefull-at.- With
full-atat or belowforms-at, the layer is fully on fromforms-atand draws nothing below it. NaNdraws 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
densityruns 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 ofclouds.layers(the first withopacity: 1.0) it runs on the thread that asks whether a location is in a cloud: theincloudtest 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 theCloudSampleand your own data that is safe from any thread. densitymust be pure: no side effects, and the same value for the same arguments and weather. Otherwiseinclouddisagrees with what players see.densitymust be fast, a few arithmetic operations andCloudSamplereads. It runs at every point the renderer tests, even when it ignoresy, and on the main thread for in-cloud checks.- MythicSeasons catches a
RuntimeExceptionor aLinkageErrorfromdensity, no other errors. The layer then reads 0 everywhere and the console printscloud layer '<layer>' (type <type>) failed and draws nothing: <error>once until the next/ms reload. A 0 still draws cloud with a negativeforms-at, or with afull-atat or below aforms-atof 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()fromonEnableandunregister()fromonDisable.
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: 64to make this see-through band follow the local ground.
List the layer to draw it, in atmosphere.yml:
clouds:
layers: [cumulus, dust]
Edit the
layersline already underclouds(do not add a secondcloudssection), 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 ofatmosphere.yml. Other worlds keep the list underclouds.