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] 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!");