diff --git a/core/src/main/java/com/volmit/iris/core/ServerConfigurator.java b/core/src/main/java/com/volmit/iris/core/ServerConfigurator.java index 6db180dbc..f9aaee49c 100644 --- a/core/src/main/java/com/volmit/iris/core/ServerConfigurator.java +++ b/core/src/main/java/com/volmit/iris/core/ServerConfigurator.java @@ -23,9 +23,7 @@ import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.core.nms.INMS; import com.volmit.iris.core.nms.datapack.DataVersion; import com.volmit.iris.core.nms.datapack.IDataFixer; -import com.volmit.iris.engine.object.IrisBiome; -import com.volmit.iris.engine.object.IrisBiomeCustom; -import com.volmit.iris.engine.object.IrisDimension; +import com.volmit.iris.engine.object.*; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.collection.KSet; @@ -296,7 +294,7 @@ public class ServerConfigurator { int logicalHeight = data.get(2); if (minY == Integer.MAX_VALUE || maxY == Integer.MIN_VALUE || Integer.MIN_VALUE == logicalHeight) return null; - return fixer.createDimension(dimension, minY, maxY, logicalHeight).toString(4); + return fixer.createDimension(dimension, minY, maxY - minY, logicalHeight, null).toString(4); } } } diff --git a/core/src/main/java/com/volmit/iris/core/nms/datapack/IDataFixer.java b/core/src/main/java/com/volmit/iris/core/nms/datapack/IDataFixer.java index 889acddff..0e8a706a3 100644 --- a/core/src/main/java/com/volmit/iris/core/nms/datapack/IDataFixer.java +++ b/core/src/main/java/com/volmit/iris/core/nms/datapack/IDataFixer.java @@ -1,21 +1,25 @@ package com.volmit.iris.core.nms.datapack; import com.volmit.iris.engine.object.IrisBiomeCustom; +import com.volmit.iris.engine.object.IrisDimensionTypeOptions; import com.volmit.iris.util.json.JSONObject; +import org.jetbrains.annotations.Nullable; public interface IDataFixer { - default JSONObject fixCustomBiome(IrisBiomeCustom biome, JSONObject json) { return json; } - JSONObject rawDimension(Dimension dimension); + JSONObject resolve(Dimension dimension, @Nullable IrisDimensionTypeOptions options); - default JSONObject createDimension(Dimension dimension, int minY, int maxY, int logicalHeight) { - JSONObject obj = rawDimension(dimension); + void fixDimension(Dimension dimension, JSONObject json); + + default JSONObject createDimension(Dimension base, int minY, int height, int logicalHeight, @Nullable IrisDimensionTypeOptions options) { + JSONObject obj = resolve(base, options); obj.put("min_y", minY); - obj.put("height", maxY - minY); + obj.put("height", height); obj.put("logical_height", logicalHeight); + fixDimension(base, obj); return obj; } diff --git a/core/src/main/java/com/volmit/iris/core/nms/datapack/v1192/DataFixerV1192.java b/core/src/main/java/com/volmit/iris/core/nms/datapack/v1192/DataFixerV1192.java index f2697cf38..a9bb59e0e 100644 --- a/core/src/main/java/com/volmit/iris/core/nms/datapack/v1192/DataFixerV1192.java +++ b/core/src/main/java/com/volmit/iris/core/nms/datapack/v1192/DataFixerV1192.java @@ -1,81 +1,104 @@ package com.volmit.iris.core.nms.datapack.v1192; import com.volmit.iris.core.nms.datapack.IDataFixer; +import com.volmit.iris.engine.object.IrisDimensionTypeOptions; import com.volmit.iris.util.json.JSONObject; +import org.jetbrains.annotations.Nullable; + import java.util.Map; +import static com.volmit.iris.engine.object.IrisDimensionTypeOptions.TriState.*; + public class DataFixerV1192 implements IDataFixer { + private static final Map OPTIONS = Map.of( + Dimension.OVERWORLD, new IrisDimensionTypeOptions( + FALSE, + TRUE, + FALSE, + FALSE, + TRUE, + TRUE, + TRUE, + FALSE, + 1d, + 0f, + null, + 192, + 0), + Dimension.NETHER, new IrisDimensionTypeOptions( + TRUE, + FALSE, + TRUE, + TRUE, + FALSE, + FALSE, + FALSE, + TRUE, + 8d, + 0.1f, + 18000L, + null, + 15), + Dimension.END, new IrisDimensionTypeOptions( + FALSE, + FALSE, + FALSE, + FALSE, + FALSE, + TRUE, + FALSE, + FALSE, + 1d, + 0f, + 6000L, + null, + 0) + ); private static final Map DIMENSIONS = Map.of( Dimension.OVERWORLD, """ { - "ambient_light": 0.0, - "bed_works": true, - "coordinate_scale": 1.0, "effects": "minecraft:overworld", - "has_ceiling": false, - "has_raids": true, - "has_skylight": true, "infiniburn": "#minecraft:infiniburn_overworld", - "monster_spawn_block_light_limit": 0, "monster_spawn_light_level": { "type": "minecraft:uniform", "value": { "max_inclusive": 7, "min_inclusive": 0 } - }, - "natural": true, - "piglin_safe": false, - "respawn_anchor_works": false, - "ultrawarm": false + } }""", Dimension.NETHER, """ { - "ambient_light": 0.1, - "bed_works": false, - "coordinate_scale": 8.0, "effects": "minecraft:the_nether", - "fixed_time": 18000, - "has_ceiling": true, - "has_raids": false, - "has_skylight": false, "infiniburn": "#minecraft:infiniburn_nether", - "monster_spawn_block_light_limit": 15, "monster_spawn_light_level": 7, - "natural": false, - "piglin_safe": true, - "respawn_anchor_works": true, - "ultrawarm": true }""", Dimension.END, """ { - "ambient_light": 0.0, - "bed_works": false, - "coordinate_scale": 1.0, "effects": "minecraft:the_end", - "fixed_time": 6000, - "has_ceiling": false, - "has_raids": true, - "has_skylight": false, "infiniburn": "#minecraft:infiniburn_end", - "monster_spawn_block_light_limit": 0, "monster_spawn_light_level": { "type": "minecraft:uniform", "value": { "max_inclusive": 7, "min_inclusive": 0 } - }, - "natural": false, - "piglin_safe": false, - "respawn_anchor_works": false, - "ultrawarm": false + } }""" ); @Override - public JSONObject rawDimension(Dimension dimension) { - return new JSONObject(DIMENSIONS.get(dimension)); + public JSONObject resolve(Dimension dimension, @Nullable IrisDimensionTypeOptions options) { + return options == null ? OPTIONS.get(dimension).toJson() : options.resolve(OPTIONS.get(dimension)).toJson(); + } + + @Override + public void fixDimension(Dimension dimension, JSONObject json) { + var missing = new JSONObject(DIMENSIONS.get(dimension)); + for (String key : missing.keySet()) { + if (json.has(key)) continue; + json.put(key, missing.get(key)); + } } } diff --git a/core/src/main/java/com/volmit/iris/core/nms/datapack/v1206/DataFixerV1206.java b/core/src/main/java/com/volmit/iris/core/nms/datapack/v1206/DataFixerV1206.java index 638f043b1..eb8b59c28 100644 --- a/core/src/main/java/com/volmit/iris/core/nms/datapack/v1206/DataFixerV1206.java +++ b/core/src/main/java/com/volmit/iris/core/nms/datapack/v1206/DataFixerV1206.java @@ -45,13 +45,12 @@ public class DataFixerV1206 extends DataFixerV1192 { } @Override - public JSONObject rawDimension(Dimension dimension) { - JSONObject json = super.rawDimension(dimension); + public void fixDimension(Dimension dimension, JSONObject json) { + super.fixDimension(dimension, json); if (!(json.get("monster_spawn_light_level") instanceof JSONObject lightLevel)) - return json; + return; var value = (JSONObject) lightLevel.remove("value"); lightLevel.put("max_inclusive", value.get("max_inclusive")); lightLevel.put("min_inclusive", value.get("min_inclusive")); - return json; } } diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisDimension.java b/core/src/main/java/com/volmit/iris/engine/object/IrisDimension.java index ba6b07717..7c1cb852c 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisDimension.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisDimension.java @@ -31,7 +31,6 @@ import com.volmit.iris.engine.object.annotations.*; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KSet; import com.volmit.iris.util.data.DataProvider; -import com.volmit.iris.util.data.Varint; import com.volmit.iris.util.io.IO; import com.volmit.iris.util.json.JSONObject; import com.volmit.iris.util.math.Position2; @@ -47,10 +46,7 @@ import org.bukkit.Material; import org.bukkit.World.Environment; import org.bukkit.block.data.BlockData; -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.File; -import java.io.IOException; +import java.io.*; @Accessors(chain = true) @AllArgsConstructor @@ -166,6 +162,8 @@ public class IrisDimension extends IrisRegistrant { private int fluidHeight = 63; @Desc("Define the min and max Y bounds of this dimension. Please keep in mind that Iris internally generates from 0 to (max - min). \n\nFor example at -64 to 320, Iris is internally generating to 0 to 384, then on outputting chunks, it shifts it down by the min height (64 blocks). The default is -64 to 320. \n\nThe fluid height is placed at (fluid height + min height). So a fluid height of 63 would actually show up in the world at 1.") private IrisRange dimensionHeight = new IrisRange(-64, 320); + @Desc("Define options for this dimension") + private IrisDimensionTypeOptions dimensionOptions = new IrisDimensionTypeOptions(); @RegistryListResource(IrisBiome.class) @Desc("Keep this either undefined or empty. Setting any biome name into this will force iris to only generate the specified biome. Great for testing.") private String focus = ""; @@ -415,21 +413,20 @@ public class IrisDimension extends IrisRegistrant { } public String getDimensionTypeKey() { - return getDimensionTypeKey(getBaseDimension(), getMinHeight(), getMaxHeight(), getLogicalHeight()); + return getDimensionType().key(); + } + + public IrisDimensionType getDimensionType() { + return new IrisDimensionType(getBaseDimension(), getDimensionOptions(), getLogicalHeight(), getMaxHeight() - getMinHeight(), getMinHeight()); } public void installDimensionType(IDataFixer fixer, KList folders) { - String key = getDimensionTypeKey(); - String json = fixer.createDimension( - getBaseDimension(), - getMinHeight(), - getMaxHeight(), - getLogicalHeight() - ).toString(4); + IrisDimensionType type = getDimensionType(); + String json = type.toJson(fixer); - Iris.verbose(" Installing Data Pack Dimension Type: \"iris:" + key + '"'); + Iris.verbose(" Installing Data Pack Dimension Type: \"iris:" + type.key() + '"'); for (File datapacks : folders) { - File output = new File(datapacks, "iris/data/iris/dimension_type/" + key + ".json"); + File output = new File(datapacks, "iris/data/iris/dimension_type/" + type.key() + ".json"); output.getParentFile().mkdirs(); try { IO.writeAll(output, json); @@ -485,20 +482,6 @@ public class IrisDimension extends IrisRegistrant { } } - public static String getDimensionTypeKey(Dimension dimension, int minY, int maxY, int logicalHeight) { - var stream = new ByteArrayOutputStream(13); - try (var dos = new DataOutputStream(stream)) { - dos.writeByte(dimension.ordinal()); - Varint.writeUnsignedVarInt(logicalHeight, dos); - Varint.writeUnsignedVarInt(maxY - minY, dos); - Varint.writeSignedVarInt(minY, dos); - } catch (IOException e) { - throw new RuntimeException("This is impossible", e); - } - - return IO.encode(stream.toByteArray()).replace("=", ".").toLowerCase(); - } - private static void write(File datapacks, String type, String json) { if (json == null) return; File dimTypeVanilla = new File(datapacks, "iris/data/minecraft/dimension_type/" + type + ".json"); diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisDimensionType.java b/core/src/main/java/com/volmit/iris/engine/object/IrisDimensionType.java new file mode 100644 index 000000000..756e4c538 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisDimensionType.java @@ -0,0 +1,91 @@ +package com.volmit.iris.engine.object; + +import com.volmit.iris.core.nms.datapack.IDataFixer; +import com.volmit.iris.util.data.Varint; +import com.volmit.iris.util.io.IO; +import lombok.*; +import lombok.experimental.Accessors; + +import java.io.*; + +@Getter +@ToString +@Accessors(fluent = true, chain = true) +@EqualsAndHashCode +public final class IrisDimensionType { + @NonNull + private final String key; + @NonNull + private final IDataFixer.Dimension base; + @NonNull + private final IrisDimensionTypeOptions options; + private final int logicalHeight; + private final int height; + private final int minY; + + public IrisDimensionType( + @NonNull IDataFixer.Dimension base, + @NonNull IrisDimensionTypeOptions options, + int logicalHeight, + int height, + int minY + ) { + if (logicalHeight > height) throw new IllegalArgumentException("Logical height cannot be greater than height"); + if (logicalHeight < 0) throw new IllegalArgumentException("Logical height cannot be less than zero"); + if (height < 16 || height > 4064 ) throw new IllegalArgumentException("Height must be between 16 and 4064"); + if ((height & 15) != 0) throw new IllegalArgumentException("Height must be a multiple of 16"); + if (minY < -2032 || minY > 2031) throw new IllegalArgumentException("Min Y must be between -2032 and 2031"); + if ((minY & 15) != 0) throw new IllegalArgumentException("Min Y must be a multiple of 16"); + + this.base = base; + this.options = options; + this.logicalHeight = logicalHeight; + this.height = height; + this.minY = minY; + this.key = createKey(); + } + + public static IrisDimensionType fromKey(String key) { + var stream = new ByteArrayInputStream(IO.decode(key.replace(".", "=").toUpperCase())); + try (var din = new DataInputStream(stream)) { + return new IrisDimensionType( + IDataFixer.Dimension.values()[din.readUnsignedByte()], + new IrisDimensionTypeOptions().read(din), + Varint.readUnsignedVarInt(din), + Varint.readUnsignedVarInt(din), + Varint.readSignedVarInt(din) + ); + } catch (IOException e) { + throw new RuntimeException("This is impossible", e); + } + } + + public String toJson(IDataFixer fixer) { + return fixer.createDimension( + base, + minY, + height, + logicalHeight, + options.copy() + ).toString(4); + } + + private String createKey() { + var stream = new ByteArrayOutputStream(41); + try (var dos = new DataOutputStream(stream)) { + dos.writeByte(base.ordinal()); + options.write(dos); + Varint.writeUnsignedVarInt(logicalHeight, dos); + Varint.writeUnsignedVarInt(height, dos); + Varint.writeSignedVarInt(minY, dos); + } catch (IOException e) { + throw new RuntimeException("This is impossible", e); + } + + return IO.encode(stream.toByteArray()).replace("=", ".").toLowerCase(); + } + + public IrisDimensionTypeOptions options() { + return options.copy(); + } +} diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisDimensionTypeOptions.java b/core/src/main/java/com/volmit/iris/engine/object/IrisDimensionTypeOptions.java new file mode 100644 index 000000000..139d6296d --- /dev/null +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisDimensionTypeOptions.java @@ -0,0 +1,320 @@ +package com.volmit.iris.engine.object; + +import com.volmit.iris.engine.object.annotations.Desc; +import com.volmit.iris.engine.object.annotations.MaxNumber; +import com.volmit.iris.engine.object.annotations.MinNumber; +import com.volmit.iris.util.data.Varint; +import com.volmit.iris.util.json.JSONObject; +import lombok.*; +import lombok.experimental.Accessors; +import org.jetbrains.annotations.Nullable; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.Arrays; + +import static com.volmit.iris.engine.object.IrisDimensionTypeOptions.TriState.*; + +@Data +@NoArgsConstructor +@Accessors(fluent = true, chain = true) +@Desc("Optional options for the dimension type") +public class IrisDimensionTypeOptions { + @MinNumber(0.00001) + @MaxNumber(30000000) + @Desc("The multiplier applied to coordinates when leaving the dimension. Value between 0.00001 and 30000000.0 (both inclusive).") + private double coordinateScale = -1; + @MinNumber(0) + @MaxNumber(1) + @Desc("How much light the dimension has. When set to 0, it completely follows the light level; when set to 1, there is no ambient lighting.") + private float ambientLight = -1; + @Nullable + @MinNumber(0) + @MaxNumber(Long.MAX_VALUE) + @Desc("If this is set to an int, the time of the day is the specified value. To ensure a normal time cycle, set it to null.") + private Long fixedTime = -1L; + @Nullable + @MinNumber(-2032) + @MaxNumber(2031) + @Desc("Optional value between -2032 and 2031. If set, determines the lower edge of the clouds. If set to null, clouds are disabled in the dimension.") + private Integer cloudHeight = -1; + @MinNumber(0) + @MaxNumber(15) + @Desc("Value between 0 and 15 (both inclusive). Maximum block light required when the monster spawns.") + private int monsterSpawnBlockLightLimit = -1; + + @NonNull + @Desc("Whether the dimensions behaves like the nether (water evaporates and sponges dry) or not. Also lets stalactites drip lava and causes lava to spread faster and thinner.") + private TriState ultrawarm = DEFAULT; + @NonNull + @Desc("When false, compasses spin randomly, and using a bed to set the respawn point or sleep, is disabled. When true, nether portals can spawn zombified piglins, and creaking hearts can spawn creakings.") + private TriState natural = DEFAULT; + @NonNull + @Desc("When false, Piglins and hoglins shake and transform to zombified entities.") + private TriState piglinSafe = DEFAULT; + @NonNull + @Desc("When false, the respawn anchor blows up when trying to set spawn point.") + private TriState respawnAnchorWorks = DEFAULT; + @NonNull + @Desc("When false, the bed blows up when trying to sleep.") + private TriState bedWorks = DEFAULT; + @NonNull + @Desc("Whether players with the Bad Omen effect can cause a raid.") + private TriState raids = DEFAULT; + @NonNull + @Desc("Whether the dimension has skylight or not.") + private TriState skylight = DEFAULT; + @NonNull + @Desc("Whether the dimension has a bedrock ceiling. Note that this is only a logical ceiling. It is unrelated with whether the dimension really has a block ceiling.") + private TriState ceiling = DEFAULT; + + public IrisDimensionTypeOptions( + @NonNull TriState ultrawarm, + @NonNull TriState natural, + @NonNull TriState piglinSafe, + @NonNull TriState respawnAnchorWorks, + @NonNull TriState bedWorks, + @NonNull TriState raids, + @NonNull TriState skylight, + @NonNull TriState ceiling, + double coordinateScale, + float ambientLight, + @Nullable Long fixedTime, + @Nullable Integer cloudHeight, + int monsterSpawnBlockLightLimit + ) { + if (coordinateScale != -1 && (coordinateScale < 0.00001 || coordinateScale > 30000000)) + throw new IllegalArgumentException("Coordinate scale must be between 0.00001 and 30000000"); + if (ambientLight != -1 && (ambientLight < 0 || ambientLight > 1)) + throw new IllegalArgumentException("Ambient light must be between 0 and 1"); + if (cloudHeight != null && cloudHeight != -1 && (cloudHeight < -2032 || cloudHeight > 2031)) + throw new IllegalArgumentException("Cloud height must be between -2032 and 2031"); + if (monsterSpawnBlockLightLimit != -1 && (monsterSpawnBlockLightLimit < 0 || monsterSpawnBlockLightLimit > 15)) + throw new IllegalArgumentException("Monster spawn block light limit must be between 0 and 15"); + + this.ultrawarm = ultrawarm; + this.natural = natural; + this.piglinSafe = piglinSafe; + this.respawnAnchorWorks = respawnAnchorWorks; + this.bedWorks = bedWorks; + this.raids = raids; + this.skylight = skylight; + this.ceiling = ceiling; + this.coordinateScale = coordinateScale; + this.ambientLight = ambientLight; + this.fixedTime = fixedTime; + this.cloudHeight = cloudHeight; + this.monsterSpawnBlockLightLimit = monsterSpawnBlockLightLimit; + } + + public IrisDimensionTypeOptions coordinateScale(double coordinateScale) { + if (coordinateScale != -1 && (coordinateScale < 0.00001 || coordinateScale > 30000000)) + throw new IllegalArgumentException("Coordinate scale must be between 0.00001 and 30000000"); + this.coordinateScale = coordinateScale; + return this; + } + + public IrisDimensionTypeOptions ambientLight(float ambientLight) { + if (ambientLight != -1 && (ambientLight < 0 || ambientLight > 1)) + throw new IllegalArgumentException("Ambient light must be between 0 and 1"); + this.ambientLight = ambientLight; + return this; + } + + public IrisDimensionTypeOptions cloudHeight(@Nullable Integer cloudHeight) { + if (cloudHeight != null && cloudHeight != -1 && (cloudHeight < -2032 || cloudHeight > 2031)) + throw new IllegalArgumentException("Cloud height must be between -2032 and 2031"); + this.cloudHeight = cloudHeight; + return this; + } + + public IrisDimensionTypeOptions monsterSpawnBlockLightLimit(int monsterSpawnBlockLightLimit) { + if (monsterSpawnBlockLightLimit != -1 && (monsterSpawnBlockLightLimit < 0 || monsterSpawnBlockLightLimit > 15)) + throw new IllegalArgumentException("Monster spawn block light limit must be between 0 and 15"); + this.monsterSpawnBlockLightLimit = monsterSpawnBlockLightLimit; + return this; + } + + public void write(DataOutput dos) throws IOException { + int bits = 0; + int index = 0; + + for (TriState state : new TriState[]{ + ultrawarm, + natural, + skylight, + ceiling, + piglinSafe, + bedWorks, + respawnAnchorWorks, + raids + }) { + if (state == DEFAULT) { + index++; + continue; + } + + bits |= (short) (1 << index++); + if (state == TRUE) + bits |= (short) (1 << index++); + } + + if (coordinateScale != -1) + bits |= (1 << index++); + if (ambientLight != -1) + bits |= (1 << index++); + if (monsterSpawnBlockLightLimit != -1) + bits |= (1 << index++); + if (fixedTime != null) { + bits |= (1 << index++); + if (fixedTime != -1L) + bits |= (1 << index++); + } + if (cloudHeight != null) { + bits |= (1 << index++); + if (cloudHeight != -1) + bits |= (1 << index); + } + + Varint.writeSignedVarInt(bits, dos); + + if (coordinateScale != -1) + Varint.writeUnsignedVarLong(Double.doubleToLongBits(coordinateScale), dos); + if (ambientLight != -1) + Varint.writeUnsignedVarInt(Float.floatToIntBits(ambientLight), dos); + if (monsterSpawnBlockLightLimit != -1) + Varint.writeSignedVarInt(monsterSpawnBlockLightLimit, dos); + if (fixedTime != null && fixedTime != -1L) + Varint.writeSignedVarLong(fixedTime, dos); + if (cloudHeight != null && cloudHeight != -1) + Varint.writeSignedVarInt(cloudHeight, dos); + } + + public IrisDimensionTypeOptions read(DataInput dis) throws IOException { + TriState[] states = new TriState[8]; + Arrays.fill(states, DEFAULT); + + int bits = Varint.readSignedVarInt(dis); + int index = 0; + + for (int i = 0; i < 8; i++) { + if ((bits & (1 << index++)) == 0) + continue; + states[i] = (bits & (1 << index++)) == 0 ? FALSE : TRUE; + } + ultrawarm = states[0]; + natural = states[1]; + skylight = states[2]; + ceiling = states[3]; + piglinSafe = states[4]; + bedWorks = states[5]; + respawnAnchorWorks = states[6]; + raids = states[7]; + + coordinateScale = (bits & (1 << index++)) != 0 ? Double.longBitsToDouble(Varint.readUnsignedVarLong(dis)) : -1; + ambientLight = (bits & (1 << index++)) != 0 ? Float.intBitsToFloat(Varint.readUnsignedVarInt(dis)) : -1; + monsterSpawnBlockLightLimit = (bits & (1 << index++)) != 0 ? Varint.readSignedVarInt(dis) : -1; + fixedTime = (bits & (1 << index++)) != 0 ? (bits & (1 << index)) != 0 ? Varint.readSignedVarLong(dis) : -1L : null; + cloudHeight = (bits & (1 << index++)) != 0 ? (bits & (1 << index)) != 0 ? Varint.readSignedVarInt(dis) : -1 : null; + + return this; + } + + public IrisDimensionTypeOptions resolve(IrisDimensionTypeOptions other) { + if (ultrawarm == DEFAULT) + ultrawarm = other.ultrawarm; + if (natural == DEFAULT) + natural = other.natural; + if (piglinSafe == DEFAULT) + piglinSafe = other.piglinSafe; + if (respawnAnchorWorks == DEFAULT) + respawnAnchorWorks = other.respawnAnchorWorks; + if (bedWorks == DEFAULT) + bedWorks = other.bedWorks; + if (raids == DEFAULT) + raids = other.raids; + if (skylight == DEFAULT) + skylight = other.skylight; + if (ceiling == DEFAULT) + ceiling = other.ceiling; + if (coordinateScale == -1) + coordinateScale = other.coordinateScale; + if (ambientLight == -1) + ambientLight = other.ambientLight; + if (fixedTime != null && fixedTime == -1L) + fixedTime = other.fixedTime; + if (cloudHeight != null && cloudHeight == -1) + cloudHeight = other.cloudHeight; + if (monsterSpawnBlockLightLimit == -1) + monsterSpawnBlockLightLimit = other.monsterSpawnBlockLightLimit; + return this; + } + + public JSONObject toJson() { + if (!isComplete()) throw new IllegalStateException("Cannot serialize incomplete options"); + JSONObject json = new JSONObject(); + json.put("ultrawarm", ultrawarm.bool()); + json.put("natural", natural.bool()); + json.put("piglin_safe", piglinSafe.bool()); + json.put("respawn_anchor_works", respawnAnchorWorks.bool()); + json.put("bed_works", bedWorks.bool()); + json.put("has_raids", raids.bool()); + json.put("has_skylight", skylight.bool()); + json.put("has_ceiling", ceiling.bool()); + json.put("coordinate_scale", coordinateScale); + json.put("ambient_light", ambientLight); + json.put("monster_spawn_block_light_limit", monsterSpawnBlockLightLimit); + if (fixedTime != null) json.put("fixed_time", fixedTime); + if (cloudHeight != null) json.put("cloud_height", cloudHeight); + return json; + } + + public IrisDimensionTypeOptions copy() { + return new IrisDimensionTypeOptions( + ultrawarm, + natural, + piglinSafe, + respawnAnchorWorks, + bedWorks, + raids, + skylight, + ceiling, + coordinateScale, + ambientLight, + fixedTime, + cloudHeight, + monsterSpawnBlockLightLimit + ); + } + + public boolean isComplete() { + return ultrawarm != DEFAULT + && natural != DEFAULT + && piglinSafe != DEFAULT + && respawnAnchorWorks != DEFAULT + && bedWorks != DEFAULT + && raids != DEFAULT + && skylight != DEFAULT + && ceiling != DEFAULT + && coordinateScale != -1 + && ambientLight != -1 + && monsterSpawnBlockLightLimit != -1 + && (fixedTime == null || fixedTime != -1L) + && (cloudHeight == null || cloudHeight != -1); + } + + @Desc("Allows reusing the behavior of the base dimension") + public enum TriState { + @Desc("Follow the behavior of the base dimension") + DEFAULT, + @Desc("True") + TRUE, + @Desc("False") + FALSE; + + public boolean bool() { + return this == TRUE; + } + } +}