From aa14242b54937ecea550b55af7d51164004e95a2 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sun, 7 Sep 2025 16:50:23 +0200 Subject: [PATCH 01/21] another mantle fix --- core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java b/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java index 01153403c..7208b6fb9 100644 --- a/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java +++ b/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java @@ -266,7 +266,7 @@ public class MantleChunk { dos.writeByte(x); dos.writeByte(z); dos.writeByte(sections.length()); - Varint.writeUnsignedVarInt(Math.ceilDiv(flags.length(), Byte.SIZE), dos); + Varint.writeUnsignedVarInt(flags.length(), dos); int count = flags.length(); for (int i = 0; i < count;) { From a8ee321eb88a42be795f67e36aab802ba40886a2 Mon Sep 17 00:00:00 2001 From: Julian Krings <47589149+CrazyDev05@users.noreply.github.com> Date: Sun, 7 Sep 2025 18:36:08 +0200 Subject: [PATCH 02/21] Allow placing datapack structures in Iris worlds (#1225) * Allow placing datapack structures in Iris worlds * command for generating configs for datapack structures * remove the sub dir from the snippet key --- core/build.gradle.kts | 1 + .../iris/core/commands/CommandDeveloper.java | 137 +++++++++++++ .../com/volmit/iris/core/loader/IrisData.java | 26 +-- .../iris/core/loader/IrisRegistrant.java | 2 + .../com/volmit/iris/core/nms/INMSBinding.java | 8 + .../nms/container/StructurePlacement.java | 77 +++++++ .../iris/core/nms/v1X/NMSBinding1X.java | 13 ++ .../components/MantleJigsawComponent.java | 1 + .../engine/object/IrisJigsawPlacement.java | 43 ---- .../engine/object/IrisJigsawStructure.java | 12 +- .../engine/object/IrisStructurePopulator.java | 74 +++++++ .../functions/StructureKeyFunction.java | 2 +- .../functions/StructureKeyOrTagFunction.java | 23 +++ .../engine/platform/BukkitChunkGenerator.java | 1 + .../com/volmit/iris/util/collection/KSet.java | 20 +- .../NullableDimensionHandler.java | 88 ++++++++ gradle/libs.versions.toml | 2 + .../core/nms/v1_20_R1/IrisChunkGenerator.java | 192 +++++++++++++++-- .../iris/core/nms/v1_20_R1/NMSBinding.java | 72 ++++++- .../core/nms/v1_20_R2/IrisChunkGenerator.java | 192 +++++++++++++++-- .../iris/core/nms/v1_20_R2/NMSBinding.java | 86 ++++++++ .../core/nms/v1_20_R3/IrisChunkGenerator.java | 192 +++++++++++++++-- .../iris/core/nms/v1_20_R3/NMSBinding.java | 86 ++++++++ .../core/nms/v1_20_R4/IrisChunkGenerator.java | 193 ++++++++++++++++-- .../iris/core/nms/v1_20_R4/NMSBinding.java | 82 ++++++++ .../core/nms/v1_21_R1/IrisChunkGenerator.java | 193 ++++++++++++++++-- .../iris/core/nms/v1_21_R1/NMSBinding.java | 86 ++++++++ .../core/nms/v1_21_R2/IrisChunkGenerator.java | 192 +++++++++++++++-- .../iris/core/nms/v1_21_R2/NMSBinding.java | 83 ++++++++ .../core/nms/v1_21_R3/IrisChunkGenerator.java | 189 ++++++++++++++--- .../iris/core/nms/v1_21_R3/NMSBinding.java | 95 ++++++++- .../core/nms/v1_21_R4/IrisChunkGenerator.java | 188 ++++++++++++++--- .../iris/core/nms/v1_21_R4/NMSBinding.java | 72 +++++++ .../core/nms/v1_21_R5/IrisChunkGenerator.java | 188 ++++++++++++++--- .../iris/core/nms/v1_21_R5/NMSBinding.java | 72 +++++++ 35 files changed, 2718 insertions(+), 265 deletions(-) create mode 100644 core/src/main/java/com/volmit/iris/core/nms/container/StructurePlacement.java delete mode 100644 core/src/main/java/com/volmit/iris/engine/object/IrisJigsawPlacement.java create mode 100644 core/src/main/java/com/volmit/iris/engine/object/IrisStructurePopulator.java create mode 100644 core/src/main/java/com/volmit/iris/engine/object/annotations/functions/StructureKeyOrTagFunction.java create mode 100644 core/src/main/java/com/volmit/iris/util/decree/specialhandlers/NullableDimensionHandler.java diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 8a3a6b97e..a7829f55f 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -83,6 +83,7 @@ dependencies { slim(libs.commons.io) slim(libs.commons.lang) slim(libs.commons.lang3) + slim(libs.commons.math3) slim(libs.oshi) slim(libs.lz4) slim(libs.fastutil) diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java b/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java index 06b961c13..d1c1f169a 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java @@ -18,19 +18,29 @@ package com.volmit.iris.core.commands; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.volmit.iris.Iris; import com.volmit.iris.core.ServerConfigurator; +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.service.IrisEngineSVC; import com.volmit.iris.core.tools.IrisPackBenchmarking; import com.volmit.iris.core.tools.IrisToolbelt; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.object.IrisDimension; +import com.volmit.iris.engine.object.annotations.Snippet; +import com.volmit.iris.util.collection.KSet; import com.volmit.iris.util.context.IrisContext; +import com.volmit.iris.engine.object.IrisJigsawStructurePlacement; +import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.decree.DecreeExecutor; import com.volmit.iris.util.decree.DecreeOrigin; import com.volmit.iris.util.decree.annotations.Decree; import com.volmit.iris.util.decree.annotations.Param; +import com.volmit.iris.util.decree.specialhandlers.NullableDimensionHandler; import com.volmit.iris.util.format.C; import com.volmit.iris.util.format.Form; import com.volmit.iris.util.io.CountingDataInputStream; @@ -42,6 +52,7 @@ import com.volmit.iris.util.nbt.mca.MCAFile; import com.volmit.iris.util.nbt.mca.MCAUtil; import com.volmit.iris.util.parallel.MultiBurst; import com.volmit.iris.util.plugin.VolmitSender; +import lombok.SneakyThrows; import net.jpountz.lz4.LZ4BlockInputStream; import net.jpountz.lz4.LZ4BlockOutputStream; import net.jpountz.lz4.LZ4FrameInputStream; @@ -59,6 +70,8 @@ import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; +import java.util.stream.Stream; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; @@ -143,6 +156,130 @@ public class CommandDeveloper implements DecreeExecutor { } } + @SneakyThrows + @Decree(description = "Generate Iris structures for all loaded datapack structures") + public void generateStructures( + @Param(description = "The pack to add the generated structures to", aliases = "pack", defaultValue = "---", customHandler = NullableDimensionHandler.class) + IrisDimension dimension, + @Param(description = "Ignore existing structures", defaultValue = "false") + boolean force + ) { + var map = INMS.get().collectStructures(); + if (map.isEmpty()) { + sender().sendMessage(C.IRIS + "No structures found"); + return; + } + + sender().sendMessage(C.IRIS + "Found " + map.size() + " structures"); + + final File dataDir; + final IrisData data; + final Set existingStructures; + final Map> snippets; + final File dimensionFile; + final File structuresFolder; + final File snippetsFolder; + + var dimensionObj = new JsonObject(); + + if (dimension == null) { + dataDir = Iris.instance.getDataFolder("structures"); + IO.delete(dataDir); + data = IrisData.get(dataDir); + existingStructures = Set.of(); + snippets = Map.of(); + dimensionFile = new File(dataDir, "structures.json"); + } else { + data = dimension.getLoader(); + dataDir = data.getDataFolder(); + existingStructures = new KSet<>(data.getJigsawStructureLoader().getPossibleKeys()); + + dimensionObj = data.getGson().fromJson(IO.readAll(dimension.getLoadFile()), JsonObject.class); + snippets = Optional.ofNullable(dimensionObj.getAsJsonArray("jigsawStructures")) + .map(array -> array.asList() + .stream() + .filter(JsonElement::isJsonPrimitive) + .collect(Collectors.toMap(element -> data.getGson() + .fromJson(element, IrisJigsawStructurePlacement.class) + .getStructure(), + element -> Set.of(element.getAsString()), + KSet::merge))) + .orElse(Map.of()); + + dimensionFile = dimension.getLoadFile(); + } + structuresFolder = new File(dataDir, "jigsaw-structures"); + snippetsFolder = new File(dataDir, "snippet" + "/" + IrisJigsawStructurePlacement.class.getAnnotation(Snippet.class).value()); + + var gson = data.getGson(); + var jigsawStructures = Optional.ofNullable(dimensionObj.getAsJsonArray("jigsawStructures")) + .orElse(new JsonArray(map.size())); + + map.forEach((key, placement) -> { + String loadKey = "datapack/" + key.namespace() + "/" + key.key(); + if (existingStructures.contains(loadKey) && !force) + return; + + var structures = placement.structures(); + var obj = placement.toJson(loadKey); + if (obj == null || structures.isEmpty()) { + sender().sendMessage(C.RED + "Failed to generate hook for " + key); + return; + } + File snippetFile = new File(snippetsFolder, loadKey + ".json"); + try { + IO.writeAll(snippetFile, gson.toJson(obj)); + } catch (IOException e) { + sender().sendMessage(C.RED + "Failed to generate snippet for " + key); + e.printStackTrace(); + return; + } + + Set loadKeys = snippets.getOrDefault(loadKey, Set.of(loadKey)); + jigsawStructures.asList().removeIf(e -> loadKeys.contains((e.isJsonObject() ? e.getAsJsonObject().get("structure") : e).getAsString())); + jigsawStructures.add("snippet/" + loadKey); + + String structureKey; + if (structures.size() > 1) { + KList common = new KList<>(); + for (int i = 0; i < structures.size(); i++) { + var tags = structures.get(i).tags(); + if (i == 0) common.addAll(tags); + else common.removeIf(tag -> !tags.contains(tag)); + } + structureKey = common.isNotEmpty() ? "#" + common.getFirst() : structures.getFirst().key(); + } else structureKey = structures.getFirst().key(); + + JsonArray array = new JsonArray(); + if (structures.size() > 1) { + structures.stream() + .flatMap(structure -> { + String[] arr = new String[structure.weight()]; + Arrays.fill(arr, structure.key()); + return Arrays.stream(arr); + }) + .forEach(array::add); + } else array.add(structureKey); + + obj = new JsonObject(); + obj.addProperty("structureKey", structureKey); + obj.add("datapackStructures", array); + + File out = new File(structuresFolder, loadKey + ".json"); + out.getParentFile().mkdirs(); + try { + IO.writeAll(out, gson.toJson(obj)); + } catch (IOException e) { + e.printStackTrace(); + } + }); + + dimensionObj.add("jigsawStructures", jigsawStructures); + IO.writeAll(dimensionFile, gson.toJson(dimensionObj)); + + data.hotloaded(); + } + @Decree(description = "Test") public void packBenchmark( @Param(description = "The pack to bench", aliases = {"pack"}, defaultValue = "overworld") diff --git a/core/src/main/java/com/volmit/iris/core/loader/IrisData.java b/core/src/main/java/com/volmit/iris/core/loader/IrisData.java index 1bfcd1ba2..9ad5e12ea 100644 --- a/core/src/main/java/com/volmit/iris/core/loader/IrisData.java +++ b/core/src/main/java/com/volmit/iris/core/loader/IrisData.java @@ -425,6 +425,7 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { } String snippetType = typeToken.getRawType().getDeclaredAnnotation(Snippet.class).value(); + String snippedBase = "snippet/" + snippetType + "/"; return new TypeAdapter<>() { @Override @@ -438,19 +439,20 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { if (reader.peek().equals(JsonToken.STRING)) { String r = reader.nextString(); + if (!r.startsWith("snippet/")) + return null; + if (!r.startsWith(snippedBase)) + r = snippedBase + r.substring(8); - if (r.startsWith("snippet/" + snippetType + "/")) { - File f = new File(getDataFolder(), r + ".json"); - - if (f.exists()) { - try (JsonReader snippetReader = new JsonReader(new FileReader(f))){ - return adapter.read(snippetReader); - } catch (Throwable e) { - Iris.error("Couldn't read snippet " + r + " in " + reader.getPath() + " (" + e.getMessage() + ")"); - } - } else { - Iris.error("Couldn't find snippet " + r + " in " + reader.getPath()); + File f = new File(getDataFolder(), r + ".json"); + if (f.exists()) { + try (JsonReader snippetReader = new JsonReader(new FileReader(f))){ + return adapter.read(snippetReader); + } catch (Throwable e) { + Iris.error("Couldn't read snippet " + r + " in " + reader.getPath() + " (" + e.getMessage() + ")"); } + } else { + Iris.error("Couldn't find snippet " + r + " in " + reader.getPath()); } return null; @@ -488,7 +490,7 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { .map(s -> s.substring(absPath.length() + 1)) .map(s -> s.replace("\\", "/")) .map(s -> s.split("\\Q.\\E")[0]) - .forEach(s -> l.add("snippet/" + f + "/" + s)); + .forEach(s -> l.add("snippet/" + s)); } catch (Throwable e) { e.printStackTrace(); } diff --git a/core/src/main/java/com/volmit/iris/core/loader/IrisRegistrant.java b/core/src/main/java/com/volmit/iris/core/loader/IrisRegistrant.java index eabba9d1c..8dd218916 100644 --- a/core/src/main/java/com/volmit/iris/core/loader/IrisRegistrant.java +++ b/core/src/main/java/com/volmit/iris/core/loader/IrisRegistrant.java @@ -28,6 +28,7 @@ import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.json.JSONObject; import com.volmit.iris.util.plugin.VolmitSender; import lombok.Data; +import lombok.EqualsAndHashCode; import java.awt.*; import java.io.File; @@ -39,6 +40,7 @@ public abstract class IrisRegistrant { @ArrayType(min = 1, type = String.class) private KList preprocessors = new KList<>(); + @EqualsAndHashCode.Exclude private transient IrisData loader; private transient String loadKey; diff --git a/core/src/main/java/com/volmit/iris/core/nms/INMSBinding.java b/core/src/main/java/com/volmit/iris/core/nms/INMSBinding.java index da039f6e9..dc8e23e22 100644 --- a/core/src/main/java/com/volmit/iris/core/nms/INMSBinding.java +++ b/core/src/main/java/com/volmit/iris/core/nms/INMSBinding.java @@ -18,8 +18,12 @@ package com.volmit.iris.core.nms; +import com.volmit.iris.core.link.Identifier; +import com.volmit.iris.core.nms.container.AutoClosing; import com.volmit.iris.core.nms.container.BiomeColor; import com.volmit.iris.core.nms.container.BlockProperty; +import com.volmit.iris.core.nms.container.Pair; +import com.volmit.iris.core.nms.container.StructurePlacement; import com.volmit.iris.core.nms.datapack.DataVersion; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.platform.PlatformChunkGenerator; @@ -136,4 +140,8 @@ public interface INMSBinding { } KMap> getBlockProperties(); + + void placeStructures(Chunk chunk); + + KMap collectStructures(); } diff --git a/core/src/main/java/com/volmit/iris/core/nms/container/StructurePlacement.java b/core/src/main/java/com/volmit/iris/core/nms/container/StructurePlacement.java new file mode 100644 index 000000000..173251ddc --- /dev/null +++ b/core/src/main/java/com/volmit/iris/core/nms/container/StructurePlacement.java @@ -0,0 +1,77 @@ +package com.volmit.iris.core.nms.container; + +import com.google.gson.JsonObject; +import com.volmit.iris.engine.object.IrisJigsawStructurePlacement.SpreadType; +import lombok.*; +import lombok.experimental.Accessors; +import lombok.experimental.SuperBuilder; +import org.apache.commons.math3.fraction.Fraction; + +import java.util.List; + +@Data +@SuperBuilder +@Accessors(fluent = true, chain = true) +public abstract class StructurePlacement { + private final int salt; + private final float frequency; + private final List structures; + + public abstract JsonObject toJson(String structure); + + protected JsonObject createBase(String structure) { + JsonObject object = new JsonObject(); + object.addProperty("structure", structure); + object.addProperty("salt", salt); + return object; + } + + public int frequencyToSpacing() { + var frac = new Fraction(Math.max(Math.min(frequency, 1), 0.000000001f)); + return (int) Math.round(Math.sqrt((double) frac.getDenominator() / frac.getNumerator())); + } + + @Getter + @Accessors(chain = true, fluent = true) + @EqualsAndHashCode(callSuper = true) + @SuperBuilder + public static class RandomSpread extends StructurePlacement { + private final int spacing; + private final int separation; + private final SpreadType spreadType; + + @Override + public JsonObject toJson(String structure) { + JsonObject object = createBase(structure); + object.addProperty("spacing", Math.max(spacing, frequencyToSpacing())); + object.addProperty("separation", separation); + object.addProperty("spreadType", spreadType.name()); + return object; + } + } + + @Getter + @EqualsAndHashCode(callSuper = true) + @SuperBuilder + public static class ConcentricRings extends StructurePlacement { + private final int distance; + private final int spread; + private final int count; + + @Override + public JsonObject toJson(String structure) { + return null; + } + } + + public record Structure( + int weight, + String key, + List tags + ) { + + public boolean isValid() { + return weight > 0 && key != null; + } + } +} diff --git a/core/src/main/java/com/volmit/iris/core/nms/v1X/NMSBinding1X.java b/core/src/main/java/com/volmit/iris/core/nms/v1X/NMSBinding1X.java index 4c13db4f4..40835b851 100644 --- a/core/src/main/java/com/volmit/iris/core/nms/v1X/NMSBinding1X.java +++ b/core/src/main/java/com/volmit/iris/core/nms/v1X/NMSBinding1X.java @@ -19,10 +19,13 @@ package com.volmit.iris.core.nms.v1X; import com.volmit.iris.Iris; +import com.volmit.iris.core.link.Identifier; import com.volmit.iris.core.nms.INMSBinding; import com.volmit.iris.core.nms.container.BiomeColor; import com.volmit.iris.core.nms.container.BlockProperty; import com.volmit.iris.core.nms.datapack.DataVersion; +import com.volmit.iris.core.nms.container.Pair; +import com.volmit.iris.core.nms.container.StructurePlacement; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; @@ -135,6 +138,16 @@ public class NMSBinding1X implements INMSBinding { return map; } + @Override + public void placeStructures(Chunk chunk) { + + } + + @Override + public KMap collectStructures() { + return new KMap<>(); + } + @Override public CompoundTag serializeEntity(Entity location) { return null; diff --git a/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleJigsawComponent.java b/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleJigsawComponent.java index 758b2b662..8e167a73e 100644 --- a/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleJigsawComponent.java +++ b/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleJigsawComponent.java @@ -167,6 +167,7 @@ public class MantleJigsawComponent extends IrisMantleComponent { @BlockCoordinates private boolean place(MantleWriter writer, IrisPosition position, IrisJigsawStructure structure, RNG rng, boolean forcePlace) { + if (structure == null || structure.getDatapackStructures().isNotEmpty()) return false; return new PlannedStructure(structure, position, rng, forcePlace).place(writer, getMantle(), writer.getEngine()); } diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisJigsawPlacement.java b/core/src/main/java/com/volmit/iris/engine/object/IrisJigsawPlacement.java deleted file mode 100644 index 8494550e7..000000000 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisJigsawPlacement.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.volmit.iris.engine.object; - -import com.volmit.iris.engine.object.annotations.*; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.Accessors; - -@Snippet("jigsaw-placer") -@Accessors(chain = true) -@NoArgsConstructor -@AllArgsConstructor -@Desc("Represents a jigsaw placement") -@Data -public class IrisJigsawPlacement { - @RegistryListResource(IrisJigsawStructure.class) - @Required - @Desc("The jigsaw structure to use") - private String structure = ""; - - @Required - @MinNumber(1) - @Desc("The rarity for this jigsaw structure to place on a per chunk basis") - private int rarity = 29; -} diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisJigsawStructure.java b/core/src/main/java/com/volmit/iris/engine/object/IrisJigsawStructure.java index 857dd9b17..ed7a61e53 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisJigsawStructure.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisJigsawStructure.java @@ -23,6 +23,7 @@ import com.volmit.iris.core.loader.IrisRegistrant; import com.volmit.iris.engine.data.cache.AtomicCache; import com.volmit.iris.engine.object.annotations.*; import com.volmit.iris.engine.object.annotations.functions.StructureKeyFunction; +import com.volmit.iris.engine.object.annotations.functions.StructureKeyOrTagFunction; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.json.JSONObject; import com.volmit.iris.util.plugin.VolmitSender; @@ -40,6 +41,11 @@ import lombok.experimental.Accessors; @Data @EqualsAndHashCode(callSuper = false) public class IrisJigsawStructure extends IrisRegistrant { + @RegistryListFunction(StructureKeyFunction.class) + @ArrayType(min = 1, type = String.class) + @Desc("The datapack structures. Randomply chooses a structure to place\nIgnores every other setting") + private KList datapackStructures = new KList<>(); + @RegistryListResource(IrisJigsawPiece.class) @Required @ArrayType(min = 1, type = String.class) @@ -70,7 +76,7 @@ public class IrisJigsawStructure extends IrisRegistrant { @Desc("Set to true to prevent rotating the initial structure piece") private boolean disableInitialRotation = false; - @RegistryListFunction(StructureKeyFunction.class) + @RegistryListFunction(StructureKeyOrTagFunction.class) @Desc("The minecraft key to use when creating treasure maps") private String structureKey = null; @@ -117,6 +123,10 @@ public class IrisJigsawStructure extends IrisRegistrant { public int getMaxDimension() { return maxDimension.aquire(() -> { + if (datapackStructures.isNotEmpty()) { + return 0; + } + if (useMaxPieceSizeForParallaxRadius) { int max = 0; KList pools = new KList<>(); diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisStructurePopulator.java b/core/src/main/java/com/volmit/iris/engine/object/IrisStructurePopulator.java new file mode 100644 index 000000000..2c77da2ce --- /dev/null +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisStructurePopulator.java @@ -0,0 +1,74 @@ +package com.volmit.iris.engine.object; + +import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.engine.framework.EngineAssignedComponent; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.documentation.ChunkCoordinates; +import com.volmit.iris.util.math.Position2; +import com.volmit.iris.util.math.RNG; +import com.volmit.iris.util.noise.CNG; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.function.BiPredicate; + +public class IrisStructurePopulator extends EngineAssignedComponent { + private final CNG cng; + private final long mantle; + private final long jigsaw; + + public IrisStructurePopulator(Engine engine) { + super(engine, "Datapack Structures"); + mantle = engine.getSeedManager().getMantle(); + jigsaw = engine.getSeedManager().getJigsaw(); + cng = NoiseStyle.STATIC.create(new RNG(jigsaw)); + } + + @ChunkCoordinates + public void populateStructures(int x, int z, BiPredicate placer) { + int bX = x << 4, bZ = z << 4; + var dimension = getDimension(); + var region = getEngine().getRegion(bX + 8, bZ + 8); + var biome = getEngine().getSurfaceBiome(bX + 8, bZ + 8); + var loader = getData().getJigsawStructureLoader(); + + long seed = cng.fit(Integer.MIN_VALUE, Integer.MAX_VALUE, x, z); + if (dimension.getStronghold() != null) { + var list = getDimension().getStrongholds(mantle); + if (list != null && list.contains(new Position2(bX, bZ))) { + place(placer, loader.load(dimension.getStronghold()), new RNG(seed), true); + return; + } + } + + boolean placed = place(placer, biome.getJigsawStructures(), seed, x, z); + if (!placed) placed = place(placer, region.getJigsawStructures(), seed, x, z); + if (!placed) place(placer, dimension.getJigsawStructures(), seed, x, z); + } + + private boolean place(BiPredicate placer, KList placements, long seed, int x, int z) { + var placement = pick(placements, seed, x, z); + if (placement == null) return false; + return place(placer, getData().getJigsawStructureLoader().load(placement.getStructure()), new RNG(seed), false); + } + + @Nullable + @ChunkCoordinates + private IrisJigsawStructurePlacement pick(List structures, long seed, int x, int z) { + return IRare.pick(structures.stream() + .filter(p -> p.shouldPlace(getData(), getDimension().getJigsawStructureDivisor(), jigsaw, x, z)) + .toList(), new RNG(seed).nextDouble()); + } + + @ChunkCoordinates + private boolean place(BiPredicate placer, IrisJigsawStructure structure, RNG rng, boolean ignoreBiomes) { + if (structure == null || structure.getDatapackStructures().isEmpty()) return false; + var keys = structure.getDatapackStructures().shuffleCopy(rng); + while (keys.isNotEmpty()) { + String key = keys.removeFirst(); + if (key != null && placer.test(key, ignoreBiomes || structure.isForcePlace())) + return true; + } + return false; + } +} diff --git a/core/src/main/java/com/volmit/iris/engine/object/annotations/functions/StructureKeyFunction.java b/core/src/main/java/com/volmit/iris/engine/object/annotations/functions/StructureKeyFunction.java index acfb7a6b5..492a5706a 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/annotations/functions/StructureKeyFunction.java +++ b/core/src/main/java/com/volmit/iris/engine/object/annotations/functions/StructureKeyFunction.java @@ -18,6 +18,6 @@ public class StructureKeyFunction implements ListFunction> { @Override public KList apply(IrisData irisData) { - return INMS.get().getStructureKeys(); + return INMS.get().getStructureKeys().removeWhere(t -> t.startsWith("#")); } } diff --git a/core/src/main/java/com/volmit/iris/engine/object/annotations/functions/StructureKeyOrTagFunction.java b/core/src/main/java/com/volmit/iris/engine/object/annotations/functions/StructureKeyOrTagFunction.java new file mode 100644 index 000000000..0d4db607a --- /dev/null +++ b/core/src/main/java/com/volmit/iris/engine/object/annotations/functions/StructureKeyOrTagFunction.java @@ -0,0 +1,23 @@ +package com.volmit.iris.engine.object.annotations.functions; + +import com.volmit.iris.core.loader.IrisData; +import com.volmit.iris.core.nms.INMS; +import com.volmit.iris.engine.framework.ListFunction; +import com.volmit.iris.util.collection.KList; + +public class StructureKeyOrTagFunction implements ListFunction> { + @Override + public String key() { + return "structure-key-or-tag"; + } + + @Override + public String fancyName() { + return "Structure Key or Tag"; + } + + @Override + public KList apply(IrisData irisData) { + return INMS.get().getStructureKeys(); + } +} diff --git a/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java b/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java index f6c0cbb2c..b0b89c7b7 100644 --- a/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java +++ b/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java @@ -240,6 +240,7 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun } }, syncExecutor)); } + futures.add(CompletableFuture.runAsync(() -> INMS.get().placeStructures(c), syncExecutor)); CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) .thenRunAsync(() -> { diff --git a/core/src/main/java/com/volmit/iris/util/collection/KSet.java b/core/src/main/java/com/volmit/iris/util/collection/KSet.java index 53b602f9a..b695b0fd3 100644 --- a/core/src/main/java/com/volmit/iris/util/collection/KSet.java +++ b/core/src/main/java/com/volmit/iris/util/collection/KSet.java @@ -22,6 +22,7 @@ import org.jetbrains.annotations.NotNull; import java.io.Serializable; import java.util.AbstractSet; +import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.concurrent.ConcurrentHashMap; @@ -30,13 +31,15 @@ public class KSet extends AbstractSet implements Serializable { private static final long serialVersionUID = 1L; private final ConcurrentHashMap map; - public KSet() { - map = new ConcurrentHashMap<>(); + public KSet(Collection c) { + this(c.size()); + addAll(c); } - public KSet(Collection c) { - this(); - addAll(c); + @SafeVarargs + public KSet(T... values) { + this(values.length); + addAll(Arrays.asList(values)); } public KSet(int initialCapacity, float loadFactor) { @@ -47,6 +50,13 @@ public class KSet extends AbstractSet implements Serializable { map = new ConcurrentHashMap<>(initialCapacity); } + public static KSet merge(Collection first, Collection second) { + var set = new KSet(); + set.addAll(first); + set.addAll(second); + return set; + } + @Override public int size() { return map.size(); diff --git a/core/src/main/java/com/volmit/iris/util/decree/specialhandlers/NullableDimensionHandler.java b/core/src/main/java/com/volmit/iris/util/decree/specialhandlers/NullableDimensionHandler.java new file mode 100644 index 000000000..706dd61a5 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/decree/specialhandlers/NullableDimensionHandler.java @@ -0,0 +1,88 @@ +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2022 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.volmit.iris.util.decree.specialhandlers; + +import com.volmit.iris.Iris; +import com.volmit.iris.core.IrisSettings; +import com.volmit.iris.core.loader.IrisData; +import com.volmit.iris.engine.object.IrisDimension; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.decree.DecreeParameterHandler; +import com.volmit.iris.util.decree.exceptions.DecreeParsingException; + +import java.io.File; + +public class NullableDimensionHandler implements DecreeParameterHandler { + @Override + public KList getPossibilities() { + KMap p = new KMap<>(); + + //noinspection ConstantConditions + for (File i : Iris.instance.getDataFolder("packs").listFiles()) { + if (i.isDirectory()) { + IrisData data = IrisData.get(i); + for (IrisDimension j : data.getDimensionLoader().loadAll(data.getDimensionLoader().getPossibleKeys())) { + p.putIfAbsent(j.getLoadKey(), j); + } + + data.close(); + } + } + + return p.v(); + } + + @Override + public String toString(IrisDimension dim) { + return dim.getLoadKey(); + } + + @Override + public IrisDimension parse(String in, boolean force) throws DecreeParsingException { + if (in.equalsIgnoreCase("---")) + return null; + + if (in.equalsIgnoreCase("default")) { + return parse(IrisSettings.get().getGenerator().getDefaultWorldType()); + } + + KList options = getPossibilities(in); + + + if (options.isEmpty()) { + throw new DecreeParsingException("Unable to find Dimension \"" + in + "\""); + } + try { + return options.stream().filter((i) -> toString(i).equalsIgnoreCase(in)).toList().get(0); + } catch (Throwable e) { + throw new DecreeParsingException("Unable to filter which Dimension \"" + in + "\""); + } + } + + @Override + public boolean supports(Class type) { + return type.equals(IrisDimension.class); + } + + @Override + public String getRandomDefault() { + return "dimension"; + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 41302ef62..6f2bb9e04 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -26,6 +26,7 @@ sentry = "8.14.0" # https://github.com/getsentry/sentry-java commons-io = "2.19.0" # https://central.sonatype.com/artifact/commons-io/commons-io commons-lang = "2.6" # https://central.sonatype.com/artifact/commons-lang/commons-lang commons-lang3 = "3.17.0" # https://central.sonatype.com/artifact/org.apache.commons/commons-lang3 +commons-math3 = "3.6.1" oshi = "6.8.2" # https://central.sonatype.com/artifact/com.github.oshi/oshi-core fastutil = "8.5.16" # https://central.sonatype.com/artifact/it.unimi.dsi/fastutil lz4 = "1.8.0" # https://central.sonatype.com/artifact/org.lz4/lz4-java @@ -75,6 +76,7 @@ sentry = { module = "io.sentry:sentry", version.ref = "sentry" } commons-io = { module = "commons-io:commons-io", version.ref = "commons-io" } commons-lang = { module = "commons-lang:commons-lang", version.ref = "commons-lang" } commons-lang3 = { module = "org.apache.commons:commons-lang3", version.ref = "commons-lang3" } +commons-math3 = { module = "org.apache.commons:commons-math3", version.ref = "commons-math3" } oshi = { module = "com.github.oshi:oshi-core", version.ref = "oshi" } lz4 = { module = "org.lz4:lz4-java", version.ref = "lz4" } fastutil = { module = "it.unimi.dsi:fastutil", version.ref = "fastutil" } diff --git a/nms/v1_20_R1/src/main/java/com/volmit/iris/core/nms/v1_20_R1/IrisChunkGenerator.java b/nms/v1_20_R1/src/main/java/com/volmit/iris/core/nms/v1_20_R1/IrisChunkGenerator.java index ab6208ae2..ce5412199 100644 --- a/nms/v1_20_R1/src/main/java/com/volmit/iris/core/nms/v1_20_R1/IrisChunkGenerator.java +++ b/nms/v1_20_R1/src/main/java/com/volmit/iris/core/nms/v1_20_R1/IrisChunkGenerator.java @@ -8,16 +8,18 @@ import com.volmit.iris.engine.framework.ResultLocator; import com.volmit.iris.engine.framework.WrongEngineBroException; import com.volmit.iris.engine.object.IrisJigsawStructure; import com.volmit.iris.engine.object.IrisJigsawStructurePlacement; +import com.volmit.iris.engine.object.IrisStructurePopulator; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.collection.KSet; import com.volmit.iris.util.mantle.flag.MantleFlag; import com.volmit.iris.util.math.Position2; import com.volmit.iris.util.reflect.WrappedField; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Holder; -import net.minecraft.core.HolderSet; -import net.minecraft.core.RegistryAccess; +import com.volmit.iris.util.reflect.WrappedReturningMethod; +import net.minecraft.CrashReport; +import net.minecraft.CrashReportCategory; +import net.minecraft.ReportedException; +import net.minecraft.core.*; import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; @@ -27,45 +29,55 @@ import net.minecraft.tags.StructureTags; import net.minecraft.tags.TagKey; import net.minecraft.util.random.WeightedRandomList; import net.minecraft.world.entity.MobCategory; -import net.minecraft.world.level.LevelHeightAccessor; -import net.minecraft.world.level.NoiseColumn; -import net.minecraft.world.level.StructureManager; -import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.*; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.BiomeManager; import net.minecraft.world.level.biome.BiomeSource; import net.minecraft.world.level.biome.MobSpawnSettings; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.chunk.ChunkGeneratorStructureState; -import net.minecraft.world.level.levelgen.GenerationStep; -import net.minecraft.world.level.levelgen.Heightmap; -import net.minecraft.world.level.levelgen.RandomState; +import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.levelgen.*; import net.minecraft.world.level.levelgen.blending.Blender; +import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; +import net.minecraft.world.level.levelgen.structure.StructureStart; import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; +import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.craftbukkit.v1_20_R1.CraftWorld; import org.bukkit.craftbukkit.v1_20_R1.generator.CustomChunkGenerator; +import org.bukkit.craftbukkit.v1_20_R1.generator.structure.CraftStructure; +import org.bukkit.event.world.AsyncStructureSpawnEvent; import javax.annotation.Nullable; import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Comparator; import java.util.List; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; +import java.util.function.Supplier; public class IrisChunkGenerator extends CustomChunkGenerator { private static final WrappedField BIOME_SOURCE; + private static final WrappedReturningMethod SET_HEIGHT; private final ChunkGenerator delegate; private final Engine engine; private final KMap, KSet> structures = new KMap<>(); + private final IrisStructurePopulator populator; public IrisChunkGenerator(ChunkGenerator delegate, long seed, Engine engine, World world) { super(((CraftWorld) world).getHandle(), edit(delegate, new CustomBiomeSource(seed, engine, world)), null); this.delegate = delegate; this.engine = engine; + this.populator = new IrisStructurePopulator(engine); var dimension = engine.getDimension(); KSet placements = new KSet<>(); @@ -182,8 +194,58 @@ public class IrisChunkGenerator extends CustomChunkGenerator { } @Override - public void createStructures(RegistryAccess iregistrycustom, ChunkGeneratorStructureState chunkgeneratorstructurestate, StructureManager structuremanager, ChunkAccess ichunkaccess, StructureTemplateManager structuretemplatemanager) { - delegate.createStructures(iregistrycustom, chunkgeneratorstructurestate, structuremanager, ichunkaccess, structuretemplatemanager); + public void createStructures(RegistryAccess registryAccess, ChunkGeneratorStructureState structureState, StructureManager structureManager, ChunkAccess access, StructureTemplateManager templateManager) { + if (!structureManager.shouldGenerateStructures()) + return; + var chunkPos = access.getPos(); + var sectionPos = SectionPos.bottomOf(access); + var registry = registryAccess.registryOrThrow(Registries.STRUCTURE); + populator.populateStructures(chunkPos.x, chunkPos.z, (key, ignoreBiomes) -> { + var loc = ResourceLocation.tryParse(key); + if (loc == null) return false; + var structure = registry.getOptional(loc).orElse(null); + if (structure == null) return false; + var biomes = structure.biomes(); + + var start = structure.generate( + registryAccess, + this, + biomeSource, + structureState.randomState(), + templateManager, + structureState.getLevelSeed(), + chunkPos, + fetchReferences(structureManager, access, sectionPos, structure), + access, + (biome) -> ignoreBiomes || biomes.contains(biome) + ); + + if (!start.isValid()) + return false; + + BoundingBox box = start.getBoundingBox(); + AsyncStructureSpawnEvent event = new AsyncStructureSpawnEvent( + structureManager.level.getMinecraftWorld().getWorld(), + CraftStructure.minecraftToBukkit(structure, registryAccess), + new org.bukkit.util.BoundingBox( + box.minX(), + box.minY(), + box.minZ(), + box.maxX(), + box.maxY(), + box.maxZ() + ), chunkPos.x, chunkPos.z); + Bukkit.getPluginManager().callEvent(event); + if (!event.isCancelled()) { + structureManager.setStartForStructure(sectionPos, structure, start, access); + } + return true; + }); + } + + private static int fetchReferences(StructureManager structureManager, ChunkAccess access, SectionPos sectionPos, Structure structure) { + StructureStart structurestart = structureManager.getStartForStructure(sectionPos, structure, access); + return structurestart != null ? structurestart.getReferences() : 0; } @Override @@ -201,11 +263,6 @@ public class IrisChunkGenerator extends CustomChunkGenerator { return delegate.fillFromNoise(executor, blender, randomstate, structuremanager, ichunkaccess); } - @Override - public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { - return delegate.getBaseHeight(i, j, heightmap_type, levelheightaccessor, randomstate); - } - @Override public WeightedRandomList getMobsAt(Holder holder, StructureManager structuremanager, MobCategory enumcreaturetype, BlockPos blockposition) { return delegate.getMobsAt(holder, structuremanager, enumcreaturetype, blockposition); @@ -213,7 +270,75 @@ public class IrisChunkGenerator extends CustomChunkGenerator { @Override public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) { - delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager); + applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, true); + } + + @Override + public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager, boolean vanilla) { + addVanillaDecorations(generatoraccessseed, ichunkaccess, structuremanager); + delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, false); + } + + @Override + public void addVanillaDecorations(WorldGenLevel level, ChunkAccess chunkAccess, StructureManager structureManager) { + if (!structureManager.shouldGenerateStructures()) + return; + + SectionPos sectionPos = SectionPos.of(chunkAccess.getPos(), level.getMinSection()); + BlockPos blockPos = sectionPos.origin(); + WorldgenRandom random = new WorldgenRandom(new XoroshiroRandomSource(RandomSupport.generateUniqueSeed())); + long i = random.setDecorationSeed(level.getSeed(), blockPos.getX(), blockPos.getZ()); + var structures = level.registryAccess().registryOrThrow(Registries.STRUCTURE); + var list = structures.stream() + .sorted(Comparator.comparingInt(s -> s.step().ordinal())) + .toList(); + + var surface = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.WORLD_SURFACE_WG); + var ocean = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.OCEAN_FLOOR_WG); + var motion = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.MOTION_BLOCKING); + var motionNoLeaves = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES); + + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + int wX = x + blockPos.getX(); + int wZ = z + blockPos.getZ(); + + int noAir = engine.getHeight(wX, wZ, false) + engine.getMinHeight() + 1; + int noFluid = engine.getHeight(wX, wZ, true) + engine.getMinHeight() + 1; + SET_HEIGHT.invoke(ocean, x, z, Math.min(noFluid, ocean.getFirstAvailable(x, z))); + SET_HEIGHT.invoke(surface, x, z, Math.min(noAir, surface.getFirstAvailable(x, z))); + SET_HEIGHT.invoke(motion, x, z, Math.min(noAir, motion.getFirstAvailable(x, z))); + SET_HEIGHT.invoke(motionNoLeaves, x, z, Math.min(noAir, motionNoLeaves.getFirstAvailable(x, z))); + } + } + + for (int j = 0; j < list.size(); j++) { + Structure structure = list.get(j); + random.setFeatureSeed(i, j, structure.step().ordinal()); + Supplier supplier = () -> structures.getResourceKey(structure).map(Object::toString).orElseGet(structure::toString); + + try { + level.setCurrentlyGenerating(supplier); + structureManager.startsForStructure(sectionPos, structure).forEach((start) -> start.placeInChunk(level, structureManager, this, random, getWritableArea(chunkAccess), chunkAccess.getPos())); + } catch (Exception exception) { + CrashReport crashReport = CrashReport.forThrowable(exception, "Feature placement"); + CrashReportCategory category = crashReport.addCategory("Feature"); + category.setDetail("Description", supplier::get); + throw new ReportedException(crashReport); + } + } + + Heightmap.primeHeightmaps(chunkAccess, ChunkStatus.POST_FEATURES); + } + + private static BoundingBox getWritableArea(ChunkAccess ichunkaccess) { + ChunkPos chunkPos = ichunkaccess.getPos(); + int minX = chunkPos.getMinBlockX(); + int minZ = chunkPos.getMinBlockZ(); + LevelHeightAccessor heightAccessor = ichunkaccess.getHeightAccessorForGeneration(); + int minY = heightAccessor.getMinBuildHeight() + 1; + int maxY = heightAccessor.getMaxBuildHeight() - 1; + return new BoundingBox(minX, minY, minZ, minX + 15, maxY, minZ + 15); } @Override @@ -236,9 +361,22 @@ public class IrisChunkGenerator extends CustomChunkGenerator { return delegate.getGenDepth(); } + @Override + public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { + return levelheightaccessor.getMinBuildHeight() + engine.getHeight(i, j, !heightmap_type.isOpaque().test(Blocks.WATER.defaultBlockState())) + 1; + } + @Override public NoiseColumn getBaseColumn(int i, int j, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { - return delegate.getBaseColumn(i, j, levelheightaccessor, randomstate); + int block = engine.getHeight(i, j, true); + int water = engine.getHeight(i, j, false); + BlockState[] column = new BlockState[levelheightaccessor.getHeight()]; + for (int k = 0; k < column.length; k++) { + if (k <= block) column[k] = Blocks.STONE.defaultBlockState(); + else if (k <= water) column[k] = Blocks.WATER.defaultBlockState(); + else column[k] = Blocks.AIR.defaultBlockState(); + } + return new NoiseColumn(levelheightaccessor.getMinBuildHeight(), column); } static { @@ -251,7 +389,21 @@ public class IrisChunkGenerator extends CustomChunkGenerator { } if (biomeSource == null) throw new RuntimeException("Could not find biomeSource field in ChunkGenerator!"); + + Method setHeight = null; + for (Method method : Heightmap.class.getDeclaredMethods()) { + var types = method.getParameterTypes(); + if (types.length != 3 || !Arrays.equals(types, new Class[]{int.class, int.class, int.class}) + || !method.getReturnType().equals(void.class)) + continue; + setHeight = method; + break; + } + if (setHeight == null) + throw new RuntimeException("Could not find setHeight method in Heightmap!"); + BIOME_SOURCE = new WrappedField<>(ChunkGenerator.class, biomeSource.getName()); + SET_HEIGHT = new WrappedReturningMethod<>(Heightmap.class, setHeight.getName(), setHeight.getParameterTypes()); } private static ChunkGenerator edit(ChunkGenerator generator, BiomeSource source) { diff --git a/nms/v1_20_R1/src/main/java/com/volmit/iris/core/nms/v1_20_R1/NMSBinding.java b/nms/v1_20_R1/src/main/java/com/volmit/iris/core/nms/v1_20_R1/NMSBinding.java index f2b923c30..4b3d04f22 100644 --- a/nms/v1_20_R1/src/main/java/com/volmit/iris/core/nms/v1_20_R1/NMSBinding.java +++ b/nms/v1_20_R1/src/main/java/com/volmit/iris/core/nms/v1_20_R1/NMSBinding.java @@ -3,11 +3,14 @@ package com.volmit.iris.core.nms.v1_20_R1; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.datafixers.util.Pair; import com.volmit.iris.Iris; +import com.volmit.iris.core.link.Identifier; import com.volmit.iris.core.nms.INMSBinding; import com.volmit.iris.core.nms.container.BiomeColor; +import com.volmit.iris.core.nms.container.StructurePlacement; import com.volmit.iris.core.nms.container.BlockProperty; import com.volmit.iris.engine.data.cache.AtomicCache; import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.engine.object.IrisJigsawStructurePlacement; import com.volmit.iris.engine.platform.PlatformChunkGenerator; import com.volmit.iris.util.agent.Agent; import com.volmit.iris.util.collection.KList; @@ -59,6 +62,8 @@ import net.minecraft.world.level.levelgen.flat.FlatLayerInfo; import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings; import net.minecraft.world.level.storage.LevelStorageSource; import net.minecraft.world.level.storage.PrimaryLevelData; +import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement; +import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement; import org.bukkit.*; import org.bukkit.block.Biome; import org.bukkit.block.data.BlockData; @@ -79,7 +84,6 @@ import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; -import java.awt.*; import java.awt.Color; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -90,6 +94,7 @@ import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; public class NMSBinding implements INMSBinding { @@ -704,6 +709,71 @@ public class NMSBinding implements INMSBinding { return new BlockProperty(property.getName(), property.getValueClass(), state.getValue(property), property.getPossibleValues(), property::getName); } + @Override + public void placeStructures(Chunk chunk) { + var craft = ((CraftChunk) chunk); + var level = craft.getCraftWorld().getHandle(); + var access = ((CraftChunk) chunk).getHandle(ChunkStatus.FULL); + level.getChunkSource().getGenerator().applyBiomeDecoration(level, access, level.structureManager()); + } + + @Override + public KMap collectStructures() { + var structureSets = registry().registryOrThrow(Registries.STRUCTURE_SET); + var structurePlacements = registry().registryOrThrow(Registries.STRUCTURE_PLACEMENT); + return structureSets.registryKeySet() + .stream() + .map(structureSets::getHolder) + .filter(Optional::isPresent) + .map(Optional::get) + .map(holder -> { + var set = holder.value(); + var placement = set.placement(); + var key = holder.key().location(); + StructurePlacement.StructurePlacementBuilder builder; + if (placement instanceof RandomSpreadStructurePlacement random) { + builder = StructurePlacement.RandomSpread.builder() + .separation(random.separation()) + .spacing(random.spacing()) + .spreadType(switch (random.spreadType()) { + case LINEAR -> IrisJigsawStructurePlacement.SpreadType.LINEAR; + case TRIANGULAR -> IrisJigsawStructurePlacement.SpreadType.TRIANGULAR; + }); + } else if (placement instanceof ConcentricRingsStructurePlacement rings) { + builder = StructurePlacement.ConcentricRings.builder() + .distance(rings.distance()) + .spread(rings.spread()) + .count(rings.count()); + } else { + Iris.warn("Unsupported structure placement for set " + key + " with type " + structurePlacements.getKey(placement.type())); + return null; + } + + return new com.volmit.iris.core.nms.container.Pair<>(new Identifier(key.getNamespace(), key.getPath()), builder + .salt(placement.salt) + .frequency(placement.frequency) + .structures(set.structures() + .stream() + .map(entry -> new StructurePlacement.Structure( + entry.weight(), + entry.structure() + .unwrapKey() + .map(ResourceKey::location) + .map(ResourceLocation::toString) + .orElse(null), + entry.structure().tags() + .map(TagKey::location) + .map(ResourceLocation::toString) + .toList() + )) + .filter(StructurePlacement.Structure::isValid) + .toList()) + .build()); + }) + .filter(Objects::nonNull) + .collect(Collectors.toMap(com.volmit.iris.core.nms.container.Pair::getA, com.volmit.iris.core.nms.container.Pair::getB, (a, b) -> a, KMap::new)); + } + public LevelStem levelStem(RegistryAccess access, ChunkGenerator raw) { if (!(raw instanceof PlatformChunkGenerator gen)) throw new IllegalStateException("Generator is not platform chunk generator!"); diff --git a/nms/v1_20_R2/src/main/java/com/volmit/iris/core/nms/v1_20_R2/IrisChunkGenerator.java b/nms/v1_20_R2/src/main/java/com/volmit/iris/core/nms/v1_20_R2/IrisChunkGenerator.java index abe671586..c22317c0f 100644 --- a/nms/v1_20_R2/src/main/java/com/volmit/iris/core/nms/v1_20_R2/IrisChunkGenerator.java +++ b/nms/v1_20_R2/src/main/java/com/volmit/iris/core/nms/v1_20_R2/IrisChunkGenerator.java @@ -8,16 +8,18 @@ import com.volmit.iris.engine.framework.ResultLocator; import com.volmit.iris.engine.framework.WrongEngineBroException; import com.volmit.iris.engine.object.IrisJigsawStructure; import com.volmit.iris.engine.object.IrisJigsawStructurePlacement; +import com.volmit.iris.engine.object.IrisStructurePopulator; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.collection.KSet; import com.volmit.iris.util.mantle.flag.MantleFlag; import com.volmit.iris.util.math.Position2; import com.volmit.iris.util.reflect.WrappedField; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Holder; -import net.minecraft.core.HolderSet; -import net.minecraft.core.RegistryAccess; +import com.volmit.iris.util.reflect.WrappedReturningMethod; +import net.minecraft.CrashReport; +import net.minecraft.CrashReportCategory; +import net.minecraft.ReportedException; +import net.minecraft.core.*; import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; @@ -27,45 +29,55 @@ import net.minecraft.tags.StructureTags; import net.minecraft.tags.TagKey; import net.minecraft.util.random.WeightedRandomList; import net.minecraft.world.entity.MobCategory; -import net.minecraft.world.level.LevelHeightAccessor; -import net.minecraft.world.level.NoiseColumn; -import net.minecraft.world.level.StructureManager; -import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.*; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.BiomeManager; import net.minecraft.world.level.biome.BiomeSource; import net.minecraft.world.level.biome.MobSpawnSettings; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.chunk.ChunkGeneratorStructureState; -import net.minecraft.world.level.levelgen.GenerationStep; -import net.minecraft.world.level.levelgen.Heightmap; -import net.minecraft.world.level.levelgen.RandomState; +import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.levelgen.*; import net.minecraft.world.level.levelgen.blending.Blender; +import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; +import net.minecraft.world.level.levelgen.structure.StructureStart; import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; +import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.craftbukkit.v1_20_R2.CraftWorld; import org.bukkit.craftbukkit.v1_20_R2.generator.CustomChunkGenerator; +import org.bukkit.craftbukkit.v1_20_R2.generator.structure.CraftStructure; +import org.bukkit.event.world.AsyncStructureSpawnEvent; import javax.annotation.Nullable; import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Comparator; import java.util.List; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; +import java.util.function.Supplier; public class IrisChunkGenerator extends CustomChunkGenerator { private static final WrappedField BIOME_SOURCE; + private static final WrappedReturningMethod SET_HEIGHT; private final ChunkGenerator delegate; private final Engine engine; private final KMap, KSet> structures = new KMap<>(); + private final IrisStructurePopulator populator; public IrisChunkGenerator(ChunkGenerator delegate, long seed, Engine engine, World world) { super(((CraftWorld) world).getHandle(), edit(delegate, new CustomBiomeSource(seed, engine, world)), null); this.delegate = delegate; this.engine = engine; + this.populator = new IrisStructurePopulator(engine); var dimension = engine.getDimension(); KSet placements = new KSet<>(); @@ -182,8 +194,58 @@ public class IrisChunkGenerator extends CustomChunkGenerator { } @Override - public void createStructures(RegistryAccess iregistrycustom, ChunkGeneratorStructureState chunkgeneratorstructurestate, StructureManager structuremanager, ChunkAccess ichunkaccess, StructureTemplateManager structuretemplatemanager) { - delegate.createStructures(iregistrycustom, chunkgeneratorstructurestate, structuremanager, ichunkaccess, structuretemplatemanager); + public void createStructures(RegistryAccess registryAccess, ChunkGeneratorStructureState structureState, StructureManager structureManager, ChunkAccess access, StructureTemplateManager templateManager) { + if (!structureManager.shouldGenerateStructures()) + return; + var chunkPos = access.getPos(); + var sectionPos = SectionPos.bottomOf(access); + var registry = registryAccess.registryOrThrow(Registries.STRUCTURE); + populator.populateStructures(chunkPos.x, chunkPos.z, (key, ignoreBiomes) -> { + var loc = ResourceLocation.tryParse(key); + if (loc == null) return false; + var structure = registry.getOptional(loc).orElse(null); + if (structure == null) return false; + var biomes = structure.biomes(); + + var start = structure.generate( + registryAccess, + this, + biomeSource, + structureState.randomState(), + templateManager, + structureState.getLevelSeed(), + chunkPos, + fetchReferences(structureManager, access, sectionPos, structure), + access, + (biome) -> ignoreBiomes || biomes.contains(biome) + ); + + if (!start.isValid()) + return false; + + BoundingBox box = start.getBoundingBox(); + AsyncStructureSpawnEvent event = new AsyncStructureSpawnEvent( + structureManager.level.getMinecraftWorld().getWorld(), + CraftStructure.minecraftToBukkit(structure, registryAccess), + new org.bukkit.util.BoundingBox( + box.minX(), + box.minY(), + box.minZ(), + box.maxX(), + box.maxY(), + box.maxZ() + ), chunkPos.x, chunkPos.z); + Bukkit.getPluginManager().callEvent(event); + if (!event.isCancelled()) { + structureManager.setStartForStructure(sectionPos, structure, start, access); + } + return true; + }); + } + + private static int fetchReferences(StructureManager structureManager, ChunkAccess access, SectionPos sectionPos, Structure structure) { + StructureStart structurestart = structureManager.getStartForStructure(sectionPos, structure, access); + return structurestart != null ? structurestart.getReferences() : 0; } @Override @@ -201,11 +263,6 @@ public class IrisChunkGenerator extends CustomChunkGenerator { return delegate.fillFromNoise(executor, blender, randomstate, structuremanager, ichunkaccess); } - @Override - public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { - return delegate.getBaseHeight(i, j, heightmap_type, levelheightaccessor, randomstate); - } - @Override public WeightedRandomList getMobsAt(Holder holder, StructureManager structuremanager, MobCategory enumcreaturetype, BlockPos blockposition) { return delegate.getMobsAt(holder, structuremanager, enumcreaturetype, blockposition); @@ -213,7 +270,75 @@ public class IrisChunkGenerator extends CustomChunkGenerator { @Override public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) { - delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager); + applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, true); + } + + @Override + public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager, boolean vanilla) { + addVanillaDecorations(generatoraccessseed, ichunkaccess, structuremanager); + delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, false); + } + + @Override + public void addVanillaDecorations(WorldGenLevel level, ChunkAccess chunkAccess, StructureManager structureManager) { + if (!structureManager.shouldGenerateStructures()) + return; + + SectionPos sectionPos = SectionPos.of(chunkAccess.getPos(), level.getMinSection()); + BlockPos blockPos = sectionPos.origin(); + WorldgenRandom random = new WorldgenRandom(new XoroshiroRandomSource(RandomSupport.generateUniqueSeed())); + long i = random.setDecorationSeed(level.getSeed(), blockPos.getX(), blockPos.getZ()); + var structures = level.registryAccess().registryOrThrow(Registries.STRUCTURE); + var list = structures.stream() + .sorted(Comparator.comparingInt(s -> s.step().ordinal())) + .toList(); + + var surface = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.WORLD_SURFACE_WG); + var ocean = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.OCEAN_FLOOR_WG); + var motion = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.MOTION_BLOCKING); + var motionNoLeaves = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES); + + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + int wX = x + blockPos.getX(); + int wZ = z + blockPos.getZ(); + + int noAir = engine.getHeight(wX, wZ, false) + engine.getMinHeight() + 1; + int noFluid = engine.getHeight(wX, wZ, true) + engine.getMinHeight() + 1; + SET_HEIGHT.invoke(ocean, x, z, Math.min(noFluid, ocean.getFirstAvailable(x, z))); + SET_HEIGHT.invoke(surface, x, z, Math.min(noAir, surface.getFirstAvailable(x, z))); + SET_HEIGHT.invoke(motion, x, z, Math.min(noAir, motion.getFirstAvailable(x, z))); + SET_HEIGHT.invoke(motionNoLeaves, x, z, Math.min(noAir, motionNoLeaves.getFirstAvailable(x, z))); + } + } + + for (int j = 0; j < list.size(); j++) { + Structure structure = list.get(j); + random.setFeatureSeed(i, j, structure.step().ordinal()); + Supplier supplier = () -> structures.getResourceKey(structure).map(Object::toString).orElseGet(structure::toString); + + try { + level.setCurrentlyGenerating(supplier); + structureManager.startsForStructure(sectionPos, structure).forEach((start) -> start.placeInChunk(level, structureManager, this, random, getWritableArea(chunkAccess), chunkAccess.getPos())); + } catch (Exception exception) { + CrashReport crashReport = CrashReport.forThrowable(exception, "Feature placement"); + CrashReportCategory category = crashReport.addCategory("Feature"); + category.setDetail("Description", supplier::get); + throw new ReportedException(crashReport); + } + } + + Heightmap.primeHeightmaps(chunkAccess, ChunkStatus.POST_FEATURES); + } + + private static BoundingBox getWritableArea(ChunkAccess ichunkaccess) { + ChunkPos chunkPos = ichunkaccess.getPos(); + int minX = chunkPos.getMinBlockX(); + int minZ = chunkPos.getMinBlockZ(); + LevelHeightAccessor heightAccessor = ichunkaccess.getHeightAccessorForGeneration(); + int minY = heightAccessor.getMinBuildHeight() + 1; + int maxY = heightAccessor.getMaxBuildHeight() - 1; + return new BoundingBox(minX, minY, minZ, minX + 15, maxY, minZ + 15); } @Override @@ -236,9 +361,22 @@ public class IrisChunkGenerator extends CustomChunkGenerator { return delegate.getGenDepth(); } + @Override + public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { + return levelheightaccessor.getMinBuildHeight() + engine.getHeight(i, j, !heightmap_type.isOpaque().test(Blocks.WATER.defaultBlockState())) + 1; + } + @Override public NoiseColumn getBaseColumn(int i, int j, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { - return delegate.getBaseColumn(i, j, levelheightaccessor, randomstate); + int block = engine.getHeight(i, j, true); + int water = engine.getHeight(i, j, false); + BlockState[] column = new BlockState[levelheightaccessor.getHeight()]; + for (int k = 0; k < column.length; k++) { + if (k <= block) column[k] = Blocks.STONE.defaultBlockState(); + else if (k <= water) column[k] = Blocks.WATER.defaultBlockState(); + else column[k] = Blocks.AIR.defaultBlockState(); + } + return new NoiseColumn(levelheightaccessor.getMinBuildHeight(), column); } static { @@ -251,7 +389,21 @@ public class IrisChunkGenerator extends CustomChunkGenerator { } if (biomeSource == null) throw new RuntimeException("Could not find biomeSource field in ChunkGenerator!"); + + Method setHeight = null; + for (Method method : Heightmap.class.getDeclaredMethods()) { + var types = method.getParameterTypes(); + if (types.length != 3 || !Arrays.equals(types, new Class[]{int.class, int.class, int.class}) + || !method.getReturnType().equals(void.class)) + continue; + setHeight = method; + break; + } + if (setHeight == null) + throw new RuntimeException("Could not find setHeight method in Heightmap!"); + BIOME_SOURCE = new WrappedField<>(ChunkGenerator.class, biomeSource.getName()); + SET_HEIGHT = new WrappedReturningMethod<>(Heightmap.class, setHeight.getName(), setHeight.getParameterTypes()); } private static ChunkGenerator edit(ChunkGenerator generator, BiomeSource source) { diff --git a/nms/v1_20_R2/src/main/java/com/volmit/iris/core/nms/v1_20_R2/NMSBinding.java b/nms/v1_20_R2/src/main/java/com/volmit/iris/core/nms/v1_20_R2/NMSBinding.java index 0086ff9d9..d1db20679 100644 --- a/nms/v1_20_R2/src/main/java/com/volmit/iris/core/nms/v1_20_R2/NMSBinding.java +++ b/nms/v1_20_R2/src/main/java/com/volmit/iris/core/nms/v1_20_R2/NMSBinding.java @@ -1,9 +1,26 @@ package com.volmit.iris.core.nms.v1_20_R2; import com.mojang.brigadier.exceptions.CommandSyntaxException; +import java.awt.Color; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Function; +import java.util.stream.Collectors; + import com.mojang.datafixers.util.Pair; import com.volmit.iris.Iris; import com.volmit.iris.core.nms.INMSBinding; +import com.mojang.serialization.Lifecycle; +import com.volmit.iris.core.link.Identifier; +import com.volmit.iris.core.nms.container.AutoClosing; import com.volmit.iris.core.nms.container.BiomeColor; import com.volmit.iris.core.nms.container.BlockProperty; import com.volmit.iris.engine.data.cache.AtomicCache; @@ -21,6 +38,8 @@ import com.volmit.iris.util.matter.MatterBiomeInject; import com.volmit.iris.util.nbt.mca.NBTWorld; import com.volmit.iris.util.nbt.mca.palette.*; import com.volmit.iris.util.nbt.tag.CompoundTag; +import com.volmit.iris.core.nms.container.StructurePlacement; +import com.volmit.iris.engine.object.IrisJigsawStructurePlacement; import com.volmit.iris.util.scheduling.J; import it.unimi.dsi.fastutil.objects.Object2IntMap; import net.bytebuddy.ByteBuddy; @@ -58,6 +77,8 @@ import net.minecraft.world.level.dimension.LevelStem; import net.minecraft.world.level.levelgen.FlatLevelSource; import net.minecraft.world.level.levelgen.flat.FlatLayerInfo; import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings; +import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement; +import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement; import net.minecraft.world.level.storage.LevelStorageSource; import net.minecraft.world.level.storage.PrimaryLevelData; import org.bukkit.*; @@ -707,6 +728,71 @@ public class NMSBinding implements INMSBinding { return new BlockProperty(property.getName(), property.getValueClass(), state.getValue(property), property.getPossibleValues(), property::getName); } + @Override + public void placeStructures(Chunk chunk) { + var craft = ((CraftChunk) chunk); + var level = craft.getCraftWorld().getHandle(); + var access = ((CraftChunk) chunk).getHandle(ChunkStatus.FULL); + level.getChunkSource().getGenerator().applyBiomeDecoration(level, access, level.structureManager()); + } + + @Override + public KMap collectStructures() { + var structureSets = registry().registryOrThrow(Registries.STRUCTURE_SET); + var structurePlacements = registry().registryOrThrow(Registries.STRUCTURE_PLACEMENT); + return structureSets.registryKeySet() + .stream() + .map(structureSets::getHolder) + .filter(Optional::isPresent) + .map(Optional::get) + .map(holder -> { + var set = holder.value(); + var placement = set.placement(); + var key = holder.key().location(); + StructurePlacement.StructurePlacementBuilder builder; + if (placement instanceof RandomSpreadStructurePlacement random) { + builder = StructurePlacement.RandomSpread.builder() + .separation(random.separation()) + .spacing(random.spacing()) + .spreadType(switch (random.spreadType()) { + case LINEAR -> IrisJigsawStructurePlacement.SpreadType.LINEAR; + case TRIANGULAR -> IrisJigsawStructurePlacement.SpreadType.TRIANGULAR; + }); + } else if (placement instanceof ConcentricRingsStructurePlacement rings) { + builder = StructurePlacement.ConcentricRings.builder() + .distance(rings.distance()) + .spread(rings.spread()) + .count(rings.count()); + } else { + Iris.warn("Unsupported structure placement for set " + key + " with type " + structurePlacements.getKey(placement.type())); + return null; + } + + return new com.volmit.iris.core.nms.container.Pair<>(new Identifier(key.getNamespace(), key.getPath()), builder + .salt(placement.salt) + .frequency(placement.frequency) + .structures(set.structures() + .stream() + .map(entry -> new StructurePlacement.Structure( + entry.weight(), + entry.structure() + .unwrapKey() + .map(ResourceKey::location) + .map(ResourceLocation::toString) + .orElse(null), + entry.structure().tags() + .map(TagKey::location) + .map(ResourceLocation::toString) + .toList() + )) + .filter(StructurePlacement.Structure::isValid) + .toList()) + .build()); + }) + .filter(Objects::nonNull) + .collect(Collectors.toMap(com.volmit.iris.core.nms.container.Pair::getA, com.volmit.iris.core.nms.container.Pair::getB, (a, b) -> a, KMap::new)); + } + public LevelStem levelStem(RegistryAccess access, ChunkGenerator raw) { if (!(raw instanceof PlatformChunkGenerator gen)) throw new IllegalStateException("Generator is not platform chunk generator!"); diff --git a/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/IrisChunkGenerator.java b/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/IrisChunkGenerator.java index 5587599e8..e415d7d3a 100644 --- a/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/IrisChunkGenerator.java +++ b/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/IrisChunkGenerator.java @@ -8,16 +8,18 @@ import com.volmit.iris.engine.framework.ResultLocator; import com.volmit.iris.engine.framework.WrongEngineBroException; import com.volmit.iris.engine.object.IrisJigsawStructure; import com.volmit.iris.engine.object.IrisJigsawStructurePlacement; +import com.volmit.iris.engine.object.IrisStructurePopulator; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.collection.KSet; import com.volmit.iris.util.mantle.flag.MantleFlag; import com.volmit.iris.util.math.Position2; import com.volmit.iris.util.reflect.WrappedField; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Holder; -import net.minecraft.core.HolderSet; -import net.minecraft.core.RegistryAccess; +import com.volmit.iris.util.reflect.WrappedReturningMethod; +import net.minecraft.CrashReport; +import net.minecraft.CrashReportCategory; +import net.minecraft.ReportedException; +import net.minecraft.core.*; import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; @@ -27,45 +29,55 @@ import net.minecraft.tags.StructureTags; import net.minecraft.tags.TagKey; import net.minecraft.util.random.WeightedRandomList; import net.minecraft.world.entity.MobCategory; -import net.minecraft.world.level.LevelHeightAccessor; -import net.minecraft.world.level.NoiseColumn; -import net.minecraft.world.level.StructureManager; -import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.*; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.BiomeManager; import net.minecraft.world.level.biome.BiomeSource; import net.minecraft.world.level.biome.MobSpawnSettings; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.chunk.ChunkGeneratorStructureState; -import net.minecraft.world.level.levelgen.GenerationStep; -import net.minecraft.world.level.levelgen.Heightmap; -import net.minecraft.world.level.levelgen.RandomState; +import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.levelgen.*; import net.minecraft.world.level.levelgen.blending.Blender; +import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; +import net.minecraft.world.level.levelgen.structure.StructureStart; import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; +import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.craftbukkit.v1_20_R3.CraftWorld; import org.bukkit.craftbukkit.v1_20_R3.generator.CustomChunkGenerator; +import org.bukkit.craftbukkit.v1_20_R3.generator.structure.CraftStructure; +import org.bukkit.event.world.AsyncStructureSpawnEvent; import javax.annotation.Nullable; import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Comparator; import java.util.List; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; +import java.util.function.Supplier; public class IrisChunkGenerator extends CustomChunkGenerator { private static final WrappedField BIOME_SOURCE; + private static final WrappedReturningMethod SET_HEIGHT; private final ChunkGenerator delegate; private final Engine engine; private final KMap, KSet> structures = new KMap<>(); + private final IrisStructurePopulator populator; public IrisChunkGenerator(ChunkGenerator delegate, long seed, Engine engine, World world) { super(((CraftWorld) world).getHandle(), edit(delegate, new CustomBiomeSource(seed, engine, world)), null); this.delegate = delegate; this.engine = engine; + this.populator = new IrisStructurePopulator(engine); var dimension = engine.getDimension(); KSet placements = new KSet<>(); @@ -182,8 +194,58 @@ public class IrisChunkGenerator extends CustomChunkGenerator { } @Override - public void createStructures(RegistryAccess iregistrycustom, ChunkGeneratorStructureState chunkgeneratorstructurestate, StructureManager structuremanager, ChunkAccess ichunkaccess, StructureTemplateManager structuretemplatemanager) { - delegate.createStructures(iregistrycustom, chunkgeneratorstructurestate, structuremanager, ichunkaccess, structuretemplatemanager); + public void createStructures(RegistryAccess registryAccess, ChunkGeneratorStructureState structureState, StructureManager structureManager, ChunkAccess access, StructureTemplateManager templateManager) { + if (!structureManager.shouldGenerateStructures()) + return; + var chunkPos = access.getPos(); + var sectionPos = SectionPos.bottomOf(access); + var registry = registryAccess.registryOrThrow(Registries.STRUCTURE); + populator.populateStructures(chunkPos.x, chunkPos.z, (key, ignoreBiomes) -> { + var loc = ResourceLocation.tryParse(key); + if (loc == null) return false; + var structure = registry.getOptional(loc).orElse(null); + if (structure == null) return false; + var biomes = structure.biomes(); + + var start = structure.generate( + registryAccess, + this, + biomeSource, + structureState.randomState(), + templateManager, + structureState.getLevelSeed(), + chunkPos, + fetchReferences(structureManager, access, sectionPos, structure), + access, + (biome) -> ignoreBiomes || biomes.contains(biome) + ); + + if (!start.isValid()) + return false; + + BoundingBox box = start.getBoundingBox(); + AsyncStructureSpawnEvent event = new AsyncStructureSpawnEvent( + structureManager.level.getMinecraftWorld().getWorld(), + CraftStructure.minecraftToBukkit(structure), + new org.bukkit.util.BoundingBox( + box.minX(), + box.minY(), + box.minZ(), + box.maxX(), + box.maxY(), + box.maxZ() + ), chunkPos.x, chunkPos.z); + Bukkit.getPluginManager().callEvent(event); + if (!event.isCancelled()) { + structureManager.setStartForStructure(sectionPos, structure, start, access); + } + return true; + }); + } + + private static int fetchReferences(StructureManager structureManager, ChunkAccess access, SectionPos sectionPos, Structure structure) { + StructureStart structurestart = structureManager.getStartForStructure(sectionPos, structure, access); + return structurestart != null ? structurestart.getReferences() : 0; } @Override @@ -201,11 +263,6 @@ public class IrisChunkGenerator extends CustomChunkGenerator { return delegate.fillFromNoise(executor, blender, randomstate, structuremanager, ichunkaccess); } - @Override - public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { - return delegate.getBaseHeight(i, j, heightmap_type, levelheightaccessor, randomstate); - } - @Override public WeightedRandomList getMobsAt(Holder holder, StructureManager structuremanager, MobCategory enumcreaturetype, BlockPos blockposition) { return delegate.getMobsAt(holder, structuremanager, enumcreaturetype, blockposition); @@ -213,7 +270,75 @@ public class IrisChunkGenerator extends CustomChunkGenerator { @Override public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) { - delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager); + applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, true); + } + + @Override + public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager, boolean vanilla) { + addVanillaDecorations(generatoraccessseed, ichunkaccess, structuremanager); + delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, false); + } + + @Override + public void addVanillaDecorations(WorldGenLevel level, ChunkAccess chunkAccess, StructureManager structureManager) { + if (!structureManager.shouldGenerateStructures()) + return; + + SectionPos sectionPos = SectionPos.of(chunkAccess.getPos(), level.getMinSection()); + BlockPos blockPos = sectionPos.origin(); + WorldgenRandom random = new WorldgenRandom(new XoroshiroRandomSource(RandomSupport.generateUniqueSeed())); + long i = random.setDecorationSeed(level.getSeed(), blockPos.getX(), blockPos.getZ()); + var structures = level.registryAccess().registryOrThrow(Registries.STRUCTURE); + var list = structures.stream() + .sorted(Comparator.comparingInt(s -> s.step().ordinal())) + .toList(); + + var surface = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.WORLD_SURFACE_WG); + var ocean = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.OCEAN_FLOOR_WG); + var motion = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.MOTION_BLOCKING); + var motionNoLeaves = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES); + + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + int wX = x + blockPos.getX(); + int wZ = z + blockPos.getZ(); + + int noAir = engine.getHeight(wX, wZ, false) + engine.getMinHeight() + 1; + int noFluid = engine.getHeight(wX, wZ, true) + engine.getMinHeight() + 1; + SET_HEIGHT.invoke(ocean, x, z, Math.min(noFluid, ocean.getFirstAvailable(x, z))); + SET_HEIGHT.invoke(surface, x, z, Math.min(noAir, surface.getFirstAvailable(x, z))); + SET_HEIGHT.invoke(motion, x, z, Math.min(noAir, motion.getFirstAvailable(x, z))); + SET_HEIGHT.invoke(motionNoLeaves, x, z, Math.min(noAir, motionNoLeaves.getFirstAvailable(x, z))); + } + } + + for (int j = 0; j < list.size(); j++) { + Structure structure = list.get(j); + random.setFeatureSeed(i, j, structure.step().ordinal()); + Supplier supplier = () -> structures.getResourceKey(structure).map(Object::toString).orElseGet(structure::toString); + + try { + level.setCurrentlyGenerating(supplier); + structureManager.startsForStructure(sectionPos, structure).forEach((start) -> start.placeInChunk(level, structureManager, this, random, getWritableArea(chunkAccess), chunkAccess.getPos())); + } catch (Exception exception) { + CrashReport crashReport = CrashReport.forThrowable(exception, "Feature placement"); + CrashReportCategory category = crashReport.addCategory("Feature"); + category.setDetail("Description", supplier::get); + throw new ReportedException(crashReport); + } + } + + Heightmap.primeHeightmaps(chunkAccess, ChunkStatus.POST_FEATURES); + } + + private static BoundingBox getWritableArea(ChunkAccess ichunkaccess) { + ChunkPos chunkPos = ichunkaccess.getPos(); + int minX = chunkPos.getMinBlockX(); + int minZ = chunkPos.getMinBlockZ(); + LevelHeightAccessor heightAccessor = ichunkaccess.getHeightAccessorForGeneration(); + int minY = heightAccessor.getMinBuildHeight() + 1; + int maxY = heightAccessor.getMaxBuildHeight() - 1; + return new BoundingBox(minX, minY, minZ, minX + 15, maxY, minZ + 15); } @Override @@ -236,9 +361,22 @@ public class IrisChunkGenerator extends CustomChunkGenerator { return delegate.getGenDepth(); } + @Override + public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { + return levelheightaccessor.getMinBuildHeight() + engine.getHeight(i, j, !heightmap_type.isOpaque().test(Blocks.WATER.defaultBlockState())) + 1; + } + @Override public NoiseColumn getBaseColumn(int i, int j, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { - return delegate.getBaseColumn(i, j, levelheightaccessor, randomstate); + int block = engine.getHeight(i, j, true); + int water = engine.getHeight(i, j, false); + BlockState[] column = new BlockState[levelheightaccessor.getHeight()]; + for (int k = 0; k < column.length; k++) { + if (k <= block) column[k] = Blocks.STONE.defaultBlockState(); + else if (k <= water) column[k] = Blocks.WATER.defaultBlockState(); + else column[k] = Blocks.AIR.defaultBlockState(); + } + return new NoiseColumn(levelheightaccessor.getMinBuildHeight(), column); } static { @@ -251,7 +389,21 @@ public class IrisChunkGenerator extends CustomChunkGenerator { } if (biomeSource == null) throw new RuntimeException("Could not find biomeSource field in ChunkGenerator!"); + + Method setHeight = null; + for (Method method : Heightmap.class.getDeclaredMethods()) { + var types = method.getParameterTypes(); + if (types.length != 3 || !Arrays.equals(types, new Class[]{int.class, int.class, int.class}) + || !method.getReturnType().equals(void.class)) + continue; + setHeight = method; + break; + } + if (setHeight == null) + throw new RuntimeException("Could not find setHeight method in Heightmap!"); + BIOME_SOURCE = new WrappedField<>(ChunkGenerator.class, biomeSource.getName()); + SET_HEIGHT = new WrappedReturningMethod<>(Heightmap.class, setHeight.getName(), setHeight.getParameterTypes()); } private static ChunkGenerator edit(ChunkGenerator generator, BiomeSource source) { diff --git a/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/NMSBinding.java b/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/NMSBinding.java index de96f541c..97f6adb54 100644 --- a/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/NMSBinding.java +++ b/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/NMSBinding.java @@ -1,9 +1,26 @@ package com.volmit.iris.core.nms.v1_20_R3; +import java.awt.Color; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Function; +import java.util.stream.Collectors; + import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.datafixers.util.Pair; import com.volmit.iris.Iris; import com.volmit.iris.core.nms.INMSBinding; +import com.mojang.serialization.Lifecycle; +import com.volmit.iris.core.link.Identifier; +import com.volmit.iris.core.nms.container.AutoClosing; import com.volmit.iris.core.nms.container.BiomeColor; import com.volmit.iris.core.nms.container.BlockProperty; import com.volmit.iris.engine.data.cache.AtomicCache; @@ -21,6 +38,8 @@ import com.volmit.iris.util.matter.MatterBiomeInject; import com.volmit.iris.util.nbt.mca.NBTWorld; import com.volmit.iris.util.nbt.mca.palette.*; import com.volmit.iris.util.nbt.tag.CompoundTag; +import com.volmit.iris.core.nms.container.StructurePlacement; +import com.volmit.iris.engine.object.IrisJigsawStructurePlacement; import com.volmit.iris.util.scheduling.J; import it.unimi.dsi.fastutil.objects.Object2IntMap; import net.bytebuddy.ByteBuddy; @@ -58,6 +77,8 @@ import net.minecraft.world.level.dimension.LevelStem; import net.minecraft.world.level.levelgen.FlatLevelSource; import net.minecraft.world.level.levelgen.flat.FlatLayerInfo; import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings; +import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement; +import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement; import net.minecraft.world.level.storage.LevelStorageSource; import net.minecraft.world.level.storage.PrimaryLevelData; import org.bukkit.*; @@ -708,6 +729,71 @@ public class NMSBinding implements INMSBinding { return new BlockProperty(property.getName(), property.getValueClass(), state.getValue(property), property.getPossibleValues(), property::getName); } + @Override + public void placeStructures(Chunk chunk) { + var craft = ((CraftChunk) chunk); + var level = craft.getCraftWorld().getHandle(); + var access = ((CraftChunk) chunk).getHandle(ChunkStatus.FULL); + level.getChunkSource().getGenerator().applyBiomeDecoration(level, access, level.structureManager()); + } + + @Override + public KMap collectStructures() { + var structureSets = registry().registryOrThrow(Registries.STRUCTURE_SET); + var structurePlacements = registry().registryOrThrow(Registries.STRUCTURE_PLACEMENT); + return structureSets.registryKeySet() + .stream() + .map(structureSets::getHolder) + .filter(Optional::isPresent) + .map(Optional::get) + .map(holder -> { + var set = holder.value(); + var placement = set.placement(); + var key = holder.key().location(); + StructurePlacement.StructurePlacementBuilder builder; + if (placement instanceof RandomSpreadStructurePlacement random) { + builder = StructurePlacement.RandomSpread.builder() + .separation(random.separation()) + .spacing(random.spacing()) + .spreadType(switch (random.spreadType()) { + case LINEAR -> IrisJigsawStructurePlacement.SpreadType.LINEAR; + case TRIANGULAR -> IrisJigsawStructurePlacement.SpreadType.TRIANGULAR; + }); + } else if (placement instanceof ConcentricRingsStructurePlacement rings) { + builder = StructurePlacement.ConcentricRings.builder() + .distance(rings.distance()) + .spread(rings.spread()) + .count(rings.count()); + } else { + Iris.warn("Unsupported structure placement for set " + key + " with type " + structurePlacements.getKey(placement.type())); + return null; + } + + return new com.volmit.iris.core.nms.container.Pair<>(new Identifier(key.getNamespace(), key.getPath()), builder + .salt(placement.salt) + .frequency(placement.frequency) + .structures(set.structures() + .stream() + .map(entry -> new StructurePlacement.Structure( + entry.weight(), + entry.structure() + .unwrapKey() + .map(ResourceKey::location) + .map(ResourceLocation::toString) + .orElse(null), + entry.structure().tags() + .map(TagKey::location) + .map(ResourceLocation::toString) + .toList() + )) + .filter(StructurePlacement.Structure::isValid) + .toList()) + .build()); + }) + .filter(Objects::nonNull) + .collect(Collectors.toMap(com.volmit.iris.core.nms.container.Pair::getA, com.volmit.iris.core.nms.container.Pair::getB, (a, b) -> a, KMap::new)); + } + public LevelStem levelStem(RegistryAccess access, ChunkGenerator raw) { if (!(raw instanceof PlatformChunkGenerator gen)) throw new IllegalStateException("Generator is not platform chunk generator!"); diff --git a/nms/v1_20_R4/src/main/java/com/volmit/iris/core/nms/v1_20_R4/IrisChunkGenerator.java b/nms/v1_20_R4/src/main/java/com/volmit/iris/core/nms/v1_20_R4/IrisChunkGenerator.java index f8af4ad86..34dba3135 100644 --- a/nms/v1_20_R4/src/main/java/com/volmit/iris/core/nms/v1_20_R4/IrisChunkGenerator.java +++ b/nms/v1_20_R4/src/main/java/com/volmit/iris/core/nms/v1_20_R4/IrisChunkGenerator.java @@ -8,16 +8,18 @@ import com.volmit.iris.engine.framework.ResultLocator; import com.volmit.iris.engine.framework.WrongEngineBroException; import com.volmit.iris.engine.object.IrisJigsawStructure; import com.volmit.iris.engine.object.IrisJigsawStructurePlacement; +import com.volmit.iris.engine.object.IrisStructurePopulator; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.collection.KSet; import com.volmit.iris.util.mantle.flag.MantleFlag; import com.volmit.iris.util.math.Position2; import com.volmit.iris.util.reflect.WrappedField; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Holder; -import net.minecraft.core.HolderSet; -import net.minecraft.core.RegistryAccess; +import com.volmit.iris.util.reflect.WrappedReturningMethod; +import net.minecraft.CrashReport; +import net.minecraft.CrashReportCategory; +import net.minecraft.ReportedException; +import net.minecraft.core.*; import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; @@ -27,43 +29,53 @@ import net.minecraft.tags.StructureTags; import net.minecraft.tags.TagKey; import net.minecraft.util.random.WeightedRandomList; import net.minecraft.world.entity.MobCategory; -import net.minecraft.world.level.LevelHeightAccessor; -import net.minecraft.world.level.NoiseColumn; -import net.minecraft.world.level.StructureManager; -import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.*; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.BiomeManager; import net.minecraft.world.level.biome.BiomeSource; import net.minecraft.world.level.biome.MobSpawnSettings; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.chunk.ChunkGeneratorStructureState; -import net.minecraft.world.level.levelgen.GenerationStep; -import net.minecraft.world.level.levelgen.Heightmap; -import net.minecraft.world.level.levelgen.RandomState; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.levelgen.*; import net.minecraft.world.level.levelgen.blending.Blender; +import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; +import net.minecraft.world.level.levelgen.structure.StructureStart; import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; +import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.craftbukkit.v1_20_R4.CraftWorld; import org.bukkit.craftbukkit.v1_20_R4.generator.CustomChunkGenerator; +import org.bukkit.craftbukkit.v1_20_R4.generator.structure.CraftStructure; +import org.bukkit.event.world.AsyncStructureSpawnEvent; import javax.annotation.Nullable; import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Comparator; import java.util.List; import java.util.Objects; import java.util.concurrent.*; +import java.util.function.Supplier; public class IrisChunkGenerator extends CustomChunkGenerator { private static final WrappedField BIOME_SOURCE; + private static final WrappedReturningMethod SET_HEIGHT; private final ChunkGenerator delegate; private final Engine engine; private final KMap, KSet> structures = new KMap<>(); + private final IrisStructurePopulator populator; public IrisChunkGenerator(ChunkGenerator delegate, long seed, Engine engine, World world) { super(((CraftWorld) world).getHandle(), edit(delegate, new CustomBiomeSource(seed, engine, world)), null); this.delegate = delegate; this.engine = engine; + this.populator = new IrisStructurePopulator(engine); var dimension = engine.getDimension(); KSet placements = new KSet<>(); @@ -180,8 +192,59 @@ public class IrisChunkGenerator extends CustomChunkGenerator { } @Override - public void createStructures(RegistryAccess iregistrycustom, ChunkGeneratorStructureState chunkgeneratorstructurestate, StructureManager structuremanager, ChunkAccess ichunkaccess, StructureTemplateManager structuretemplatemanager) { - delegate.createStructures(iregistrycustom, chunkgeneratorstructurestate, structuremanager, ichunkaccess, structuretemplatemanager); + public void createStructures(RegistryAccess registryAccess, ChunkGeneratorStructureState structureState, StructureManager structureManager, ChunkAccess access, StructureTemplateManager templateManager) { + if (!structureManager.shouldGenerateStructures()) + return; + var chunkPos = access.getPos(); + var sectionPos = SectionPos.bottomOf(access); + var registry = registryAccess.registryOrThrow(Registries.STRUCTURE); + populator.populateStructures(chunkPos.x, chunkPos.z, (key, ignoreBiomes) -> { + var loc = ResourceLocation.tryParse(key); + if (loc == null) return false; + var holder = registry.getHolder(loc).orElse(null); + if (holder == null) return false; + var structure = holder.value(); + var biomes = structure.biomes(); + + var start = structure.generate( + registryAccess, + this, + biomeSource, + structureState.randomState(), + templateManager, + structureState.getLevelSeed(), + chunkPos, + fetchReferences(structureManager, access, sectionPos, structure), + access, + (biome) -> ignoreBiomes || biomes.contains(biome) + ); + + if (!start.isValid()) + return false; + + BoundingBox box = start.getBoundingBox(); + AsyncStructureSpawnEvent event = new AsyncStructureSpawnEvent( + structureManager.level.getMinecraftWorld().getWorld(), + CraftStructure.minecraftToBukkit(structure), + new org.bukkit.util.BoundingBox( + box.minX(), + box.minY(), + box.minZ(), + box.maxX(), + box.maxY(), + box.maxZ() + ), chunkPos.x, chunkPos.z); + Bukkit.getPluginManager().callEvent(event); + if (!event.isCancelled()) { + structureManager.setStartForStructure(sectionPos, structure, start, access); + } + return true; + }); + } + + private static int fetchReferences(StructureManager structureManager, ChunkAccess access, SectionPos sectionPos, Structure structure) { + StructureStart structurestart = structureManager.getStartForStructure(sectionPos, structure, access); + return structurestart != null ? structurestart.getReferences() : 0; } @Override @@ -199,11 +262,6 @@ public class IrisChunkGenerator extends CustomChunkGenerator { return delegate.fillFromNoise(executor, blender, randomstate, structuremanager, ichunkaccess); } - @Override - public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { - return delegate.getBaseHeight(i, j, heightmap_type, levelheightaccessor, randomstate); - } - @Override public WeightedRandomList getMobsAt(Holder holder, StructureManager structuremanager, MobCategory enumcreaturetype, BlockPos blockposition) { return delegate.getMobsAt(holder, structuremanager, enumcreaturetype, blockposition); @@ -211,7 +269,75 @@ public class IrisChunkGenerator extends CustomChunkGenerator { @Override public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) { - delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager); + applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, true); + } + + @Override + public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager, boolean vanilla) { + addVanillaDecorations(generatoraccessseed, ichunkaccess, structuremanager); + delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, false); + } + + @Override + public void addVanillaDecorations(WorldGenLevel level, ChunkAccess chunkAccess, StructureManager structureManager) { + if (!structureManager.shouldGenerateStructures()) + return; + + SectionPos sectionPos = SectionPos.of(chunkAccess.getPos(), level.getMinSection()); + BlockPos blockPos = sectionPos.origin(); + WorldgenRandom random = new WorldgenRandom(new XoroshiroRandomSource(RandomSupport.generateUniqueSeed())); + long i = random.setDecorationSeed(level.getSeed(), blockPos.getX(), blockPos.getZ()); + var structures = level.registryAccess().registryOrThrow(Registries.STRUCTURE); + var list = structures.stream() + .sorted(Comparator.comparingInt(s -> s.step().ordinal())) + .toList(); + + var surface = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.WORLD_SURFACE_WG); + var ocean = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.OCEAN_FLOOR_WG); + var motion = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.MOTION_BLOCKING); + var motionNoLeaves = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES); + + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + int wX = x + blockPos.getX(); + int wZ = z + blockPos.getZ(); + + int noAir = engine.getHeight(wX, wZ, false) + engine.getMinHeight() + 1; + int noFluid = engine.getHeight(wX, wZ, true) + engine.getMinHeight() + 1; + SET_HEIGHT.invoke(ocean, x, z, Math.min(noFluid, ocean.getFirstAvailable(x, z))); + SET_HEIGHT.invoke(surface, x, z, Math.min(noAir, surface.getFirstAvailable(x, z))); + SET_HEIGHT.invoke(motion, x, z, Math.min(noAir, motion.getFirstAvailable(x, z))); + SET_HEIGHT.invoke(motionNoLeaves, x, z, Math.min(noAir, motionNoLeaves.getFirstAvailable(x, z))); + } + } + + for (int j = 0; j < list.size(); j++) { + Structure structure = list.get(j); + random.setFeatureSeed(i, j, structure.step().ordinal()); + Supplier supplier = () -> structures.getResourceKey(structure).map(Object::toString).orElseGet(structure::toString); + + try { + level.setCurrentlyGenerating(supplier); + structureManager.startsForStructure(sectionPos, structure).forEach((start) -> start.placeInChunk(level, structureManager, this, random, getWritableArea(chunkAccess), chunkAccess.getPos())); + } catch (Exception exception) { + CrashReport crashReport = CrashReport.forThrowable(exception, "Feature placement"); + CrashReportCategory category = crashReport.addCategory("Feature"); + category.setDetail("Description", supplier::get); + throw new ReportedException(crashReport); + } + } + + Heightmap.primeHeightmaps(chunkAccess, ChunkStatus.POST_FEATURES); + } + + private static BoundingBox getWritableArea(ChunkAccess ichunkaccess) { + ChunkPos chunkPos = ichunkaccess.getPos(); + int minX = chunkPos.getMinBlockX(); + int minZ = chunkPos.getMinBlockZ(); + LevelHeightAccessor heightAccessor = ichunkaccess.getHeightAccessorForGeneration(); + int minY = heightAccessor.getMinBuildHeight() + 1; + int maxY = heightAccessor.getMaxBuildHeight() - 1; + return new BoundingBox(minX, minY, minZ, minX + 15, maxY, minZ + 15); } @Override @@ -234,9 +360,22 @@ public class IrisChunkGenerator extends CustomChunkGenerator { return delegate.getGenDepth(); } + @Override + public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { + return levelheightaccessor.getMinBuildHeight() + engine.getHeight(i, j, !heightmap_type.isOpaque().test(Blocks.WATER.defaultBlockState())) + 1; + } + @Override public NoiseColumn getBaseColumn(int i, int j, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { - return delegate.getBaseColumn(i, j, levelheightaccessor, randomstate); + int block = engine.getHeight(i, j, true); + int water = engine.getHeight(i, j, false); + BlockState[] column = new BlockState[levelheightaccessor.getHeight()]; + for (int k = 0; k < column.length; k++) { + if (k <= block) column[k] = Blocks.STONE.defaultBlockState(); + else if (k <= water) column[k] = Blocks.WATER.defaultBlockState(); + else column[k] = Blocks.AIR.defaultBlockState(); + } + return new NoiseColumn(levelheightaccessor.getMinBuildHeight(), column); } static { @@ -249,7 +388,21 @@ public class IrisChunkGenerator extends CustomChunkGenerator { } if (biomeSource == null) throw new RuntimeException("Could not find biomeSource field in ChunkGenerator!"); + + Method setHeight = null; + for (Method method : Heightmap.class.getDeclaredMethods()) { + var types = method.getParameterTypes(); + if (types.length != 3 || !Arrays.equals(types, new Class[]{int.class, int.class, int.class}) + || !method.getReturnType().equals(void.class)) + continue; + setHeight = method; + break; + } + if (setHeight == null) + throw new RuntimeException("Could not find setHeight method in Heightmap!"); + BIOME_SOURCE = new WrappedField<>(ChunkGenerator.class, biomeSource.getName()); + SET_HEIGHT = new WrappedReturningMethod<>(Heightmap.class, setHeight.getName(), setHeight.getParameterTypes()); } private static ChunkGenerator edit(ChunkGenerator generator, BiomeSource source) { diff --git a/nms/v1_20_R4/src/main/java/com/volmit/iris/core/nms/v1_20_R4/NMSBinding.java b/nms/v1_20_R4/src/main/java/com/volmit/iris/core/nms/v1_20_R4/NMSBinding.java index ded43678c..fb6939c8d 100644 --- a/nms/v1_20_R4/src/main/java/com/volmit/iris/core/nms/v1_20_R4/NMSBinding.java +++ b/nms/v1_20_R4/src/main/java/com/volmit/iris/core/nms/v1_20_R4/NMSBinding.java @@ -1,11 +1,25 @@ package com.volmit.iris.core.nms.v1_20_R4; +import java.awt.Color; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Function; +import java.util.stream.Collectors; + import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.datafixers.util.Pair; import com.volmit.iris.Iris; import com.volmit.iris.core.nms.INMSBinding; +import com.mojang.serialization.Lifecycle; +import com.volmit.iris.core.link.Identifier; +import com.volmit.iris.core.nms.container.AutoClosing; import com.volmit.iris.core.nms.container.BiomeColor; import com.volmit.iris.core.nms.container.BlockProperty; +import com.volmit.iris.core.nms.container.StructurePlacement; import com.volmit.iris.core.nms.datapack.DataVersion; import com.volmit.iris.engine.data.cache.AtomicCache; import com.volmit.iris.engine.framework.Engine; @@ -21,6 +35,7 @@ import com.volmit.iris.util.math.Vector3d; import com.volmit.iris.util.matter.MatterBiomeInject; import com.volmit.iris.util.nbt.mca.NBTWorld; import com.volmit.iris.util.nbt.mca.palette.*; +import com.volmit.iris.engine.object.IrisJigsawStructurePlacement; import com.volmit.iris.util.nbt.tag.CompoundTag; import com.volmit.iris.util.scheduling.J; import it.unimi.dsi.fastutil.objects.Object2IntMap; @@ -59,6 +74,8 @@ import net.minecraft.world.level.dimension.LevelStem; import net.minecraft.world.level.levelgen.FlatLevelSource; import net.minecraft.world.level.levelgen.flat.FlatLayerInfo; import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings; +import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement; +import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement; import net.minecraft.world.level.storage.LevelStorageSource; import net.minecraft.world.level.storage.PrimaryLevelData; import org.bukkit.*; @@ -726,6 +743,71 @@ public class NMSBinding implements INMSBinding { return new BlockProperty(property.getName(), property.getValueClass(), state.getValue(property), property.getPossibleValues(), property::getName); } + @Override + public void placeStructures(Chunk chunk) { + var craft = ((CraftChunk) chunk); + var level = craft.getCraftWorld().getHandle(); + var access = ((CraftChunk) chunk).getHandle(ChunkStatus.FULL); + level.getChunkSource().getGenerator().applyBiomeDecoration(level, access, level.structureManager()); + } + + @Override + public KMap collectStructures() { + var structureSets = registry().registryOrThrow(Registries.STRUCTURE_SET); + var structurePlacements = registry().registryOrThrow(Registries.STRUCTURE_PLACEMENT); + return structureSets.keySet() + .stream() + .map(structureSets::getHolder) + .filter(Optional::isPresent) + .map(Optional::get) + .map(holder -> { + var set = holder.value(); + var placement = set.placement(); + var key = holder.key().location(); + StructurePlacement.StructurePlacementBuilder builder; + if (placement instanceof RandomSpreadStructurePlacement random) { + builder = StructurePlacement.RandomSpread.builder() + .separation(random.separation()) + .spacing(random.spacing()) + .spreadType(switch (random.spreadType()) { + case LINEAR -> IrisJigsawStructurePlacement.SpreadType.LINEAR; + case TRIANGULAR -> IrisJigsawStructurePlacement.SpreadType.TRIANGULAR; + }); + } else if (placement instanceof ConcentricRingsStructurePlacement rings) { + builder = StructurePlacement.ConcentricRings.builder() + .distance(rings.distance()) + .spread(rings.spread()) + .count(rings.count()); + } else { + Iris.warn("Unsupported structure placement for set " + key + " with type " + structurePlacements.getKey(placement.type())); + return null; + } + + return new com.volmit.iris.core.nms.container.Pair<>(new Identifier(key.getNamespace(), key.getPath()), builder + .salt(placement.salt) + .frequency(placement.frequency) + .structures(set.structures() + .stream() + .map(entry -> new StructurePlacement.Structure( + entry.weight(), + entry.structure() + .unwrapKey() + .map(ResourceKey::location) + .map(ResourceLocation::toString) + .orElse(null), + entry.structure().tags() + .map(TagKey::location) + .map(ResourceLocation::toString) + .toList() + )) + .filter(StructurePlacement.Structure::isValid) + .toList()) + .build()); + }) + .filter(Objects::nonNull) + .collect(Collectors.toMap(com.volmit.iris.core.nms.container.Pair::getA, com.volmit.iris.core.nms.container.Pair::getB, (a, b) -> a, KMap::new)); + } + public LevelStem levelStem(RegistryAccess access, ChunkGenerator raw) { if (!(raw instanceof PlatformChunkGenerator gen)) throw new IllegalStateException("Generator is not platform chunk generator!"); diff --git a/nms/v1_21_R1/src/main/java/com/volmit/iris/core/nms/v1_21_R1/IrisChunkGenerator.java b/nms/v1_21_R1/src/main/java/com/volmit/iris/core/nms/v1_21_R1/IrisChunkGenerator.java index b9d4dc4d5..9b63764e9 100644 --- a/nms/v1_21_R1/src/main/java/com/volmit/iris/core/nms/v1_21_R1/IrisChunkGenerator.java +++ b/nms/v1_21_R1/src/main/java/com/volmit/iris/core/nms/v1_21_R1/IrisChunkGenerator.java @@ -8,16 +8,18 @@ import com.volmit.iris.engine.framework.ResultLocator; import com.volmit.iris.engine.framework.WrongEngineBroException; import com.volmit.iris.engine.object.IrisJigsawStructure; import com.volmit.iris.engine.object.IrisJigsawStructurePlacement; +import com.volmit.iris.engine.object.IrisStructurePopulator; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.collection.KSet; import com.volmit.iris.util.mantle.flag.MantleFlag; import com.volmit.iris.util.math.Position2; import com.volmit.iris.util.reflect.WrappedField; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Holder; -import net.minecraft.core.HolderSet; -import net.minecraft.core.RegistryAccess; +import com.volmit.iris.util.reflect.WrappedReturningMethod; +import net.minecraft.CrashReport; +import net.minecraft.CrashReportCategory; +import net.minecraft.ReportedException; +import net.minecraft.core.*; import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; @@ -27,44 +29,54 @@ import net.minecraft.tags.StructureTags; import net.minecraft.tags.TagKey; import net.minecraft.util.random.WeightedRandomList; import net.minecraft.world.entity.MobCategory; -import net.minecraft.world.level.LevelHeightAccessor; -import net.minecraft.world.level.NoiseColumn; -import net.minecraft.world.level.StructureManager; -import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.*; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.BiomeManager; import net.minecraft.world.level.biome.BiomeSource; import net.minecraft.world.level.biome.MobSpawnSettings; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.chunk.ChunkGeneratorStructureState; -import net.minecraft.world.level.levelgen.GenerationStep; -import net.minecraft.world.level.levelgen.Heightmap; -import net.minecraft.world.level.levelgen.RandomState; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.levelgen.*; import net.minecraft.world.level.levelgen.blending.Blender; +import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; +import net.minecraft.world.level.levelgen.structure.StructureStart; import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; +import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.craftbukkit.v1_21_R1.CraftWorld; import org.bukkit.craftbukkit.v1_21_R1.generator.CustomChunkGenerator; +import org.bukkit.craftbukkit.v1_21_R1.generator.structure.CraftStructure; +import org.bukkit.event.world.AsyncStructureSpawnEvent; import javax.annotation.Nullable; import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Comparator; import java.util.List; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +import java.util.function.Supplier; public class IrisChunkGenerator extends CustomChunkGenerator { private static final WrappedField BIOME_SOURCE; + private static final WrappedReturningMethod SET_HEIGHT; private final ChunkGenerator delegate; private final Engine engine; private final KMap, KSet> structures = new KMap<>(); + private final IrisStructurePopulator populator; public IrisChunkGenerator(ChunkGenerator delegate, long seed, Engine engine, World world) { super(((CraftWorld) world).getHandle(), edit(delegate, new CustomBiomeSource(seed, engine, world)), null); this.delegate = delegate; this.engine = engine; + this.populator = new IrisStructurePopulator(engine); var dimension = engine.getDimension(); KSet placements = new KSet<>(); @@ -181,8 +193,59 @@ public class IrisChunkGenerator extends CustomChunkGenerator { } @Override - public void createStructures(RegistryAccess iregistrycustom, ChunkGeneratorStructureState chunkgeneratorstructurestate, StructureManager structuremanager, ChunkAccess ichunkaccess, StructureTemplateManager structuretemplatemanager) { - delegate.createStructures(iregistrycustom, chunkgeneratorstructurestate, structuremanager, ichunkaccess, structuretemplatemanager); + public void createStructures(RegistryAccess registryAccess, ChunkGeneratorStructureState structureState, StructureManager structureManager, ChunkAccess access, StructureTemplateManager templateManager) { + if (!structureManager.shouldGenerateStructures()) + return; + var chunkPos = access.getPos(); + var sectionPos = SectionPos.bottomOf(access); + var registry = registryAccess.registryOrThrow(Registries.STRUCTURE); + populator.populateStructures(chunkPos.x, chunkPos.z, (key, ignoreBiomes) -> { + var loc = ResourceLocation.tryParse(key); + if (loc == null) return false; + var holder = registry.getHolder(loc).orElse(null); + if (holder == null) return false; + var structure = holder.value(); + var biomes = structure.biomes(); + + var start = structure.generate( + registryAccess, + this, + biomeSource, + structureState.randomState(), + templateManager, + structureState.getLevelSeed(), + chunkPos, + fetchReferences(structureManager, access, sectionPos, structure), + access, + (biome) -> ignoreBiomes || biomes.contains(biome) + ); + + if (!start.isValid()) + return false; + + BoundingBox box = start.getBoundingBox(); + AsyncStructureSpawnEvent event = new AsyncStructureSpawnEvent( + structureManager.level.getMinecraftWorld().getWorld(), + CraftStructure.minecraftToBukkit(structure), + new org.bukkit.util.BoundingBox( + box.minX(), + box.minY(), + box.minZ(), + box.maxX(), + box.maxY(), + box.maxZ() + ), chunkPos.x, chunkPos.z); + Bukkit.getPluginManager().callEvent(event); + if (!event.isCancelled()) { + structureManager.setStartForStructure(sectionPos, structure, start, access); + } + return true; + }); + } + + private static int fetchReferences(StructureManager structureManager, ChunkAccess access, SectionPos sectionPos, Structure structure) { + StructureStart structurestart = structureManager.getStartForStructure(sectionPos, structure, access); + return structurestart != null ? structurestart.getReferences() : 0; } @Override @@ -200,11 +263,6 @@ public class IrisChunkGenerator extends CustomChunkGenerator { return delegate.fillFromNoise(blender, randomstate, structuremanager, ichunkaccess); } - @Override - public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { - return delegate.getBaseHeight(i, j, heightmap_type, levelheightaccessor, randomstate); - } - @Override public WeightedRandomList getMobsAt(Holder holder, StructureManager structuremanager, MobCategory enumcreaturetype, BlockPos blockposition) { return delegate.getMobsAt(holder, structuremanager, enumcreaturetype, blockposition); @@ -212,7 +270,75 @@ public class IrisChunkGenerator extends CustomChunkGenerator { @Override public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) { - delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager); + applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, true); + } + + @Override + public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager, boolean vanilla) { + addVanillaDecorations(generatoraccessseed, ichunkaccess, structuremanager); + delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, false); + } + + @Override + public void addVanillaDecorations(WorldGenLevel level, ChunkAccess chunkAccess, StructureManager structureManager) { + if (!structureManager.shouldGenerateStructures()) + return; + + SectionPos sectionPos = SectionPos.of(chunkAccess.getPos(), level.getMinSection()); + BlockPos blockPos = sectionPos.origin(); + WorldgenRandom random = new WorldgenRandom(new XoroshiroRandomSource(RandomSupport.generateUniqueSeed())); + long i = random.setDecorationSeed(level.getSeed(), blockPos.getX(), blockPos.getZ()); + var structures = level.registryAccess().registryOrThrow(Registries.STRUCTURE); + var list = structures.stream() + .sorted(Comparator.comparingInt(s -> s.step().ordinal())) + .toList(); + + var surface = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.WORLD_SURFACE_WG); + var ocean = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.OCEAN_FLOOR_WG); + var motion = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.MOTION_BLOCKING); + var motionNoLeaves = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES); + + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + int wX = x + blockPos.getX(); + int wZ = z + blockPos.getZ(); + + int noAir = engine.getHeight(wX, wZ, false) + engine.getMinHeight() + 1; + int noFluid = engine.getHeight(wX, wZ, true) + engine.getMinHeight() + 1; + SET_HEIGHT.invoke(ocean, x, z, Math.min(noFluid, ocean.getFirstAvailable(x, z))); + SET_HEIGHT.invoke(surface, x, z, Math.min(noAir, surface.getFirstAvailable(x, z))); + SET_HEIGHT.invoke(motion, x, z, Math.min(noAir, motion.getFirstAvailable(x, z))); + SET_HEIGHT.invoke(motionNoLeaves, x, z, Math.min(noAir, motionNoLeaves.getFirstAvailable(x, z))); + } + } + + for (int j = 0; j < list.size(); j++) { + Structure structure = list.get(j); + random.setFeatureSeed(i, j, structure.step().ordinal()); + Supplier supplier = () -> structures.getResourceKey(structure).map(Object::toString).orElseGet(structure::toString); + + try { + level.setCurrentlyGenerating(supplier); + structureManager.startsForStructure(sectionPos, structure).forEach((start) -> start.placeInChunk(level, structureManager, this, random, getWritableArea(chunkAccess), chunkAccess.getPos())); + } catch (Exception exception) { + CrashReport crashReport = CrashReport.forThrowable(exception, "Feature placement"); + CrashReportCategory category = crashReport.addCategory("Feature"); + category.setDetail("Description", supplier::get); + throw new ReportedException(crashReport); + } + } + + Heightmap.primeHeightmaps(chunkAccess, ChunkStatus.FINAL_HEIGHTMAPS); + } + + private static BoundingBox getWritableArea(ChunkAccess ichunkaccess) { + ChunkPos chunkPos = ichunkaccess.getPos(); + int minX = chunkPos.getMinBlockX(); + int minZ = chunkPos.getMinBlockZ(); + LevelHeightAccessor heightAccessor = ichunkaccess.getHeightAccessorForGeneration(); + int minY = heightAccessor.getMinBuildHeight() + 1; + int maxY = heightAccessor.getMaxBuildHeight() - 1; + return new BoundingBox(minX, minY, minZ, minX + 15, maxY, minZ + 15); } @Override @@ -235,9 +361,22 @@ public class IrisChunkGenerator extends CustomChunkGenerator { return delegate.getGenDepth(); } + @Override + public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { + return levelheightaccessor.getMinBuildHeight() + engine.getHeight(i, j, !heightmap_type.isOpaque().test(Blocks.WATER.defaultBlockState())) + 1; + } + @Override public NoiseColumn getBaseColumn(int i, int j, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { - return delegate.getBaseColumn(i, j, levelheightaccessor, randomstate); + int block = engine.getHeight(i, j, true); + int water = engine.getHeight(i, j, false); + BlockState[] column = new BlockState[levelheightaccessor.getHeight()]; + for (int k = 0; k < column.length; k++) { + if (k <= block) column[k] = Blocks.STONE.defaultBlockState(); + else if (k <= water) column[k] = Blocks.WATER.defaultBlockState(); + else column[k] = Blocks.AIR.defaultBlockState(); + } + return new NoiseColumn(levelheightaccessor.getMinBuildHeight(), column); } static { @@ -250,7 +389,21 @@ public class IrisChunkGenerator extends CustomChunkGenerator { } if (biomeSource == null) throw new RuntimeException("Could not find biomeSource field in ChunkGenerator!"); + + Method setHeight = null; + for (Method method : Heightmap.class.getDeclaredMethods()) { + var types = method.getParameterTypes(); + if (types.length != 3 || !Arrays.equals(types, new Class[]{int.class, int.class, int.class}) + || !method.getReturnType().equals(void.class)) + continue; + setHeight = method; + break; + } + if (setHeight == null) + throw new RuntimeException("Could not find setHeight method in Heightmap!"); + BIOME_SOURCE = new WrappedField<>(ChunkGenerator.class, biomeSource.getName()); + SET_HEIGHT = new WrappedReturningMethod<>(Heightmap.class, setHeight.getName(), setHeight.getParameterTypes()); } private static ChunkGenerator edit(ChunkGenerator generator, BiomeSource source) { diff --git a/nms/v1_21_R1/src/main/java/com/volmit/iris/core/nms/v1_21_R1/NMSBinding.java b/nms/v1_21_R1/src/main/java/com/volmit/iris/core/nms/v1_21_R1/NMSBinding.java index 758d57b8c..0738a5eea 100644 --- a/nms/v1_21_R1/src/main/java/com/volmit/iris/core/nms/v1_21_R1/NMSBinding.java +++ b/nms/v1_21_R1/src/main/java/com/volmit/iris/core/nms/v1_21_R1/NMSBinding.java @@ -1,12 +1,31 @@ package com.volmit.iris.core.nms.v1_21_R1; +import java.awt.Color; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Function; +import java.util.stream.Collectors; + import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.Lifecycle; +import com.volmit.iris.core.link.Identifier; +import com.volmit.iris.core.nms.container.AutoClosing; import com.volmit.iris.Iris; import com.volmit.iris.core.nms.INMSBinding; import com.volmit.iris.core.nms.container.BiomeColor; import com.volmit.iris.core.nms.container.BlockProperty; +import com.volmit.iris.core.nms.container.StructurePlacement; import com.volmit.iris.core.nms.datapack.DataVersion; +import com.volmit.iris.engine.object.IrisJigsawStructurePlacement; import com.volmit.iris.engine.data.cache.AtomicCache; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.platform.PlatformChunkGenerator; @@ -63,6 +82,8 @@ import net.minecraft.world.level.dimension.LevelStem; import net.minecraft.world.level.levelgen.FlatLevelSource; import net.minecraft.world.level.levelgen.flat.FlatLayerInfo; import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings; +import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement; +import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement; import net.minecraft.world.level.storage.LevelStorageSource; import net.minecraft.world.level.storage.PrimaryLevelData; import org.bukkit.*; @@ -737,6 +758,71 @@ public class NMSBinding implements INMSBinding { return new BlockProperty(property.getName(), property.getValueClass(), state.getValue(property), property.getPossibleValues(), property::getName); } + @Override + public void placeStructures(Chunk chunk) { + var craft = ((CraftChunk) chunk); + var level = craft.getCraftWorld().getHandle(); + var access = ((CraftChunk) chunk).getHandle(ChunkStatus.FULL); + level.getChunkSource().getGenerator().applyBiomeDecoration(level, access, level.structureManager()); + } + + @Override + public KMap collectStructures() { + var structureSets = registry().registryOrThrow(Registries.STRUCTURE_SET); + var structurePlacements = registry().registryOrThrow(Registries.STRUCTURE_PLACEMENT); + return structureSets.keySet() + .stream() + .map(structureSets::getHolder) + .filter(Optional::isPresent) + .map(Optional::get) + .map(holder -> { + var set = holder.value(); + var placement = set.placement(); + var key = holder.key().location(); + StructurePlacement.StructurePlacementBuilder builder; + if (placement instanceof RandomSpreadStructurePlacement random) { + builder = StructurePlacement.RandomSpread.builder() + .separation(random.separation()) + .spacing(random.spacing()) + .spreadType(switch (random.spreadType()) { + case LINEAR -> IrisJigsawStructurePlacement.SpreadType.LINEAR; + case TRIANGULAR -> IrisJigsawStructurePlacement.SpreadType.TRIANGULAR; + }); + } else if (placement instanceof ConcentricRingsStructurePlacement rings) { + builder = StructurePlacement.ConcentricRings.builder() + .distance(rings.distance()) + .spread(rings.spread()) + .count(rings.count()); + } else { + Iris.warn("Unsupported structure placement for set " + key + " with type " + structurePlacements.getKey(placement.type())); + return null; + } + + return new com.volmit.iris.core.nms.container.Pair<>(new Identifier(key.getNamespace(), key.getPath()), builder + .salt(placement.salt) + .frequency(placement.frequency) + .structures(set.structures() + .stream() + .map(entry -> new StructurePlacement.Structure( + entry.weight(), + entry.structure() + .unwrapKey() + .map(ResourceKey::location) + .map(ResourceLocation::toString) + .orElse(null), + entry.structure().tags() + .map(TagKey::location) + .map(ResourceLocation::toString) + .toList() + )) + .filter(StructurePlacement.Structure::isValid) + .toList()) + .build()); + }) + .filter(Objects::nonNull) + .collect(Collectors.toMap(com.volmit.iris.core.nms.container.Pair::getA, com.volmit.iris.core.nms.container.Pair::getB, (a, b) -> a, KMap::new)); + } + public LevelStem levelStem(RegistryAccess access, ChunkGenerator raw) { if (!(raw instanceof PlatformChunkGenerator gen)) throw new IllegalStateException("Generator is not platform chunk generator!"); diff --git a/nms/v1_21_R2/src/main/java/com/volmit/iris/core/nms/v1_21_R2/IrisChunkGenerator.java b/nms/v1_21_R2/src/main/java/com/volmit/iris/core/nms/v1_21_R2/IrisChunkGenerator.java index 4b6abd4c1..b91a4733f 100644 --- a/nms/v1_21_R2/src/main/java/com/volmit/iris/core/nms/v1_21_R2/IrisChunkGenerator.java +++ b/nms/v1_21_R2/src/main/java/com/volmit/iris/core/nms/v1_21_R2/IrisChunkGenerator.java @@ -8,16 +8,18 @@ import com.volmit.iris.engine.framework.ResultLocator; import com.volmit.iris.engine.framework.WrongEngineBroException; import com.volmit.iris.engine.object.IrisJigsawStructure; import com.volmit.iris.engine.object.IrisJigsawStructurePlacement; +import com.volmit.iris.engine.object.IrisStructurePopulator; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.collection.KSet; import com.volmit.iris.util.mantle.flag.MantleFlag; import com.volmit.iris.util.math.Position2; import com.volmit.iris.util.reflect.WrappedField; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Holder; -import net.minecraft.core.HolderSet; -import net.minecraft.core.RegistryAccess; +import com.volmit.iris.util.reflect.WrappedReturningMethod; +import net.minecraft.CrashReport; +import net.minecraft.CrashReportCategory; +import net.minecraft.ReportedException; +import net.minecraft.core.*; import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; @@ -27,43 +29,54 @@ import net.minecraft.tags.StructureTags; import net.minecraft.tags.TagKey; import net.minecraft.util.random.WeightedRandomList; import net.minecraft.world.entity.MobCategory; -import net.minecraft.world.level.LevelHeightAccessor; -import net.minecraft.world.level.NoiseColumn; -import net.minecraft.world.level.StructureManager; -import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.*; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.BiomeManager; import net.minecraft.world.level.biome.BiomeSource; import net.minecraft.world.level.biome.MobSpawnSettings; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.chunk.ChunkGeneratorStructureState; -import net.minecraft.world.level.levelgen.Heightmap; -import net.minecraft.world.level.levelgen.RandomState; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.levelgen.*; import net.minecraft.world.level.levelgen.blending.Blender; +import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; +import net.minecraft.world.level.levelgen.structure.StructureStart; import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; +import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.craftbukkit.v1_21_R2.CraftWorld; import org.bukkit.craftbukkit.v1_21_R2.generator.CustomChunkGenerator; +import org.bukkit.craftbukkit.v1_21_R2.generator.structure.CraftStructure; +import org.bukkit.event.world.AsyncStructureSpawnEvent; import javax.annotation.Nullable; import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Comparator; import java.util.List; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +import java.util.function.Supplier; public class IrisChunkGenerator extends CustomChunkGenerator { private static final WrappedField BIOME_SOURCE; + private static final WrappedReturningMethod SET_HEIGHT; private final ChunkGenerator delegate; private final Engine engine; private final KMap, KSet> structures = new KMap<>(); + private final IrisStructurePopulator populator; public IrisChunkGenerator(ChunkGenerator delegate, long seed, Engine engine, World world) { super(((CraftWorld) world).getHandle(), edit(delegate, new CustomBiomeSource(seed, engine, world)), null); this.delegate = delegate; this.engine = engine; + this.populator = new IrisStructurePopulator(engine); var dimension = engine.getDimension(); KSet placements = new KSet<>(); @@ -180,8 +193,59 @@ public class IrisChunkGenerator extends CustomChunkGenerator { } @Override - public void createStructures(RegistryAccess iregistrycustom, ChunkGeneratorStructureState chunkgeneratorstructurestate, StructureManager structuremanager, ChunkAccess ichunkaccess, StructureTemplateManager structuretemplatemanager) { - delegate.createStructures(iregistrycustom, chunkgeneratorstructurestate, structuremanager, ichunkaccess, structuretemplatemanager); + public void createStructures(RegistryAccess registryAccess, ChunkGeneratorStructureState structureState, StructureManager structureManager, ChunkAccess access, StructureTemplateManager templateManager) { + if (!structureManager.shouldGenerateStructures()) + return; + var chunkPos = access.getPos(); + var sectionPos = SectionPos.bottomOf(access); + var registry = registryAccess.lookupOrThrow(Registries.STRUCTURE); + populator.populateStructures(chunkPos.x, chunkPos.z, (key, ignoreBiomes) -> { + var loc = ResourceLocation.tryParse(key); + if (loc == null) return false; + var holder = registry.get(loc).orElse(null); + if (holder == null) return false; + var structure = holder.value(); + var biomes = structure.biomes(); + + var start = structure.generate( + registryAccess, + this, + biomeSource, + structureState.randomState(), + templateManager, + structureState.getLevelSeed(), + chunkPos, + fetchReferences(structureManager, access, sectionPos, structure), + access, + (biome) -> ignoreBiomes || biomes.contains(biome) + ); + + if (!start.isValid()) + return false; + + BoundingBox box = start.getBoundingBox(); + AsyncStructureSpawnEvent event = new AsyncStructureSpawnEvent( + structureManager.level.getMinecraftWorld().getWorld(), + CraftStructure.minecraftToBukkit(structure), + new org.bukkit.util.BoundingBox( + box.minX(), + box.minY(), + box.minZ(), + box.maxX(), + box.maxY(), + box.maxZ() + ), chunkPos.x, chunkPos.z); + Bukkit.getPluginManager().callEvent(event); + if (!event.isCancelled()) { + structureManager.setStartForStructure(sectionPos, structure, start, access); + } + return true; + }); + } + + private static int fetchReferences(StructureManager structureManager, ChunkAccess access, SectionPos sectionPos, Structure structure) { + StructureStart structurestart = structureManager.getStartForStructure(sectionPos, structure, access); + return structurestart != null ? structurestart.getReferences() : 0; } @Override @@ -199,11 +263,6 @@ public class IrisChunkGenerator extends CustomChunkGenerator { return delegate.fillFromNoise(blender, randomstate, structuremanager, ichunkaccess); } - @Override - public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { - return delegate.getBaseHeight(i, j, heightmap_type, levelheightaccessor, randomstate); - } - @Override public WeightedRandomList getMobsAt(Holder holder, StructureManager structuremanager, MobCategory enumcreaturetype, BlockPos blockposition) { return delegate.getMobsAt(holder, structuremanager, enumcreaturetype, blockposition); @@ -211,7 +270,75 @@ public class IrisChunkGenerator extends CustomChunkGenerator { @Override public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) { - delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager); + applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, true); + } + + @Override + public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager, boolean vanilla) { + addVanillaDecorations(generatoraccessseed, ichunkaccess, structuremanager); + delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, false); + } + + @Override + public void addVanillaDecorations(WorldGenLevel level, ChunkAccess chunkAccess, StructureManager structureManager) { + if (!structureManager.shouldGenerateStructures()) + return; + + SectionPos sectionPos = SectionPos.of(chunkAccess.getPos(), level.getMinSectionY()); + BlockPos blockPos = sectionPos.origin(); + WorldgenRandom random = new WorldgenRandom(new XoroshiroRandomSource(RandomSupport.generateUniqueSeed())); + long i = random.setDecorationSeed(level.getSeed(), blockPos.getX(), blockPos.getZ()); + var structures = level.registryAccess().lookupOrThrow(Registries.STRUCTURE); + var list = structures.stream() + .sorted(Comparator.comparingInt(s -> s.step().ordinal())) + .toList(); + + var surface = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.WORLD_SURFACE_WG); + var ocean = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.OCEAN_FLOOR_WG); + var motion = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.MOTION_BLOCKING); + var motionNoLeaves = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES); + + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + int wX = x + blockPos.getX(); + int wZ = z + blockPos.getZ(); + + int noAir = engine.getHeight(wX, wZ, false) + engine.getMinHeight() + 1; + int noFluid = engine.getHeight(wX, wZ, true) + engine.getMinHeight() + 1; + SET_HEIGHT.invoke(ocean, x, z, Math.min(noFluid, ocean.getFirstAvailable(x, z))); + SET_HEIGHT.invoke(surface, x, z, Math.min(noAir, surface.getFirstAvailable(x, z))); + SET_HEIGHT.invoke(motion, x, z, Math.min(noAir, motion.getFirstAvailable(x, z))); + SET_HEIGHT.invoke(motionNoLeaves, x, z, Math.min(noAir, motionNoLeaves.getFirstAvailable(x, z))); + } + } + + for (int j = 0; j < list.size(); j++) { + Structure structure = list.get(j); + random.setFeatureSeed(i, j, structure.step().ordinal()); + Supplier supplier = () -> structures.getResourceKey(structure).map(Object::toString).orElseGet(structure::toString); + + try { + level.setCurrentlyGenerating(supplier); + structureManager.startsForStructure(sectionPos, structure).forEach((start) -> start.placeInChunk(level, structureManager, this, random, getWritableArea(chunkAccess), chunkAccess.getPos())); + } catch (Exception exception) { + CrashReport crashReport = CrashReport.forThrowable(exception, "Feature placement"); + CrashReportCategory category = crashReport.addCategory("Feature"); + category.setDetail("Description", supplier::get); + throw new ReportedException(crashReport); + } + } + + Heightmap.primeHeightmaps(chunkAccess, ChunkStatus.FINAL_HEIGHTMAPS); + } + + private static BoundingBox getWritableArea(ChunkAccess ichunkaccess) { + ChunkPos chunkPos = ichunkaccess.getPos(); + int minX = chunkPos.getMinBlockX(); + int minZ = chunkPos.getMinBlockZ(); + LevelHeightAccessor heightAccessor = ichunkaccess.getHeightAccessorForGeneration(); + int minY = heightAccessor.getMinY() + 1; + int maxY = heightAccessor.getMaxY(); + return new BoundingBox(minX, minY, minZ, minX + 15, maxY, minZ + 15); } @Override @@ -234,9 +361,22 @@ public class IrisChunkGenerator extends CustomChunkGenerator { return delegate.getGenDepth(); } + @Override + public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { + return levelheightaccessor.getMinY() + engine.getHeight(i, j, !heightmap_type.isOpaque().test(Blocks.WATER.defaultBlockState())) + 1; + } + @Override public NoiseColumn getBaseColumn(int i, int j, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { - return delegate.getBaseColumn(i, j, levelheightaccessor, randomstate); + int block = engine.getHeight(i, j, true); + int water = engine.getHeight(i, j, false); + BlockState[] column = new BlockState[levelheightaccessor.getHeight()]; + for (int k = 0; k < column.length; k++) { + if (k <= block) column[k] = Blocks.STONE.defaultBlockState(); + else if (k <= water) column[k] = Blocks.WATER.defaultBlockState(); + else column[k] = Blocks.AIR.defaultBlockState(); + } + return new NoiseColumn(levelheightaccessor.getMinY(), column); } static { @@ -249,7 +389,21 @@ public class IrisChunkGenerator extends CustomChunkGenerator { } if (biomeSource == null) throw new RuntimeException("Could not find biomeSource field in ChunkGenerator!"); + + Method setHeight = null; + for (Method method : Heightmap.class.getDeclaredMethods()) { + var types = method.getParameterTypes(); + if (types.length != 3 || !Arrays.equals(types, new Class[]{int.class, int.class, int.class}) + || !method.getReturnType().equals(void.class)) + continue; + setHeight = method; + break; + } + if (setHeight == null) + throw new RuntimeException("Could not find setHeight method in Heightmap!"); + BIOME_SOURCE = new WrappedField<>(ChunkGenerator.class, biomeSource.getName()); + SET_HEIGHT = new WrappedReturningMethod<>(Heightmap.class, setHeight.getName(), setHeight.getParameterTypes()); } private static ChunkGenerator edit(ChunkGenerator generator, BiomeSource source) { diff --git a/nms/v1_21_R2/src/main/java/com/volmit/iris/core/nms/v1_21_R2/NMSBinding.java b/nms/v1_21_R2/src/main/java/com/volmit/iris/core/nms/v1_21_R2/NMSBinding.java index c724806c3..1f14b42a2 100644 --- a/nms/v1_21_R2/src/main/java/com/volmit/iris/core/nms/v1_21_R2/NMSBinding.java +++ b/nms/v1_21_R2/src/main/java/com/volmit/iris/core/nms/v1_21_R2/NMSBinding.java @@ -1,9 +1,24 @@ package com.volmit.iris.core.nms.v1_21_R2; +import java.awt.Color; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Function; +import java.util.stream.Collectors; + +import com.mojang.serialization.Lifecycle; +import com.volmit.iris.core.link.Identifier; +import com.volmit.iris.core.nms.container.AutoClosing; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.volmit.iris.Iris; import com.volmit.iris.core.nms.INMSBinding; import com.volmit.iris.core.nms.container.BiomeColor; +import com.volmit.iris.core.nms.container.Pair; +import com.volmit.iris.core.nms.container.StructurePlacement; import com.volmit.iris.core.nms.container.BlockProperty; import com.volmit.iris.core.nms.datapack.DataVersion; import com.volmit.iris.engine.data.cache.AtomicCache; @@ -21,6 +36,7 @@ import com.volmit.iris.util.matter.MatterBiomeInject; import com.volmit.iris.util.nbt.mca.NBTWorld; import com.volmit.iris.util.nbt.mca.palette.*; import com.volmit.iris.util.nbt.tag.CompoundTag; +import com.volmit.iris.engine.object.IrisJigsawStructurePlacement; import com.volmit.iris.util.scheduling.J; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.shorts.ShortList; @@ -60,6 +76,8 @@ import net.minecraft.world.level.dimension.LevelStem; import net.minecraft.world.level.levelgen.FlatLevelSource; import net.minecraft.world.level.levelgen.flat.FlatLayerInfo; import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings; +import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement; +import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement; import net.minecraft.world.level.storage.LevelStorageSource; import net.minecraft.world.level.storage.PrimaryLevelData; import org.bukkit.*; @@ -734,6 +752,71 @@ public class NMSBinding implements INMSBinding { return new BlockProperty(property.getName(), property.getValueClass(), state.getValue(property), property.getPossibleValues(), property::getName); } + @Override + public void placeStructures(Chunk chunk) { + var craft = ((CraftChunk) chunk); + var level = craft.getCraftWorld().getHandle(); + var access = ((CraftChunk) chunk).getHandle(ChunkStatus.FULL); + level.getChunkSource().getGenerator().applyBiomeDecoration(level, access, level.structureManager()); + } + + @Override + public KMap collectStructures() { + var structureSets = registry().lookupOrThrow(Registries.STRUCTURE_SET); + var structurePlacements = registry().lookupOrThrow(Registries.STRUCTURE_PLACEMENT); + return structureSets.keySet() + .stream() + .map(structureSets::get) + .filter(Optional::isPresent) + .map(Optional::get) + .map(holder -> { + var set = holder.value(); + var placement = set.placement(); + var key = holder.key().location(); + StructurePlacement.StructurePlacementBuilder builder; + if (placement instanceof RandomSpreadStructurePlacement random) { + builder = StructurePlacement.RandomSpread.builder() + .separation(random.separation()) + .spacing(random.spacing()) + .spreadType(switch (random.spreadType()) { + case LINEAR -> IrisJigsawStructurePlacement.SpreadType.LINEAR; + case TRIANGULAR -> IrisJigsawStructurePlacement.SpreadType.TRIANGULAR; + }); + } else if (placement instanceof ConcentricRingsStructurePlacement rings) { + builder = StructurePlacement.ConcentricRings.builder() + .distance(rings.distance()) + .spread(rings.spread()) + .count(rings.count()); + } else { + Iris.warn("Unsupported structure placement for set " + key + " with type " + structurePlacements.getKey(placement.type())); + return null; + } + + return new Pair<>(new Identifier(key.getNamespace(), key.getPath()), builder + .salt(placement.salt) + .frequency(placement.frequency) + .structures(set.structures() + .stream() + .map(entry -> new StructurePlacement.Structure( + entry.weight(), + entry.structure() + .unwrapKey() + .map(ResourceKey::location) + .map(ResourceLocation::toString) + .orElse(null), + entry.structure().tags() + .map(TagKey::location) + .map(ResourceLocation::toString) + .toList() + )) + .filter(StructurePlacement.Structure::isValid) + .toList()) + .build()); + }) + .filter(Objects::nonNull) + .collect(Collectors.toMap(Pair::getA, Pair::getB, (a, b) -> a, KMap::new)); + } + public LevelStem levelStem(RegistryAccess access, ChunkGenerator raw) { if (!(raw instanceof PlatformChunkGenerator gen)) throw new IllegalStateException("Generator is not platform chunk generator!"); diff --git a/nms/v1_21_R3/src/main/java/com/volmit/iris/core/nms/v1_21_R3/IrisChunkGenerator.java b/nms/v1_21_R3/src/main/java/com/volmit/iris/core/nms/v1_21_R3/IrisChunkGenerator.java index a26bb67cc..65b785929 100644 --- a/nms/v1_21_R3/src/main/java/com/volmit/iris/core/nms/v1_21_R3/IrisChunkGenerator.java +++ b/nms/v1_21_R3/src/main/java/com/volmit/iris/core/nms/v1_21_R3/IrisChunkGenerator.java @@ -8,12 +8,17 @@ import com.volmit.iris.engine.framework.ResultLocator; import com.volmit.iris.engine.framework.WrongEngineBroException; import com.volmit.iris.engine.object.IrisJigsawStructure; import com.volmit.iris.engine.object.IrisJigsawStructurePlacement; +import com.volmit.iris.engine.object.IrisStructurePopulator; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.collection.KSet; import com.volmit.iris.util.mantle.flag.MantleFlag; import com.volmit.iris.util.math.Position2; import com.volmit.iris.util.reflect.WrappedField; +import com.volmit.iris.util.reflect.WrappedReturningMethod; +import net.minecraft.CrashReport; +import net.minecraft.CrashReportCategory; +import net.minecraft.ReportedException; import net.minecraft.core.*; import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceKey; @@ -26,38 +31,49 @@ import net.minecraft.util.random.WeightedRandomList; import net.minecraft.world.entity.MobCategory; import net.minecraft.world.level.*; import net.minecraft.world.level.biome.*; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.chunk.ChunkGeneratorStructureState; -import net.minecraft.world.level.levelgen.Heightmap; -import net.minecraft.world.level.levelgen.RandomState; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.levelgen.*; import net.minecraft.world.level.levelgen.blending.Blender; +import net.minecraft.world.level.levelgen.structure.BuiltinStructures; +import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureSet; +import net.minecraft.world.level.levelgen.structure.StructureStart; import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; +import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.craftbukkit.v1_21_R3.CraftWorld; import org.bukkit.craftbukkit.v1_21_R3.generator.CustomChunkGenerator; +import org.bukkit.craftbukkit.v1_21_R3.generator.structure.CraftStructure; +import org.bukkit.event.world.AsyncStructureSpawnEvent; import org.spigotmc.SpigotWorldConfig; import javax.annotation.Nullable; import java.lang.reflect.Field; -import java.util.List; -import java.util.Objects; -import java.util.Optional; +import java.lang.reflect.Method; +import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +import java.util.function.Supplier; public class IrisChunkGenerator extends CustomChunkGenerator { private static final WrappedField BIOME_SOURCE; + private static final WrappedReturningMethod SET_HEIGHT; private final ChunkGenerator delegate; private final Engine engine; private final KMap, KSet> structures = new KMap<>(); + private final IrisStructurePopulator populator; public IrisChunkGenerator(ChunkGenerator delegate, long seed, Engine engine, World world) { super(((CraftWorld) world).getHandle(), edit(delegate, new CustomBiomeSource(seed, engine, world)), null); this.delegate = delegate; this.engine = engine; + this.populator = new IrisStructurePopulator(engine); var dimension = engine.getDimension(); KSet placements = new KSet<>(); @@ -174,8 +190,61 @@ public class IrisChunkGenerator extends CustomChunkGenerator { } @Override - public void createStructures(RegistryAccess iregistrycustom, ChunkGeneratorStructureState chunkgeneratorstructurestate, StructureManager structuremanager, ChunkAccess ichunkaccess, StructureTemplateManager structuretemplatemanager, ResourceKey resourcekey) { - delegate.createStructures(iregistrycustom, chunkgeneratorstructurestate, structuremanager, ichunkaccess, structuretemplatemanager, resourcekey); + public void createStructures(RegistryAccess registryAccess, ChunkGeneratorStructureState structureState, StructureManager structureManager, ChunkAccess access, StructureTemplateManager templateManager, ResourceKey levelKey) { + if (!structureManager.shouldGenerateStructures()) + return; + var chunkPos = access.getPos(); + var sectionPos = SectionPos.bottomOf(access); + var registry = registryAccess.lookupOrThrow(Registries.STRUCTURE); + populator.populateStructures(chunkPos.x, chunkPos.z, (key, ignoreBiomes) -> { + var loc = ResourceLocation.tryParse(key); + if (loc == null) return false; + var holder = registry.get(loc).orElse(null); + if (holder == null) return false; + var structure = holder.value(); + var biomes = structure.biomes(); + + var start = structure.generate( + holder, + levelKey, + registryAccess, + this, + biomeSource, + structureState.randomState(), + templateManager, + structureState.getLevelSeed(), + chunkPos, + fetchReferences(structureManager, access, sectionPos, structure), + access, + biome -> ignoreBiomes || biomes.contains(biome) + ); + + if (!start.isValid()) + return false; + + BoundingBox box = start.getBoundingBox(); + AsyncStructureSpawnEvent event = new AsyncStructureSpawnEvent( + structureManager.level.getMinecraftWorld().getWorld(), + CraftStructure.minecraftToBukkit(structure), + new org.bukkit.util.BoundingBox( + box.minX(), + box.minY(), + box.minZ(), + box.maxX(), + box.maxY(), + box.maxZ() + ), chunkPos.x, chunkPos.z); + Bukkit.getPluginManager().callEvent(event); + if (!event.isCancelled()) { + structureManager.setStartForStructure(sectionPos, structure, start, access); + } + return true; + }); + } + + private static int fetchReferences(StructureManager structureManager, ChunkAccess access, SectionPos sectionPos, Structure structure) { + StructureStart structurestart = structureManager.getStartForStructure(sectionPos, structure, access); + return structurestart != null ? structurestart.getReferences() : 0; } @Override @@ -208,11 +277,6 @@ public class IrisChunkGenerator extends CustomChunkGenerator { return delegate.fillFromNoise(blender, randomstate, structuremanager, ichunkaccess); } - @Override - public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { - return delegate.getBaseHeight(i, j, heightmap_type, levelheightaccessor, randomstate); - } - @Override public WeightedRandomList getMobsAt(Holder holder, StructureManager structuremanager, MobCategory enumcreaturetype, BlockPos blockposition) { return delegate.getMobsAt(holder, structuremanager, enumcreaturetype, blockposition); @@ -220,7 +284,7 @@ public class IrisChunkGenerator extends CustomChunkGenerator { @Override public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) { - delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager); + applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, true); } @Override @@ -230,22 +294,70 @@ public class IrisChunkGenerator extends CustomChunkGenerator { @Override public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager, boolean vanilla) { - delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, vanilla); + addVanillaDecorations(generatoraccessseed, ichunkaccess, structuremanager); + delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, false); } @Override - public int getFirstFreeHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { - return delegate.getFirstFreeHeight(i, j, heightmap_type, levelheightaccessor, randomstate); + public void addVanillaDecorations(WorldGenLevel level, ChunkAccess chunkAccess, StructureManager structureManager) { + if (!structureManager.shouldGenerateStructures()) + return; + + SectionPos sectionPos = SectionPos.of(chunkAccess.getPos(), level.getMinSectionY()); + BlockPos blockPos = sectionPos.origin(); + WorldgenRandom random = new WorldgenRandom(new XoroshiroRandomSource(RandomSupport.generateUniqueSeed())); + long i = random.setDecorationSeed(level.getSeed(), blockPos.getX(), blockPos.getZ()); + var structures = level.registryAccess().lookupOrThrow(Registries.STRUCTURE); + var list = structures.stream() + .sorted(Comparator.comparingInt(s -> s.step().ordinal())) + .toList(); + + var surface = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.WORLD_SURFACE_WG); + var ocean = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.OCEAN_FLOOR_WG); + var motion = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.MOTION_BLOCKING); + var motionNoLeaves = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES); + + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + int wX = x + blockPos.getX(); + int wZ = z + blockPos.getZ(); + + int noAir = engine.getHeight(wX, wZ, false) + engine.getMinHeight() + 1; + int noFluid = engine.getHeight(wX, wZ, true) + engine.getMinHeight() + 1; + SET_HEIGHT.invoke(ocean, x, z, Math.min(noFluid, ocean.getFirstAvailable(x, z))); + SET_HEIGHT.invoke(surface, x, z, Math.min(noAir, surface.getFirstAvailable(x, z))); + SET_HEIGHT.invoke(motion, x, z, Math.min(noAir, motion.getFirstAvailable(x, z))); + SET_HEIGHT.invoke(motionNoLeaves, x, z, Math.min(noAir, motionNoLeaves.getFirstAvailable(x, z))); + } + } + + for (int j = 0; j < list.size(); j++) { + Structure structure = list.get(j); + random.setFeatureSeed(i, j, structure.step().ordinal()); + Supplier supplier = () -> structures.getResourceKey(structure).map(Object::toString).orElseGet(structure::toString); + + try { + level.setCurrentlyGenerating(supplier); + structureManager.startsForStructure(sectionPos, structure).forEach((start) -> start.placeInChunk(level, structureManager, this, random, getWritableArea(chunkAccess), chunkAccess.getPos())); + } catch (Exception exception) { + CrashReport crashReport = CrashReport.forThrowable(exception, "Feature placement"); + CrashReportCategory category = crashReport.addCategory("Feature"); + category.setDetail("Description", supplier::get); + throw new ReportedException(crashReport); + } + } + + Heightmap.primeHeightmaps(chunkAccess, ChunkStatus.FINAL_HEIGHTMAPS); } - @Override - public int getFirstOccupiedHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { - return delegate.getFirstOccupiedHeight(i, j, heightmap_type, levelheightaccessor, randomstate); - } - - @Override - public void addVanillaDecorations(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) { - delegate.addVanillaDecorations(generatoraccessseed, ichunkaccess, structuremanager); + private static BoundingBox getWritableArea(ChunkAccess ichunkaccess) { + ChunkPos chunkPos = ichunkaccess.getPos(); + int minX = chunkPos.getMinBlockX(); + int minZ = chunkPos.getMinBlockZ(); + LevelHeightAccessor heightAccessor = ichunkaccess.getHeightAccessorForGeneration(); + int minY = heightAccessor.getMinY() + 1; + int maxY = heightAccessor.getMaxY(); + return new BoundingBox(minX, minY, minZ, minX + 15, maxY, minZ + 15); } @Override @@ -263,9 +375,22 @@ public class IrisChunkGenerator extends CustomChunkGenerator { return delegate.getGenDepth(); } + @Override + public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { + return levelheightaccessor.getMinY() + engine.getHeight(i, j, !heightmap_type.isOpaque().test(Blocks.WATER.defaultBlockState())) + 1; + } + @Override public NoiseColumn getBaseColumn(int i, int j, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { - return delegate.getBaseColumn(i, j, levelheightaccessor, randomstate); + int block = engine.getHeight(i, j, true); + int water = engine.getHeight(i, j, false); + BlockState[] column = new BlockState[levelheightaccessor.getHeight()]; + for (int k = 0; k < column.length; k++) { + if (k <= block) column[k] = Blocks.STONE.defaultBlockState(); + else if (k <= water) column[k] = Blocks.WATER.defaultBlockState(); + else column[k] = Blocks.AIR.defaultBlockState(); + } + return new NoiseColumn(levelheightaccessor.getMinY(), column); } @Override @@ -294,7 +419,21 @@ public class IrisChunkGenerator extends CustomChunkGenerator { } if (biomeSource == null) throw new RuntimeException("Could not find biomeSource field in ChunkGenerator!"); + + Method setHeight = null; + for (Method method : Heightmap.class.getDeclaredMethods()) { + var types = method.getParameterTypes(); + if (types.length != 3 || !Arrays.equals(types, new Class[]{int.class, int.class, int.class}) + || !method.getReturnType().equals(void.class)) + continue; + setHeight = method; + break; + } + if (setHeight == null) + throw new RuntimeException("Could not find setHeight method in Heightmap!"); + BIOME_SOURCE = new WrappedField<>(ChunkGenerator.class, biomeSource.getName()); + SET_HEIGHT = new WrappedReturningMethod<>(Heightmap.class, setHeight.getName(), setHeight.getParameterTypes()); } private static ChunkGenerator edit(ChunkGenerator generator, BiomeSource source) { diff --git a/nms/v1_21_R3/src/main/java/com/volmit/iris/core/nms/v1_21_R3/NMSBinding.java b/nms/v1_21_R3/src/main/java/com/volmit/iris/core/nms/v1_21_R3/NMSBinding.java index 9f2449ec4..061a8effa 100644 --- a/nms/v1_21_R3/src/main/java/com/volmit/iris/core/nms/v1_21_R3/NMSBinding.java +++ b/nms/v1_21_R3/src/main/java/com/volmit/iris/core/nms/v1_21_R3/NMSBinding.java @@ -2,12 +2,17 @@ package com.volmit.iris.core.nms.v1_21_R3; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.volmit.iris.Iris; +import com.volmit.iris.core.link.Identifier; import com.volmit.iris.core.nms.INMSBinding; import com.volmit.iris.core.nms.container.BiomeColor; +import com.volmit.iris.core.nms.container.Pair; +import com.volmit.iris.core.nms.container.StructurePlacement; import com.volmit.iris.core.nms.container.BlockProperty; import com.volmit.iris.core.nms.datapack.DataVersion; import com.volmit.iris.engine.data.cache.AtomicCache; import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.engine.object.IrisJigsawStructure; +import com.volmit.iris.engine.object.IrisJigsawStructurePlacement; import com.volmit.iris.engine.platform.PlatformChunkGenerator; import com.volmit.iris.util.agent.Agent; import com.volmit.iris.util.collection.KList; @@ -61,6 +66,9 @@ import net.minecraft.world.level.dimension.LevelStem; import net.minecraft.world.level.levelgen.FlatLevelSource; import net.minecraft.world.level.levelgen.flat.FlatLayerInfo; import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings; +import net.minecraft.world.level.levelgen.structure.StructureSet; +import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement; +import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement; import net.minecraft.world.level.storage.LevelStorageSource; import net.minecraft.world.level.storage.PrimaryLevelData; import org.bukkit.*; @@ -89,10 +97,16 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; public class NMSBinding implements INMSBinding { private final KMap baseBiomeCache = new KMap<>(); @@ -179,14 +193,14 @@ public class NMSBinding implements INMSBinding { } @Contract(value = "null, _, _ -> null", pure = true) - private Object convertFromTag(net.minecraft.nbt.Tag tag, int depth, int maxDepth) { + private Object convertFromTag(Tag tag, int depth, int maxDepth) { if (tag == null || depth > maxDepth) return null; return switch (tag) { case CollectionTag collection -> { KList list = new KList<>(); for (Object i : collection) { - if (i instanceof net.minecraft.nbt.Tag t) + if (i instanceof Tag t) list.add(convertFromTag(t, depth + 1, maxDepth)); else list.add(i); } @@ -243,7 +257,7 @@ public class NMSBinding implements INMSBinding { yield tag; } case List list -> { - var tag = new net.minecraft.nbt.ListTag(); + var tag = new ListTag(); for (var i : list) { tag.add(convertToTag(i, depth + 1, maxDepth)); } @@ -497,13 +511,13 @@ public class NMSBinding implements INMSBinding { @Override public MCAPaletteAccess createPalette() { MCAIdMapper registry = registryCache.aquireNasty(() -> { - Field cf = net.minecraft.core.IdMapper.class.getDeclaredField("tToId"); - Field df = net.minecraft.core.IdMapper.class.getDeclaredField("idToT"); - Field bf = net.minecraft.core.IdMapper.class.getDeclaredField("nextId"); + Field cf = IdMapper.class.getDeclaredField("tToId"); + Field df = IdMapper.class.getDeclaredField("idToT"); + Field bf = IdMapper.class.getDeclaredField("nextId"); cf.setAccessible(true); df.setAccessible(true); bf.setAccessible(true); - net.minecraft.core.IdMapper blockData = Block.BLOCK_STATE_REGISTRY; + IdMapper blockData = Block.BLOCK_STATE_REGISTRY; int b = bf.getInt(blockData); Object2IntMap c = (Object2IntMap) cf.get(blockData); List d = (List) df.get(blockData); @@ -601,7 +615,7 @@ public class NMSBinding implements INMSBinding { } @Override - public java.awt.Color getBiomeColor(Location location, BiomeColor type) { + public Color getBiomeColor(Location location, BiomeColor type) { LevelReader reader = ((CraftWorld) location.getWorld()).getHandle(); var holder = reader.getBiome(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ())); var biome = holder.value(); @@ -734,6 +748,71 @@ public class NMSBinding implements INMSBinding { return new BlockProperty(property.getName(), property.getValueClass(), state.getValue(property), property.getPossibleValues(), property::getName); } + @Override + public void placeStructures(Chunk chunk) { + var craft = ((CraftChunk) chunk); + var level = craft.getCraftWorld().getHandle(); + var access = ((CraftChunk) chunk).getHandle(ChunkStatus.FULL); + level.getChunkSource().getGenerator().applyBiomeDecoration(level, access, level.structureManager()); + } + + @Override + public KMap collectStructures() { + var structureSets = registry().lookupOrThrow(Registries.STRUCTURE_SET); + var structurePlacements = registry().lookupOrThrow(Registries.STRUCTURE_PLACEMENT); + return structureSets.keySet() + .stream() + .map(structureSets::get) + .filter(Optional::isPresent) + .map(Optional::get) + .map(holder -> { + var set = holder.value(); + var placement = set.placement(); + var key = holder.key().location(); + StructurePlacement.StructurePlacementBuilder builder; + if (placement instanceof RandomSpreadStructurePlacement random) { + builder = StructurePlacement.RandomSpread.builder() + .separation(random.separation()) + .spacing(random.spacing()) + .spreadType(switch (random.spreadType()) { + case LINEAR -> IrisJigsawStructurePlacement.SpreadType.LINEAR; + case TRIANGULAR -> IrisJigsawStructurePlacement.SpreadType.TRIANGULAR; + }); + } else if (placement instanceof ConcentricRingsStructurePlacement rings) { + builder = StructurePlacement.ConcentricRings.builder() + .distance(rings.distance()) + .spread(rings.spread()) + .count(rings.count()); + } else { + Iris.warn("Unsupported structure placement for set " + key + " with type " + structurePlacements.getKey(placement.type())); + return null; + } + + return new Pair<>(new Identifier(key.getNamespace(), key.getPath()), builder + .salt(placement.salt) + .frequency(placement.frequency) + .structures(set.structures() + .stream() + .map(entry -> new StructurePlacement.Structure( + entry.weight(), + entry.structure() + .unwrapKey() + .map(ResourceKey::location) + .map(ResourceLocation::toString) + .orElse(null), + entry.structure().tags() + .map(TagKey::location) + .map(ResourceLocation::toString) + .toList() + )) + .filter(StructurePlacement.Structure::isValid) + .toList()) + .build()); + }) + .filter(Objects::nonNull) + .collect(Collectors.toMap(Pair::getA, Pair::getB, (a,b) -> a, KMap::new)); + } + public LevelStem levelStem(RegistryAccess access, ChunkGenerator raw) { if (!(raw instanceof PlatformChunkGenerator gen)) throw new IllegalStateException("Generator is not platform chunk generator!"); diff --git a/nms/v1_21_R4/src/main/java/com/volmit/iris/core/nms/v1_21_R4/IrisChunkGenerator.java b/nms/v1_21_R4/src/main/java/com/volmit/iris/core/nms/v1_21_R4/IrisChunkGenerator.java index 11621ad98..91495316c 100644 --- a/nms/v1_21_R4/src/main/java/com/volmit/iris/core/nms/v1_21_R4/IrisChunkGenerator.java +++ b/nms/v1_21_R4/src/main/java/com/volmit/iris/core/nms/v1_21_R4/IrisChunkGenerator.java @@ -8,12 +8,17 @@ import com.volmit.iris.engine.framework.ResultLocator; import com.volmit.iris.engine.framework.WrongEngineBroException; import com.volmit.iris.engine.object.IrisJigsawStructure; import com.volmit.iris.engine.object.IrisJigsawStructurePlacement; +import com.volmit.iris.engine.object.IrisStructurePopulator; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.collection.KSet; import com.volmit.iris.util.mantle.flag.MantleFlag; import com.volmit.iris.util.math.Position2; import com.volmit.iris.util.reflect.WrappedField; +import com.volmit.iris.util.reflect.WrappedReturningMethod; +import net.minecraft.CrashReport; +import net.minecraft.CrashReportCategory; +import net.minecraft.ReportedException; import net.minecraft.core.*; import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceKey; @@ -26,38 +31,48 @@ import net.minecraft.util.random.WeightedList; import net.minecraft.world.entity.MobCategory; import net.minecraft.world.level.*; import net.minecraft.world.level.biome.*; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.chunk.ChunkGeneratorStructureState; -import net.minecraft.world.level.levelgen.Heightmap; -import net.minecraft.world.level.levelgen.RandomState; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.levelgen.*; import net.minecraft.world.level.levelgen.blending.Blender; +import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureSet; +import net.minecraft.world.level.levelgen.structure.StructureStart; import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; +import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.craftbukkit.v1_21_R4.CraftWorld; import org.bukkit.craftbukkit.v1_21_R4.generator.CustomChunkGenerator; +import org.bukkit.craftbukkit.v1_21_R4.generator.structure.CraftStructure; +import org.bukkit.event.world.AsyncStructureSpawnEvent; import org.spigotmc.SpigotWorldConfig; import javax.annotation.Nullable; import java.lang.reflect.Field; -import java.util.List; -import java.util.Objects; -import java.util.Optional; +import java.lang.reflect.Method; +import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +import java.util.function.Supplier; public class IrisChunkGenerator extends CustomChunkGenerator { private static final WrappedField BIOME_SOURCE; + private static final WrappedReturningMethod SET_HEIGHT; private final ChunkGenerator delegate; private final Engine engine; private final KMap, KSet> structures = new KMap<>(); + private final IrisStructurePopulator populator; public IrisChunkGenerator(ChunkGenerator delegate, long seed, Engine engine, World world) { super(((CraftWorld) world).getHandle(), edit(delegate, new CustomBiomeSource(seed, engine, world)), null); this.delegate = delegate; this.engine = engine; + this.populator = new IrisStructurePopulator(engine); var dimension = engine.getDimension(); KSet placements = new KSet<>(); @@ -174,8 +189,61 @@ public class IrisChunkGenerator extends CustomChunkGenerator { } @Override - public void createStructures(RegistryAccess iregistrycustom, ChunkGeneratorStructureState chunkgeneratorstructurestate, StructureManager structuremanager, ChunkAccess ichunkaccess, StructureTemplateManager structuretemplatemanager, ResourceKey resourcekey) { - delegate.createStructures(iregistrycustom, chunkgeneratorstructurestate, structuremanager, ichunkaccess, structuretemplatemanager, resourcekey); + public void createStructures(RegistryAccess registryAccess, ChunkGeneratorStructureState structureState, StructureManager structureManager, ChunkAccess access, StructureTemplateManager templateManager, ResourceKey levelKey) { + if (!structureManager.shouldGenerateStructures()) + return; + var chunkPos = access.getPos(); + var sectionPos = SectionPos.bottomOf(access); + var registry = registryAccess.lookupOrThrow(Registries.STRUCTURE); + populator.populateStructures(chunkPos.x, chunkPos.z, (key, ignoreBiomes) -> { + var loc = ResourceLocation.tryParse(key); + if (loc == null) return false; + var holder = registry.get(loc).orElse(null); + if (holder == null) return false; + var structure = holder.value(); + var biomes = structure.biomes(); + + var start = structure.generate( + holder, + levelKey, + registryAccess, + this, + biomeSource, + structureState.randomState(), + templateManager, + structureState.getLevelSeed(), + chunkPos, + fetchReferences(structureManager, access, sectionPos, structure), + access, + biome -> ignoreBiomes || biomes.contains(biome) + ); + + if (!start.isValid()) + return false; + + BoundingBox box = start.getBoundingBox(); + AsyncStructureSpawnEvent event = new AsyncStructureSpawnEvent( + structureManager.level.getMinecraftWorld().getWorld(), + CraftStructure.minecraftToBukkit(structure), + new org.bukkit.util.BoundingBox( + box.minX(), + box.minY(), + box.minZ(), + box.maxX(), + box.maxY(), + box.maxZ() + ), chunkPos.x, chunkPos.z); + Bukkit.getPluginManager().callEvent(event); + if (!event.isCancelled()) { + structureManager.setStartForStructure(sectionPos, structure, start, access); + } + return true; + }); + } + + private static int fetchReferences(StructureManager structureManager, ChunkAccess access, SectionPos sectionPos, Structure structure) { + StructureStart structurestart = structureManager.getStartForStructure(sectionPos, structure, access); + return structurestart != null ? structurestart.getReferences() : 0; } @Override @@ -208,11 +276,6 @@ public class IrisChunkGenerator extends CustomChunkGenerator { return delegate.fillFromNoise(blender, randomstate, structuremanager, ichunkaccess); } - @Override - public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { - return delegate.getBaseHeight(i, j, heightmap_type, levelheightaccessor, randomstate); - } - @Override public WeightedList getMobsAt(Holder holder, StructureManager structuremanager, MobCategory enumcreaturetype, BlockPos blockposition) { return delegate.getMobsAt(holder, structuremanager, enumcreaturetype, blockposition); @@ -220,7 +283,7 @@ public class IrisChunkGenerator extends CustomChunkGenerator { @Override public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) { - delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager); + applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, true); } @Override @@ -230,22 +293,70 @@ public class IrisChunkGenerator extends CustomChunkGenerator { @Override public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager, boolean vanilla) { - delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, vanilla); + addVanillaDecorations(generatoraccessseed, ichunkaccess, structuremanager); + delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, false); } @Override - public int getFirstFreeHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { - return delegate.getFirstFreeHeight(i, j, heightmap_type, levelheightaccessor, randomstate); + public void addVanillaDecorations(WorldGenLevel level, ChunkAccess chunkAccess, StructureManager structureManager) { + if (!structureManager.shouldGenerateStructures()) + return; + + SectionPos sectionPos = SectionPos.of(chunkAccess.getPos(), level.getMinSectionY()); + BlockPos blockPos = sectionPos.origin(); + WorldgenRandom random = new WorldgenRandom(new XoroshiroRandomSource(RandomSupport.generateUniqueSeed())); + long i = random.setDecorationSeed(level.getSeed(), blockPos.getX(), blockPos.getZ()); + var structures = level.registryAccess().lookupOrThrow(Registries.STRUCTURE); + var list = structures.stream() + .sorted(Comparator.comparingInt(s -> s.step().ordinal())) + .toList(); + + var surface = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.WORLD_SURFACE_WG); + var ocean = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.OCEAN_FLOOR_WG); + var motion = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.MOTION_BLOCKING); + var motionNoLeaves = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES); + + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + int wX = x + blockPos.getX(); + int wZ = z + blockPos.getZ(); + + int noAir = engine.getHeight(wX, wZ, false) + engine.getMinHeight() + 1; + int noFluid = engine.getHeight(wX, wZ, true) + engine.getMinHeight() + 1; + SET_HEIGHT.invoke(ocean, x, z, Math.min(noFluid, ocean.getFirstAvailable(x, z))); + SET_HEIGHT.invoke(surface, x, z, Math.min(noAir, surface.getFirstAvailable(x, z))); + SET_HEIGHT.invoke(motion, x, z, Math.min(noAir, motion.getFirstAvailable(x, z))); + SET_HEIGHT.invoke(motionNoLeaves, x, z, Math.min(noAir, motionNoLeaves.getFirstAvailable(x, z))); + } + } + + for (int j = 0; j < list.size(); j++) { + Structure structure = list.get(j); + random.setFeatureSeed(i, j, structure.step().ordinal()); + Supplier supplier = () -> structures.getResourceKey(structure).map(Object::toString).orElseGet(structure::toString); + + try { + level.setCurrentlyGenerating(supplier); + structureManager.startsForStructure(sectionPos, structure).forEach((start) -> start.placeInChunk(level, structureManager, this, random, getWritableArea(chunkAccess), chunkAccess.getPos())); + } catch (Exception exception) { + CrashReport crashReport = CrashReport.forThrowable(exception, "Feature placement"); + CrashReportCategory category = crashReport.addCategory("Feature"); + category.setDetail("Description", supplier::get); + throw new ReportedException(crashReport); + } + } + + Heightmap.primeHeightmaps(chunkAccess, ChunkStatus.FINAL_HEIGHTMAPS); } - @Override - public int getFirstOccupiedHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { - return delegate.getFirstOccupiedHeight(i, j, heightmap_type, levelheightaccessor, randomstate); - } - - @Override - public void addVanillaDecorations(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) { - delegate.addVanillaDecorations(generatoraccessseed, ichunkaccess, structuremanager); + private static BoundingBox getWritableArea(ChunkAccess ichunkaccess) { + ChunkPos chunkPos = ichunkaccess.getPos(); + int minX = chunkPos.getMinBlockX(); + int minZ = chunkPos.getMinBlockZ(); + LevelHeightAccessor heightAccessor = ichunkaccess.getHeightAccessorForGeneration(); + int minY = heightAccessor.getMinY() + 1; + int maxY = heightAccessor.getMaxY(); + return new BoundingBox(minX, minY, minZ, minX + 15, maxY, minZ + 15); } @Override @@ -263,9 +374,22 @@ public class IrisChunkGenerator extends CustomChunkGenerator { return delegate.getGenDepth(); } + @Override + public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { + return levelheightaccessor.getMinY() + engine.getHeight(i, j, !heightmap_type.isOpaque().test(Blocks.WATER.defaultBlockState())) + 1; + } + @Override public NoiseColumn getBaseColumn(int i, int j, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { - return delegate.getBaseColumn(i, j, levelheightaccessor, randomstate); + int block = engine.getHeight(i, j, true); + int water = engine.getHeight(i, j, false); + BlockState[] column = new BlockState[levelheightaccessor.getHeight()]; + for (int k = 0; k < column.length; k++) { + if (k <= block) column[k] = Blocks.STONE.defaultBlockState(); + else if (k <= water) column[k] = Blocks.WATER.defaultBlockState(); + else column[k] = Blocks.AIR.defaultBlockState(); + } + return new NoiseColumn(levelheightaccessor.getMinY(), column); } @Override @@ -294,7 +418,21 @@ public class IrisChunkGenerator extends CustomChunkGenerator { } if (biomeSource == null) throw new RuntimeException("Could not find biomeSource field in ChunkGenerator!"); + + Method setHeight = null; + for (Method method : Heightmap.class.getDeclaredMethods()) { + var types = method.getParameterTypes(); + if (types.length != 3 || !Arrays.equals(types, new Class[]{int.class, int.class, int.class}) + || !method.getReturnType().equals(void.class)) + continue; + setHeight = method; + break; + } + if (setHeight == null) + throw new RuntimeException("Could not find setHeight method in Heightmap!"); + BIOME_SOURCE = new WrappedField<>(ChunkGenerator.class, biomeSource.getName()); + SET_HEIGHT = new WrappedReturningMethod<>(Heightmap.class, setHeight.getName(), setHeight.getParameterTypes()); } private static ChunkGenerator edit(ChunkGenerator generator, BiomeSource source) { diff --git a/nms/v1_21_R4/src/main/java/com/volmit/iris/core/nms/v1_21_R4/NMSBinding.java b/nms/v1_21_R4/src/main/java/com/volmit/iris/core/nms/v1_21_R4/NMSBinding.java index ddcd66bdd..04d1acae2 100644 --- a/nms/v1_21_R4/src/main/java/com/volmit/iris/core/nms/v1_21_R4/NMSBinding.java +++ b/nms/v1_21_R4/src/main/java/com/volmit/iris/core/nms/v1_21_R4/NMSBinding.java @@ -2,12 +2,16 @@ package com.volmit.iris.core.nms.v1_21_R4; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.volmit.iris.Iris; +import com.volmit.iris.core.link.Identifier; import com.volmit.iris.core.nms.INMSBinding; import com.volmit.iris.core.nms.container.BiomeColor; +import com.volmit.iris.core.nms.container.Pair; +import com.volmit.iris.core.nms.container.StructurePlacement; import com.volmit.iris.core.nms.container.BlockProperty; import com.volmit.iris.core.nms.datapack.DataVersion; import com.volmit.iris.engine.data.cache.AtomicCache; import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.engine.object.IrisJigsawStructurePlacement; import com.volmit.iris.engine.platform.PlatformChunkGenerator; import com.volmit.iris.util.agent.Agent; import com.volmit.iris.util.collection.KList; @@ -61,6 +65,8 @@ import net.minecraft.world.level.dimension.LevelStem; import net.minecraft.world.level.levelgen.FlatLevelSource; import net.minecraft.world.level.levelgen.flat.FlatLayerInfo; import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings; +import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement; +import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement; import net.minecraft.world.level.storage.LevelStorageSource; import net.minecraft.world.level.storage.PrimaryLevelData; import org.bukkit.*; @@ -93,6 +99,7 @@ import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; public class NMSBinding implements INMSBinding { private final KMap baseBiomeCache = new KMap<>(); @@ -734,6 +741,71 @@ public class NMSBinding implements INMSBinding { return new BlockProperty(property.getName(), property.getValueClass(), state.getValue(property), property.getPossibleValues(), property::getName); } + @Override + public void placeStructures(Chunk chunk) { + var craft = ((CraftChunk) chunk); + var level = craft.getCraftWorld().getHandle(); + var access = ((CraftChunk) chunk).getHandle(ChunkStatus.FULL); + level.getChunkSource().getGenerator().applyBiomeDecoration(level, access, level.structureManager()); + } + + @Override + public KMap collectStructures() { + var structureSets = registry().lookupOrThrow(Registries.STRUCTURE_SET); + var structurePlacements = registry().lookupOrThrow(Registries.STRUCTURE_PLACEMENT); + return structureSets.keySet() + .stream() + .map(structureSets::get) + .filter(Optional::isPresent) + .map(Optional::get) + .map(holder -> { + var set = holder.value(); + var placement = set.placement(); + var key = holder.key().location(); + StructurePlacement.StructurePlacementBuilder builder; + if (placement instanceof RandomSpreadStructurePlacement random) { + builder = StructurePlacement.RandomSpread.builder() + .separation(random.separation()) + .spacing(random.spacing()) + .spreadType(switch (random.spreadType()) { + case LINEAR -> IrisJigsawStructurePlacement.SpreadType.LINEAR; + case TRIANGULAR -> IrisJigsawStructurePlacement.SpreadType.TRIANGULAR; + }); + } else if (placement instanceof ConcentricRingsStructurePlacement rings) { + builder = StructurePlacement.ConcentricRings.builder() + .distance(rings.distance()) + .spread(rings.spread()) + .count(rings.count()); + } else { + Iris.warn("Unsupported structure placement for set " + key + " with type " + structurePlacements.getKey(placement.type())); + return null; + } + + return new Pair<>(new Identifier(key.getNamespace(), key.getPath()), builder + .salt(placement.salt) + .frequency(placement.frequency) + .structures(set.structures() + .stream() + .map(entry -> new StructurePlacement.Structure( + entry.weight(), + entry.structure() + .unwrapKey() + .map(ResourceKey::location) + .map(ResourceLocation::toString) + .orElse(null), + entry.structure().tags() + .map(TagKey::location) + .map(ResourceLocation::toString) + .toList() + )) + .filter(StructurePlacement.Structure::isValid) + .toList()) + .build()); + }) + .filter(Objects::nonNull) + .collect(Collectors.toMap(Pair::getA, Pair::getB, (a, b) -> a, KMap::new)); + } + public LevelStem levelStem(RegistryAccess access, ChunkGenerator raw) { if (!(raw instanceof PlatformChunkGenerator gen)) throw new IllegalStateException("Generator is not platform chunk generator!"); diff --git a/nms/v1_21_R5/src/main/java/com/volmit/iris/core/nms/v1_21_R5/IrisChunkGenerator.java b/nms/v1_21_R5/src/main/java/com/volmit/iris/core/nms/v1_21_R5/IrisChunkGenerator.java index 740511f77..341c853fd 100644 --- a/nms/v1_21_R5/src/main/java/com/volmit/iris/core/nms/v1_21_R5/IrisChunkGenerator.java +++ b/nms/v1_21_R5/src/main/java/com/volmit/iris/core/nms/v1_21_R5/IrisChunkGenerator.java @@ -8,12 +8,17 @@ import com.volmit.iris.engine.framework.ResultLocator; import com.volmit.iris.engine.framework.WrongEngineBroException; import com.volmit.iris.engine.object.IrisJigsawStructure; import com.volmit.iris.engine.object.IrisJigsawStructurePlacement; +import com.volmit.iris.engine.object.IrisStructurePopulator; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.collection.KSet; import com.volmit.iris.util.mantle.flag.MantleFlag; import com.volmit.iris.util.math.Position2; import com.volmit.iris.util.reflect.WrappedField; +import com.volmit.iris.util.reflect.WrappedReturningMethod; +import net.minecraft.CrashReport; +import net.minecraft.CrashReportCategory; +import net.minecraft.ReportedException; import net.minecraft.core.*; import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceKey; @@ -26,38 +31,48 @@ import net.minecraft.util.random.WeightedList; import net.minecraft.world.entity.MobCategory; import net.minecraft.world.level.*; import net.minecraft.world.level.biome.*; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.chunk.ChunkGeneratorStructureState; -import net.minecraft.world.level.levelgen.Heightmap; -import net.minecraft.world.level.levelgen.RandomState; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.levelgen.*; import net.minecraft.world.level.levelgen.blending.Blender; +import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureSet; +import net.minecraft.world.level.levelgen.structure.StructureStart; import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; +import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.craftbukkit.v1_21_R5.CraftWorld; import org.bukkit.craftbukkit.v1_21_R5.generator.CustomChunkGenerator; +import org.bukkit.craftbukkit.v1_21_R5.generator.structure.CraftStructure; +import org.bukkit.event.world.AsyncStructureSpawnEvent; import org.spigotmc.SpigotWorldConfig; import javax.annotation.Nullable; import java.lang.reflect.Field; -import java.util.List; -import java.util.Objects; -import java.util.Optional; +import java.lang.reflect.Method; +import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +import java.util.function.Supplier; public class IrisChunkGenerator extends CustomChunkGenerator { private static final WrappedField BIOME_SOURCE; + private static final WrappedReturningMethod SET_HEIGHT; private final ChunkGenerator delegate; private final Engine engine; private final KMap, KSet> structures = new KMap<>(); + private final IrisStructurePopulator populator; public IrisChunkGenerator(ChunkGenerator delegate, long seed, Engine engine, World world) { super(((CraftWorld) world).getHandle(), edit(delegate, new CustomBiomeSource(seed, engine, world)), null); this.delegate = delegate; this.engine = engine; + this.populator = new IrisStructurePopulator(engine); var dimension = engine.getDimension(); KSet placements = new KSet<>(); @@ -174,8 +189,61 @@ public class IrisChunkGenerator extends CustomChunkGenerator { } @Override - public void createStructures(RegistryAccess iregistrycustom, ChunkGeneratorStructureState chunkgeneratorstructurestate, StructureManager structuremanager, ChunkAccess ichunkaccess, StructureTemplateManager structuretemplatemanager, ResourceKey resourcekey) { - delegate.createStructures(iregistrycustom, chunkgeneratorstructurestate, structuremanager, ichunkaccess, structuretemplatemanager, resourcekey); + public void createStructures(RegistryAccess registryAccess, ChunkGeneratorStructureState structureState, StructureManager structureManager, ChunkAccess access, StructureTemplateManager templateManager, ResourceKey levelKey) { + if (!structureManager.shouldGenerateStructures()) + return; + var chunkPos = access.getPos(); + var sectionPos = SectionPos.bottomOf(access); + var registry = registryAccess.lookupOrThrow(Registries.STRUCTURE); + populator.populateStructures(chunkPos.x, chunkPos.z, (key, ignoreBiomes) -> { + var loc = ResourceLocation.tryParse(key); + if (loc == null) return false; + var holder = registry.get(loc).orElse(null); + if (holder == null) return false; + var structure = holder.value(); + var biomes = structure.biomes(); + + var start = structure.generate( + holder, + levelKey, + registryAccess, + this, + biomeSource, + structureState.randomState(), + templateManager, + structureState.getLevelSeed(), + chunkPos, + fetchReferences(structureManager, access, sectionPos, structure), + access, + biome -> ignoreBiomes || biomes.contains(biome) + ); + + if (!start.isValid()) + return false; + + BoundingBox box = start.getBoundingBox(); + AsyncStructureSpawnEvent event = new AsyncStructureSpawnEvent( + structureManager.level.getMinecraftWorld().getWorld(), + CraftStructure.minecraftToBukkit(structure), + new org.bukkit.util.BoundingBox( + box.minX(), + box.minY(), + box.minZ(), + box.maxX(), + box.maxY(), + box.maxZ() + ), chunkPos.x, chunkPos.z); + Bukkit.getPluginManager().callEvent(event); + if (!event.isCancelled()) { + structureManager.setStartForStructure(sectionPos, structure, start, access); + } + return true; + }); + } + + private static int fetchReferences(StructureManager structureManager, ChunkAccess access, SectionPos sectionPos, Structure structure) { + StructureStart structurestart = structureManager.getStartForStructure(sectionPos, structure, access); + return structurestart != null ? structurestart.getReferences() : 0; } @Override @@ -208,11 +276,6 @@ public class IrisChunkGenerator extends CustomChunkGenerator { return delegate.fillFromNoise(blender, randomstate, structuremanager, ichunkaccess); } - @Override - public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { - return delegate.getBaseHeight(i, j, heightmap_type, levelheightaccessor, randomstate); - } - @Override public WeightedList getMobsAt(Holder holder, StructureManager structuremanager, MobCategory enumcreaturetype, BlockPos blockposition) { return delegate.getMobsAt(holder, structuremanager, enumcreaturetype, blockposition); @@ -220,7 +283,7 @@ public class IrisChunkGenerator extends CustomChunkGenerator { @Override public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) { - delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager); + applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, true); } @Override @@ -230,22 +293,70 @@ public class IrisChunkGenerator extends CustomChunkGenerator { @Override public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager, boolean vanilla) { - delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, vanilla); + addVanillaDecorations(generatoraccessseed, ichunkaccess, structuremanager); + delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, false); } @Override - public int getFirstFreeHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { - return delegate.getFirstFreeHeight(i, j, heightmap_type, levelheightaccessor, randomstate); + public void addVanillaDecorations(WorldGenLevel level, ChunkAccess chunkAccess, StructureManager structureManager) { + if (!structureManager.shouldGenerateStructures()) + return; + + SectionPos sectionPos = SectionPos.of(chunkAccess.getPos(), level.getMinSectionY()); + BlockPos blockPos = sectionPos.origin(); + WorldgenRandom random = new WorldgenRandom(new XoroshiroRandomSource(RandomSupport.generateUniqueSeed())); + long i = random.setDecorationSeed(level.getSeed(), blockPos.getX(), blockPos.getZ()); + var structures = level.registryAccess().lookupOrThrow(Registries.STRUCTURE); + var list = structures.stream() + .sorted(Comparator.comparingInt(s -> s.step().ordinal())) + .toList(); + + var surface = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.WORLD_SURFACE_WG); + var ocean = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.OCEAN_FLOOR_WG); + var motion = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.MOTION_BLOCKING); + var motionNoLeaves = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES); + + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + int wX = x + blockPos.getX(); + int wZ = z + blockPos.getZ(); + + int noAir = engine.getHeight(wX, wZ, false) + engine.getMinHeight() + 1; + int noFluid = engine.getHeight(wX, wZ, true) + engine.getMinHeight() + 1; + SET_HEIGHT.invoke(ocean, x, z, Math.min(noFluid, ocean.getFirstAvailable(x, z))); + SET_HEIGHT.invoke(surface, x, z, Math.min(noAir, surface.getFirstAvailable(x, z))); + SET_HEIGHT.invoke(motion, x, z, Math.min(noAir, motion.getFirstAvailable(x, z))); + SET_HEIGHT.invoke(motionNoLeaves, x, z, Math.min(noAir, motionNoLeaves.getFirstAvailable(x, z))); + } + } + + for (int j = 0; j < list.size(); j++) { + Structure structure = list.get(j); + random.setFeatureSeed(i, j, structure.step().ordinal()); + Supplier supplier = () -> structures.getResourceKey(structure).map(Object::toString).orElseGet(structure::toString); + + try { + level.setCurrentlyGenerating(supplier); + structureManager.startsForStructure(sectionPos, structure).forEach((start) -> start.placeInChunk(level, structureManager, this, random, getWritableArea(chunkAccess), chunkAccess.getPos())); + } catch (Exception exception) { + CrashReport crashReport = CrashReport.forThrowable(exception, "Feature placement"); + CrashReportCategory category = crashReport.addCategory("Feature"); + category.setDetail("Description", supplier::get); + throw new ReportedException(crashReport); + } + } + + Heightmap.primeHeightmaps(chunkAccess, ChunkStatus.FINAL_HEIGHTMAPS); } - @Override - public int getFirstOccupiedHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { - return delegate.getFirstOccupiedHeight(i, j, heightmap_type, levelheightaccessor, randomstate); - } - - @Override - public void addVanillaDecorations(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) { - delegate.addVanillaDecorations(generatoraccessseed, ichunkaccess, structuremanager); + private static BoundingBox getWritableArea(ChunkAccess ichunkaccess) { + ChunkPos chunkPos = ichunkaccess.getPos(); + int minX = chunkPos.getMinBlockX(); + int minZ = chunkPos.getMinBlockZ(); + LevelHeightAccessor heightAccessor = ichunkaccess.getHeightAccessorForGeneration(); + int minY = heightAccessor.getMinY() + 1; + int maxY = heightAccessor.getMaxY(); + return new BoundingBox(minX, minY, minZ, minX + 15, maxY, minZ + 15); } @Override @@ -263,9 +374,22 @@ public class IrisChunkGenerator extends CustomChunkGenerator { return delegate.getGenDepth(); } + @Override + public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { + return levelheightaccessor.getMinY() + engine.getHeight(i, j, !heightmap_type.isOpaque().test(Blocks.WATER.defaultBlockState())) + 1; + } + @Override public NoiseColumn getBaseColumn(int i, int j, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { - return delegate.getBaseColumn(i, j, levelheightaccessor, randomstate); + int block = engine.getHeight(i, j, true); + int water = engine.getHeight(i, j, false); + BlockState[] column = new BlockState[levelheightaccessor.getHeight()]; + for (int k = 0; k < column.length; k++) { + if (k <= block) column[k] = Blocks.STONE.defaultBlockState(); + else if (k <= water) column[k] = Blocks.WATER.defaultBlockState(); + else column[k] = Blocks.AIR.defaultBlockState(); + } + return new NoiseColumn(levelheightaccessor.getMinY(), column); } @Override @@ -294,7 +418,21 @@ public class IrisChunkGenerator extends CustomChunkGenerator { } if (biomeSource == null) throw new RuntimeException("Could not find biomeSource field in ChunkGenerator!"); + + Method setHeight = null; + for (Method method : Heightmap.class.getDeclaredMethods()) { + var types = method.getParameterTypes(); + if (types.length != 3 || !Arrays.equals(types, new Class[]{int.class, int.class, int.class}) + || !method.getReturnType().equals(void.class)) + continue; + setHeight = method; + break; + } + if (setHeight == null) + throw new RuntimeException("Could not find setHeight method in Heightmap!"); + BIOME_SOURCE = new WrappedField<>(ChunkGenerator.class, biomeSource.getName()); + SET_HEIGHT = new WrappedReturningMethod<>(Heightmap.class, setHeight.getName(), setHeight.getParameterTypes()); } private static ChunkGenerator edit(ChunkGenerator generator, BiomeSource source) { diff --git a/nms/v1_21_R5/src/main/java/com/volmit/iris/core/nms/v1_21_R5/NMSBinding.java b/nms/v1_21_R5/src/main/java/com/volmit/iris/core/nms/v1_21_R5/NMSBinding.java index c8f50cbb2..e4e0f6808 100644 --- a/nms/v1_21_R5/src/main/java/com/volmit/iris/core/nms/v1_21_R5/NMSBinding.java +++ b/nms/v1_21_R5/src/main/java/com/volmit/iris/core/nms/v1_21_R5/NMSBinding.java @@ -2,12 +2,16 @@ package com.volmit.iris.core.nms.v1_21_R5; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.volmit.iris.Iris; +import com.volmit.iris.core.link.Identifier; import com.volmit.iris.core.nms.INMSBinding; import com.volmit.iris.core.nms.container.BiomeColor; +import com.volmit.iris.core.nms.container.Pair; +import com.volmit.iris.core.nms.container.StructurePlacement; import com.volmit.iris.core.nms.container.BlockProperty; import com.volmit.iris.core.nms.datapack.DataVersion; import com.volmit.iris.engine.data.cache.AtomicCache; import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.engine.object.IrisJigsawStructurePlacement; import com.volmit.iris.engine.platform.PlatformChunkGenerator; import com.volmit.iris.util.agent.Agent; import com.volmit.iris.util.collection.KList; @@ -60,6 +64,8 @@ import net.minecraft.world.level.dimension.LevelStem; import net.minecraft.world.level.levelgen.FlatLevelSource; import net.minecraft.world.level.levelgen.flat.FlatLayerInfo; import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings; +import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement; +import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement; import net.minecraft.world.level.storage.LevelStorageSource; import net.minecraft.world.level.storage.PrimaryLevelData; import org.bukkit.*; @@ -92,6 +98,7 @@ import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; public class NMSBinding implements INMSBinding { private final KMap baseBiomeCache = new KMap<>(); @@ -733,6 +740,71 @@ public class NMSBinding implements INMSBinding { return new BlockProperty(property.getName(), property.getValueClass(), state.getValue(property), property.getPossibleValues(), property::getName); } + @Override + public void placeStructures(Chunk chunk) { + var craft = ((CraftChunk) chunk); + var level = craft.getCraftWorld().getHandle(); + var access = ((CraftChunk) chunk).getHandle(ChunkStatus.FULL); + level.getChunkSource().getGenerator().applyBiomeDecoration(level, access, level.structureManager()); + } + + @Override + public KMap collectStructures() { + var structureSets = registry().lookupOrThrow(Registries.STRUCTURE_SET); + var structurePlacements = registry().lookupOrThrow(Registries.STRUCTURE_PLACEMENT); + return structureSets.keySet() + .stream() + .map(structureSets::get) + .filter(Optional::isPresent) + .map(Optional::get) + .map(holder -> { + var set = holder.value(); + var placement = set.placement(); + var key = holder.key().location(); + StructurePlacement.StructurePlacementBuilder builder; + if (placement instanceof RandomSpreadStructurePlacement random) { + builder = StructurePlacement.RandomSpread.builder() + .separation(random.separation()) + .spacing(random.spacing()) + .spreadType(switch (random.spreadType()) { + case LINEAR -> IrisJigsawStructurePlacement.SpreadType.LINEAR; + case TRIANGULAR -> IrisJigsawStructurePlacement.SpreadType.TRIANGULAR; + }); + } else if (placement instanceof ConcentricRingsStructurePlacement rings) { + builder = StructurePlacement.ConcentricRings.builder() + .distance(rings.distance()) + .spread(rings.spread()) + .count(rings.count()); + } else { + Iris.warn("Unsupported structure placement for set " + key + " with type " + structurePlacements.getKey(placement.type())); + return null; + } + + return new Pair<>(new Identifier(key.getNamespace(), key.getPath()), builder + .salt(placement.salt) + .frequency(placement.frequency) + .structures(set.structures() + .stream() + .map(entry -> new StructurePlacement.Structure( + entry.weight(), + entry.structure() + .unwrapKey() + .map(ResourceKey::location) + .map(ResourceLocation::toString) + .orElse(null), + entry.structure().tags() + .map(TagKey::location) + .map(ResourceLocation::toString) + .toList() + )) + .filter(StructurePlacement.Structure::isValid) + .toList()) + .build()); + }) + .filter(Objects::nonNull) + .collect(Collectors.toMap(Pair::getA, Pair::getB, (a, b) -> a, KMap::new)); + } + public LevelStem levelStem(RegistryAccess access, ChunkGenerator raw) { if (!(raw instanceof PlatformChunkGenerator gen)) throw new IllegalStateException("Generator is not platform chunk generator!"); From e5908285af6cbca8766f5af266167f21c4576cf6 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Wed, 10 Sep 2025 16:15:01 +0200 Subject: [PATCH 03/21] remove old worlds from cache before retrieving --- core/src/main/java/com/volmit/iris/core/IrisWorlds.java | 4 ++-- core/src/main/java/com/volmit/iris/util/io/IO.java | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/core/IrisWorlds.java b/core/src/main/java/com/volmit/iris/core/IrisWorlds.java index 69721ced2..72dc0b5cd 100644 --- a/core/src/main/java/com/volmit/iris/core/IrisWorlds.java +++ b/core/src/main/java/com/volmit/iris/core/IrisWorlds.java @@ -66,6 +66,7 @@ public class IrisWorlds { } public KMap getWorlds() { + clean(); return readBukkitWorlds().put(worlds); } @@ -76,8 +77,7 @@ public class IrisWorlds { } public Stream getDimensions() { - return readBukkitWorlds() - .put(worlds) + return getWorlds() .entrySet() .stream() .map(entry -> Iris.loadDimension(entry.getKey(), entry.getValue())) diff --git a/core/src/main/java/com/volmit/iris/util/io/IO.java b/core/src/main/java/com/volmit/iris/util/io/IO.java index 7506c5651..44ee35f4d 100644 --- a/core/src/main/java/com/volmit/iris/util/io/IO.java +++ b/core/src/main/java/com/volmit/iris/util/io/IO.java @@ -1705,6 +1705,7 @@ public class IO { action.accept(out); } Files.copy(temp.toPath(), Channels.newOutputStream(target)); + target.truncate(temp.length()); } finally { temp.delete(); } From 2793ed1035a6717c0ac0fad861c5580524039078 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Wed, 10 Sep 2025 17:01:29 +0200 Subject: [PATCH 04/21] send block update after placing itemsadder blocks --- .../volmit/iris/core/link/data/ItemAdderDataProvider.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/volmit/iris/core/link/data/ItemAdderDataProvider.java b/core/src/main/java/com/volmit/iris/core/link/data/ItemAdderDataProvider.java index 5327578bd..64ad7e52a 100644 --- a/core/src/main/java/com/volmit/iris/core/link/data/ItemAdderDataProvider.java +++ b/core/src/main/java/com/volmit/iris/core/link/data/ItemAdderDataProvider.java @@ -62,7 +62,10 @@ public class ItemAdderDataProvider extends ExternalDataProvider { @Override public void processUpdate(@NotNull Engine engine, @NotNull Block block, @NotNull Identifier blockId) { - CustomBlock.place(blockId.toString(), block.getLocation()); + CustomBlock custom; + if ((custom = CustomBlock.place(blockId.toString(), block.getLocation())) == null) + return; + block.setBlockData(custom.getBaseBlockData(), false); } @Override From fca309dec7857c859cab5dd0269eec2ab6221f47 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Wed, 10 Sep 2025 17:02:51 +0200 Subject: [PATCH 05/21] move the loading block data message into debug --- core/src/main/java/com/volmit/iris/util/data/B.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/com/volmit/iris/util/data/B.java b/core/src/main/java/com/volmit/iris/util/data/B.java index 7f98d13f9..982df8d2d 100644 --- a/core/src/main/java/com/volmit/iris/util/data/B.java +++ b/core/src/main/java/com/volmit/iris/util/data/B.java @@ -498,7 +498,7 @@ public class B { if (!ix.startsWith("minecraft:") && ix.contains(":")) { Identifier key = Identifier.fromString(ix); Optional bd = Iris.service(ExternalDataSVC.class).getBlockData(key); - Iris.info("Loading block data " + key); + Iris.debug("Loading block data " + key); if (bd.isPresent()) bx = bd.get(); } From 3095a92522af930a297d124881c3627574f64b43 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Wed, 10 Sep 2025 23:06:46 +0200 Subject: [PATCH 06/21] add experimental setting to force place custom blocks as early as possible --- .../com/volmit/iris/core/IrisSettings.java | 1 + .../volmit/iris/engine/IrisWorldManager.java | 25 ++++++++++++++++--- .../volmit/iris/engine/framework/Engine.java | 6 ++--- .../volmit/iris/util/mantle/MantleChunk.java | 5 ++++ 4 files changed, 31 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/core/IrisSettings.java b/core/src/main/java/com/volmit/iris/core/IrisSettings.java index 65991a686..212b1551f 100644 --- a/core/src/main/java/com/volmit/iris/core/IrisSettings.java +++ b/core/src/main/java/com/volmit/iris/core/IrisSettings.java @@ -244,6 +244,7 @@ public class IrisSettings { public boolean preventLeafDecay = true; public boolean useMulticore = false; public boolean offsetNoiseTypes = false; + public boolean earlyCustomBlocks = false; } @Data diff --git a/core/src/main/java/com/volmit/iris/engine/IrisWorldManager.java b/core/src/main/java/com/volmit/iris/engine/IrisWorldManager.java index e44ac8700..1edfee1f1 100644 --- a/core/src/main/java/com/volmit/iris/engine/IrisWorldManager.java +++ b/core/src/main/java/com/volmit/iris/engine/IrisWorldManager.java @@ -20,7 +20,9 @@ package com.volmit.iris.engine; import com.volmit.iris.Iris; import com.volmit.iris.core.IrisSettings; +import com.volmit.iris.core.link.Identifier; import com.volmit.iris.core.loader.IrisData; +import com.volmit.iris.core.service.ExternalDataSVC; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.framework.EngineAssignedWorldManager; import com.volmit.iris.engine.object.*; @@ -424,18 +426,35 @@ public class IrisWorldManager extends EngineAssignedWorldManager { } var ref = new WeakReference<>(e.getWorld()); - int x = e.getX(), z = e.getZ(); + int cX = e.getX(), cZ = e.getZ(); J.s(() -> { World world = ref.get(); - if (world == null || !world.isChunkLoaded(x, z)) + if (world == null || !world.isChunkLoaded(cX, cZ)) return; energy += 0.3; fixEnergy(); - getEngine().cleanupMantleChunk(x, z); + getEngine().cleanupMantleChunk(cX, cZ); }, IrisSettings.get().getPerformance().mantleCleanupDelay); if (generated) { //INMS.get().injectBiomesFromMantle(e, getMantle()); + + if (!IrisSettings.get().getGenerator().earlyCustomBlocks) return; + e.addPluginChunkTicket(Iris.instance); + J.s(() -> { + var chunk = getMantle().getChunk(e).use(); + int minY = getTarget().getWorld().minHeight(); + try { + chunk.raiseFlagUnchecked(MantleFlag.CUSTOM, () -> { + chunk.iterate(Identifier.class, (x, y, z, v) -> { + Iris.service(ExternalDataSVC.class).processUpdate(getEngine(), e.getBlock(x & 15, y + minY, z & 15), v); + }); + }); + } finally { + chunk.release(); + e.removePluginChunkTicket(Iris.instance); + } + }, RNG.r.i(20, 60)); } } diff --git a/core/src/main/java/com/volmit/iris/engine/framework/Engine.java b/core/src/main/java/com/volmit/iris/engine/framework/Engine.java index 9026729e9..e6ac9f954 100644 --- a/core/src/main/java/com/volmit/iris/engine/framework/Engine.java +++ b/core/src/main/java/com/volmit/iris/engine/framework/Engine.java @@ -296,20 +296,20 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat try { Semaphore semaphore = new Semaphore(3); chunk.raiseFlag(MantleFlag.ETCHED, () -> { - chunk.raiseFlag(MantleFlag.TILE, run(semaphore, () -> { + chunk.raiseFlagUnchecked(MantleFlag.TILE, run(semaphore, () -> { chunk.iterate(TileWrapper.class, (x, y, z, v) -> { Block block = c.getBlock(x & 15, y + getWorld().minHeight(), z & 15); if (!TileData.setTileState(block, v.getData())) Iris.warn("Failed to set tile entity data at [%d %d %d | %s] for tile %s!", block.getX(), block.getY(), block.getZ(), block.getType().getKey(), v.getData().getMaterial().getKey()); }); }, 0)); - chunk.raiseFlag(MantleFlag.CUSTOM, run(semaphore, () -> { + chunk.raiseFlagUnchecked(MantleFlag.CUSTOM, run(semaphore, () -> { chunk.iterate(Identifier.class, (x, y, z, v) -> { Iris.service(ExternalDataSVC.class).processUpdate(this, c.getBlock(x & 15, y + getWorld().minHeight(), z & 15), v); }); }, 0)); - chunk.raiseFlag(MantleFlag.UPDATE, run(semaphore, () -> { + chunk.raiseFlagUnchecked(MantleFlag.UPDATE, run(semaphore, () -> { PrecisionStopwatch p = PrecisionStopwatch.start(); int[][] grid = new int[16][16]; for (int x = 0; x < 16; x++) { diff --git a/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java b/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java index 7208b6fb9..f7cf935cc 100644 --- a/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java +++ b/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java @@ -181,6 +181,11 @@ public class MantleChunk { } } + public void raiseFlagUnchecked(MantleFlag flag, Runnable r) { + if (closed.get()) throw new IllegalStateException("Chunk is closed!"); + if (flags.compareAndSet(flag.ordinal(), false, true)) r.run(); + } + public boolean isFlagged(MantleFlag flag) { return flags.get(flag.ordinal()); } From 00997c190299e1a95374d50df2d42eed9a48244d Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Wed, 17 Sep 2025 17:36:18 +0200 Subject: [PATCH 07/21] return if missing datapack entries were found --- .../com/volmit/iris/core/ServerConfigurator.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) 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 51ab974c5..b4bd3e083 100644 --- a/core/src/main/java/com/volmit/iris/core/ServerConfigurator.java +++ b/core/src/main/java/com/volmit/iris/core/ServerConfigurator.java @@ -103,14 +103,14 @@ public class ServerConfigurator { return worlds; } - public static void installDataPacks(boolean fullInstall) { - installDataPacks(DataVersion.getDefault(), fullInstall); + public static boolean installDataPacks(boolean fullInstall) { + return installDataPacks(DataVersion.getDefault(), fullInstall); } - public static void installDataPacks(IDataFixer fixer, boolean fullInstall) { + public static boolean installDataPacks(IDataFixer fixer, boolean fullInstall) { if (fixer == null) { Iris.error("Unable to install datapacks, fixer is null!"); - return; + return false; } Iris.info("Checking Data Packs..."); DimensionHeight height = new DimensionHeight(fixer); @@ -129,11 +129,10 @@ public class ServerConfigurator { IrisDimension.writeShared(folders, height); Iris.info("Data Packs Setup!"); - if (fullInstall) - verifyDataPacksPost(IrisSettings.get().getAutoConfiguration().isAutoRestartOnCustomBiomeInstall()); + return fullInstall && verifyDataPacksPost(IrisSettings.get().getAutoConfiguration().isAutoRestartOnCustomBiomeInstall()); } - private static void verifyDataPacksPost(boolean allowRestarting) { + private static boolean verifyDataPacksPost(boolean allowRestarting) { try (Stream stream = allPacks()) { boolean bad = stream .map(data -> { @@ -148,7 +147,7 @@ public class ServerConfigurator { }) .toList() .contains(true); - if (!bad) return; + if (!bad) return false; } @@ -172,6 +171,7 @@ public class ServerConfigurator { J.sleep(3000); } + return true; } public static void restart() { From d50cdfec3e03fc27ea458383843bb52b4f39996b Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Wed, 17 Sep 2025 18:02:20 +0200 Subject: [PATCH 08/21] cleanup pack installation --- .../volmit/iris/core/service/StudioSVC.java | 26 +++++++++++-------- .../engine/platform/BukkitChunkGenerator.java | 5 +--- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/core/service/StudioSVC.java b/core/src/main/java/com/volmit/iris/core/service/StudioSVC.java index bf070d063..0b0b40717 100644 --- a/core/src/main/java/com/volmit/iris/core/service/StudioSVC.java +++ b/core/src/main/java/com/volmit/iris/core/service/StudioSVC.java @@ -88,16 +88,18 @@ public class StudioSVC implements IrisService { } public IrisDimension installIntoWorld(VolmitSender sender, String type, File folder) { + return installInto(sender, type, new File(folder, "iris/pack")); + } + + public IrisDimension installInto(VolmitSender sender, String type, File folder) { sender.sendMessage("Looking for Package: " + type); - File iris = new File(folder, "iris"); - File irispack = new File(folder, "iris/pack"); IrisDimension dim = IrisData.loadAnyDimension(type); if (dim == null) { for (File i : getWorkspaceFolder().listFiles()) { if (i.isFile() && i.getName().equals(type + ".iris")) { sender.sendMessage("Found " + type + ".iris in " + WORKSPACE_NAME + " folder"); - ZipUtil.unpack(i, irispack); + ZipUtil.unpack(i, folder); break; } } @@ -106,29 +108,29 @@ public class StudioSVC implements IrisService { File f = new IrisProject(new File(getWorkspaceFolder(), type)).getPath(); try { - FileUtils.copyDirectory(f, irispack); + FileUtils.copyDirectory(f, folder); } catch (IOException e) { Iris.reportError(e); } } - File dimf = new File(irispack, "dimensions/" + type + ".json"); + File dimensionFile = new File(folder, "dimensions/" + type + ".json"); - if (!dimf.exists() || !dimf.isFile()) { + if (!dimensionFile.exists() || !dimensionFile.isFile()) { downloadSearch(sender, type, false); File downloaded = getWorkspaceFolder(type); for (File i : downloaded.listFiles()) { if (i.isFile()) { try { - FileUtils.copyFile(i, new File(irispack, i.getName())); + FileUtils.copyFile(i, new File(folder, i.getName())); } catch (IOException e) { e.printStackTrace(); Iris.reportError(e); } } else { try { - FileUtils.copyDirectory(i, new File(irispack, i.getName())); + FileUtils.copyDirectory(i, new File(folder, i.getName())); } catch (IOException e) { e.printStackTrace(); Iris.reportError(e); @@ -139,12 +141,14 @@ public class StudioSVC implements IrisService { IO.delete(downloaded); } - if (!dimf.exists() || !dimf.isFile()) { - sender.sendMessage("Can't find the " + dimf.getName() + " in the dimensions folder of this pack! Failed!"); + if (!dimensionFile.exists() || !dimensionFile.isFile()) { + sender.sendMessage("Can't find the " + dimensionFile.getName() + " in the dimensions folder of this pack! Failed!"); return null; } - IrisData dm = IrisData.get(irispack); + IrisData dm = IrisData.get(folder); + dm.dump(); + dm.clearLists(); dim = dm.getDimensionLoader().load(type); if (dim == null) { diff --git a/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java b/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java index b0b89c7b7..991b14e46 100644 --- a/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java +++ b/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java @@ -174,11 +174,8 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun if (test != null) { Iris.warn("Looks like " + dimensionKey + " exists in " + test.getLoadFile().getPath() + " "); - Iris.service(StudioSVC.class).installIntoWorld(Iris.getSender(), dimensionKey, dataLocation.getParentFile().getParentFile()); + test = Iris.service(StudioSVC.class).installInto(Iris.getSender(), dimensionKey, dataLocation); Iris.warn("Attempted to install into " + data.getDataFolder().getPath()); - data.dump(); - data.clearLists(); - test = data.getDimensionLoader().load(dimensionKey); if (test != null) { Iris.success("Woo! Patched the Engine!"); From e5e0561d5a1fd5ffdf8f5776d973e286d79def70 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Wed, 17 Sep 2025 18:02:45 +0200 Subject: [PATCH 09/21] cleanup world creator --- .../volmit/iris/core/tools/IrisCreator.java | 60 ++++++++----------- .../iris/core/tools/IrisWorldCreator.java | 2 +- 2 files changed, 26 insertions(+), 36 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/core/tools/IrisCreator.java b/core/src/main/java/com/volmit/iris/core/tools/IrisCreator.java index f10cbdfd2..b112ed779 100644 --- a/core/src/main/java/com/volmit/iris/core/tools/IrisCreator.java +++ b/core/src/main/java/com/volmit/iris/core/tools/IrisCreator.java @@ -43,8 +43,7 @@ import java.io.File; import java.io.IOException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Supplier; +import java.util.function.IntSupplier; import static com.volmit.iris.util.misc.ServerProperties.BUKKIT_YML; @@ -128,8 +127,6 @@ public class IrisCreator { Iris.service(StudioSVC.class).installIntoWorld(sender, d.getLoadKey(), new File(Bukkit.getWorldContainer(), name())); } - PlatformChunkGenerator access; - AtomicReference world = new AtomicReference<>(); AtomicDouble pp = new AtomicDouble(0); O done = new O<>(); done.set(false); @@ -139,30 +136,29 @@ public class IrisCreator { .seed(seed) .studio(studio) .create(); - ServerConfigurator.installDataPacks(false); + if (ServerConfigurator.installDataPacks(true)) { + throw new IrisException("Datapacks were missing!"); + } - access = (PlatformChunkGenerator) wc.generator(); - PlatformChunkGenerator finalAccess1 = access; + PlatformChunkGenerator access = (PlatformChunkGenerator) wc.generator(); + if (access == null) throw new IrisException("Access is null. Something bad happened."); - J.a(() -> - { - Supplier g = () -> { - if (finalAccess1 == null || finalAccess1.getEngine() == null) { + J.a(() -> { + IntSupplier g = () -> { + if (access.getEngine() == null) { return 0; } - return finalAccess1.getEngine().getGenerated(); + return access.getEngine().getGenerated(); }; if(!benchmark) { - if (finalAccess1 == null) return; - int req = finalAccess1.getSpawnChunks().join(); - - while (g.get() < req) { - double v = (double) g.get() / (double) req; + int req = access.getSpawnChunks().join(); + for (int c = 0; c < req && !done.get(); c = g.getAsInt()) { + double v = (double) c / req; if (sender.isPlayer()) { sender.sendProgress(v, "Generating"); J.sleep(16); } else { - sender.sendMessage(C.WHITE + "Generating " + Form.pc(v) + ((C.GRAY + " (" + (req - g.get()) + " Left)"))); + sender.sendMessage(C.WHITE + "Generating " + Form.pc(v) + ((C.GRAY + " (" + (req - c) + " Left)"))); J.sleep(1000); } } @@ -170,39 +166,33 @@ public class IrisCreator { }); + World world; try { - J.sfut(() -> { - world.set(INMS.get().createWorld(wc)); - }).get(); + world = J.sfut(() -> INMS.get().createWorld(wc)).get(); } catch (Throwable e) { - e.printStackTrace(); - } - - if (access == null) { - throw new IrisException("Access is null. Something bad happened."); + done.set(true); + throw new IrisException("Failed to create world!", e); } done.set(true); if (sender.isPlayer() && !benchmark) { - J.s(() -> { - sender.player().teleport(new Location(world.get(), 0, world.get().getHighestBlockYAt(0, 0), 0)); - }); + J.s(() -> sender.player().teleport(new Location(world, 0, world.getHighestBlockYAt(0, 0) + 1, 0))); } if (studio || benchmark) { J.s(() -> { - Iris.linkMultiverseCore.removeFromConfig(world.get()); + Iris.linkMultiverseCore.removeFromConfig(world); if (IrisSettings.get().getStudio().isDisableTimeAndWeather()) { - world.get().setGameRule(GameRule.DO_WEATHER_CYCLE, false); - world.get().setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false); - world.get().setTime(6000); + world.setGameRule(GameRule.DO_WEATHER_CYCLE, false); + world.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false); + world.setTime(6000); } }); } else { addToBukkitYml(); - J.s(() -> Iris.linkMultiverseCore.updateWorld(world.get(), dimension)); + J.s(() -> Iris.linkMultiverseCore.updateWorld(world, dimension)); } if (pregen != null) { @@ -233,7 +223,7 @@ public class IrisCreator { e.printStackTrace(); } } - return world.get(); + return world; } private void addToBukkitYml() { diff --git a/core/src/main/java/com/volmit/iris/core/tools/IrisWorldCreator.java b/core/src/main/java/com/volmit/iris/core/tools/IrisWorldCreator.java index cd6c17a91..b63b77056 100644 --- a/core/src/main/java/com/volmit/iris/core/tools/IrisWorldCreator.java +++ b/core/src/main/java/com/volmit/iris/core/tools/IrisWorldCreator.java @@ -80,7 +80,7 @@ public class IrisWorldCreator { return new WorldCreator(name) - .environment(findEnvironment()) + .environment(w.environment()) .generateStructures(true) .generator(g).seed(seed); } From 7c41f86fb3ed89681f703159f2e73b981b01a09a Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sat, 20 Sep 2025 14:28:33 +0200 Subject: [PATCH 10/21] add slope condition for slabs --- .../com/volmit/iris/engine/modifier/IrisPostModifier.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/volmit/iris/engine/modifier/IrisPostModifier.java b/core/src/main/java/com/volmit/iris/engine/modifier/IrisPostModifier.java index 6d85e3205..aa80a05b6 100644 --- a/core/src/main/java/com/volmit/iris/engine/modifier/IrisPostModifier.java +++ b/core/src/main/java/com/volmit/iris/engine/modifier/IrisPostModifier.java @@ -21,6 +21,7 @@ package com.volmit.iris.engine.modifier; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.framework.EngineAssignedModifier; import com.volmit.iris.engine.object.IrisBiome; +import com.volmit.iris.engine.object.IrisSlopeClip; import com.volmit.iris.util.context.ChunkContext; import com.volmit.iris.util.data.B; import com.volmit.iris.util.hunk.Hunk; @@ -174,7 +175,8 @@ public class IrisPostModifier extends EngineAssignedModifier { || (hd == h + 1 && isSolidNonSlab(x, hd, z - 1, currentPostX, currentPostZ, currentData))) //@done { - BlockData d = biome.getSlab().get(rng, x, h, z, getData()); + IrisSlopeClip sc = biome.getSlab().getSlopeCondition(); + BlockData d = sc.isValid(getComplex().getSlopeStream().get(x, z)) ? biome.getSlab().get(rng, x, h, z, getData()) : null; if (d != null) { boolean cancel = B.isAir(d); From a7d874d37f2046dcadf1669d9e9ba6894bb64d22 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sat, 20 Sep 2025 15:27:49 +0200 Subject: [PATCH 11/21] use scheduled thread pool for scoreboard svc to prevent freezes --- .../volmit/iris/core/service/BoardSVC.java | 123 +++++++++--------- .../com/volmit/iris/util/board/Board.java | 20 ++- .../iris/util/board/BoardUpdateTask.java | 8 +- 3 files changed, 80 insertions(+), 71 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/core/service/BoardSVC.java b/core/src/main/java/com/volmit/iris/core/service/BoardSVC.java index e5557b023..e8eadc558 100644 --- a/core/src/main/java/com/volmit/iris/core/service/BoardSVC.java +++ b/core/src/main/java/com/volmit/iris/core/service/BoardSVC.java @@ -22,33 +22,39 @@ import com.volmit.iris.Iris; import com.volmit.iris.core.IrisSettings; import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.core.tools.IrisToolbelt; -import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.util.board.BoardManager; import com.volmit.iris.util.board.BoardProvider; import com.volmit.iris.util.board.BoardSettings; import com.volmit.iris.util.board.ScoreDirection; -import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.format.C; import com.volmit.iris.util.format.Form; import com.volmit.iris.util.plugin.IrisService; import com.volmit.iris.util.scheduling.J; import lombok.Data; +import org.bukkit.Location; +import org.bukkit.World; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.player.PlayerChangedWorldEvent; import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; public class BoardSVC implements IrisService, BoardProvider { private final KMap boards = new KMap<>(); - private com.volmit.iris.util.board.BoardManager manager; + private ScheduledExecutorService executor; + private BoardManager manager; @Override public void onEnable() { - J.ar(this::tick, 20); + executor = Executors.newScheduledThreadPool(0, Thread.ofVirtual().factory()); manager = new BoardManager(Iris.instance, BoardSettings.builder() .boardProvider(this) .scoreDirection(ScoreDirection.DOWN) @@ -57,6 +63,7 @@ public class BoardSVC implements IrisService, BoardProvider { @Override public void onDisable() { + executor.shutdownNow(); manager.onDisable(); boards.clear(); } @@ -71,14 +78,22 @@ public class BoardSVC implements IrisService, BoardProvider { J.s(() -> updatePlayer(e.getPlayer())); } + @EventHandler + public void on(PlayerQuitEvent e) { + remove(e.getPlayer()); + } + public void updatePlayer(Player p) { if (IrisToolbelt.isIrisStudioWorld(p.getWorld())) { manager.remove(p); manager.setup(p); - } else { - manager.remove(p); - boards.remove(p); - } + } else remove(p); + } + + private void remove(Player player) { + manager.remove(player); + var board = boards.remove(player); + if (board != null) board.task.cancel(true); } @Override @@ -86,73 +101,63 @@ public class BoardSVC implements IrisService, BoardProvider { return C.GREEN + "Iris"; } - public void tick() { - if (!Iris.service(StudioSVC.class).isProjectOpen()) { - return; - } - - boards.forEach((k, v) -> v.update()); - } - @Override public List getLines(Player player) { - PlayerBoard pb = boards.computeIfAbsent(player, PlayerBoard::new); - synchronized (pb.lines) { - return pb.lines; - } + return boards.computeIfAbsent(player, PlayerBoard::new).lines; } @Data - public static class PlayerBoard { + public class PlayerBoard { private final Player player; - private final CopyOnWriteArrayList lines; + private final ScheduledFuture task; + private volatile List lines; public PlayerBoard(Player player) { this.player = player; - this.lines = new CopyOnWriteArrayList<>(); + this.lines = new ArrayList<>(); + this.task = executor.scheduleAtFixedRate(this::tick, 0, 1, TimeUnit.SECONDS); + } + + private void tick() { + if (!Iris.service(StudioSVC.class).isProjectOpen()) { + return; + } + update(); } public void update() { - synchronized (lines) { - lines.clear(); + final World world = player.getWorld(); + final Location loc = player.getLocation(); - if (!IrisToolbelt.isIrisStudioWorld(player.getWorld())) { - return; - } + final var access = IrisToolbelt.access(world); + if (access == null) return; - Engine engine = IrisToolbelt.access(player.getWorld()).getEngine(); - int x = player.getLocation().getBlockX(); - int y = player.getLocation().getBlockY() - player.getWorld().getMinHeight(); - int z = player.getLocation().getBlockZ(); + final var engine = access.getEngine(); + if (engine == null) return; - if(IrisSettings.get().getGeneral().debug){ - lines.add("&7&m "); - lines.add(C.GREEN + "Speed" + C.GRAY + ": " + Form.f(engine.getGeneratedPerSecond(), 0) + "/s " + Form.duration(1000D / engine.getGeneratedPerSecond(), 0)); - lines.add(C.AQUA + "Cache" + C.GRAY + ": " + Form.f(IrisData.cacheSize())); - lines.add(C.AQUA + "Mantle" + C.GRAY + ": " + engine.getMantle().getLoadedRegionCount()); - lines.add(C.LIGHT_PURPLE + "Carving" + C.GRAY + ": " + engine.getMantle().isCarved(x,y,z)); - lines.add("&7&m "); - lines.add(C.AQUA + "Region" + C.GRAY + ": " + engine.getRegion(x, z).getName()); - lines.add(C.AQUA + "Biome" + C.GRAY + ": " + engine.getBiomeOrMantle(x, y, z).getName()); - lines.add(C.AQUA + "Height" + C.GRAY + ": " + Math.round(engine.getHeight(x, z))); - lines.add(C.AQUA + "Slope" + C.GRAY + ": " + Form.f(engine.getComplex().getSlopeStream().get(x, z), 2)); - lines.add(C.AQUA + "BUD/s" + C.GRAY + ": " + Form.f(engine.getBlockUpdatesPerSecond())); - lines.add("&7&m "); - } else { - lines.add("&7&m "); - lines.add(C.GREEN + "Speed" + C.GRAY + ": " + Form.f(engine.getGeneratedPerSecond(), 0) + "/s " + Form.duration(1000D / engine.getGeneratedPerSecond(), 0)); - lines.add(C.AQUA + "Cache" + C.GRAY + ": " + Form.f(IrisData.cacheSize())); - lines.add(C.AQUA + "Mantle" + C.GRAY + ": " + engine.getMantle().getLoadedRegionCount()); - lines.add("&7&m "); - lines.add(C.AQUA + "Region" + C.GRAY + ": " + engine.getRegion(x, z).getName()); - lines.add(C.AQUA + "Biome" + C.GRAY + ": " + engine.getBiomeOrMantle(x, y, z).getName()); - lines.add(C.AQUA + "Height" + C.GRAY + ": " + Math.round(engine.getHeight(x, z))); - lines.add(C.AQUA + "Slope" + C.GRAY + ": " + Form.f(engine.getComplex().getSlopeStream().get(x, z), 2)); - lines.add(C.AQUA + "BUD/s" + C.GRAY + ": " + Form.f(engine.getBlockUpdatesPerSecond())); - lines.add("&7&m "); - } + int x = loc.getBlockX(); + int y = loc.getBlockY() - world.getMinHeight(); + int z = loc.getBlockZ(); + + List lines = new ArrayList<>(this.lines.size()); + lines.add("&7&m "); + lines.add(C.GREEN + "Speed" + C.GRAY + ": " + Form.f(engine.getGeneratedPerSecond(), 0) + "/s " + Form.duration(1000D / engine.getGeneratedPerSecond(), 0)); + lines.add(C.AQUA + "Cache" + C.GRAY + ": " + Form.f(IrisData.cacheSize())); + lines.add(C.AQUA + "Mantle" + C.GRAY + ": " + engine.getMantle().getLoadedRegionCount()); + + if (IrisSettings.get().getGeneral().debug) { + lines.add(C.LIGHT_PURPLE + "Carving" + C.GRAY + ": " + engine.getMantle().isCarved(x,y,z)); } + + lines.add("&7&m "); + lines.add(C.AQUA + "Region" + C.GRAY + ": " + engine.getRegion(x, z).getName()); + lines.add(C.AQUA + "Biome" + C.GRAY + ": " + engine.getBiomeOrMantle(x, y, z).getName()); + lines.add(C.AQUA + "Height" + C.GRAY + ": " + Math.round(engine.getHeight(x, z))); + lines.add(C.AQUA + "Slope" + C.GRAY + ": " + Form.f(engine.getComplex().getSlopeStream().get(x, z), 2)); + lines.add(C.AQUA + "BUD/s" + C.GRAY + ": " + Form.f(engine.getBlockUpdatesPerSecond())); + lines.add("&7&m "); + this.lines = lines; } } } diff --git a/core/src/main/java/com/volmit/iris/util/board/Board.java b/core/src/main/java/com/volmit/iris/util/board/Board.java index eb37ef182..eecbac731 100644 --- a/core/src/main/java/com/volmit/iris/util/board/Board.java +++ b/core/src/main/java/com/volmit/iris/util/board/Board.java @@ -23,15 +23,11 @@ import lombok.NonNull; import lombok.Setter; import org.bukkit.Bukkit; import org.bukkit.entity.Player; -import org.bukkit.scoreboard.DisplaySlot; -import org.bukkit.scoreboard.Objective; -import org.bukkit.scoreboard.Scoreboard; -import org.bukkit.scoreboard.Team; +import org.bukkit.scoreboard.*; import java.util.Collections; import java.util.List; -import java.util.function.Function; -import java.util.stream.Collectors; +import java.util.function.UnaryOperator; import java.util.stream.IntStream; /** @@ -42,7 +38,7 @@ public class Board { private static final String[] CACHED_ENTRIES = new String[C.values().length]; - private static final Function APPLY_COLOR_TRANSLATION = s -> C.translateAlternateColorCodes('&', s); + private static final UnaryOperator APPLY_COLOR_TRANSLATION = s -> C.translateAlternateColorCodes('&', s); static { IntStream.range(0, 15).forEach(i -> CACHED_ENTRIES[i] = C.values()[i].toString() + C.RESET); @@ -54,13 +50,14 @@ public class Board { private BoardSettings boardSettings; private boolean ready; - @SuppressWarnings("deprecation") public Board(@NonNull final Player player, final BoardSettings boardSettings) { this.player = player; this.boardSettings = boardSettings; - this.objective = this.getScoreboard().getObjective("board") == null ? this.getScoreboard().registerNewObjective("board", "dummy") : this.getScoreboard().getObjective("board"); + var obj = getScoreboard().getObjective("board"); + this.objective = obj == null ? this.getScoreboard().registerNewObjective("board", Criteria.DUMMY, "Iris") : obj; this.objective.setDisplaySlot(DisplaySlot.SIDEBAR); - Team team = this.getScoreboard().getTeam("board") == null ? this.getScoreboard().registerNewTeam("board") : this.getScoreboard().getTeam("board"); + Team team = getScoreboard().getTeam("board"); + team = team == null ? getScoreboard().registerNewTeam("board") : team; team.setAllowFriendlyFire(true); team.setCanSeeFriendlyInvisibles(false); team.setPrefix(""); @@ -94,7 +91,8 @@ public class Board { } // Getting their Scoreboard display from the Scoreboard Provider. - final List entries = boardSettings.getBoardProvider().getLines(player).stream().map(APPLY_COLOR_TRANSLATION).collect(Collectors.toList()); + final List entries = boardSettings.getBoardProvider().getLines(player); + entries.replaceAll(APPLY_COLOR_TRANSLATION); if (boardSettings.getScoreDirection() == ScoreDirection.UP) { Collections.reverse(entries); diff --git a/core/src/main/java/com/volmit/iris/util/board/BoardUpdateTask.java b/core/src/main/java/com/volmit/iris/util/board/BoardUpdateTask.java index ce7ded229..1a54bf498 100644 --- a/core/src/main/java/com/volmit/iris/util/board/BoardUpdateTask.java +++ b/core/src/main/java/com/volmit/iris/util/board/BoardUpdateTask.java @@ -38,6 +38,12 @@ public class BoardUpdateTask extends BukkitRunnable { @Override public void run() { - boardManager.getScoreboards().entrySet().stream().filter(entrySet -> PLAYER_IS_ONLINE.test(entrySet.getKey())).forEach(entrySet -> entrySet.getValue().update()); + for (var entry : boardManager.getScoreboards().entrySet()) { + if (!PLAYER_IS_ONLINE.test(entry.getKey())) { + continue; + } + + entry.getValue().update(); + } } } From c998fd1fd9f366d2c16e3bc6553ccb0bcac7ee13 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sat, 20 Sep 2025 17:25:20 +0200 Subject: [PATCH 12/21] move additional build data in its own constants class --- core/build.gradle.kts | 41 ++++++++++++------- .../engine/platform/BukkitChunkGenerator.java | 8 ++-- .../com/volmit/iris/util/misc/Bindings.java | 33 +++------------ core/src/main/resources/plugin.yml | 2 - core/src/main/templates/BuildConstants.java | 7 ++++ 5 files changed, 43 insertions(+), 48 deletions(-) create mode 100644 core/src/main/templates/BuildConstants.java diff --git a/core/build.gradle.kts b/core/build.gradle.kts index a7829f55f..1e847d793 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -162,15 +162,6 @@ tasks { "version" to rootProject.version, "apiVersion" to apiVersion, "main" to main, - "environment" to if (project.hasProperty("release")) "production" else "development", - "commit" to provider { - val res = runCatching { project.extensions.getByType().head().id } - res.getOrDefault("") - .takeIf { it.length == 40 } ?: { - logger.error("Git commit hash not found", res.exceptionOrNull()) - "unknown" - }() - }, ) filesMatching("**/plugin.yml") { expand(inputs.properties) @@ -185,9 +176,31 @@ tasks { } } -/** - * Gradle is weird sometimes, we need to delete the plugin yml from the build folder to actually filter properly. - */ -afterEvaluate { - layout.buildDirectory.file("resources/main/plugin.yml").get().asFile.delete() +val templateSource = file("src/main/templates") +val templateDest = layout.buildDirectory.dir("generated/sources/templates") +val generateTemplates = tasks.register("generateTemplates") { + inputs.properties( + "environment" to if (project.hasProperty("release")) "production" else "development", + "commit" to provider { + val res = runCatching { project.extensions.getByType().head().id } + res.getOrDefault("") + .takeIf { it.length == 40 } ?: { + logger.error("Git commit hash not found", res.exceptionOrNull()) + "unknown" + }() + }, + ) + + from(templateSource) + into(templateDest) + rename { "com/volmit/iris/$it" } + expand(inputs.properties) +} + +rootProject.tasks.named("prepareKotlinBuildScriptModel") { + dependsOn(generateTemplates) +} + +sourceSets.main { + java.srcDir(generateTemplates.map { it.outputs }) } \ No newline at end of file diff --git a/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java b/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java index 991b14e46..0be74eef8 100644 --- a/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java +++ b/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java @@ -242,7 +242,7 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) .thenRunAsync(() -> { c.removePluginChunkTicket(Iris.instance); - c.unload(); + engine.getWorldManager().onChunkLoad(c, true); }, syncExecutor) .get(); Iris.debug("Regenerated " + x + " " + z); @@ -354,16 +354,16 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun @Override public void generateNoise(@NotNull WorldInfo world, @NotNull Random random, int x, int z, @NotNull ChunkGenerator.ChunkData d) { try { - getEngine(world); + Engine engine = getEngine(world); computeStudioGenerator(); TerrainChunk tc = TerrainChunk.create(d, new IrisBiomeStorage()); this.world.bind(world); if (studioGenerator != null) { - studioGenerator.generateChunk(getEngine(), tc, x, z); + studioGenerator.generateChunk(engine, tc, x, z); } else { ChunkDataHunkHolder blocks = new ChunkDataHunkHolder(tc); BiomeGridHunkHolder biomes = new BiomeGridHunkHolder(tc, tc.getMinHeight(), tc.getMaxHeight()); - getEngine().generate(x << 4, z << 4, blocks, biomes, IrisSettings.get().getGenerator().useMulticore); + engine.generate(x << 4, z << 4, blocks, biomes, IrisSettings.get().getGenerator().useMulticore); blocks.apply(); biomes.apply(); } diff --git a/core/src/main/java/com/volmit/iris/util/misc/Bindings.java b/core/src/main/java/com/volmit/iris/util/misc/Bindings.java index 1a82c55c4..48bb8ae61 100644 --- a/core/src/main/java/com/volmit/iris/util/misc/Bindings.java +++ b/core/src/main/java/com/volmit/iris/util/misc/Bindings.java @@ -1,5 +1,6 @@ package com.volmit.iris.util.misc; +import com.volmit.iris.BuildConstants; import com.volmit.iris.Iris; import com.volmit.iris.core.IrisSettings; import com.volmit.iris.core.nms.INMS; @@ -19,19 +20,12 @@ import net.kyori.adventure.platform.bukkit.BukkitAudiences; import net.kyori.adventure.text.serializer.ComponentSerializer; import org.bstats.bukkit.Metrics; import org.bstats.charts.DrilldownPie; -import org.bstats.charts.SimplePie; import org.bstats.charts.SingleLineChart; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; -import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.entity.Player; -import oshi.SystemInfo; -import java.io.InputStreamReader; -import java.math.RoundingMode; -import java.text.NumberFormat; import java.util.HashMap; -import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; @@ -47,8 +41,6 @@ public class Bindings { if (settings.disableAutoReporting || Sentry.isEnabled() || Boolean.getBoolean("iris.suppressReporting")) return; Iris.info("Enabling Sentry for anonymous error reporting. You can disable this in the settings."); Iris.info("Your server ID is: " + ServerID.ID); - var resource = Iris.instance.getResource("plugin.yml"); - YamlConfiguration desc = resource != null ? YamlConfiguration.loadConfiguration(new InputStreamReader(resource)) : new YamlConfiguration(); Sentry.init(options -> { options.setDsn("http://4cdbb9ac953306529947f4ca1e8e6b26@sentry.volmit.com:8080/2"); @@ -60,7 +52,7 @@ public class Bindings { options.setAttachServerName(false); options.setEnableUncaughtExceptionHandler(false); options.setRelease(Iris.instance.getDescription().getVersion()); - options.setEnvironment(desc.getString("environment", "production")); + options.setEnvironment(BuildConstants.ENVIRONMENT); options.setBeforeSend((event, hint) -> { if (suppress(event.getThrowable())) return null; event.setTag("iris.safeguard", IrisSafeguard.mode()); @@ -77,7 +69,7 @@ public class Bindings { scope.setTag("server", Bukkit.getVersion()); scope.setTag("server.type", Bukkit.getName()); scope.setTag("server.api", Bukkit.getBukkitVersion()); - scope.setTag("iris.commit", desc.getString("commit", "unknown")); + scope.setTag("iris.commit", BuildConstants.COMMIT); }); } @@ -99,6 +91,7 @@ public class Bindings { .map(IrisToolbelt::access) .filter(Objects::nonNull) .map(PlatformChunkGenerator::getEngine) + .filter(Objects::nonNull) .collect(Collectors.toMap(engine -> engine.getDimension().getLoadKey(), engine -> { var hash32 = engine.getHash32().getNow(null); if (hash32 == null) return Map.of(); @@ -111,23 +104,7 @@ public class Bindings { b.forEach((k, v) -> merged.merge(k, v, Integer::sum)); return merged; })))); - - - var info = new SystemInfo().getHardware(); - var cpu = info.getProcessor().getProcessorIdentifier(); - var mem = info.getMemory(); - metrics.addCustomChart(new SimplePie("cpu_model", cpu::getName)); - - var nf = NumberFormat.getInstance(Locale.ENGLISH); - nf.setMinimumFractionDigits(0); - nf.setMaximumFractionDigits(2); - nf.setRoundingMode(RoundingMode.HALF_UP); - - metrics.addCustomChart(new DrilldownPie("memory", () -> { - double total = mem.getTotal() * 1E-9; - double alloc = Math.min(total, Runtime.getRuntime().maxMemory() * 1E-9); - return Map.of(nf.format(alloc), Map.of(nf.format(total), 1)); - })); + metrics.addCustomChart(new DrilldownPie("environment", () -> Map.of(BuildConstants.ENVIRONMENT, Map.of(BuildConstants.COMMIT, 1)))); plugin.postShutdown(metrics::shutdown); }); diff --git a/core/src/main/resources/plugin.yml b/core/src/main/resources/plugin.yml index f871ec1e3..99fa326be 100644 --- a/core/src/main/resources/plugin.yml +++ b/core/src/main/resources/plugin.yml @@ -5,8 +5,6 @@ load: STARTUP authors: [ cyberpwn, NextdoorPsycho, Vatuu ] website: volmit.com description: More than a Dimension! -environment: '${environment}' -commit: '${commit}' commands: iris: aliases: [ ir, irs ] diff --git a/core/src/main/templates/BuildConstants.java b/core/src/main/templates/BuildConstants.java new file mode 100644 index 000000000..0bdbbf1cc --- /dev/null +++ b/core/src/main/templates/BuildConstants.java @@ -0,0 +1,7 @@ +package com.volmit.iris; + +// The constants are replaced before compilation +public interface BuildConstants { + String ENVIRONMENT = "${environment}"; + String COMMIT = "${commit}"; +} \ No newline at end of file From b5811cae08c65f7360d73214adddffb856f7340b Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sat, 20 Sep 2025 23:04:11 +0200 Subject: [PATCH 13/21] cleanup IrisData usage --- core/src/main/java/com/volmit/iris/Iris.java | 4 +- .../com/volmit/iris/core/loader/IrisData.java | 106 +++++++++++------- .../volmit/iris/core/project/IrisProject.java | 4 +- .../volmit/iris/core/service/StudioSVC.java | 6 +- .../iris/core/tools/IrisWorldCreator.java | 4 +- .../iris/engine/object/IrisDimension.java | 2 +- .../volmit/iris/engine/object/IrisRegion.java | 2 +- .../engine/platform/BukkitChunkGenerator.java | 2 +- 8 files changed, 76 insertions(+), 54 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/Iris.java b/core/src/main/java/com/volmit/iris/Iris.java index 615d40d89..f95557d2f 100644 --- a/core/src/main/java/com/volmit/iris/Iris.java +++ b/core/src/main/java/com/volmit/iris/Iris.java @@ -719,11 +719,11 @@ public class Iris extends VolmitPlugin implements Listener { public static IrisDimension loadDimension(@NonNull String worldName, @NonNull String id) { var data = IrisData.get(new File(Bukkit.getWorldContainer(), String.join(File.separator, worldName, "iris", "pack"))); var dimension = data.getDimensionLoader().load(id); - if (dimension == null) dimension = IrisData.loadAnyDimension(id); + if (dimension == null) dimension = IrisData.loadAnyDimension(id, null); if (dimension == null) { Iris.warn("Unable to find dimension type " + id + " Looking for online packs..."); Iris.service(StudioSVC.class).downloadSearch(new VolmitSender(Bukkit.getConsoleSender()), id, false); - dimension = IrisData.loadAnyDimension(id); + dimension = IrisData.loadAnyDimension(id, null); if (dimension != null) { Iris.info("Resolved missing dimension, proceeding."); diff --git a/core/src/main/java/com/volmit/iris/core/loader/IrisData.java b/core/src/main/java/com/volmit/iris/core/loader/IrisData.java index 9ad5e12ea..58605c949 100644 --- a/core/src/main/java/com/volmit/iris/core/loader/IrisData.java +++ b/core/src/main/java/com/volmit/iris/core/loader/IrisData.java @@ -43,6 +43,7 @@ import com.volmit.iris.util.reflect.KeyedType; import com.volmit.iris.util.scheduling.ChronoLatch; import com.volmit.iris.util.scheduling.J; import lombok.Data; +import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.FileReader; @@ -50,7 +51,6 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Objects; -import java.util.function.Function; @Data public class IrisData implements ExclusionStrategy, TypeAdapterFactory { @@ -118,92 +118,100 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { Iris.warn(" " + rl.getResourceTypeName() + " @ /" + rl.getFolderName() + ": Cache=" + rl.getLoadCache().getSize() + " Folders=" + rl.getFolders().size()); } - public static IrisObject loadAnyObject(String key) { - return loadAny(key, (dm) -> dm.getObjectLoader().load(key, false)); + public static IrisObject loadAnyObject(String key, @Nullable IrisData nearest) { + return loadAny(IrisObject.class, key, nearest); } - public static IrisMatterObject loadAnyMatter(String key) { - return loadAny(key, (dm) -> dm.getMatterLoader().load(key, false)); + public static IrisMatterObject loadAnyMatter(String key, @Nullable IrisData nearest) { + return loadAny(IrisMatterObject.class, key, nearest); } - public static IrisBiome loadAnyBiome(String key) { - return loadAny(key, (dm) -> dm.getBiomeLoader().load(key, false)); + public static IrisBiome loadAnyBiome(String key, @Nullable IrisData nearest) { + return loadAny(IrisBiome.class, key, nearest); } - public static IrisExpression loadAnyExpression(String key) { - return loadAny(key, (dm) -> dm.getExpressionLoader().load(key, false)); + public static IrisExpression loadAnyExpression(String key, @Nullable IrisData nearest) { + return loadAny(IrisExpression.class, key, nearest); } - public static IrisMod loadAnyMod(String key) { - return loadAny(key, (dm) -> dm.getModLoader().load(key, false)); + public static IrisMod loadAnyMod(String key, @Nullable IrisData nearest) { + return loadAny(IrisMod.class, key, nearest); } - public static IrisJigsawPiece loadAnyJigsawPiece(String key) { - return loadAny(key, (dm) -> dm.getJigsawPieceLoader().load(key, false)); + public static IrisJigsawPiece loadAnyJigsawPiece(String key, @Nullable IrisData nearest) { + return loadAny(IrisJigsawPiece.class, key, nearest); } - public static IrisJigsawPool loadAnyJigsawPool(String key) { - return loadAny(key, (dm) -> dm.getJigsawPoolLoader().load(key, false)); + public static IrisJigsawPool loadAnyJigsawPool(String key, @Nullable IrisData nearest) { + return loadAny(IrisJigsawPool.class, key, nearest); } - public static IrisEntity loadAnyEntity(String key) { - return loadAny(key, (dm) -> dm.getEntityLoader().load(key, false)); + public static IrisEntity loadAnyEntity(String key, @Nullable IrisData nearest) { + return loadAny(IrisEntity.class, key, nearest); } - public static IrisLootTable loadAnyLootTable(String key) { - return loadAny(key, (dm) -> dm.getLootLoader().load(key, false)); + public static IrisLootTable loadAnyLootTable(String key, @Nullable IrisData nearest) { + return loadAny(IrisLootTable.class, key, nearest); } - public static IrisBlockData loadAnyBlock(String key) { - return loadAny(key, (dm) -> dm.getBlockLoader().load(key, false)); + public static IrisBlockData loadAnyBlock(String key, @Nullable IrisData nearest) { + return loadAny(IrisBlockData.class, key, nearest); } - public static IrisSpawner loadAnySpaner(String key) { - return loadAny(key, (dm) -> dm.getSpawnerLoader().load(key, false)); + public static IrisSpawner loadAnySpaner(String key, @Nullable IrisData nearest) { + return loadAny(IrisSpawner.class, key, nearest); } - public static IrisScript loadAnyScript(String key) { - return loadAny(key, (dm) -> dm.getScriptLoader().load(key, false)); + public static IrisScript loadAnyScript(String key, @Nullable IrisData nearest) { + return loadAny(IrisScript.class, key, nearest); } - public static IrisRavine loadAnyRavine(String key) { - return loadAny(key, (dm) -> dm.getRavineLoader().load(key, false)); + public static IrisRavine loadAnyRavine(String key, @Nullable IrisData nearest) { + return loadAny(IrisRavine.class, key, nearest); } - public static IrisRegion loadAnyRegion(String key) { - return loadAny(key, (dm) -> dm.getRegionLoader().load(key, false)); + public static IrisRegion loadAnyRegion(String key, @Nullable IrisData nearest) { + return loadAny(IrisRegion.class, key, nearest); } - public static IrisMarker loadAnyMarker(String key) { - return loadAny(key, (dm) -> dm.getMarkerLoader().load(key, false)); + public static IrisMarker loadAnyMarker(String key, @Nullable IrisData nearest) { + return loadAny(IrisMarker.class, key, nearest); } - public static IrisCave loadAnyCave(String key) { - return loadAny(key, (dm) -> dm.getCaveLoader().load(key, false)); + public static IrisCave loadAnyCave(String key, @Nullable IrisData nearest) { + return loadAny(IrisCave.class, key, nearest); } - public static IrisImage loadAnyImage(String key) { - return loadAny(key, (dm) -> dm.getImageLoader().load(key, false)); + public static IrisImage loadAnyImage(String key, @Nullable IrisData nearest) { + return loadAny(IrisImage.class, key, nearest); } - public static IrisDimension loadAnyDimension(String key) { - return loadAny(key, (dm) -> dm.getDimensionLoader().load(key, false)); + public static IrisDimension loadAnyDimension(String key, @Nullable IrisData nearest) { + return loadAny(IrisDimension.class, key, nearest); } - public static IrisJigsawStructure loadAnyJigsawStructure(String key) { - return loadAny(key, (dm) -> dm.getJigsawStructureLoader().load(key, false)); + public static IrisJigsawStructure loadAnyJigsawStructure(String key, @Nullable IrisData nearest) { + return loadAny(IrisJigsawStructure.class, key, nearest); } - public static IrisGenerator loadAnyGenerator(String key) { - return loadAny(key, (dm) -> dm.getGeneratorLoader().load(key, false)); + public static IrisGenerator loadAnyGenerator(String key, @Nullable IrisData nearest) { + return loadAny(IrisGenerator.class, key, nearest); } - public static T loadAny(String key, Function v) { + public static T loadAny(Class type, String key, @Nullable IrisData nearest) { try { + if (nearest != null) { + T t = nearest.load(type, key, false); + if (t != null) { + return t; + } + } + for (File i : Objects.requireNonNull(Iris.instance.getDataFolder("packs").listFiles())) { if (i.isDirectory()) { IrisData dm = get(i); - T t = v.apply(dm); + if (dm == nearest) continue; + T t = dm.load(type, key, false); if (t != null) { return t; @@ -218,6 +226,17 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { return null; } + public T load(Class type, String key, boolean warn) { + var loader = getLoader(type); + if (loader == null) return null; + return loader.load(key, warn); + } + + @SuppressWarnings("unchecked") + public ResourceLoader getLoader(Class type) { + return (ResourceLoader) loaders.get(type); + } + public ResourceLoader getTypedLoaderFor(File f) { String[] k = f.getPath().split("\\Q" + File.separator + "\\E"); @@ -325,6 +344,7 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { } public synchronized void hotloaded() { + closed = false; environment.close(); possibleSnippets = new KMap<>(); builder = new GsonBuilder() diff --git a/core/src/main/java/com/volmit/iris/core/project/IrisProject.java b/core/src/main/java/com/volmit/iris/core/project/IrisProject.java index 716173bd6..ad5a42bff 100644 --- a/core/src/main/java/com/volmit/iris/core/project/IrisProject.java +++ b/core/src/main/java/com/volmit/iris/core/project/IrisProject.java @@ -159,7 +159,7 @@ public class IrisProject { public void openVSCode(VolmitSender sender) { - IrisDimension d = IrisData.loadAnyDimension(getName()); + IrisDimension d = IrisData.loadAnyDimension(getName(), null); J.attemptAsync(() -> { try { @@ -221,7 +221,7 @@ public class IrisProject { } J.a(() -> { - IrisDimension d = IrisData.loadAnyDimension(getName()); + IrisDimension d = IrisData.loadAnyDimension(getName(), null); if (d == null) { sender.sendMessage("Can't find dimension: " + getName()); return; diff --git a/core/src/main/java/com/volmit/iris/core/service/StudioSVC.java b/core/src/main/java/com/volmit/iris/core/service/StudioSVC.java index 0b0b40717..3d5c36a30 100644 --- a/core/src/main/java/com/volmit/iris/core/service/StudioSVC.java +++ b/core/src/main/java/com/volmit/iris/core/service/StudioSVC.java @@ -93,7 +93,7 @@ public class StudioSVC implements IrisService { public IrisDimension installInto(VolmitSender sender, String type, File folder) { sender.sendMessage("Looking for Package: " + type); - IrisDimension dim = IrisData.loadAnyDimension(type); + IrisDimension dim = IrisData.loadAnyDimension(type, null); if (dim == null) { for (File i : getWorkspaceFolder().listFiles()) { @@ -265,6 +265,7 @@ public class StudioSVC implements IrisService { } IrisDimension d = data.getDimensionLoader().load(dimensions[0]); + data.close(); if (d == null) { sender.sendMessage("Invalid dimension (folder) in dimensions folder"); @@ -279,7 +280,7 @@ public class StudioSVC implements IrisService { IO.delete(packEntry); } - if (IrisData.loadAnyDimension(key) != null) { + if (IrisData.loadAnyDimension(key, null) != null) { sender.sendMessage("Another dimension in the packs folder is already using the key " + key + " IMPORT FAILED!"); return; } @@ -298,6 +299,7 @@ public class StudioSVC implements IrisService { packEntry.mkdirs(); ZipUtil.unpack(cp, packEntry); } + IrisData.get(packEntry).hotloaded(); sender.sendMessage("Successfully Aquired " + d.getName()); ServerConfigurator.installDataPacks(true); diff --git a/core/src/main/java/com/volmit/iris/core/tools/IrisWorldCreator.java b/core/src/main/java/com/volmit/iris/core/tools/IrisWorldCreator.java index b63b77056..a6fd79a83 100644 --- a/core/src/main/java/com/volmit/iris/core/tools/IrisWorldCreator.java +++ b/core/src/main/java/com/volmit/iris/core/tools/IrisWorldCreator.java @@ -64,7 +64,7 @@ public class IrisWorldCreator { } public WorldCreator create() { - IrisDimension dim = IrisData.loadAnyDimension(dimensionName); + IrisDimension dim = IrisData.loadAnyDimension(dimensionName, null); IrisWorld w = IrisWorld.builder() .name(name) @@ -86,7 +86,7 @@ public class IrisWorldCreator { } private World.Environment findEnvironment() { - IrisDimension dim = IrisData.loadAnyDimension(dimensionName); + IrisDimension dim = IrisData.loadAnyDimension(dimensionName, null); if (dim == null || dim.getEnvironment() == null) { return World.Environment.NORMAL; } else { 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 2d7917a6d..a9daa6fef 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 @@ -355,7 +355,7 @@ public class IrisDimension extends IrisRegistrant { KList r = new KList<>(); for (String i : getRegions()) { - r.add(IrisData.loadAnyRegion(i)); + r.add(IrisData.loadAnyRegion(i, getLoader())); } return r; diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisRegion.java b/core/src/main/java/com/volmit/iris/engine/object/IrisRegion.java index a2712786a..157cb61c9 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisRegion.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisRegion.java @@ -346,7 +346,7 @@ public class IrisRegion extends IrisRegistrant implements IRare { continue; } - IrisBiome biome = IrisData.loadAnyBiome(i); + IrisBiome biome = IrisData.loadAnyBiome(i, getLoader()); names.remove(i); if (biome == null) { diff --git a/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java b/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java index 0be74eef8..c16bebc3a 100644 --- a/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java +++ b/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java @@ -170,7 +170,7 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun if (dimension == null) { Iris.error("Oh No! There's no pack in " + data.getDataFolder().getPath() + " or... there's no dimension for the key " + dimensionKey); - IrisDimension test = IrisData.loadAnyDimension(dimensionKey); + IrisDimension test = IrisData.loadAnyDimension(dimensionKey, null); if (test != null) { Iris.warn("Looks like " + dimensionKey + " exists in " + test.getLoadFile().getPath() + " "); From 3981b0976d516b5af6db7edfc78919d2376feeb8 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sat, 20 Sep 2025 23:05:16 +0200 Subject: [PATCH 14/21] cleanup command framework and fix random locator fails --- .../iris/core/commands/CommandDeveloper.java | 3 +- .../iris/core/commands/CommandJigsaw.java | 4 +- .../iris/core/commands/CommandObject.java | 6 +- .../iris/core/commands/CommandStudio.java | 4 +- .../volmit/iris/core/service/CommandSVC.java | 10 ++- .../volmit/iris/engine/framework/Engine.java | 4 +- .../iris/util/decree/DecreeContext.java | 21 ++--- .../iris/util/decree/DecreeExecutor.java | 9 +++ .../util/decree/DecreeParameterHandler.java | 2 +- .../volmit/iris/util/decree/DecreeSystem.java | 27 ++++--- .../util/decree/handlers/BiomeHandler.java | 59 +------------- .../util/decree/handlers/CaveHandler.java | 58 +------------- .../decree/handlers/DimensionHandler.java | 53 ++----------- .../util/decree/handlers/EntityHandler.java | 79 +------------------ .../decree/handlers/GeneratorHandler.java | 55 +------------ .../decree/handlers/JigsawPieceHandler.java | 58 +------------- .../decree/handlers/JigsawPoolHandler.java | 58 +------------- .../handlers/JigsawStructureHandler.java | 58 +------------- .../util/decree/handlers/RegionHandler.java | 58 +------------- .../util/decree/handlers/ScriptHandler.java | 55 +------------ .../NullableDimensionHandler.java | 54 +------------ .../decree/specialhandlers/ObjectHandler.java | 6 +- .../specialhandlers/RegistrantHandler.java | 73 +++++++++++++++++ .../decree/virtual/VirtualDecreeCommand.java | 32 +++++--- 24 files changed, 196 insertions(+), 650 deletions(-) create mode 100644 core/src/main/java/com/volmit/iris/util/decree/specialhandlers/RegistrantHandler.java diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java b/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java index d1c1f169a..5b0ab41cf 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java @@ -71,7 +71,6 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; -import java.util.stream.Stream; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; @@ -159,7 +158,7 @@ public class CommandDeveloper implements DecreeExecutor { @SneakyThrows @Decree(description = "Generate Iris structures for all loaded datapack structures") public void generateStructures( - @Param(description = "The pack to add the generated structures to", aliases = "pack", defaultValue = "---", customHandler = NullableDimensionHandler.class) + @Param(description = "The pack to add the generated structures to", aliases = "pack", defaultValue = "null", customHandler = NullableDimensionHandler.class) IrisDimension dimension, @Param(description = "Ignore existing structures", defaultValue = "false") boolean force diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandJigsaw.java b/core/src/main/java/com/volmit/iris/core/commands/CommandJigsaw.java index 3022b866c..7777f1764 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandJigsaw.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandJigsaw.java @@ -48,7 +48,7 @@ public class CommandJigsaw implements DecreeExecutor { IrisJigsawPiece piece ) { File dest = piece.getLoadFile(); - new JigsawEditor(player(), piece, IrisData.loadAnyObject(piece.getObject()), dest); + new JigsawEditor(player(), piece, IrisData.loadAnyObject(piece.getObject(), data()), dest); } @Decree(description = "Place a jigsaw structure") @@ -78,7 +78,7 @@ public class CommandJigsaw implements DecreeExecutor { @Param(description = "The object to use for this piece", customHandler = ObjectHandler.class) String object ) { - IrisObject o = IrisData.loadAnyObject(object); + IrisObject o = IrisData.loadAnyObject(object, data()); if (object == null) { sender().sendMessage(C.RED + "Failed to find existing object"); diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandObject.java b/core/src/main/java/com/volmit/iris/core/commands/CommandObject.java index ccbf91c11..fd057b300 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandObject.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandObject.java @@ -136,7 +136,7 @@ public class CommandObject implements DecreeExecutor { @Param(description = "The object to analyze", customHandler = ObjectHandler.class) String object ) { - IrisObject o = IrisData.loadAnyObject(object); + IrisObject o = IrisData.loadAnyObject(object, data()); sender().sendMessage("Object Size: " + o.getW() + " * " + o.getH() + " * " + o.getD() + ""); sender().sendMessage("Blocks Used: " + NumberFormat.getIntegerInstance().format(o.getBlocks().size())); @@ -201,7 +201,7 @@ public class CommandObject implements DecreeExecutor { @Decree(description = "Shrink an object to its minimum size") public void shrink(@Param(description = "The object to shrink", customHandler = ObjectHandler.class) String object) { - IrisObject o = IrisData.loadAnyObject(object); + IrisObject o = IrisData.loadAnyObject(object, data()); sender().sendMessage("Current Object Size: " + o.getW() + " * " + o.getH() + " * " + o.getD()); o.shrinkwrap(); sender().sendMessage("New Object Size: " + o.getW() + " * " + o.getH() + " * " + o.getD()); @@ -325,7 +325,7 @@ public class CommandObject implements DecreeExecutor { // @Param(description = "The scale interpolator to use", defaultValue = "none") // IrisObjectPlacementScaleInterpolator interpolator ) { - IrisObject o = IrisData.loadAnyObject(object); + IrisObject o = IrisData.loadAnyObject(object, data()); double maxScale = Double.max(10 - o.getBlocks().size() / 10000d, 1); if (scale > maxScale) { sender().sendMessage(C.YELLOW + "Indicated scale exceeds maximum. Downscaled to maximum: " + maxScale); diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandStudio.java b/core/src/main/java/com/volmit/iris/core/commands/CommandStudio.java index 6ebb7bac1..555ce4f48 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandStudio.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandStudio.java @@ -171,9 +171,9 @@ public class CommandStudio implements DecreeExecutor { var loc = player().getLocation().clone(); J.a(() -> { - DecreeContext.touch(sender); PlatformChunkGenerator plat = IrisToolbelt.access(world); Engine engine = plat.getEngine(); + DecreeContext.touch(sender); try (SyncExecutor executor = new SyncExecutor(20)) { int x = loc.getBlockX() >> 4; int z = loc.getBlockZ() >> 4; @@ -247,6 +247,8 @@ public class CommandStudio implements DecreeExecutor { } catch (Throwable e) { sender().sendMessage("Error while regenerating chunks"); e.printStackTrace(); + } finally { + DecreeContext.remove(); } }); } diff --git a/core/src/main/java/com/volmit/iris/core/service/CommandSVC.java b/core/src/main/java/com/volmit/iris/core/service/CommandSVC.java index d2e56d587..7ca3ba94e 100644 --- a/core/src/main/java/com/volmit/iris/core/service/CommandSVC.java +++ b/core/src/main/java/com/volmit/iris/core/service/CommandSVC.java @@ -23,6 +23,7 @@ import com.volmit.iris.core.commands.CommandIris; import com.volmit.iris.core.tools.IrisToolbelt; import com.volmit.iris.engine.data.cache.AtomicCache; import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.decree.DecreeContext; import com.volmit.iris.util.decree.DecreeSystem; import com.volmit.iris.util.decree.virtual.VirtualDecreeCommand; import com.volmit.iris.util.format.C; @@ -44,7 +45,14 @@ public class CommandSVC implements IrisService, DecreeSystem { @Override public void onEnable() { Iris.instance.getCommand("iris").setExecutor(this); - J.a(() -> getRoot().cacheAll()); + J.a(() -> { + DecreeContext.touch(Iris.getSender()); + try { + getRoot().cacheAll(); + } finally { + DecreeContext.remove(); + } + }); } @Override diff --git a/core/src/main/java/com/volmit/iris/engine/framework/Engine.java b/core/src/main/java/com/volmit/iris/engine/framework/Engine.java index e6ac9f954..9d5cfad00 100644 --- a/core/src/main/java/com/volmit/iris/engine/framework/Engine.java +++ b/core/src/main/java/com/volmit/iris/engine/framework/Engine.java @@ -863,7 +863,7 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat default void gotoBiome(IrisBiome biome, Player player, boolean teleport) { Set regionKeys = getDimension() .getAllRegions(this).stream() - .filter((i) -> i.getAllBiomes(this).contains(biome)) + .filter((i) -> i.getAllBiomeIds().contains(biome.getLoadKey())) .map(IrisRegistrant::getLoadKey) .collect(Collectors.toSet()); Locator lb = Locator.surfaceBiome(biome.getLoadKey()); @@ -959,7 +959,7 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat } default void gotoRegion(IrisRegion r, Player player, boolean teleport) { - if (!getDimension().getAllRegions(this).contains(r)) { + if (!getDimension().getRegions().contains(r.getLoadKey())) { player.sendMessage(C.RED + r.getName() + " is not defined in the dimension!"); return; } diff --git a/core/src/main/java/com/volmit/iris/util/decree/DecreeContext.java b/core/src/main/java/com/volmit/iris/util/decree/DecreeContext.java index f8ee1e233..b19fe32f4 100644 --- a/core/src/main/java/com/volmit/iris/util/decree/DecreeContext.java +++ b/core/src/main/java/com/volmit/iris/util/decree/DecreeContext.java @@ -18,29 +18,20 @@ package com.volmit.iris.util.decree; -import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.plugin.VolmitSender; -import com.volmit.iris.util.scheduling.ChronoLatch; public class DecreeContext { - private static final ChronoLatch cl = new ChronoLatch(60000); - private static final KMap context = new KMap<>(); + private static final ThreadLocal context = new ThreadLocal<>(); public static VolmitSender get() { - return context.get(Thread.currentThread()); + return context.get(); } public static void touch(VolmitSender c) { - synchronized (context) { - context.put(Thread.currentThread(), c); + context.set(c); + } - if (cl.flip()) { - for (Thread i : context.k()) { - if (!i.isAlive()) { - context.remove(i); - } - } - } - } + public static void remove() { + context.remove(); } } diff --git a/core/src/main/java/com/volmit/iris/util/decree/DecreeExecutor.java b/core/src/main/java/com/volmit/iris/util/decree/DecreeExecutor.java index 8d25d6ca3..12404d1c0 100644 --- a/core/src/main/java/com/volmit/iris/util/decree/DecreeExecutor.java +++ b/core/src/main/java/com/volmit/iris/util/decree/DecreeExecutor.java @@ -18,6 +18,7 @@ package com.volmit.iris.util.decree; +import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.core.tools.IrisToolbelt; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.platform.PlatformChunkGenerator; @@ -34,6 +35,14 @@ public interface DecreeExecutor { return sender().player(); } + default IrisData data() { + var access = access(); + if (access != null) { + return access.getData(); + } + return null; + } + default Engine engine() { if (sender().isPlayer() && IrisToolbelt.access(sender().player().getWorld()) != null) { PlatformChunkGenerator gen = IrisToolbelt.access(sender().player().getWorld()); diff --git a/core/src/main/java/com/volmit/iris/util/decree/DecreeParameterHandler.java b/core/src/main/java/com/volmit/iris/util/decree/DecreeParameterHandler.java index ee959d2b3..097361799 100644 --- a/core/src/main/java/com/volmit/iris/util/decree/DecreeParameterHandler.java +++ b/core/src/main/java/com/volmit/iris/util/decree/DecreeParameterHandler.java @@ -23,7 +23,7 @@ import com.volmit.iris.util.decree.exceptions.DecreeParsingException; import java.util.concurrent.atomic.AtomicReference; -public interface DecreeParameterHandler { +public interface DecreeParameterHandler extends DecreeExecutor { /** * Should return the possible values for this type * diff --git a/core/src/main/java/com/volmit/iris/util/decree/DecreeSystem.java b/core/src/main/java/com/volmit/iris/util/decree/DecreeSystem.java index d22a8b822..5edacb51f 100644 --- a/core/src/main/java/com/volmit/iris/util/decree/DecreeSystem.java +++ b/core/src/main/java/com/volmit/iris/util/decree/DecreeSystem.java @@ -133,23 +133,32 @@ public interface DecreeSystem extends CommandExecutor, TabCompleter { default boolean call(VolmitSender sender, String[] args) { DecreeContext.touch(sender); - return getRoot().invoke(sender, enhanceArgs(args)); + try { + return getRoot().invoke(sender, enhanceArgs(args)); + } finally { + DecreeContext.remove(); + } } @Nullable @Override default List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) { - KList enhanced = new KList<>(args); - KList v = getRoot().tabComplete(enhanced, enhanced.toString(" ")); - v.removeDuplicates(); + DecreeContext.touch(new VolmitSender(sender)); + try { + KList enhanced = new KList<>(args); + KList v = getRoot().tabComplete(enhanced, enhanced.toString(" ")); + v.removeDuplicates(); - if (sender instanceof Player) { - if (IrisSettings.get().getGeneral().isCommandSounds()) { - ((Player) sender).playSound(((Player) sender).getLocation(), Sound.BLOCK_AMETHYST_BLOCK_CHIME, 0.25f, RNG.r.f(0.125f, 1.95f)); + if (sender instanceof Player) { + if (IrisSettings.get().getGeneral().isCommandSounds()) { + ((Player) sender).playSound(((Player) sender).getLocation(), Sound.BLOCK_AMETHYST_BLOCK_CHIME, 0.25f, RNG.r.f(0.125f, 1.95f)); + } } - } - return v; + return v; + } finally { + DecreeContext.remove(); + } } @Override diff --git a/core/src/main/java/com/volmit/iris/util/decree/handlers/BiomeHandler.java b/core/src/main/java/com/volmit/iris/util/decree/handlers/BiomeHandler.java index ca950c8e6..36f78ee16 100644 --- a/core/src/main/java/com/volmit/iris/util/decree/handlers/BiomeHandler.java +++ b/core/src/main/java/com/volmit/iris/util/decree/handlers/BiomeHandler.java @@ -18,63 +18,12 @@ package com.volmit.iris.util.decree.handlers; -import com.volmit.iris.Iris; -import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.engine.object.IrisBiome; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.decree.DecreeParameterHandler; -import com.volmit.iris.util.decree.exceptions.DecreeParsingException; +import com.volmit.iris.util.decree.specialhandlers.RegistrantHandler; -import java.io.File; -import java.util.stream.Collectors; - -public class BiomeHandler implements DecreeParameterHandler { - @Override - public KList getPossibilities() { - KMap p = new KMap<>(); - - //noinspection ConstantConditions - for (File i : Iris.instance.getDataFolder("packs").listFiles()) { - if (i.isDirectory()) { - IrisData data = IrisData.get(i); - for (IrisBiome j : data.getBiomeLoader().loadAll(data.getBiomeLoader().getPossibleKeys())) { - p.putIfAbsent(j.getLoadKey(), j); - } - - data.close(); - } - } - - return p.v(); - } - - @Override - public String toString(IrisBiome dim) { - return dim.getLoadKey(); - } - - @Override - public IrisBiome parse(String in, boolean force) throws DecreeParsingException { - if (in.equals("null")) { - return null; - } - KList options = getPossibilities(in); - - if (options.isEmpty()) { - throw new DecreeParsingException("Unable to find Biome \"" + in + "\""); - } - - try { - return options.stream().filter((i) -> toString(i).equalsIgnoreCase(in)).collect(Collectors.toList()).get(0); - } catch (Throwable e) { - throw new DecreeParsingException("Unable to filter which Biome \"" + in + "\""); - } - } - - @Override - public boolean supports(Class type) { - return type.equals(IrisBiome.class); +public class BiomeHandler extends RegistrantHandler { + public BiomeHandler() { + super(IrisBiome.class, true); } @Override diff --git a/core/src/main/java/com/volmit/iris/util/decree/handlers/CaveHandler.java b/core/src/main/java/com/volmit/iris/util/decree/handlers/CaveHandler.java index db8f86651..bd14aab68 100644 --- a/core/src/main/java/com/volmit/iris/util/decree/handlers/CaveHandler.java +++ b/core/src/main/java/com/volmit/iris/util/decree/handlers/CaveHandler.java @@ -18,62 +18,12 @@ package com.volmit.iris.util.decree.handlers; -import com.volmit.iris.Iris; -import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.engine.object.IrisCave; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.decree.DecreeParameterHandler; -import com.volmit.iris.util.decree.exceptions.DecreeParsingException; +import com.volmit.iris.util.decree.specialhandlers.RegistrantHandler; -import java.io.File; -import java.util.stream.Collectors; - -public class CaveHandler implements DecreeParameterHandler { - @Override - public KList getPossibilities() { - KMap p = new KMap<>(); - - //noinspection ConstantConditions - for (File i : Iris.instance.getDataFolder("packs").listFiles()) { - if (i.isDirectory()) { - IrisData data = IrisData.get(i); - for (IrisCave j : data.getCaveLoader().loadAll(data.getCaveLoader().getPossibleKeys())) { - p.putIfAbsent(j.getLoadKey(), j); - } - - data.close(); - } - } - - return p.v(); - } - - @Override - public String toString(IrisCave dim) { - return dim.getLoadKey(); - } - - @Override - public IrisCave parse(String in, boolean force) throws DecreeParsingException { - if (in.equals("null")) { - return null; - } - KList options = getPossibilities(in); - - if (options.isEmpty()) { - throw new DecreeParsingException("Unable to find Cave \"" + in + "\""); - } - try { - return options.stream().filter((i) -> toString(i).equalsIgnoreCase(in)).collect(Collectors.toList()).get(0); - } catch (Throwable e) { - throw new DecreeParsingException("Unable to filter which Cave\"" + in + "\""); - } - } - - @Override - public boolean supports(Class type) { - return type.equals(IrisCave.class); +public class CaveHandler extends RegistrantHandler { + public CaveHandler() { + super(IrisCave.class, true); } @Override diff --git a/core/src/main/java/com/volmit/iris/util/decree/handlers/DimensionHandler.java b/core/src/main/java/com/volmit/iris/util/decree/handlers/DimensionHandler.java index 5d36af07f..d8eeb998c 100644 --- a/core/src/main/java/com/volmit/iris/util/decree/handlers/DimensionHandler.java +++ b/core/src/main/java/com/volmit/iris/util/decree/handlers/DimensionHandler.java @@ -18,65 +18,22 @@ package com.volmit.iris.util.decree.handlers; -import com.volmit.iris.Iris; import com.volmit.iris.core.IrisSettings; -import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.engine.object.IrisDimension; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.decree.DecreeParameterHandler; import com.volmit.iris.util.decree.exceptions.DecreeParsingException; +import com.volmit.iris.util.decree.specialhandlers.RegistrantHandler; -import java.io.File; - -public class DimensionHandler implements DecreeParameterHandler { - @Override - public KList getPossibilities() { - KMap p = new KMap<>(); - - //noinspection ConstantConditions - for (File i : Iris.instance.getDataFolder("packs").listFiles()) { - if (i.isDirectory()) { - IrisData data = IrisData.get(i); - for (IrisDimension j : data.getDimensionLoader().loadAll(data.getDimensionLoader().getPossibleKeys())) { - p.putIfAbsent(j.getLoadKey(), j); - } - - data.close(); - } - } - - return p.v(); - } - - @Override - public String toString(IrisDimension dim) { - return dim.getLoadKey(); +public class DimensionHandler extends RegistrantHandler { + public DimensionHandler() { + super(IrisDimension.class, false); } @Override public IrisDimension parse(String in, boolean force) throws DecreeParsingException { - if (in.equalsIgnoreCase("default")) { return parse(IrisSettings.get().getGenerator().getDefaultWorldType()); } - - KList options = getPossibilities(in); - - - if (options.isEmpty()) { - throw new DecreeParsingException("Unable to find Dimension \"" + in + "\""); - } - try { - return options.stream().filter((i) -> toString(i).equalsIgnoreCase(in)).toList().get(0); - } catch (Throwable e) { - throw new DecreeParsingException("Unable to filter which Dimension \"" + in + "\""); - } - } - - @Override - public boolean supports(Class type) { - return type.equals(IrisDimension.class); + return super.parse(in, force); } @Override diff --git a/core/src/main/java/com/volmit/iris/util/decree/handlers/EntityHandler.java b/core/src/main/java/com/volmit/iris/util/decree/handlers/EntityHandler.java index 01034c747..3cf4b4e08 100644 --- a/core/src/main/java/com/volmit/iris/util/decree/handlers/EntityHandler.java +++ b/core/src/main/java/com/volmit/iris/util/decree/handlers/EntityHandler.java @@ -18,84 +18,13 @@ package com.volmit.iris.util.decree.handlers; -import com.volmit.iris.Iris; -import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.engine.object.IrisEntity; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.decree.DecreeParameterHandler; -import com.volmit.iris.util.decree.exceptions.DecreeParsingException; +import com.volmit.iris.util.decree.specialhandlers.RegistrantHandler; -import java.io.File; -import java.util.stream.Collectors; +public class EntityHandler extends RegistrantHandler { -public class EntityHandler implements DecreeParameterHandler { - - /** - * Should return the possible values for this type - * - * @return Possibilities for this type. - */ - @Override - public KList getPossibilities() { - KMap p = new KMap<>(); - - //noinspection ConstantConditions - for (File i : Iris.instance.getDataFolder("packs").listFiles()) { - if (i.isDirectory()) { - IrisData data = IrisData.get(i); - for (IrisEntity j : data.getEntityLoader().loadAll(data.getEntityLoader().getPossibleKeys())) { - p.putIfAbsent(j.getLoadKey(), j); - } - - data.close(); - } - } - - return p.v(); - } - - /** - * Converting the type back to a string (inverse of the {@link #parse(String) parse} method) - * - * @param entity The input of the designated type to convert to a String - * @return The resulting string - */ - @Override - public String toString(IrisEntity entity) { - return entity.getLoadKey(); - } - - /** - * Should parse a String into the designated type - * - * @param in The string to parse - * @return The value extracted from the string, of the designated type - * @throws DecreeParsingException Thrown when the parsing fails (ex: "oop" translated to an integer throws this) - */ - @Override - public IrisEntity parse(String in, boolean force) throws DecreeParsingException { - KList options = getPossibilities(in); - - if (options.isEmpty()) { - throw new DecreeParsingException("Unable to find Entity \"" + in + "\""); - } - try { - return options.stream().filter((i) -> toString(i).equalsIgnoreCase(in)).collect(Collectors.toList()).get(0); - } catch (Throwable e) { - throw new DecreeParsingException("Unable to filter which Entity \"" + in + "\""); - } - } - - /** - * Returns whether a certain type is supported by this handler
- * - * @param type The type to check - * @return True if supported, false if not - */ - @Override - public boolean supports(Class type) { - return type.equals(IrisEntity.class); + public EntityHandler() { + super(IrisEntity.class, false); } @Override diff --git a/core/src/main/java/com/volmit/iris/util/decree/handlers/GeneratorHandler.java b/core/src/main/java/com/volmit/iris/util/decree/handlers/GeneratorHandler.java index 7a89ef155..8b1baf0ae 100644 --- a/core/src/main/java/com/volmit/iris/util/decree/handlers/GeneratorHandler.java +++ b/core/src/main/java/com/volmit/iris/util/decree/handlers/GeneratorHandler.java @@ -18,59 +18,12 @@ package com.volmit.iris.util.decree.handlers; -import com.volmit.iris.Iris; -import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.engine.object.IrisGenerator; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.decree.DecreeParameterHandler; -import com.volmit.iris.util.decree.exceptions.DecreeParsingException; +import com.volmit.iris.util.decree.specialhandlers.RegistrantHandler; -import java.io.File; -import java.util.stream.Collectors; - -public class GeneratorHandler implements DecreeParameterHandler { - @Override - public KList getPossibilities() { - KMap p = new KMap<>(); - - //noinspection ConstantConditions - for (File i : Iris.instance.getDataFolder("packs").listFiles()) { - if (i.isDirectory()) { - IrisData data = IrisData.get(i); - for (IrisGenerator j : data.getGeneratorLoader().loadAll(data.getGeneratorLoader().getPossibleKeys())) { - p.putIfAbsent(j.getLoadKey(), j); - } - - data.close(); - } - } - - return p.v(); - } - - @Override - public String toString(IrisGenerator gen) { - return gen.getLoadKey(); - } - - @Override - public IrisGenerator parse(String in, boolean force) throws DecreeParsingException { - KList options = getPossibilities(in); - - if (options.isEmpty()) { - throw new DecreeParsingException("Unable to find Generator \"" + in + "\""); - } - try { - return options.stream().filter((i) -> toString(i).equalsIgnoreCase(in)).collect(Collectors.toList()).get(0); - } catch (Throwable e) { - throw new DecreeParsingException("Unable to filter which Generator \"" + in + "\""); - } - } - - @Override - public boolean supports(Class type) { - return type.equals(IrisGenerator.class); +public class GeneratorHandler extends RegistrantHandler { + public GeneratorHandler() { + super(IrisGenerator.class, false); } @Override diff --git a/core/src/main/java/com/volmit/iris/util/decree/handlers/JigsawPieceHandler.java b/core/src/main/java/com/volmit/iris/util/decree/handlers/JigsawPieceHandler.java index 0457674be..3199d3039 100644 --- a/core/src/main/java/com/volmit/iris/util/decree/handlers/JigsawPieceHandler.java +++ b/core/src/main/java/com/volmit/iris/util/decree/handlers/JigsawPieceHandler.java @@ -18,62 +18,12 @@ package com.volmit.iris.util.decree.handlers; -import com.volmit.iris.Iris; -import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.engine.object.IrisJigsawPiece; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.decree.DecreeParameterHandler; -import com.volmit.iris.util.decree.exceptions.DecreeParsingException; +import com.volmit.iris.util.decree.specialhandlers.RegistrantHandler; -import java.io.File; -import java.util.stream.Collectors; - -public class JigsawPieceHandler implements DecreeParameterHandler { - @Override - public KList getPossibilities() { - KMap p = new KMap<>(); - - //noinspection ConstantConditions - for (File i : Iris.instance.getDataFolder("packs").listFiles()) { - if (i.isDirectory()) { - IrisData data = IrisData.get(i); - for (IrisJigsawPiece j : data.getJigsawPieceLoader().loadAll(data.getJigsawPieceLoader().getPossibleKeys())) { - p.putIfAbsent(j.getLoadKey(), j); - } - - data.close(); - } - } - - return p.v(); - } - - @Override - public String toString(IrisJigsawPiece dim) { - return dim.getLoadKey(); - } - - @Override - public IrisJigsawPiece parse(String in, boolean force) throws DecreeParsingException { - if (in.equals("null")) { - return null; - } - KList options = getPossibilities(in); - - if (options.isEmpty()) { - throw new DecreeParsingException("Unable to find Jigsaw Piece \"" + in + "\""); - } - try { - return options.stream().filter((i) -> toString(i).equalsIgnoreCase(in)).collect(Collectors.toList()).get(0); - } catch (Throwable e) { - throw new DecreeParsingException("Unable to filter which Jigsaw Piece \"" + in + "\""); - } - } - - @Override - public boolean supports(Class type) { - return type.equals(IrisJigsawPiece.class); +public class JigsawPieceHandler extends RegistrantHandler { + public JigsawPieceHandler() { + super(IrisJigsawPiece.class, true); } @Override diff --git a/core/src/main/java/com/volmit/iris/util/decree/handlers/JigsawPoolHandler.java b/core/src/main/java/com/volmit/iris/util/decree/handlers/JigsawPoolHandler.java index da29d9fe8..83a14ab4c 100644 --- a/core/src/main/java/com/volmit/iris/util/decree/handlers/JigsawPoolHandler.java +++ b/core/src/main/java/com/volmit/iris/util/decree/handlers/JigsawPoolHandler.java @@ -18,62 +18,12 @@ package com.volmit.iris.util.decree.handlers; -import com.volmit.iris.Iris; -import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.engine.object.IrisJigsawPool; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.decree.DecreeParameterHandler; -import com.volmit.iris.util.decree.exceptions.DecreeParsingException; +import com.volmit.iris.util.decree.specialhandlers.RegistrantHandler; -import java.io.File; -import java.util.stream.Collectors; - -public class JigsawPoolHandler implements DecreeParameterHandler { - @Override - public KList getPossibilities() { - KMap p = new KMap<>(); - - //noinspection ConstantConditions - for (File i : Iris.instance.getDataFolder("packs").listFiles()) { - if (i.isDirectory()) { - IrisData data = IrisData.get(i); - for (IrisJigsawPool j : data.getJigsawPoolLoader().loadAll(data.getJigsawPoolLoader().getPossibleKeys())) { - p.putIfAbsent(j.getLoadKey(), j); - } - - data.close(); - } - } - - return p.v(); - } - - @Override - public String toString(IrisJigsawPool dim) { - return dim.getLoadKey(); - } - - @Override - public IrisJigsawPool parse(String in, boolean force) throws DecreeParsingException { - if (in.equals("null")) { - return null; - } - KList options = getPossibilities(in); - - if (options.isEmpty()) { - throw new DecreeParsingException("Unable to find Jigsaw Pool \"" + in + "\""); - } - try { - return options.stream().filter((i) -> toString(i).equalsIgnoreCase(in)).collect(Collectors.toList()).get(0); - } catch (Throwable e) { - throw new DecreeParsingException("Unable to filter which Jigsaw Pool \"" + in + "\""); - } - } - - @Override - public boolean supports(Class type) { - return type.equals(IrisJigsawPool.class); +public class JigsawPoolHandler extends RegistrantHandler { + public JigsawPoolHandler() { + super(IrisJigsawPool.class, true); } @Override diff --git a/core/src/main/java/com/volmit/iris/util/decree/handlers/JigsawStructureHandler.java b/core/src/main/java/com/volmit/iris/util/decree/handlers/JigsawStructureHandler.java index 21dec6246..b7d6e35cd 100644 --- a/core/src/main/java/com/volmit/iris/util/decree/handlers/JigsawStructureHandler.java +++ b/core/src/main/java/com/volmit/iris/util/decree/handlers/JigsawStructureHandler.java @@ -18,62 +18,12 @@ package com.volmit.iris.util.decree.handlers; -import com.volmit.iris.Iris; -import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.engine.object.IrisJigsawStructure; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.decree.DecreeParameterHandler; -import com.volmit.iris.util.decree.exceptions.DecreeParsingException; +import com.volmit.iris.util.decree.specialhandlers.RegistrantHandler; -import java.io.File; -import java.util.stream.Collectors; - -public class JigsawStructureHandler implements DecreeParameterHandler { - @Override - public KList getPossibilities() { - KMap p = new KMap<>(); - - //noinspection ConstantConditions - for (File i : Iris.instance.getDataFolder("packs").listFiles()) { - if (i.isDirectory()) { - IrisData data = IrisData.get(i); - for (IrisJigsawStructure j : data.getJigsawStructureLoader().loadAll(data.getJigsawStructureLoader().getPossibleKeys())) { - p.putIfAbsent(j.getLoadKey(), j); - } - - data.close(); - } - } - - return p.v(); - } - - @Override - public String toString(IrisJigsawStructure dim) { - return dim.getLoadKey(); - } - - @Override - public IrisJigsawStructure parse(String in, boolean force) throws DecreeParsingException { - if (in.equals("null")) { - return null; - } - KList options = getPossibilities(in); - - if (options.isEmpty()) { - throw new DecreeParsingException("Unable to find Jigsaw Structure \"" + in + "\""); - } - try { - return options.stream().filter((i) -> toString(i).equalsIgnoreCase(in)).collect(Collectors.toList()).get(0); - } catch (Throwable e) { - throw new DecreeParsingException("Unable to filter which Jigsaw Structure \"" + in + "\""); - } - } - - @Override - public boolean supports(Class type) { - return type.equals(IrisJigsawStructure.class); +public class JigsawStructureHandler extends RegistrantHandler { + public JigsawStructureHandler() { + super(IrisJigsawStructure.class, true); } @Override diff --git a/core/src/main/java/com/volmit/iris/util/decree/handlers/RegionHandler.java b/core/src/main/java/com/volmit/iris/util/decree/handlers/RegionHandler.java index c12c5be09..5b47af457 100644 --- a/core/src/main/java/com/volmit/iris/util/decree/handlers/RegionHandler.java +++ b/core/src/main/java/com/volmit/iris/util/decree/handlers/RegionHandler.java @@ -18,62 +18,12 @@ package com.volmit.iris.util.decree.handlers; -import com.volmit.iris.Iris; -import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.engine.object.IrisRegion; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.decree.DecreeParameterHandler; -import com.volmit.iris.util.decree.exceptions.DecreeParsingException; +import com.volmit.iris.util.decree.specialhandlers.RegistrantHandler; -import java.io.File; -import java.util.stream.Collectors; - -public class RegionHandler implements DecreeParameterHandler { - @Override - public KList getPossibilities() { - KMap p = new KMap<>(); - - //noinspection ConstantConditions - for (File i : Iris.instance.getDataFolder("packs").listFiles()) { - if (i.isDirectory()) { - IrisData data = IrisData.get(i); - for (IrisRegion j : data.getRegionLoader().loadAll(data.getRegionLoader().getPossibleKeys())) { - p.putIfAbsent(j.getLoadKey(), j); - } - - data.close(); - } - } - - return p.v(); - } - - @Override - public String toString(IrisRegion dim) { - return dim.getLoadKey(); - } - - @Override - public IrisRegion parse(String in, boolean force) throws DecreeParsingException { - if (in.equals("null")) { - return null; - } - KList options = getPossibilities(in); - - if (options.isEmpty()) { - throw new DecreeParsingException("Unable to find Region \"" + in + "\""); - } - try { - return options.stream().filter((i) -> toString(i).equalsIgnoreCase(in)).collect(Collectors.toList()).get(0); - } catch (Throwable e) { - throw new DecreeParsingException("Unable to filter which Region \"" + in + "\""); - } - } - - @Override - public boolean supports(Class type) { - return type.equals(IrisRegion.class); +public class RegionHandler extends RegistrantHandler { + public RegionHandler() { + super(IrisRegion.class, true); } @Override diff --git a/core/src/main/java/com/volmit/iris/util/decree/handlers/ScriptHandler.java b/core/src/main/java/com/volmit/iris/util/decree/handlers/ScriptHandler.java index a32670cf6..f5eaf01a4 100644 --- a/core/src/main/java/com/volmit/iris/util/decree/handlers/ScriptHandler.java +++ b/core/src/main/java/com/volmit/iris/util/decree/handlers/ScriptHandler.java @@ -18,59 +18,12 @@ package com.volmit.iris.util.decree.handlers; -import com.volmit.iris.Iris; -import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.engine.object.IrisScript; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.decree.DecreeParameterHandler; -import com.volmit.iris.util.decree.exceptions.DecreeParsingException; +import com.volmit.iris.util.decree.specialhandlers.RegistrantHandler; -import java.io.File; -import java.util.stream.Collectors; - -public class ScriptHandler implements DecreeParameterHandler { - @Override - public KList getPossibilities() { - KMap p = new KMap<>(); - - //noinspection ConstantConditions - for (File i : Iris.instance.getDataFolder("packs").listFiles()) { - if (i.isDirectory()) { - IrisData data = IrisData.get(i); - for (IrisScript j : data.getScriptLoader().loadAll(data.getScriptLoader().getPossibleKeys())) { - p.putIfAbsent(j.getLoadKey(), j); - } - - data.close(); - } - } - - return p.v(); - } - - @Override - public String toString(IrisScript script) { - return script.getLoadKey(); - } - - @Override - public IrisScript parse(String in, boolean force) throws DecreeParsingException { - KList options = getPossibilities(in); - - if (options.isEmpty()) { - throw new DecreeParsingException("Unable to find Script \"" + in + "\""); - } - try { - return options.stream().filter((i) -> toString(i).equalsIgnoreCase(in)).collect(Collectors.toList()).get(0); - } catch (Throwable e) { - throw new DecreeParsingException("Unable to filter which Script \"" + in + "\""); - } - } - - @Override - public boolean supports(Class type) { - return type.equals(IrisScript.class); +public class ScriptHandler extends RegistrantHandler { + public ScriptHandler() { + super(IrisScript.class, false); } @Override diff --git a/core/src/main/java/com/volmit/iris/util/decree/specialhandlers/NullableDimensionHandler.java b/core/src/main/java/com/volmit/iris/util/decree/specialhandlers/NullableDimensionHandler.java index 706dd61a5..c8278558b 100644 --- a/core/src/main/java/com/volmit/iris/util/decree/specialhandlers/NullableDimensionHandler.java +++ b/core/src/main/java/com/volmit/iris/util/decree/specialhandlers/NullableDimensionHandler.java @@ -18,67 +18,21 @@ package com.volmit.iris.util.decree.specialhandlers; -import com.volmit.iris.Iris; import com.volmit.iris.core.IrisSettings; -import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.engine.object.IrisDimension; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.decree.DecreeParameterHandler; import com.volmit.iris.util.decree.exceptions.DecreeParsingException; -import java.io.File; - -public class NullableDimensionHandler implements DecreeParameterHandler { - @Override - public KList getPossibilities() { - KMap p = new KMap<>(); - - //noinspection ConstantConditions - for (File i : Iris.instance.getDataFolder("packs").listFiles()) { - if (i.isDirectory()) { - IrisData data = IrisData.get(i); - for (IrisDimension j : data.getDimensionLoader().loadAll(data.getDimensionLoader().getPossibleKeys())) { - p.putIfAbsent(j.getLoadKey(), j); - } - - data.close(); - } - } - - return p.v(); - } - - @Override - public String toString(IrisDimension dim) { - return dim.getLoadKey(); +public class NullableDimensionHandler extends RegistrantHandler { + public NullableDimensionHandler() { + super(IrisDimension.class, true); } @Override public IrisDimension parse(String in, boolean force) throws DecreeParsingException { - if (in.equalsIgnoreCase("---")) - return null; - if (in.equalsIgnoreCase("default")) { return parse(IrisSettings.get().getGenerator().getDefaultWorldType()); } - - KList options = getPossibilities(in); - - - if (options.isEmpty()) { - throw new DecreeParsingException("Unable to find Dimension \"" + in + "\""); - } - try { - return options.stream().filter((i) -> toString(i).equalsIgnoreCase(in)).toList().get(0); - } catch (Throwable e) { - throw new DecreeParsingException("Unable to filter which Dimension \"" + in + "\""); - } - } - - @Override - public boolean supports(Class type) { - return type.equals(IrisDimension.class); + return super.parse(in, force); } @Override diff --git a/core/src/main/java/com/volmit/iris/util/decree/specialhandlers/ObjectHandler.java b/core/src/main/java/com/volmit/iris/util/decree/specialhandlers/ObjectHandler.java index ee7d30901..84d3dceb0 100644 --- a/core/src/main/java/com/volmit/iris/util/decree/specialhandlers/ObjectHandler.java +++ b/core/src/main/java/com/volmit/iris/util/decree/specialhandlers/ObjectHandler.java @@ -31,11 +31,15 @@ public class ObjectHandler implements DecreeParameterHandler { @Override public KList getPossibilities() { KList p = new KList<>(); + IrisData data = data(); + if (data != null) { + return new KList<>(data.getObjectLoader().getPossibleKeys()); + } //noinspection ConstantConditions for (File i : Iris.instance.getDataFolder("packs").listFiles()) { if (i.isDirectory()) { - IrisData data = IrisData.get(i); + data = IrisData.get(i); p.add(data.getObjectLoader().getPossibleKeys()); } } diff --git a/core/src/main/java/com/volmit/iris/util/decree/specialhandlers/RegistrantHandler.java b/core/src/main/java/com/volmit/iris/util/decree/specialhandlers/RegistrantHandler.java new file mode 100644 index 000000000..a249a92ce --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/decree/specialhandlers/RegistrantHandler.java @@ -0,0 +1,73 @@ +package com.volmit.iris.util.decree.specialhandlers; + +import com.volmit.iris.Iris; +import com.volmit.iris.core.loader.IrisData; +import com.volmit.iris.core.loader.IrisRegistrant; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.decree.DecreeParameterHandler; +import com.volmit.iris.util.decree.exceptions.DecreeParsingException; + +import java.io.File; +import java.util.HashSet; +import java.util.Set; + +public abstract class RegistrantHandler implements DecreeParameterHandler { + private final Class type; + private final String name; + private final boolean nullable; + + public RegistrantHandler(Class type, boolean nullable) { + this.type = type; + this.name = type.getSimpleName().replaceFirst("Iris", ""); + this.nullable = nullable; + } + + @Override + public KList getPossibilities() { + KList p = new KList<>(); + Set known = new HashSet<>(); + IrisData data = data(); + if (data != null) { + return data.getLoader(type).loadAll(data.getLoader(type).getPossibleKeys()).qadd(null); + } + + //noinspection ConstantConditions + for (File i : Iris.instance.getDataFolder("packs").listFiles()) { + if (i.isDirectory()) { + data = IrisData.get(i); + for (T j : data.getLoader(type).loadAll(data.getLoader(type).getPossibleKeys())) { + if (known.add(j.getLoadKey())) + p.add(j); + } + } + } + + return p; + } + + @Override + public String toString(T t) { + return t != null ? t.getLoadKey() : "null"; + } + + @Override + public T parse(String in, boolean force) throws DecreeParsingException { + if (in.equals("null") && nullable) { + return null; + } + KList options = getPossibilities(in); + if (options.isEmpty()) { + throw new DecreeParsingException("Unable to find " + name + " \"" + in + "\""); + } + + return options.stream() + .filter((i) -> toString(i).equalsIgnoreCase(in)) + .findFirst() + .orElseThrow(() -> new DecreeParsingException("Unable to filter which " + name + " \"" + in + "\"")); + } + + @Override + public boolean supports(Class type) { + return type.equals(this.type); + } +} diff --git a/core/src/main/java/com/volmit/iris/util/decree/virtual/VirtualDecreeCommand.java b/core/src/main/java/com/volmit/iris/util/decree/virtual/VirtualDecreeCommand.java index f1eeab8b7..4bd5e9168 100644 --- a/core/src/main/java/com/volmit/iris/util/decree/virtual/VirtualDecreeCommand.java +++ b/core/src/main/java/com/volmit/iris/util/decree/virtual/VirtualDecreeCommand.java @@ -478,21 +478,27 @@ public class VirtualDecreeCommand { } DecreeContext.touch(sender); - Runnable rx = () -> { - try { + try { + Runnable rx = () -> { DecreeContext.touch(sender); - getNode().getMethod().setAccessible(true); - getNode().getMethod().invoke(getNode().getInstance(), params); - } catch (Throwable e) { - e.printStackTrace(); - throw new RuntimeException("Failed to execute "); // TODO: - } - }; + try { + getNode().getMethod().setAccessible(true); + getNode().getMethod().invoke(getNode().getInstance(), params); + } catch (Throwable e) { + e.printStackTrace(); + throw new RuntimeException("Failed to execute "); // TODO: + } finally { + DecreeContext.remove(); + } + }; - if (getNode().isSync()) { - J.s(rx); - } else { - rx.run(); + if (getNode().isSync()) { + J.s(rx); + } else { + rx.run(); + } + } finally { + DecreeContext.remove(); } return true; From 0524adb0df010f65c192daf1d817e1556e9cdfc1 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sat, 20 Sep 2025 23:05:24 +0200 Subject: [PATCH 15/21] fix compile --- core/build.gradle.kts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 1e847d793..8e4894bd4 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -197,6 +197,10 @@ val generateTemplates = tasks.register("generateTemplates") { expand(inputs.properties) } +tasks.generateSentryBundleIdJava { + dependsOn(generateTemplates) +} + rootProject.tasks.named("prepareKotlinBuildScriptModel") { dependsOn(generateTemplates) } From 27b2fd0823e2bda8eb34dcd6124000d448ed8cea Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sat, 20 Sep 2025 23:31:33 +0200 Subject: [PATCH 16/21] show other packs data again --- .../iris/util/decree/specialhandlers/RegistrantHandler.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/volmit/iris/util/decree/specialhandlers/RegistrantHandler.java b/core/src/main/java/com/volmit/iris/util/decree/specialhandlers/RegistrantHandler.java index a249a92ce..090407bba 100644 --- a/core/src/main/java/com/volmit/iris/util/decree/specialhandlers/RegistrantHandler.java +++ b/core/src/main/java/com/volmit/iris/util/decree/specialhandlers/RegistrantHandler.java @@ -28,7 +28,10 @@ public abstract class RegistrantHandler implements Dec Set known = new HashSet<>(); IrisData data = data(); if (data != null) { - return data.getLoader(type).loadAll(data.getLoader(type).getPossibleKeys()).qadd(null); + for (T j : data.getLoader(type).loadAll(data.getLoader(type).getPossibleKeys())) { + known.add(j.getLoadKey()); + p.add(j); + } } //noinspection ConstantConditions From 258d0d3aaafb94d5ddca36f23730b144a7d5aa3f Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sun, 21 Sep 2025 00:15:34 +0200 Subject: [PATCH 17/21] make sure that the pack is installed correctly --- core/src/main/java/com/volmit/iris/Iris.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/Iris.java b/core/src/main/java/com/volmit/iris/Iris.java index f95557d2f..14607fdba 100644 --- a/core/src/main/java/com/volmit/iris/Iris.java +++ b/core/src/main/java/com/volmit/iris/Iris.java @@ -707,7 +707,11 @@ public class Iris extends VolmitPlugin implements Listener { Iris.debug("Generator Config: " + w.toString()); File ff = new File(w.worldFolder(), "iris/pack"); - if (!ff.exists() || ff.listFiles().length == 0) { + var files = ff.listFiles(); + if (files == null || files.length == 0) + IO.delete(ff); + + if (!ff.exists()) { ff.mkdirs(); service(StudioSVC.class).installIntoWorld(getSender(), dim.getLoadKey(), w.worldFolder()); } @@ -717,8 +721,8 @@ public class Iris extends VolmitPlugin implements Listener { @Nullable public static IrisDimension loadDimension(@NonNull String worldName, @NonNull String id) { - var data = IrisData.get(new File(Bukkit.getWorldContainer(), String.join(File.separator, worldName, "iris", "pack"))); - var dimension = data.getDimensionLoader().load(id); + File pack = new File(Bukkit.getWorldContainer(), String.join(File.separator, worldName, "iris", "pack")); + var dimension = pack.isDirectory() ? IrisData.get(pack).getDimensionLoader().load(id) : null; if (dimension == null) dimension = IrisData.loadAnyDimension(id, null); if (dimension == null) { Iris.warn("Unable to find dimension type " + id + " Looking for online packs..."); From 21a2e4feefc7e191ee80af4ef00a0d0a7e91c298 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sun, 21 Sep 2025 12:13:57 +0200 Subject: [PATCH 18/21] fix hotloading when changing kts --- .../src/main/java/com/volmit/iris/core/loader/IrisData.java | 5 +++++ .../main/java/com/volmit/iris/core/service/StudioSVC.java | 6 +++--- core/src/main/java/com/volmit/iris/util/io/IO.java | 2 ++ .../main/java/com/volmit/iris/util/io/ReactiveFolder.java | 6 +++--- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/core/loader/IrisData.java b/core/src/main/java/com/volmit/iris/core/loader/IrisData.java index 58605c949..aaf1a49de 100644 --- a/core/src/main/java/com/volmit/iris/core/loader/IrisData.java +++ b/core/src/main/java/com/volmit/iris/core/loader/IrisData.java @@ -51,6 +51,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Objects; +import java.util.Optional; @Data public class IrisData implements ExclusionStrategy, TypeAdapterFactory { @@ -99,6 +100,10 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { return dataLoaders.computeIfAbsent(dataFolder, IrisData::new); } + public static Optional getLoaded(File dataFolder) { + return Optional.ofNullable(dataLoaders.get(dataFolder)); + } + public static void dereference() { dataLoaders.v().forEach(IrisData::cleanupEngine); } diff --git a/core/src/main/java/com/volmit/iris/core/service/StudioSVC.java b/core/src/main/java/com/volmit/iris/core/service/StudioSVC.java index 3d5c36a30..fb2be0f33 100644 --- a/core/src/main/java/com/volmit/iris/core/service/StudioSVC.java +++ b/core/src/main/java/com/volmit/iris/core/service/StudioSVC.java @@ -147,8 +147,7 @@ public class StudioSVC implements IrisService { } IrisData dm = IrisData.get(folder); - dm.dump(); - dm.clearLists(); + dm.hotloaded(); dim = dm.getDimensionLoader().load(type); if (dim == null) { @@ -299,7 +298,8 @@ public class StudioSVC implements IrisService { packEntry.mkdirs(); ZipUtil.unpack(cp, packEntry); } - IrisData.get(packEntry).hotloaded(); + IrisData.getLoaded(packEntry) + .ifPresent(IrisData::hotloaded); sender.sendMessage("Successfully Aquired " + d.getName()); ServerConfigurator.installDataPacks(true); diff --git a/core/src/main/java/com/volmit/iris/util/io/IO.java b/core/src/main/java/com/volmit/iris/util/io/IO.java index 44ee35f4d..b4bfe489d 100644 --- a/core/src/main/java/com/volmit/iris/util/io/IO.java +++ b/core/src/main/java/com/volmit/iris/util/io/IO.java @@ -179,6 +179,8 @@ public class IO { JsonElement json; try (FileReader reader = new FileReader(file)) { json = JsonParser.parseReader(reader); + } catch (Throwable e) { + throw new IOException("Failed to read json file " + file, e); } var queue = new LinkedList(); diff --git a/core/src/main/java/com/volmit/iris/util/io/ReactiveFolder.java b/core/src/main/java/com/volmit/iris/util/io/ReactiveFolder.java index b05e38760..ed547a40e 100644 --- a/core/src/main/java/com/volmit/iris/util/io/ReactiveFolder.java +++ b/core/src/main/java/com/volmit/iris/util/io/ReactiveFolder.java @@ -46,7 +46,7 @@ public class ReactiveFolder { if (checkCycle % 3 == 0 ? fw.checkModified() : fw.checkModifiedFast()) { for (File i : fw.getCreated()) { - if (i.getName().endsWith(".iob") || i.getName().endsWith(".json") || i.getName().endsWith(".js")) { + if (i.getName().endsWith(".iob") || i.getName().endsWith(".json") || i.getName().endsWith(".kts")) { if (i.getPath().contains(".iris")) { continue; } @@ -62,7 +62,7 @@ public class ReactiveFolder { continue; } - if (i.getName().endsWith(".iob") || i.getName().endsWith(".json") || i.getName().endsWith(".js")) { + if (i.getName().endsWith(".iob") || i.getName().endsWith(".json") || i.getName().endsWith(".kts")) { modified = true; break; } @@ -75,7 +75,7 @@ public class ReactiveFolder { continue; } - if (i.getName().endsWith(".iob") || i.getName().endsWith(".json") || i.getName().endsWith(".js")) { + if (i.getName().endsWith(".iob") || i.getName().endsWith(".json") || i.getName().endsWith(".kts")) { modified = true; break; } From f4617c19960fba7a929c429e4b87ad72caef745c Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sun, 21 Sep 2025 12:51:59 +0200 Subject: [PATCH 19/21] add tabcompletion for mythic mobs mob stacks --- .../link/data/MythicMobsDataProvider.java | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/core/link/data/MythicMobsDataProvider.java b/core/src/main/java/com/volmit/iris/core/link/data/MythicMobsDataProvider.java index c43e0e282..b87963345 100644 --- a/core/src/main/java/com/volmit/iris/core/link/data/MythicMobsDataProvider.java +++ b/core/src/main/java/com/volmit/iris/core/link/data/MythicMobsDataProvider.java @@ -5,10 +5,14 @@ import com.volmit.iris.core.link.Identifier; import com.volmit.iris.core.tools.IrisToolbelt; import io.lumine.mythic.api.adapters.AbstractLocation; import io.lumine.mythic.api.config.MythicLineConfig; +import io.lumine.mythic.api.mobs.entities.SpawnReason; import io.lumine.mythic.api.skills.conditions.ILocationCondition; +import io.lumine.mythic.bukkit.BukkitAdapter; import io.lumine.mythic.bukkit.MythicBukkit; import io.lumine.mythic.bukkit.adapters.BukkitWorld; import io.lumine.mythic.bukkit.events.MythicConditionLoadEvent; +import io.lumine.mythic.core.mobs.ActiveMob; +import io.lumine.mythic.core.mobs.MobStack; import io.lumine.mythic.core.skills.SkillCondition; import io.lumine.mythic.core.utils.annotations.MythicCondition; import io.lumine.mythic.core.utils.annotations.MythicField; @@ -17,10 +21,10 @@ import org.bukkit.entity.Entity; import org.bukkit.event.EventHandler; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Stream; public class MythicMobsDataProvider extends ExternalDataProvider { public MythicMobsDataProvider() { @@ -33,18 +37,31 @@ public class MythicMobsDataProvider extends ExternalDataProvider { @Override public @Nullable Entity spawnMob(@NotNull Location location, @NotNull Identifier entityId) throws MissingResourceException { - var mm = MythicBukkit.inst().getMobManager().spawnMob(entityId.key(), location); - if (mm == null) throw new MissingResourceException("Failed to find mob!", entityId.namespace(), entityId.key()); - return mm.getEntity().getBukkitEntity(); + var mm = spawnMob(BukkitAdapter.adapt(location), entityId); + return mm == null ? null : mm.getEntity().getBukkitEntity(); + } + + private ActiveMob spawnMob(AbstractLocation location, Identifier entityId) throws MissingResourceException { + var manager = MythicBukkit.inst().getMobManager(); + var mm = manager.getMythicMob(entityId.key()).orElse(null); + if (mm == null) { + var stack = manager.getMythicMobStack(entityId.key()); + if (stack == null) throw new MissingResourceException("Failed to find Mob!", entityId.namespace(), entityId.key()); + return stack.spawn(location, 1d, SpawnReason.OTHER, null); + } + return mm.spawn(location, 1d, SpawnReason.OTHER, null, null); } @Override public @NotNull Collection<@NotNull Identifier> getTypes(@NotNull DataType dataType) { if (dataType != DataType.ENTITY) return List.of(); - return MythicBukkit.inst() - .getMobManager() - .getMobNames() - .stream() + var manager = MythicBukkit.inst().getMobManager(); + return Stream.concat(manager.getMobNames().stream(), + manager.getMobStacks() + .stream() + .map(MobStack::getName) + ) + .distinct() .map(name -> new Identifier("mythicmobs", name)) .toList(); } From 9c073ecbcb65e673d074e36f71efa2b03b2a15f7 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sun, 21 Sep 2025 14:01:59 +0200 Subject: [PATCH 20/21] fix infinite loop due to writing the gradle.kts --- .../main/java/com/volmit/iris/util/io/ReactiveFolder.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/util/io/ReactiveFolder.java b/core/src/main/java/com/volmit/iris/util/io/ReactiveFolder.java index ed547a40e..5bf0d6eb6 100644 --- a/core/src/main/java/com/volmit/iris/util/io/ReactiveFolder.java +++ b/core/src/main/java/com/volmit/iris/util/io/ReactiveFolder.java @@ -47,7 +47,7 @@ public class ReactiveFolder { if (checkCycle % 3 == 0 ? fw.checkModified() : fw.checkModifiedFast()) { for (File i : fw.getCreated()) { if (i.getName().endsWith(".iob") || i.getName().endsWith(".json") || i.getName().endsWith(".kts")) { - if (i.getPath().contains(".iris")) { + if (i.getPath().contains(".iris") || i.getName().endsWith(".gradle.kts")) { continue; } @@ -58,7 +58,7 @@ public class ReactiveFolder { if (!modified) { for (File i : fw.getChanged()) { - if (i.getPath().contains(".iris")) { + if (i.getPath().contains(".iris") || i.getName().endsWith(".gradle.kts")) { continue; } @@ -71,7 +71,7 @@ public class ReactiveFolder { if (!modified) { for (File i : fw.getDeleted()) { - if (i.getPath().contains(".iris")) { + if (i.getPath().contains(".iris") || i.getName().endsWith(".gradle.kts")) { continue; } From 7e7933858b1854c4393c254421e009427f191833 Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Sun, 21 Sep 2025 15:47:29 +0200 Subject: [PATCH 21/21] suppress json syntax exceptions from being reported to sentry --- core/src/main/java/com/volmit/iris/util/misc/Bindings.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/volmit/iris/util/misc/Bindings.java b/core/src/main/java/com/volmit/iris/util/misc/Bindings.java index 48bb8ae61..d48879151 100644 --- a/core/src/main/java/com/volmit/iris/util/misc/Bindings.java +++ b/core/src/main/java/com/volmit/iris/util/misc/Bindings.java @@ -1,5 +1,6 @@ package com.volmit.iris.util.misc; +import com.google.gson.JsonSyntaxException; import com.volmit.iris.BuildConstants; import com.volmit.iris.Iris; import com.volmit.iris.core.IrisSettings; @@ -74,7 +75,9 @@ public class Bindings { } private static boolean suppress(Throwable e) { - return (e instanceof IllegalStateException ex && "zip file closed".equals(ex.getMessage())) || e instanceof JSONException; + return (e instanceof IllegalStateException ex && "zip file closed".equals(ex.getMessage())) + || e instanceof JSONException + || e instanceof JsonSyntaxException; }