diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 3fa09a1c4..904586dbe 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -87,6 +87,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) @@ -165,15 +166,6 @@ tasks { "version" to rootProject.version, "apiVersion" to apiVersion, "main" to main, - "environment" to if (project.hasProperty("release")) "production" else "development", - "commit" to provider { - val res = runCatching { project.extensions.getByType().head().id } - res.getOrDefault("") - .takeIf { it.length == 40 } ?: { - logger.error("Git commit hash not found", res.exceptionOrNull()) - "unknown" - }() - }, ) filesMatching("**/plugin.yml") { expand(inputs.properties) @@ -188,9 +180,35 @@ tasks { } } -/** - * Gradle is weird sometimes, we need to delete the plugin yml from the build folder to actually filter properly. - */ -afterEvaluate { - layout.buildDirectory.file("resources/main/plugin.yml").get().asFile.delete() +val templateSource = file("src/main/templates") +val templateDest = layout.buildDirectory.dir("generated/sources/templates") +val generateTemplates = tasks.register("generateTemplates") { + inputs.properties( + "environment" to if (project.hasProperty("release")) "production" else "development", + "commit" to provider { + val res = runCatching { project.extensions.getByType().head().id } + res.getOrDefault("") + .takeIf { it.length == 40 } ?: { + logger.error("Git commit hash not found", res.exceptionOrNull()) + "unknown" + }() + }, + ) + + from(templateSource) + into(templateDest) + rename { "com/volmit/iris/$it" } + expand(inputs.properties) +} + +tasks.generateSentryBundleIdJava { + dependsOn(generateTemplates) +} + +rootProject.tasks.named("prepareKotlinBuildScriptModel") { + dependsOn(generateTemplates) +} + +sourceSets.main { + java.srcDir(generateTemplates.map { it.outputs }) } \ No newline at end of file diff --git a/core/src/main/java/com/volmit/iris/Iris.java b/core/src/main/java/com/volmit/iris/Iris.java index f49115e1d..045c58c10 100644 --- a/core/src/main/java/com/volmit/iris/Iris.java +++ b/core/src/main/java/com/volmit/iris/Iris.java @@ -715,7 +715,11 @@ public class Iris extends VolmitPlugin implements Listener { Iris.debug("Generator Config: " + w.toString()); File ff = new File(w.worldFolder(), "iris/pack"); - if (!ff.exists() || ff.listFiles().length == 0) { + var files = ff.listFiles(); + if (files == null || files.length == 0) + IO.delete(ff); + + if (!ff.exists()) { ff.mkdirs(); service(StudioSVC.class).installIntoWorld(getSender(), dim.getLoadKey(), w.worldFolder()); } @@ -725,13 +729,13 @@ public class Iris extends VolmitPlugin implements Listener { @Nullable public static IrisDimension loadDimension(@NonNull String worldName, @NonNull String id) { - var data = IrisData.get(new File(Bukkit.getWorldContainer(), String.join(File.separator, worldName, "iris", "pack"))); - var dimension = data.getDimensionLoader().load(id); - if (dimension == null) dimension = IrisData.loadAnyDimension(id); + File pack = new File(Bukkit.getWorldContainer(), String.join(File.separator, worldName, "iris", "pack")); + var dimension = pack.isDirectory() ? IrisData.get(pack).getDimensionLoader().load(id) : null; + if (dimension == null) dimension = IrisData.loadAnyDimension(id, null); if (dimension == null) { Iris.warn("Unable to find dimension type " + id + " Looking for online packs..."); Iris.service(StudioSVC.class).downloadSearch(new VolmitSender(Bukkit.getConsoleSender()), id, false); - dimension = IrisData.loadAnyDimension(id); + dimension = IrisData.loadAnyDimension(id, null); if (dimension != null) { Iris.info("Resolved missing dimension, proceeding."); diff --git a/core/src/main/java/com/volmit/iris/core/IrisSettings.java b/core/src/main/java/com/volmit/iris/core/IrisSettings.java index 65991a686..212b1551f 100644 --- a/core/src/main/java/com/volmit/iris/core/IrisSettings.java +++ b/core/src/main/java/com/volmit/iris/core/IrisSettings.java @@ -244,6 +244,7 @@ public class IrisSettings { public boolean preventLeafDecay = true; public boolean useMulticore = false; public boolean offsetNoiseTypes = false; + public boolean earlyCustomBlocks = false; } @Data diff --git a/core/src/main/java/com/volmit/iris/core/IrisWorlds.java b/core/src/main/java/com/volmit/iris/core/IrisWorlds.java index 69721ced2..72dc0b5cd 100644 --- a/core/src/main/java/com/volmit/iris/core/IrisWorlds.java +++ b/core/src/main/java/com/volmit/iris/core/IrisWorlds.java @@ -66,6 +66,7 @@ public class IrisWorlds { } public KMap getWorlds() { + clean(); return readBukkitWorlds().put(worlds); } @@ -76,8 +77,7 @@ public class IrisWorlds { } public Stream getDimensions() { - return readBukkitWorlds() - .put(worlds) + return getWorlds() .entrySet() .stream() .map(entry -> Iris.loadDimension(entry.getKey(), entry.getValue())) diff --git a/core/src/main/java/com/volmit/iris/core/ServerConfigurator.java b/core/src/main/java/com/volmit/iris/core/ServerConfigurator.java index 1e91d68a8..206d69efd 100644 --- a/core/src/main/java/com/volmit/iris/core/ServerConfigurator.java +++ b/core/src/main/java/com/volmit/iris/core/ServerConfigurator.java @@ -103,14 +103,14 @@ public class ServerConfigurator { return worlds; } - public static void installDataPacks(boolean fullInstall) { - installDataPacks(DataVersion.getDefault(), fullInstall); + public static boolean installDataPacks(boolean fullInstall) { + return installDataPacks(DataVersion.getDefault(), fullInstall); } - public static void installDataPacks(IDataFixer fixer, boolean fullInstall) { + public static boolean installDataPacks(IDataFixer fixer, boolean fullInstall) { if (fixer == null) { Iris.error("Unable to install datapacks, fixer is null!"); - return; + return false; } Iris.info("Checking Data Packs..."); DimensionHeight height = new DimensionHeight(fixer); @@ -129,11 +129,10 @@ public class ServerConfigurator { IrisDimension.writeShared(folders, height); Iris.info("Data Packs Setup!"); - if (fullInstall) - verifyDataPacksPost(IrisSettings.get().getAutoConfiguration().isAutoRestartOnCustomBiomeInstall()); + return fullInstall && verifyDataPacksPost(IrisSettings.get().getAutoConfiguration().isAutoRestartOnCustomBiomeInstall()); } - private static void verifyDataPacksPost(boolean allowRestarting) { + private static boolean verifyDataPacksPost(boolean allowRestarting) { try (Stream stream = allPacks()) { boolean bad = stream .map(data -> { @@ -148,7 +147,7 @@ public class ServerConfigurator { }) .toList() .contains(true); - if (!bad) return; + if (!bad) return false; } @@ -172,6 +171,7 @@ public class ServerConfigurator { J.sleep(3000); } + return true; } public static void restart() { 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..5b0ab41cf 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandDeveloper.java @@ -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,7 @@ 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.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; @@ -143,6 +155,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 = "null", 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/commands/CommandJigsaw.java b/core/src/main/java/com/volmit/iris/core/commands/CommandJigsaw.java index 3022b866c..7777f1764 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandJigsaw.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandJigsaw.java @@ -48,7 +48,7 @@ public class CommandJigsaw implements DecreeExecutor { IrisJigsawPiece piece ) { File dest = piece.getLoadFile(); - new JigsawEditor(player(), piece, IrisData.loadAnyObject(piece.getObject()), dest); + new JigsawEditor(player(), piece, IrisData.loadAnyObject(piece.getObject(), data()), dest); } @Decree(description = "Place a jigsaw structure") @@ -78,7 +78,7 @@ public class CommandJigsaw implements DecreeExecutor { @Param(description = "The object to use for this piece", customHandler = ObjectHandler.class) String object ) { - IrisObject o = IrisData.loadAnyObject(object); + IrisObject o = IrisData.loadAnyObject(object, data()); if (object == null) { sender().sendMessage(C.RED + "Failed to find existing object"); diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandObject.java b/core/src/main/java/com/volmit/iris/core/commands/CommandObject.java index ccbf91c11..fd057b300 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandObject.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandObject.java @@ -136,7 +136,7 @@ public class CommandObject implements DecreeExecutor { @Param(description = "The object to analyze", customHandler = ObjectHandler.class) String object ) { - IrisObject o = IrisData.loadAnyObject(object); + IrisObject o = IrisData.loadAnyObject(object, data()); sender().sendMessage("Object Size: " + o.getW() + " * " + o.getH() + " * " + o.getD() + ""); sender().sendMessage("Blocks Used: " + NumberFormat.getIntegerInstance().format(o.getBlocks().size())); @@ -201,7 +201,7 @@ public class CommandObject implements DecreeExecutor { @Decree(description = "Shrink an object to its minimum size") public void shrink(@Param(description = "The object to shrink", customHandler = ObjectHandler.class) String object) { - IrisObject o = IrisData.loadAnyObject(object); + IrisObject o = IrisData.loadAnyObject(object, data()); sender().sendMessage("Current Object Size: " + o.getW() + " * " + o.getH() + " * " + o.getD()); o.shrinkwrap(); sender().sendMessage("New Object Size: " + o.getW() + " * " + o.getH() + " * " + o.getD()); @@ -325,7 +325,7 @@ public class CommandObject implements DecreeExecutor { // @Param(description = "The scale interpolator to use", defaultValue = "none") // IrisObjectPlacementScaleInterpolator interpolator ) { - IrisObject o = IrisData.loadAnyObject(object); + IrisObject o = IrisData.loadAnyObject(object, data()); double maxScale = Double.max(10 - o.getBlocks().size() / 10000d, 1); if (scale > maxScale) { sender().sendMessage(C.YELLOW + "Indicated scale exceeds maximum. Downscaled to maximum: " + maxScale); diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandStudio.java b/core/src/main/java/com/volmit/iris/core/commands/CommandStudio.java index 319267e1b..e02728d80 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandStudio.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandStudio.java @@ -171,9 +171,9 @@ public class CommandStudio implements DecreeExecutor { var loc = player().getLocation().clone(); J.a(() -> { - DecreeContext.touch(sender); PlatformChunkGenerator plat = IrisToolbelt.access(world); Engine engine = plat.getEngine(); + DecreeContext.touch(sender); try (SyncExecutor executor = new SyncExecutor(20)) { int x = loc.getBlockX() >> 4; int z = loc.getBlockZ() >> 4; @@ -247,6 +247,8 @@ public class CommandStudio implements DecreeExecutor { } catch (Throwable e) { sender().sendMessage("Error while regenerating chunks"); e.printStackTrace(); + } finally { + DecreeContext.remove(); } }); } diff --git a/core/src/main/java/com/volmit/iris/core/link/data/ItemAdderDataProvider.java b/core/src/main/java/com/volmit/iris/core/link/data/ItemAdderDataProvider.java index 5327578bd..64ad7e52a 100644 --- a/core/src/main/java/com/volmit/iris/core/link/data/ItemAdderDataProvider.java +++ b/core/src/main/java/com/volmit/iris/core/link/data/ItemAdderDataProvider.java @@ -62,7 +62,10 @@ public class ItemAdderDataProvider extends ExternalDataProvider { @Override public void processUpdate(@NotNull Engine engine, @NotNull Block block, @NotNull Identifier blockId) { - CustomBlock.place(blockId.toString(), block.getLocation()); + CustomBlock custom; + if ((custom = CustomBlock.place(blockId.toString(), block.getLocation())) == null) + return; + block.setBlockData(custom.getBaseBlockData(), false); } @Override diff --git a/core/src/main/java/com/volmit/iris/core/link/data/MythicMobsDataProvider.java b/core/src/main/java/com/volmit/iris/core/link/data/MythicMobsDataProvider.java index c43e0e282..b87963345 100644 --- a/core/src/main/java/com/volmit/iris/core/link/data/MythicMobsDataProvider.java +++ b/core/src/main/java/com/volmit/iris/core/link/data/MythicMobsDataProvider.java @@ -5,10 +5,14 @@ import com.volmit.iris.core.link.Identifier; import com.volmit.iris.core.tools.IrisToolbelt; import io.lumine.mythic.api.adapters.AbstractLocation; import io.lumine.mythic.api.config.MythicLineConfig; +import io.lumine.mythic.api.mobs.entities.SpawnReason; import io.lumine.mythic.api.skills.conditions.ILocationCondition; +import io.lumine.mythic.bukkit.BukkitAdapter; import io.lumine.mythic.bukkit.MythicBukkit; import io.lumine.mythic.bukkit.adapters.BukkitWorld; import io.lumine.mythic.bukkit.events.MythicConditionLoadEvent; +import io.lumine.mythic.core.mobs.ActiveMob; +import io.lumine.mythic.core.mobs.MobStack; import io.lumine.mythic.core.skills.SkillCondition; import io.lumine.mythic.core.utils.annotations.MythicCondition; import io.lumine.mythic.core.utils.annotations.MythicField; @@ -17,10 +21,10 @@ import org.bukkit.entity.Entity; import org.bukkit.event.EventHandler; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Stream; public class MythicMobsDataProvider extends ExternalDataProvider { public MythicMobsDataProvider() { @@ -33,18 +37,31 @@ public class MythicMobsDataProvider extends ExternalDataProvider { @Override public @Nullable Entity spawnMob(@NotNull Location location, @NotNull Identifier entityId) throws MissingResourceException { - var mm = MythicBukkit.inst().getMobManager().spawnMob(entityId.key(), location); - if (mm == null) throw new MissingResourceException("Failed to find mob!", entityId.namespace(), entityId.key()); - return mm.getEntity().getBukkitEntity(); + var mm = spawnMob(BukkitAdapter.adapt(location), entityId); + return mm == null ? null : mm.getEntity().getBukkitEntity(); + } + + private ActiveMob spawnMob(AbstractLocation location, Identifier entityId) throws MissingResourceException { + var manager = MythicBukkit.inst().getMobManager(); + var mm = manager.getMythicMob(entityId.key()).orElse(null); + if (mm == null) { + var stack = manager.getMythicMobStack(entityId.key()); + if (stack == null) throw new MissingResourceException("Failed to find Mob!", entityId.namespace(), entityId.key()); + return stack.spawn(location, 1d, SpawnReason.OTHER, null); + } + return mm.spawn(location, 1d, SpawnReason.OTHER, null, null); } @Override public @NotNull Collection<@NotNull Identifier> getTypes(@NotNull DataType dataType) { if (dataType != DataType.ENTITY) return List.of(); - return MythicBukkit.inst() - .getMobManager() - .getMobNames() - .stream() + var manager = MythicBukkit.inst().getMobManager(); + return Stream.concat(manager.getMobNames().stream(), + manager.getMobStacks() + .stream() + .map(MobStack::getName) + ) + .distinct() .map(name -> new Identifier("mythicmobs", name)) .toList(); } 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..aaf1a49de 100644 --- a/core/src/main/java/com/volmit/iris/core/loader/IrisData.java +++ b/core/src/main/java/com/volmit/iris/core/loader/IrisData.java @@ -43,6 +43,7 @@ import com.volmit.iris.util.reflect.KeyedType; import com.volmit.iris.util.scheduling.ChronoLatch; import com.volmit.iris.util.scheduling.J; import lombok.Data; +import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.FileReader; @@ -50,7 +51,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Objects; -import java.util.function.Function; +import java.util.Optional; @Data public class IrisData implements ExclusionStrategy, TypeAdapterFactory { @@ -99,6 +100,10 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { return dataLoaders.computeIfAbsent(dataFolder, IrisData::new); } + public static Optional getLoaded(File dataFolder) { + return Optional.ofNullable(dataLoaders.get(dataFolder)); + } + public static void dereference() { dataLoaders.v().forEach(IrisData::cleanupEngine); } @@ -118,92 +123,100 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { Iris.warn(" " + rl.getResourceTypeName() + " @ /" + rl.getFolderName() + ": Cache=" + rl.getLoadCache().getSize() + " Folders=" + rl.getFolders().size()); } - public static IrisObject loadAnyObject(String key) { - return loadAny(key, (dm) -> dm.getObjectLoader().load(key, false)); + public static IrisObject loadAnyObject(String key, @Nullable IrisData nearest) { + return loadAny(IrisObject.class, key, nearest); } - public static IrisMatterObject loadAnyMatter(String key) { - return loadAny(key, (dm) -> dm.getMatterLoader().load(key, false)); + public static IrisMatterObject loadAnyMatter(String key, @Nullable IrisData nearest) { + return loadAny(IrisMatterObject.class, key, nearest); } - public static IrisBiome loadAnyBiome(String key) { - return loadAny(key, (dm) -> dm.getBiomeLoader().load(key, false)); + public static IrisBiome loadAnyBiome(String key, @Nullable IrisData nearest) { + return loadAny(IrisBiome.class, key, nearest); } - public static IrisExpression loadAnyExpression(String key) { - return loadAny(key, (dm) -> dm.getExpressionLoader().load(key, false)); + public static IrisExpression loadAnyExpression(String key, @Nullable IrisData nearest) { + return loadAny(IrisExpression.class, key, nearest); } - public static IrisMod loadAnyMod(String key) { - return loadAny(key, (dm) -> dm.getModLoader().load(key, false)); + public static IrisMod loadAnyMod(String key, @Nullable IrisData nearest) { + return loadAny(IrisMod.class, key, nearest); } - public static IrisJigsawPiece loadAnyJigsawPiece(String key) { - return loadAny(key, (dm) -> dm.getJigsawPieceLoader().load(key, false)); + public static IrisJigsawPiece loadAnyJigsawPiece(String key, @Nullable IrisData nearest) { + return loadAny(IrisJigsawPiece.class, key, nearest); } - public static IrisJigsawPool loadAnyJigsawPool(String key) { - return loadAny(key, (dm) -> dm.getJigsawPoolLoader().load(key, false)); + public static IrisJigsawPool loadAnyJigsawPool(String key, @Nullable IrisData nearest) { + return loadAny(IrisJigsawPool.class, key, nearest); } - public static IrisEntity loadAnyEntity(String key) { - return loadAny(key, (dm) -> dm.getEntityLoader().load(key, false)); + public static IrisEntity loadAnyEntity(String key, @Nullable IrisData nearest) { + return loadAny(IrisEntity.class, key, nearest); } - public static IrisLootTable loadAnyLootTable(String key) { - return loadAny(key, (dm) -> dm.getLootLoader().load(key, false)); + public static IrisLootTable loadAnyLootTable(String key, @Nullable IrisData nearest) { + return loadAny(IrisLootTable.class, key, nearest); } - public static IrisBlockData loadAnyBlock(String key) { - return loadAny(key, (dm) -> dm.getBlockLoader().load(key, false)); + public static IrisBlockData loadAnyBlock(String key, @Nullable IrisData nearest) { + return loadAny(IrisBlockData.class, key, nearest); } - public static IrisSpawner loadAnySpaner(String key) { - return loadAny(key, (dm) -> dm.getSpawnerLoader().load(key, false)); + public static IrisSpawner loadAnySpaner(String key, @Nullable IrisData nearest) { + return loadAny(IrisSpawner.class, key, nearest); } - public static IrisScript loadAnyScript(String key) { - return loadAny(key, (dm) -> dm.getScriptLoader().load(key, false)); + public static IrisScript loadAnyScript(String key, @Nullable IrisData nearest) { + return loadAny(IrisScript.class, key, nearest); } - public static IrisRavine loadAnyRavine(String key) { - return loadAny(key, (dm) -> dm.getRavineLoader().load(key, false)); + public static IrisRavine loadAnyRavine(String key, @Nullable IrisData nearest) { + return loadAny(IrisRavine.class, key, nearest); } - public static IrisRegion loadAnyRegion(String key) { - return loadAny(key, (dm) -> dm.getRegionLoader().load(key, false)); + public static IrisRegion loadAnyRegion(String key, @Nullable IrisData nearest) { + return loadAny(IrisRegion.class, key, nearest); } - public static IrisMarker loadAnyMarker(String key) { - return loadAny(key, (dm) -> dm.getMarkerLoader().load(key, false)); + public static IrisMarker loadAnyMarker(String key, @Nullable IrisData nearest) { + return loadAny(IrisMarker.class, key, nearest); } - public static IrisCave loadAnyCave(String key) { - return loadAny(key, (dm) -> dm.getCaveLoader().load(key, false)); + public static IrisCave loadAnyCave(String key, @Nullable IrisData nearest) { + return loadAny(IrisCave.class, key, nearest); } - public static IrisImage loadAnyImage(String key) { - return loadAny(key, (dm) -> dm.getImageLoader().load(key, false)); + public static IrisImage loadAnyImage(String key, @Nullable IrisData nearest) { + return loadAny(IrisImage.class, key, nearest); } - public static IrisDimension loadAnyDimension(String key) { - return loadAny(key, (dm) -> dm.getDimensionLoader().load(key, false)); + public static IrisDimension loadAnyDimension(String key, @Nullable IrisData nearest) { + return loadAny(IrisDimension.class, key, nearest); } - public static IrisJigsawStructure loadAnyJigsawStructure(String key) { - return loadAny(key, (dm) -> dm.getJigsawStructureLoader().load(key, false)); + public static IrisJigsawStructure loadAnyJigsawStructure(String key, @Nullable IrisData nearest) { + return loadAny(IrisJigsawStructure.class, key, nearest); } - public static IrisGenerator loadAnyGenerator(String key) { - return loadAny(key, (dm) -> dm.getGeneratorLoader().load(key, false)); + public static IrisGenerator loadAnyGenerator(String key, @Nullable IrisData nearest) { + return loadAny(IrisGenerator.class, key, nearest); } - public static T loadAny(String key, Function v) { + public static T loadAny(Class type, String key, @Nullable IrisData nearest) { try { + if (nearest != null) { + T t = nearest.load(type, key, false); + if (t != null) { + return t; + } + } + for (File i : Objects.requireNonNull(Iris.instance.getDataFolder("packs").listFiles())) { if (i.isDirectory()) { IrisData dm = get(i); - T t = v.apply(dm); + if (dm == nearest) continue; + T t = dm.load(type, key, false); if (t != null) { return t; @@ -218,6 +231,17 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { return null; } + public T load(Class type, String key, boolean warn) { + var loader = getLoader(type); + if (loader == null) return null; + return loader.load(key, warn); + } + + @SuppressWarnings("unchecked") + public ResourceLoader getLoader(Class type) { + return (ResourceLoader) loaders.get(type); + } + public ResourceLoader getTypedLoaderFor(File f) { String[] k = f.getPath().split("\\Q" + File.separator + "\\E"); @@ -325,6 +349,7 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { } public synchronized void hotloaded() { + closed = false; environment.close(); possibleSnippets = new KMap<>(); builder = new GsonBuilder() @@ -425,6 +450,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 +464,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 +515,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 c27b30cc8..37e85b08e 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,9 +18,13 @@ 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.link.FoliaWorldsLink; 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; @@ -147,4 +151,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/core/project/IrisProject.java b/core/src/main/java/com/volmit/iris/core/project/IrisProject.java index 716173bd6..ad5a42bff 100644 --- a/core/src/main/java/com/volmit/iris/core/project/IrisProject.java +++ b/core/src/main/java/com/volmit/iris/core/project/IrisProject.java @@ -159,7 +159,7 @@ public class IrisProject { public void openVSCode(VolmitSender sender) { - IrisDimension d = IrisData.loadAnyDimension(getName()); + IrisDimension d = IrisData.loadAnyDimension(getName(), null); J.attemptAsync(() -> { try { @@ -221,7 +221,7 @@ public class IrisProject { } J.a(() -> { - IrisDimension d = IrisData.loadAnyDimension(getName()); + IrisDimension d = IrisData.loadAnyDimension(getName(), null); if (d == null) { sender.sendMessage("Can't find dimension: " + getName()); return; diff --git a/core/src/main/java/com/volmit/iris/core/service/BoardSVC.java b/core/src/main/java/com/volmit/iris/core/service/BoardSVC.java index e5557b023..e8eadc558 100644 --- a/core/src/main/java/com/volmit/iris/core/service/BoardSVC.java +++ b/core/src/main/java/com/volmit/iris/core/service/BoardSVC.java @@ -22,33 +22,39 @@ import com.volmit.iris.Iris; import com.volmit.iris.core.IrisSettings; import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.core.tools.IrisToolbelt; -import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.util.board.BoardManager; import com.volmit.iris.util.board.BoardProvider; import com.volmit.iris.util.board.BoardSettings; import com.volmit.iris.util.board.ScoreDirection; -import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.format.C; import com.volmit.iris.util.format.Form; import com.volmit.iris.util.plugin.IrisService; import com.volmit.iris.util.scheduling.J; import lombok.Data; +import org.bukkit.Location; +import org.bukkit.World; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.player.PlayerChangedWorldEvent; import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; public class BoardSVC implements IrisService, BoardProvider { private final KMap boards = new KMap<>(); - private com.volmit.iris.util.board.BoardManager manager; + private ScheduledExecutorService executor; + private BoardManager manager; @Override public void onEnable() { - J.ar(this::tick, 20); + executor = Executors.newScheduledThreadPool(0, Thread.ofVirtual().factory()); manager = new BoardManager(Iris.instance, BoardSettings.builder() .boardProvider(this) .scoreDirection(ScoreDirection.DOWN) @@ -57,6 +63,7 @@ public class BoardSVC implements IrisService, BoardProvider { @Override public void onDisable() { + executor.shutdownNow(); manager.onDisable(); boards.clear(); } @@ -71,14 +78,22 @@ public class BoardSVC implements IrisService, BoardProvider { J.s(() -> updatePlayer(e.getPlayer())); } + @EventHandler + public void on(PlayerQuitEvent e) { + remove(e.getPlayer()); + } + public void updatePlayer(Player p) { if (IrisToolbelt.isIrisStudioWorld(p.getWorld())) { manager.remove(p); manager.setup(p); - } else { - manager.remove(p); - boards.remove(p); - } + } else remove(p); + } + + private void remove(Player player) { + manager.remove(player); + var board = boards.remove(player); + if (board != null) board.task.cancel(true); } @Override @@ -86,73 +101,63 @@ public class BoardSVC implements IrisService, BoardProvider { return C.GREEN + "Iris"; } - public void tick() { - if (!Iris.service(StudioSVC.class).isProjectOpen()) { - return; - } - - boards.forEach((k, v) -> v.update()); - } - @Override public List getLines(Player player) { - PlayerBoard pb = boards.computeIfAbsent(player, PlayerBoard::new); - synchronized (pb.lines) { - return pb.lines; - } + return boards.computeIfAbsent(player, PlayerBoard::new).lines; } @Data - public static class PlayerBoard { + public class PlayerBoard { private final Player player; - private final CopyOnWriteArrayList lines; + private final ScheduledFuture task; + private volatile List lines; public PlayerBoard(Player player) { this.player = player; - this.lines = new CopyOnWriteArrayList<>(); + this.lines = new ArrayList<>(); + this.task = executor.scheduleAtFixedRate(this::tick, 0, 1, TimeUnit.SECONDS); + } + + private void tick() { + if (!Iris.service(StudioSVC.class).isProjectOpen()) { + return; + } + update(); } public void update() { - synchronized (lines) { - lines.clear(); + final World world = player.getWorld(); + final Location loc = player.getLocation(); - if (!IrisToolbelt.isIrisStudioWorld(player.getWorld())) { - return; - } + final var access = IrisToolbelt.access(world); + if (access == null) return; - Engine engine = IrisToolbelt.access(player.getWorld()).getEngine(); - int x = player.getLocation().getBlockX(); - int y = player.getLocation().getBlockY() - player.getWorld().getMinHeight(); - int z = player.getLocation().getBlockZ(); + final var engine = access.getEngine(); + if (engine == null) return; - if(IrisSettings.get().getGeneral().debug){ - lines.add("&7&m "); - lines.add(C.GREEN + "Speed" + C.GRAY + ": " + Form.f(engine.getGeneratedPerSecond(), 0) + "/s " + Form.duration(1000D / engine.getGeneratedPerSecond(), 0)); - lines.add(C.AQUA + "Cache" + C.GRAY + ": " + Form.f(IrisData.cacheSize())); - lines.add(C.AQUA + "Mantle" + C.GRAY + ": " + engine.getMantle().getLoadedRegionCount()); - lines.add(C.LIGHT_PURPLE + "Carving" + C.GRAY + ": " + engine.getMantle().isCarved(x,y,z)); - lines.add("&7&m "); - lines.add(C.AQUA + "Region" + C.GRAY + ": " + engine.getRegion(x, z).getName()); - lines.add(C.AQUA + "Biome" + C.GRAY + ": " + engine.getBiomeOrMantle(x, y, z).getName()); - lines.add(C.AQUA + "Height" + C.GRAY + ": " + Math.round(engine.getHeight(x, z))); - lines.add(C.AQUA + "Slope" + C.GRAY + ": " + Form.f(engine.getComplex().getSlopeStream().get(x, z), 2)); - lines.add(C.AQUA + "BUD/s" + C.GRAY + ": " + Form.f(engine.getBlockUpdatesPerSecond())); - lines.add("&7&m "); - } else { - lines.add("&7&m "); - lines.add(C.GREEN + "Speed" + C.GRAY + ": " + Form.f(engine.getGeneratedPerSecond(), 0) + "/s " + Form.duration(1000D / engine.getGeneratedPerSecond(), 0)); - lines.add(C.AQUA + "Cache" + C.GRAY + ": " + Form.f(IrisData.cacheSize())); - lines.add(C.AQUA + "Mantle" + C.GRAY + ": " + engine.getMantle().getLoadedRegionCount()); - lines.add("&7&m "); - lines.add(C.AQUA + "Region" + C.GRAY + ": " + engine.getRegion(x, z).getName()); - lines.add(C.AQUA + "Biome" + C.GRAY + ": " + engine.getBiomeOrMantle(x, y, z).getName()); - lines.add(C.AQUA + "Height" + C.GRAY + ": " + Math.round(engine.getHeight(x, z))); - lines.add(C.AQUA + "Slope" + C.GRAY + ": " + Form.f(engine.getComplex().getSlopeStream().get(x, z), 2)); - lines.add(C.AQUA + "BUD/s" + C.GRAY + ": " + Form.f(engine.getBlockUpdatesPerSecond())); - lines.add("&7&m "); - } + int x = loc.getBlockX(); + int y = loc.getBlockY() - world.getMinHeight(); + int z = loc.getBlockZ(); + + List lines = new ArrayList<>(this.lines.size()); + lines.add("&7&m "); + lines.add(C.GREEN + "Speed" + C.GRAY + ": " + Form.f(engine.getGeneratedPerSecond(), 0) + "/s " + Form.duration(1000D / engine.getGeneratedPerSecond(), 0)); + lines.add(C.AQUA + "Cache" + C.GRAY + ": " + Form.f(IrisData.cacheSize())); + lines.add(C.AQUA + "Mantle" + C.GRAY + ": " + engine.getMantle().getLoadedRegionCount()); + + if (IrisSettings.get().getGeneral().debug) { + lines.add(C.LIGHT_PURPLE + "Carving" + C.GRAY + ": " + engine.getMantle().isCarved(x,y,z)); } + + lines.add("&7&m "); + lines.add(C.AQUA + "Region" + C.GRAY + ": " + engine.getRegion(x, z).getName()); + lines.add(C.AQUA + "Biome" + C.GRAY + ": " + engine.getBiomeOrMantle(x, y, z).getName()); + lines.add(C.AQUA + "Height" + C.GRAY + ": " + Math.round(engine.getHeight(x, z))); + lines.add(C.AQUA + "Slope" + C.GRAY + ": " + Form.f(engine.getComplex().getSlopeStream().get(x, z), 2)); + lines.add(C.AQUA + "BUD/s" + C.GRAY + ": " + Form.f(engine.getBlockUpdatesPerSecond())); + lines.add("&7&m "); + this.lines = lines; } } } diff --git a/core/src/main/java/com/volmit/iris/core/service/CommandSVC.java b/core/src/main/java/com/volmit/iris/core/service/CommandSVC.java index d2e56d587..7ca3ba94e 100644 --- a/core/src/main/java/com/volmit/iris/core/service/CommandSVC.java +++ b/core/src/main/java/com/volmit/iris/core/service/CommandSVC.java @@ -23,6 +23,7 @@ import com.volmit.iris.core.commands.CommandIris; import com.volmit.iris.core.tools.IrisToolbelt; import com.volmit.iris.engine.data.cache.AtomicCache; import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.decree.DecreeContext; import com.volmit.iris.util.decree.DecreeSystem; import com.volmit.iris.util.decree.virtual.VirtualDecreeCommand; import com.volmit.iris.util.format.C; @@ -44,7 +45,14 @@ public class CommandSVC implements IrisService, DecreeSystem { @Override public void onEnable() { Iris.instance.getCommand("iris").setExecutor(this); - J.a(() -> getRoot().cacheAll()); + J.a(() -> { + DecreeContext.touch(Iris.getSender()); + try { + getRoot().cacheAll(); + } finally { + DecreeContext.remove(); + } + }); } @Override diff --git a/core/src/main/java/com/volmit/iris/core/service/StudioSVC.java b/core/src/main/java/com/volmit/iris/core/service/StudioSVC.java index bf070d063..fb2be0f33 100644 --- a/core/src/main/java/com/volmit/iris/core/service/StudioSVC.java +++ b/core/src/main/java/com/volmit/iris/core/service/StudioSVC.java @@ -88,16 +88,18 @@ public class StudioSVC implements IrisService { } public IrisDimension installIntoWorld(VolmitSender sender, String type, File folder) { + return installInto(sender, type, new File(folder, "iris/pack")); + } + + public IrisDimension installInto(VolmitSender sender, String type, File folder) { sender.sendMessage("Looking for Package: " + type); - File iris = new File(folder, "iris"); - File irispack = new File(folder, "iris/pack"); - IrisDimension dim = IrisData.loadAnyDimension(type); + IrisDimension dim = IrisData.loadAnyDimension(type, null); if (dim == null) { for (File i : getWorkspaceFolder().listFiles()) { if (i.isFile() && i.getName().equals(type + ".iris")) { sender.sendMessage("Found " + type + ".iris in " + WORKSPACE_NAME + " folder"); - ZipUtil.unpack(i, irispack); + ZipUtil.unpack(i, folder); break; } } @@ -106,29 +108,29 @@ public class StudioSVC implements IrisService { File f = new IrisProject(new File(getWorkspaceFolder(), type)).getPath(); try { - FileUtils.copyDirectory(f, irispack); + FileUtils.copyDirectory(f, folder); } catch (IOException e) { Iris.reportError(e); } } - File dimf = new File(irispack, "dimensions/" + type + ".json"); + File dimensionFile = new File(folder, "dimensions/" + type + ".json"); - if (!dimf.exists() || !dimf.isFile()) { + if (!dimensionFile.exists() || !dimensionFile.isFile()) { downloadSearch(sender, type, false); File downloaded = getWorkspaceFolder(type); for (File i : downloaded.listFiles()) { if (i.isFile()) { try { - FileUtils.copyFile(i, new File(irispack, i.getName())); + FileUtils.copyFile(i, new File(folder, i.getName())); } catch (IOException e) { e.printStackTrace(); Iris.reportError(e); } } else { try { - FileUtils.copyDirectory(i, new File(irispack, i.getName())); + FileUtils.copyDirectory(i, new File(folder, i.getName())); } catch (IOException e) { e.printStackTrace(); Iris.reportError(e); @@ -139,12 +141,13 @@ public class StudioSVC implements IrisService { IO.delete(downloaded); } - if (!dimf.exists() || !dimf.isFile()) { - sender.sendMessage("Can't find the " + dimf.getName() + " in the dimensions folder of this pack! Failed!"); + if (!dimensionFile.exists() || !dimensionFile.isFile()) { + sender.sendMessage("Can't find the " + dimensionFile.getName() + " in the dimensions folder of this pack! Failed!"); return null; } - IrisData dm = IrisData.get(irispack); + IrisData dm = IrisData.get(folder); + dm.hotloaded(); dim = dm.getDimensionLoader().load(type); if (dim == null) { @@ -261,6 +264,7 @@ public class StudioSVC implements IrisService { } IrisDimension d = data.getDimensionLoader().load(dimensions[0]); + data.close(); if (d == null) { sender.sendMessage("Invalid dimension (folder) in dimensions folder"); @@ -275,7 +279,7 @@ public class StudioSVC implements IrisService { IO.delete(packEntry); } - if (IrisData.loadAnyDimension(key) != null) { + if (IrisData.loadAnyDimension(key, null) != null) { sender.sendMessage("Another dimension in the packs folder is already using the key " + key + " IMPORT FAILED!"); return; } @@ -294,6 +298,8 @@ public class StudioSVC implements IrisService { packEntry.mkdirs(); ZipUtil.unpack(cp, packEntry); } + IrisData.getLoaded(packEntry) + .ifPresent(IrisData::hotloaded); sender.sendMessage("Successfully Aquired " + d.getName()); ServerConfigurator.installDataPacks(true); diff --git a/core/src/main/java/com/volmit/iris/core/tools/IrisCreator.java b/core/src/main/java/com/volmit/iris/core/tools/IrisCreator.java index 6575ba5dd..46bb66b8b 100644 --- a/core/src/main/java/com/volmit/iris/core/tools/IrisCreator.java +++ b/core/src/main/java/com/volmit/iris/core/tools/IrisCreator.java @@ -135,12 +135,14 @@ public class IrisCreator { .seed(seed) .studio(studio) .create(); - ServerConfigurator.installDataPacks(false); + if (ServerConfigurator.installDataPacks(true)) { + throw new IrisException("Datapacks were missing!"); + } PlatformChunkGenerator access = (PlatformChunkGenerator) wc.generator(); if (access == null) throw new IrisException("Access is null. Something bad happened."); - AtomicBoolean failed = new AtomicBoolean(false); + AtomicBoolean done = new AtomicBoolean(false); J.a(() -> { IntSupplier g = () -> { if (access.getEngine() == null) { @@ -150,19 +152,13 @@ public class IrisCreator { }; if(!benchmark) { int req = access.getSpawnChunks().join(); - - while (g.getAsInt() < req) { - if (failed.get()) { - sender.sendMessage(C.RED + "Failed to create world!"); - return; - } - - double v = (double) g.getAsInt() / (double) req; + for (int c = 0; c < req && !done.get(); c = g.getAsInt()) { + double v = (double) c / req; if (sender.isPlayer()) { sender.sendProgress(v, "Generating"); J.sleep(16); } else { - sender.sendMessage(C.WHITE + "Generating " + Form.pc(v) + ((C.GRAY + " (" + (req - g.getAsInt()) + " Left)"))); + sender.sendMessage(C.WHITE + "Generating " + Form.pc(v) + ((C.GRAY + " (" + (req - c) + " Left)"))); J.sleep(1000); } } @@ -176,10 +172,12 @@ public class IrisCreator { .thenCompose(Function.identity()) .get(); } catch (Throwable e) { - failed.set(true); + done.set(true); throw new IrisException("Failed to create world!", e); } + done.set(true); + if (sender.isPlayer() && !benchmark) { Iris.platform.getChunkAtAsync(world, 0, 0, true, true) .thenApply(Objects::requireNonNull) diff --git a/core/src/main/java/com/volmit/iris/core/tools/IrisWorldCreator.java b/core/src/main/java/com/volmit/iris/core/tools/IrisWorldCreator.java index cd6c17a91..a6fd79a83 100644 --- a/core/src/main/java/com/volmit/iris/core/tools/IrisWorldCreator.java +++ b/core/src/main/java/com/volmit/iris/core/tools/IrisWorldCreator.java @@ -64,7 +64,7 @@ public class IrisWorldCreator { } public WorldCreator create() { - IrisDimension dim = IrisData.loadAnyDimension(dimensionName); + IrisDimension dim = IrisData.loadAnyDimension(dimensionName, null); IrisWorld w = IrisWorld.builder() .name(name) @@ -80,13 +80,13 @@ public class IrisWorldCreator { return new WorldCreator(name) - .environment(findEnvironment()) + .environment(w.environment()) .generateStructures(true) .generator(g).seed(seed); } private World.Environment findEnvironment() { - IrisDimension dim = IrisData.loadAnyDimension(dimensionName); + IrisDimension dim = IrisData.loadAnyDimension(dimensionName, null); if (dim == null || dim.getEnvironment() == null) { return World.Environment.NORMAL; } else { diff --git a/core/src/main/java/com/volmit/iris/engine/IrisWorldManager.java b/core/src/main/java/com/volmit/iris/engine/IrisWorldManager.java index e0e974a66..2e867a87b 100644 --- a/core/src/main/java/com/volmit/iris/engine/IrisWorldManager.java +++ b/core/src/main/java/com/volmit/iris/engine/IrisWorldManager.java @@ -20,7 +20,9 @@ package com.volmit.iris.engine; import com.volmit.iris.Iris; import com.volmit.iris.core.IrisSettings; +import com.volmit.iris.core.link.Identifier; import com.volmit.iris.core.loader.IrisData; +import com.volmit.iris.core.service.ExternalDataSVC; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.framework.EngineAssignedWorldManager; import com.volmit.iris.engine.object.*; @@ -426,18 +428,35 @@ public class IrisWorldManager extends EngineAssignedWorldManager { } var ref = new WeakReference<>(e.getWorld()); - int x = e.getX(), z = e.getZ(); + int cX = e.getX(), cZ = e.getZ(); J.s(() -> { World world = ref.get(); - if (world == null || !world.isChunkLoaded(x, z)) + if (world == null || !world.isChunkLoaded(cX, cZ)) return; energy += 0.3; fixEnergy(); - getEngine().cleanupMantleChunk(x, z); + getEngine().cleanupMantleChunk(cX, cZ); }, IrisSettings.get().getPerformance().mantleCleanupDelay); if (generated) { //INMS.get().injectBiomesFromMantle(e, getMantle()); + + if (!IrisSettings.get().getGenerator().earlyCustomBlocks) return; + e.addPluginChunkTicket(Iris.instance); + J.s(() -> { + var chunk = getMantle().getChunk(e).use(); + int minY = getTarget().getWorld().minHeight(); + try { + chunk.raiseFlagUnchecked(MantleFlag.CUSTOM, () -> { + chunk.iterate(Identifier.class, (x, y, z, v) -> { + Iris.service(ExternalDataSVC.class).processUpdate(getEngine(), e.getBlock(x & 15, y + minY, z & 15), v); + }); + }); + } finally { + chunk.release(); + e.removePluginChunkTicket(Iris.instance); + } + }, RNG.r.i(20, 60)); } } diff --git a/core/src/main/java/com/volmit/iris/engine/framework/Engine.java b/core/src/main/java/com/volmit/iris/engine/framework/Engine.java index 8c9009119..df9fd129e 100644 --- a/core/src/main/java/com/volmit/iris/engine/framework/Engine.java +++ b/core/src/main/java/com/volmit/iris/engine/framework/Engine.java @@ -295,20 +295,20 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat try { Semaphore semaphore = new Semaphore(3); chunk.raiseFlag(MantleFlag.ETCHED, () -> { - chunk.raiseFlag(MantleFlag.TILE, run(semaphore, () -> { + chunk.raiseFlagUnchecked(MantleFlag.TILE, run(semaphore, () -> { chunk.iterate(TileWrapper.class, (x, y, z, v) -> { Block block = c.getBlock(x & 15, y + getWorld().minHeight(), z & 15); if (!TileData.setTileState(block, v.getData())) Iris.warn("Failed to set tile entity data at [%d %d %d | %s] for tile %s!", block.getX(), block.getY(), block.getZ(), block.getType().getKey(), v.getData().getMaterial().getKey()); }); }, c, 1)); - chunk.raiseFlag(MantleFlag.CUSTOM, run(semaphore, () -> { + chunk.raiseFlagUnchecked(MantleFlag.CUSTOM, run(semaphore, () -> { chunk.iterate(Identifier.class, (x, y, z, v) -> { Iris.service(ExternalDataSVC.class).processUpdate(this, c.getBlock(x & 15, y + getWorld().minHeight(), z & 15), v); }); }, c, 1)); - chunk.raiseFlag(MantleFlag.UPDATE, run(semaphore, () -> { + chunk.raiseFlagUnchecked(MantleFlag.UPDATE, run(semaphore, () -> { PrecisionStopwatch p = PrecisionStopwatch.start(); int[][] grid = new int[16][16]; for (int x = 0; x < 16; x++) { @@ -863,7 +863,7 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat default void gotoBiome(IrisBiome biome, Player player, boolean teleport) { Set regionKeys = getDimension() .getAllRegions(this).stream() - .filter((i) -> i.getAllBiomes(this).contains(biome)) + .filter((i) -> i.getAllBiomeIds().contains(biome.getLoadKey())) .map(IrisRegistrant::getLoadKey) .collect(Collectors.toSet()); Locator lb = Locator.surfaceBiome(biome.getLoadKey()); @@ -959,7 +959,7 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat } default void gotoRegion(IrisRegion r, Player player, boolean teleport) { - if (!getDimension().getAllRegions(this).contains(r)) { + if (!getDimension().getRegions().contains(r.getLoadKey())) { player.sendMessage(C.RED + r.getName() + " is not defined in the dimension!"); return; } diff --git a/core/src/main/java/com/volmit/iris/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/modifier/IrisPostModifier.java b/core/src/main/java/com/volmit/iris/engine/modifier/IrisPostModifier.java index 6d85e3205..aa80a05b6 100644 --- a/core/src/main/java/com/volmit/iris/engine/modifier/IrisPostModifier.java +++ b/core/src/main/java/com/volmit/iris/engine/modifier/IrisPostModifier.java @@ -21,6 +21,7 @@ package com.volmit.iris.engine.modifier; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.framework.EngineAssignedModifier; import com.volmit.iris.engine.object.IrisBiome; +import com.volmit.iris.engine.object.IrisSlopeClip; import com.volmit.iris.util.context.ChunkContext; import com.volmit.iris.util.data.B; import com.volmit.iris.util.hunk.Hunk; @@ -174,7 +175,8 @@ public class IrisPostModifier extends EngineAssignedModifier { || (hd == h + 1 && isSolidNonSlab(x, hd, z - 1, currentPostX, currentPostZ, currentData))) //@done { - BlockData d = biome.getSlab().get(rng, x, h, z, getData()); + IrisSlopeClip sc = biome.getSlab().getSlopeCondition(); + BlockData d = sc.isValid(getComplex().getSlopeStream().get(x, z)) ? biome.getSlab().get(rng, x, h, z, getData()) : null; if (d != null) { boolean cancel = B.isAir(d); diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisDimension.java b/core/src/main/java/com/volmit/iris/engine/object/IrisDimension.java index 2d7917a6d..a9daa6fef 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisDimension.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisDimension.java @@ -355,7 +355,7 @@ public class IrisDimension extends IrisRegistrant { KList r = new KList<>(); for (String i : getRegions()) { - r.add(IrisData.loadAnyRegion(i)); + r.add(IrisData.loadAnyRegion(i, getLoader())); } return r; diff --git a/core/src/main/java/com/volmit/iris/engine/object/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/IrisRegion.java b/core/src/main/java/com/volmit/iris/engine/object/IrisRegion.java index a2712786a..157cb61c9 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisRegion.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisRegion.java @@ -346,7 +346,7 @@ public class IrisRegion extends IrisRegistrant implements IRare { continue; } - IrisBiome biome = IrisData.loadAnyBiome(i); + IrisBiome biome = IrisData.loadAnyBiome(i, getLoader()); names.remove(i); if (biome == null) { diff --git a/core/src/main/java/com/volmit/iris/engine/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 060e80bb4..c3a3b13c5 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 @@ -169,15 +169,12 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun if (dimension == null) { Iris.error("Oh No! There's no pack in " + data.getDataFolder().getPath() + " or... there's no dimension for the key " + dimensionKey); - IrisDimension test = IrisData.loadAnyDimension(dimensionKey); + IrisDimension test = IrisData.loadAnyDimension(dimensionKey, null); if (test != null) { Iris.warn("Looks like " + dimensionKey + " exists in " + test.getLoadFile().getPath() + " "); - Iris.service(StudioSVC.class).installIntoWorld(Iris.getSender(), dimensionKey, dataLocation.getParentFile().getParentFile()); + test = Iris.service(StudioSVC.class).installInto(Iris.getSender(), dimensionKey, dataLocation); Iris.warn("Attempted to install into " + data.getDataFolder().getPath()); - data.dump(); - data.clearLists(); - test = data.getDimensionLoader().load(dimensionKey); if (test != null) { Iris.success("Woo! Patched the Engine!"); @@ -240,11 +237,12 @@ 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(() -> { c.removePluginChunkTicket(Iris.instance); - c.unload(); + engine.getWorldManager().onChunkLoad(c, true); }, syncExecutor) .get(); Iris.debug("Regenerated " + x + " " + z); @@ -356,16 +354,16 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun @Override public void generateNoise(@NotNull WorldInfo world, @NotNull Random random, int x, int z, @NotNull ChunkGenerator.ChunkData d) { try { - getEngine(world); + Engine engine = getEngine(world); computeStudioGenerator(); TerrainChunk tc = TerrainChunk.create(d, new IrisBiomeStorage()); this.world.bind(world); if (studioGenerator != null) { - studioGenerator.generateChunk(getEngine(), tc, x, z); + studioGenerator.generateChunk(engine, tc, x, z); } else { ChunkDataHunkHolder blocks = new ChunkDataHunkHolder(tc); BiomeGridHunkHolder biomes = new BiomeGridHunkHolder(tc, tc.getMinHeight(), tc.getMaxHeight()); - getEngine().generate(x << 4, z << 4, blocks, biomes, IrisSettings.get().getGenerator().useMulticore); + engine.generate(x << 4, z << 4, blocks, biomes, IrisSettings.get().getGenerator().useMulticore); blocks.apply(); biomes.apply(); } diff --git a/core/src/main/java/com/volmit/iris/util/board/Board.java b/core/src/main/java/com/volmit/iris/util/board/Board.java index eb37ef182..eecbac731 100644 --- a/core/src/main/java/com/volmit/iris/util/board/Board.java +++ b/core/src/main/java/com/volmit/iris/util/board/Board.java @@ -23,15 +23,11 @@ import lombok.NonNull; import lombok.Setter; import org.bukkit.Bukkit; import org.bukkit.entity.Player; -import org.bukkit.scoreboard.DisplaySlot; -import org.bukkit.scoreboard.Objective; -import org.bukkit.scoreboard.Scoreboard; -import org.bukkit.scoreboard.Team; +import org.bukkit.scoreboard.*; import java.util.Collections; import java.util.List; -import java.util.function.Function; -import java.util.stream.Collectors; +import java.util.function.UnaryOperator; import java.util.stream.IntStream; /** @@ -42,7 +38,7 @@ public class Board { private static final String[] CACHED_ENTRIES = new String[C.values().length]; - private static final Function APPLY_COLOR_TRANSLATION = s -> C.translateAlternateColorCodes('&', s); + private static final UnaryOperator APPLY_COLOR_TRANSLATION = s -> C.translateAlternateColorCodes('&', s); static { IntStream.range(0, 15).forEach(i -> CACHED_ENTRIES[i] = C.values()[i].toString() + C.RESET); @@ -54,13 +50,14 @@ public class Board { private BoardSettings boardSettings; private boolean ready; - @SuppressWarnings("deprecation") public Board(@NonNull final Player player, final BoardSettings boardSettings) { this.player = player; this.boardSettings = boardSettings; - this.objective = this.getScoreboard().getObjective("board") == null ? this.getScoreboard().registerNewObjective("board", "dummy") : this.getScoreboard().getObjective("board"); + var obj = getScoreboard().getObjective("board"); + this.objective = obj == null ? this.getScoreboard().registerNewObjective("board", Criteria.DUMMY, "Iris") : obj; this.objective.setDisplaySlot(DisplaySlot.SIDEBAR); - Team team = this.getScoreboard().getTeam("board") == null ? this.getScoreboard().registerNewTeam("board") : this.getScoreboard().getTeam("board"); + Team team = getScoreboard().getTeam("board"); + team = team == null ? getScoreboard().registerNewTeam("board") : team; team.setAllowFriendlyFire(true); team.setCanSeeFriendlyInvisibles(false); team.setPrefix(""); @@ -94,7 +91,8 @@ public class Board { } // Getting their Scoreboard display from the Scoreboard Provider. - final List entries = boardSettings.getBoardProvider().getLines(player).stream().map(APPLY_COLOR_TRANSLATION).collect(Collectors.toList()); + final List entries = boardSettings.getBoardProvider().getLines(player); + entries.replaceAll(APPLY_COLOR_TRANSLATION); if (boardSettings.getScoreDirection() == ScoreDirection.UP) { Collections.reverse(entries); diff --git a/core/src/main/java/com/volmit/iris/util/board/BoardUpdateTask.java b/core/src/main/java/com/volmit/iris/util/board/BoardUpdateTask.java index 9ae24d7d1..7436f4d02 100644 --- a/core/src/main/java/com/volmit/iris/util/board/BoardUpdateTask.java +++ b/core/src/main/java/com/volmit/iris/util/board/BoardUpdateTask.java @@ -37,6 +37,12 @@ public class BoardUpdateTask implements Runnable { @Override public void run() { - boardManager.getScoreboards().entrySet().stream().filter(entrySet -> PLAYER_IS_ONLINE.test(entrySet.getKey())).forEach(entrySet -> entrySet.getValue().update()); + for (var entry : boardManager.getScoreboards().entrySet()) { + if (!PLAYER_IS_ONLINE.test(entry.getKey())) { + continue; + } + + entry.getValue().update(); + } } } 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/data/B.java b/core/src/main/java/com/volmit/iris/util/data/B.java index 7f98d13f9..982df8d2d 100644 --- a/core/src/main/java/com/volmit/iris/util/data/B.java +++ b/core/src/main/java/com/volmit/iris/util/data/B.java @@ -498,7 +498,7 @@ public class B { if (!ix.startsWith("minecraft:") && ix.contains(":")) { Identifier key = Identifier.fromString(ix); Optional bd = Iris.service(ExternalDataSVC.class).getBlockData(key); - Iris.info("Loading block data " + key); + Iris.debug("Loading block data " + key); if (bd.isPresent()) bx = bd.get(); } diff --git a/core/src/main/java/com/volmit/iris/util/decree/DecreeContext.java b/core/src/main/java/com/volmit/iris/util/decree/DecreeContext.java index f8ee1e233..b19fe32f4 100644 --- a/core/src/main/java/com/volmit/iris/util/decree/DecreeContext.java +++ b/core/src/main/java/com/volmit/iris/util/decree/DecreeContext.java @@ -18,29 +18,20 @@ package com.volmit.iris.util.decree; -import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.plugin.VolmitSender; -import com.volmit.iris.util.scheduling.ChronoLatch; public class DecreeContext { - private static final ChronoLatch cl = new ChronoLatch(60000); - private static final KMap context = new KMap<>(); + private static final ThreadLocal context = new ThreadLocal<>(); public static VolmitSender get() { - return context.get(Thread.currentThread()); + return context.get(); } public static void touch(VolmitSender c) { - synchronized (context) { - context.put(Thread.currentThread(), c); + context.set(c); + } - if (cl.flip()) { - for (Thread i : context.k()) { - if (!i.isAlive()) { - context.remove(i); - } - } - } - } + public static void remove() { + context.remove(); } } diff --git a/core/src/main/java/com/volmit/iris/util/decree/DecreeExecutor.java b/core/src/main/java/com/volmit/iris/util/decree/DecreeExecutor.java index 8d25d6ca3..12404d1c0 100644 --- a/core/src/main/java/com/volmit/iris/util/decree/DecreeExecutor.java +++ b/core/src/main/java/com/volmit/iris/util/decree/DecreeExecutor.java @@ -18,6 +18,7 @@ package com.volmit.iris.util.decree; +import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.core.tools.IrisToolbelt; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.platform.PlatformChunkGenerator; @@ -34,6 +35,14 @@ public interface DecreeExecutor { return sender().player(); } + default IrisData data() { + var access = access(); + if (access != null) { + return access.getData(); + } + return null; + } + default Engine engine() { if (sender().isPlayer() && IrisToolbelt.access(sender().player().getWorld()) != null) { PlatformChunkGenerator gen = IrisToolbelt.access(sender().player().getWorld()); diff --git a/core/src/main/java/com/volmit/iris/util/decree/DecreeParameterHandler.java b/core/src/main/java/com/volmit/iris/util/decree/DecreeParameterHandler.java index ee959d2b3..097361799 100644 --- a/core/src/main/java/com/volmit/iris/util/decree/DecreeParameterHandler.java +++ b/core/src/main/java/com/volmit/iris/util/decree/DecreeParameterHandler.java @@ -23,7 +23,7 @@ import com.volmit.iris.util.decree.exceptions.DecreeParsingException; import java.util.concurrent.atomic.AtomicReference; -public interface DecreeParameterHandler { +public interface DecreeParameterHandler extends DecreeExecutor { /** * Should return the possible values for this type * diff --git a/core/src/main/java/com/volmit/iris/util/decree/DecreeSystem.java b/core/src/main/java/com/volmit/iris/util/decree/DecreeSystem.java index d22a8b822..5edacb51f 100644 --- a/core/src/main/java/com/volmit/iris/util/decree/DecreeSystem.java +++ b/core/src/main/java/com/volmit/iris/util/decree/DecreeSystem.java @@ -133,23 +133,32 @@ public interface DecreeSystem extends CommandExecutor, TabCompleter { default boolean call(VolmitSender sender, String[] args) { DecreeContext.touch(sender); - return getRoot().invoke(sender, enhanceArgs(args)); + try { + return getRoot().invoke(sender, enhanceArgs(args)); + } finally { + DecreeContext.remove(); + } } @Nullable @Override default List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) { - KList enhanced = new KList<>(args); - KList v = getRoot().tabComplete(enhanced, enhanced.toString(" ")); - v.removeDuplicates(); + DecreeContext.touch(new VolmitSender(sender)); + try { + KList enhanced = new KList<>(args); + KList v = getRoot().tabComplete(enhanced, enhanced.toString(" ")); + v.removeDuplicates(); - if (sender instanceof Player) { - if (IrisSettings.get().getGeneral().isCommandSounds()) { - ((Player) sender).playSound(((Player) sender).getLocation(), Sound.BLOCK_AMETHYST_BLOCK_CHIME, 0.25f, RNG.r.f(0.125f, 1.95f)); + if (sender instanceof Player) { + if (IrisSettings.get().getGeneral().isCommandSounds()) { + ((Player) sender).playSound(((Player) sender).getLocation(), Sound.BLOCK_AMETHYST_BLOCK_CHIME, 0.25f, RNG.r.f(0.125f, 1.95f)); + } } - } - return v; + return v; + } finally { + DecreeContext.remove(); + } } @Override diff --git a/core/src/main/java/com/volmit/iris/util/decree/handlers/BiomeHandler.java b/core/src/main/java/com/volmit/iris/util/decree/handlers/BiomeHandler.java index ca950c8e6..36f78ee16 100644 --- a/core/src/main/java/com/volmit/iris/util/decree/handlers/BiomeHandler.java +++ b/core/src/main/java/com/volmit/iris/util/decree/handlers/BiomeHandler.java @@ -18,63 +18,12 @@ package com.volmit.iris.util.decree.handlers; -import com.volmit.iris.Iris; -import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.engine.object.IrisBiome; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.decree.DecreeParameterHandler; -import com.volmit.iris.util.decree.exceptions.DecreeParsingException; +import com.volmit.iris.util.decree.specialhandlers.RegistrantHandler; -import java.io.File; -import java.util.stream.Collectors; - -public class BiomeHandler implements DecreeParameterHandler { - @Override - public KList getPossibilities() { - KMap p = new KMap<>(); - - //noinspection ConstantConditions - for (File i : Iris.instance.getDataFolder("packs").listFiles()) { - if (i.isDirectory()) { - IrisData data = IrisData.get(i); - for (IrisBiome j : data.getBiomeLoader().loadAll(data.getBiomeLoader().getPossibleKeys())) { - p.putIfAbsent(j.getLoadKey(), j); - } - - data.close(); - } - } - - return p.v(); - } - - @Override - public String toString(IrisBiome dim) { - return dim.getLoadKey(); - } - - @Override - public IrisBiome parse(String in, boolean force) throws DecreeParsingException { - if (in.equals("null")) { - return null; - } - KList options = getPossibilities(in); - - if (options.isEmpty()) { - throw new DecreeParsingException("Unable to find Biome \"" + in + "\""); - } - - try { - return options.stream().filter((i) -> toString(i).equalsIgnoreCase(in)).collect(Collectors.toList()).get(0); - } catch (Throwable e) { - throw new DecreeParsingException("Unable to filter which Biome \"" + in + "\""); - } - } - - @Override - public boolean supports(Class type) { - return type.equals(IrisBiome.class); +public class BiomeHandler extends RegistrantHandler { + public BiomeHandler() { + super(IrisBiome.class, true); } @Override diff --git a/core/src/main/java/com/volmit/iris/util/decree/handlers/CaveHandler.java b/core/src/main/java/com/volmit/iris/util/decree/handlers/CaveHandler.java index db8f86651..bd14aab68 100644 --- a/core/src/main/java/com/volmit/iris/util/decree/handlers/CaveHandler.java +++ b/core/src/main/java/com/volmit/iris/util/decree/handlers/CaveHandler.java @@ -18,62 +18,12 @@ package com.volmit.iris.util.decree.handlers; -import com.volmit.iris.Iris; -import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.engine.object.IrisCave; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.decree.DecreeParameterHandler; -import com.volmit.iris.util.decree.exceptions.DecreeParsingException; +import com.volmit.iris.util.decree.specialhandlers.RegistrantHandler; -import java.io.File; -import java.util.stream.Collectors; - -public class CaveHandler implements DecreeParameterHandler { - @Override - public KList getPossibilities() { - KMap p = new KMap<>(); - - //noinspection ConstantConditions - for (File i : Iris.instance.getDataFolder("packs").listFiles()) { - if (i.isDirectory()) { - IrisData data = IrisData.get(i); - for (IrisCave j : data.getCaveLoader().loadAll(data.getCaveLoader().getPossibleKeys())) { - p.putIfAbsent(j.getLoadKey(), j); - } - - data.close(); - } - } - - return p.v(); - } - - @Override - public String toString(IrisCave dim) { - return dim.getLoadKey(); - } - - @Override - public IrisCave parse(String in, boolean force) throws DecreeParsingException { - if (in.equals("null")) { - return null; - } - KList options = getPossibilities(in); - - if (options.isEmpty()) { - throw new DecreeParsingException("Unable to find Cave \"" + in + "\""); - } - try { - return options.stream().filter((i) -> toString(i).equalsIgnoreCase(in)).collect(Collectors.toList()).get(0); - } catch (Throwable e) { - throw new DecreeParsingException("Unable to filter which Cave\"" + in + "\""); - } - } - - @Override - public boolean supports(Class type) { - return type.equals(IrisCave.class); +public class CaveHandler extends RegistrantHandler { + public CaveHandler() { + super(IrisCave.class, true); } @Override diff --git a/core/src/main/java/com/volmit/iris/util/decree/handlers/DimensionHandler.java b/core/src/main/java/com/volmit/iris/util/decree/handlers/DimensionHandler.java index 5d36af07f..d8eeb998c 100644 --- a/core/src/main/java/com/volmit/iris/util/decree/handlers/DimensionHandler.java +++ b/core/src/main/java/com/volmit/iris/util/decree/handlers/DimensionHandler.java @@ -18,65 +18,22 @@ package com.volmit.iris.util.decree.handlers; -import com.volmit.iris.Iris; import com.volmit.iris.core.IrisSettings; -import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.engine.object.IrisDimension; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.decree.DecreeParameterHandler; import com.volmit.iris.util.decree.exceptions.DecreeParsingException; +import com.volmit.iris.util.decree.specialhandlers.RegistrantHandler; -import java.io.File; - -public class DimensionHandler implements DecreeParameterHandler { - @Override - public KList getPossibilities() { - KMap p = new KMap<>(); - - //noinspection ConstantConditions - for (File i : Iris.instance.getDataFolder("packs").listFiles()) { - if (i.isDirectory()) { - IrisData data = IrisData.get(i); - for (IrisDimension j : data.getDimensionLoader().loadAll(data.getDimensionLoader().getPossibleKeys())) { - p.putIfAbsent(j.getLoadKey(), j); - } - - data.close(); - } - } - - return p.v(); - } - - @Override - public String toString(IrisDimension dim) { - return dim.getLoadKey(); +public class DimensionHandler extends RegistrantHandler { + public DimensionHandler() { + super(IrisDimension.class, false); } @Override public IrisDimension parse(String in, boolean force) throws DecreeParsingException { - if (in.equalsIgnoreCase("default")) { return parse(IrisSettings.get().getGenerator().getDefaultWorldType()); } - - KList options = getPossibilities(in); - - - if (options.isEmpty()) { - throw new DecreeParsingException("Unable to find Dimension \"" + in + "\""); - } - try { - return options.stream().filter((i) -> toString(i).equalsIgnoreCase(in)).toList().get(0); - } catch (Throwable e) { - throw new DecreeParsingException("Unable to filter which Dimension \"" + in + "\""); - } - } - - @Override - public boolean supports(Class type) { - return type.equals(IrisDimension.class); + return super.parse(in, force); } @Override diff --git a/core/src/main/java/com/volmit/iris/util/decree/handlers/EntityHandler.java b/core/src/main/java/com/volmit/iris/util/decree/handlers/EntityHandler.java index 01034c747..3cf4b4e08 100644 --- a/core/src/main/java/com/volmit/iris/util/decree/handlers/EntityHandler.java +++ b/core/src/main/java/com/volmit/iris/util/decree/handlers/EntityHandler.java @@ -18,84 +18,13 @@ package com.volmit.iris.util.decree.handlers; -import com.volmit.iris.Iris; -import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.engine.object.IrisEntity; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.decree.DecreeParameterHandler; -import com.volmit.iris.util.decree.exceptions.DecreeParsingException; +import com.volmit.iris.util.decree.specialhandlers.RegistrantHandler; -import java.io.File; -import java.util.stream.Collectors; +public class EntityHandler extends RegistrantHandler { -public class EntityHandler implements DecreeParameterHandler { - - /** - * Should return the possible values for this type - * - * @return Possibilities for this type. - */ - @Override - public KList getPossibilities() { - KMap p = new KMap<>(); - - //noinspection ConstantConditions - for (File i : Iris.instance.getDataFolder("packs").listFiles()) { - if (i.isDirectory()) { - IrisData data = IrisData.get(i); - for (IrisEntity j : data.getEntityLoader().loadAll(data.getEntityLoader().getPossibleKeys())) { - p.putIfAbsent(j.getLoadKey(), j); - } - - data.close(); - } - } - - return p.v(); - } - - /** - * Converting the type back to a string (inverse of the {@link #parse(String) parse} method) - * - * @param entity The input of the designated type to convert to a String - * @return The resulting string - */ - @Override - public String toString(IrisEntity entity) { - return entity.getLoadKey(); - } - - /** - * Should parse a String into the designated type - * - * @param in The string to parse - * @return The value extracted from the string, of the designated type - * @throws DecreeParsingException Thrown when the parsing fails (ex: "oop" translated to an integer throws this) - */ - @Override - public IrisEntity parse(String in, boolean force) throws DecreeParsingException { - KList options = getPossibilities(in); - - if (options.isEmpty()) { - throw new DecreeParsingException("Unable to find Entity \"" + in + "\""); - } - try { - return options.stream().filter((i) -> toString(i).equalsIgnoreCase(in)).collect(Collectors.toList()).get(0); - } catch (Throwable e) { - throw new DecreeParsingException("Unable to filter which Entity \"" + in + "\""); - } - } - - /** - * Returns whether a certain type is supported by this handler
- * - * @param type The type to check - * @return True if supported, false if not - */ - @Override - public boolean supports(Class type) { - return type.equals(IrisEntity.class); + public EntityHandler() { + super(IrisEntity.class, false); } @Override diff --git a/core/src/main/java/com/volmit/iris/util/decree/handlers/GeneratorHandler.java b/core/src/main/java/com/volmit/iris/util/decree/handlers/GeneratorHandler.java index 7a89ef155..8b1baf0ae 100644 --- a/core/src/main/java/com/volmit/iris/util/decree/handlers/GeneratorHandler.java +++ b/core/src/main/java/com/volmit/iris/util/decree/handlers/GeneratorHandler.java @@ -18,59 +18,12 @@ package com.volmit.iris.util.decree.handlers; -import com.volmit.iris.Iris; -import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.engine.object.IrisGenerator; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.decree.DecreeParameterHandler; -import com.volmit.iris.util.decree.exceptions.DecreeParsingException; +import com.volmit.iris.util.decree.specialhandlers.RegistrantHandler; -import java.io.File; -import java.util.stream.Collectors; - -public class GeneratorHandler implements DecreeParameterHandler { - @Override - public KList getPossibilities() { - KMap p = new KMap<>(); - - //noinspection ConstantConditions - for (File i : Iris.instance.getDataFolder("packs").listFiles()) { - if (i.isDirectory()) { - IrisData data = IrisData.get(i); - for (IrisGenerator j : data.getGeneratorLoader().loadAll(data.getGeneratorLoader().getPossibleKeys())) { - p.putIfAbsent(j.getLoadKey(), j); - } - - data.close(); - } - } - - return p.v(); - } - - @Override - public String toString(IrisGenerator gen) { - return gen.getLoadKey(); - } - - @Override - public IrisGenerator parse(String in, boolean force) throws DecreeParsingException { - KList options = getPossibilities(in); - - if (options.isEmpty()) { - throw new DecreeParsingException("Unable to find Generator \"" + in + "\""); - } - try { - return options.stream().filter((i) -> toString(i).equalsIgnoreCase(in)).collect(Collectors.toList()).get(0); - } catch (Throwable e) { - throw new DecreeParsingException("Unable to filter which Generator \"" + in + "\""); - } - } - - @Override - public boolean supports(Class type) { - return type.equals(IrisGenerator.class); +public class GeneratorHandler extends RegistrantHandler { + public GeneratorHandler() { + super(IrisGenerator.class, false); } @Override diff --git a/core/src/main/java/com/volmit/iris/util/decree/handlers/JigsawPieceHandler.java b/core/src/main/java/com/volmit/iris/util/decree/handlers/JigsawPieceHandler.java index 0457674be..3199d3039 100644 --- a/core/src/main/java/com/volmit/iris/util/decree/handlers/JigsawPieceHandler.java +++ b/core/src/main/java/com/volmit/iris/util/decree/handlers/JigsawPieceHandler.java @@ -18,62 +18,12 @@ package com.volmit.iris.util.decree.handlers; -import com.volmit.iris.Iris; -import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.engine.object.IrisJigsawPiece; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.decree.DecreeParameterHandler; -import com.volmit.iris.util.decree.exceptions.DecreeParsingException; +import com.volmit.iris.util.decree.specialhandlers.RegistrantHandler; -import java.io.File; -import java.util.stream.Collectors; - -public class JigsawPieceHandler implements DecreeParameterHandler { - @Override - public KList getPossibilities() { - KMap p = new KMap<>(); - - //noinspection ConstantConditions - for (File i : Iris.instance.getDataFolder("packs").listFiles()) { - if (i.isDirectory()) { - IrisData data = IrisData.get(i); - for (IrisJigsawPiece j : data.getJigsawPieceLoader().loadAll(data.getJigsawPieceLoader().getPossibleKeys())) { - p.putIfAbsent(j.getLoadKey(), j); - } - - data.close(); - } - } - - return p.v(); - } - - @Override - public String toString(IrisJigsawPiece dim) { - return dim.getLoadKey(); - } - - @Override - public IrisJigsawPiece parse(String in, boolean force) throws DecreeParsingException { - if (in.equals("null")) { - return null; - } - KList options = getPossibilities(in); - - if (options.isEmpty()) { - throw new DecreeParsingException("Unable to find Jigsaw Piece \"" + in + "\""); - } - try { - return options.stream().filter((i) -> toString(i).equalsIgnoreCase(in)).collect(Collectors.toList()).get(0); - } catch (Throwable e) { - throw new DecreeParsingException("Unable to filter which Jigsaw Piece \"" + in + "\""); - } - } - - @Override - public boolean supports(Class type) { - return type.equals(IrisJigsawPiece.class); +public class JigsawPieceHandler extends RegistrantHandler { + public JigsawPieceHandler() { + super(IrisJigsawPiece.class, true); } @Override diff --git a/core/src/main/java/com/volmit/iris/util/decree/handlers/JigsawPoolHandler.java b/core/src/main/java/com/volmit/iris/util/decree/handlers/JigsawPoolHandler.java index da29d9fe8..83a14ab4c 100644 --- a/core/src/main/java/com/volmit/iris/util/decree/handlers/JigsawPoolHandler.java +++ b/core/src/main/java/com/volmit/iris/util/decree/handlers/JigsawPoolHandler.java @@ -18,62 +18,12 @@ package com.volmit.iris.util.decree.handlers; -import com.volmit.iris.Iris; -import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.engine.object.IrisJigsawPool; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.decree.DecreeParameterHandler; -import com.volmit.iris.util.decree.exceptions.DecreeParsingException; +import com.volmit.iris.util.decree.specialhandlers.RegistrantHandler; -import java.io.File; -import java.util.stream.Collectors; - -public class JigsawPoolHandler implements DecreeParameterHandler { - @Override - public KList getPossibilities() { - KMap p = new KMap<>(); - - //noinspection ConstantConditions - for (File i : Iris.instance.getDataFolder("packs").listFiles()) { - if (i.isDirectory()) { - IrisData data = IrisData.get(i); - for (IrisJigsawPool j : data.getJigsawPoolLoader().loadAll(data.getJigsawPoolLoader().getPossibleKeys())) { - p.putIfAbsent(j.getLoadKey(), j); - } - - data.close(); - } - } - - return p.v(); - } - - @Override - public String toString(IrisJigsawPool dim) { - return dim.getLoadKey(); - } - - @Override - public IrisJigsawPool parse(String in, boolean force) throws DecreeParsingException { - if (in.equals("null")) { - return null; - } - KList options = getPossibilities(in); - - if (options.isEmpty()) { - throw new DecreeParsingException("Unable to find Jigsaw Pool \"" + in + "\""); - } - try { - return options.stream().filter((i) -> toString(i).equalsIgnoreCase(in)).collect(Collectors.toList()).get(0); - } catch (Throwable e) { - throw new DecreeParsingException("Unable to filter which Jigsaw Pool \"" + in + "\""); - } - } - - @Override - public boolean supports(Class type) { - return type.equals(IrisJigsawPool.class); +public class JigsawPoolHandler extends RegistrantHandler { + public JigsawPoolHandler() { + super(IrisJigsawPool.class, true); } @Override diff --git a/core/src/main/java/com/volmit/iris/util/decree/handlers/JigsawStructureHandler.java b/core/src/main/java/com/volmit/iris/util/decree/handlers/JigsawStructureHandler.java index 21dec6246..b7d6e35cd 100644 --- a/core/src/main/java/com/volmit/iris/util/decree/handlers/JigsawStructureHandler.java +++ b/core/src/main/java/com/volmit/iris/util/decree/handlers/JigsawStructureHandler.java @@ -18,62 +18,12 @@ package com.volmit.iris.util.decree.handlers; -import com.volmit.iris.Iris; -import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.engine.object.IrisJigsawStructure; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.decree.DecreeParameterHandler; -import com.volmit.iris.util.decree.exceptions.DecreeParsingException; +import com.volmit.iris.util.decree.specialhandlers.RegistrantHandler; -import java.io.File; -import java.util.stream.Collectors; - -public class JigsawStructureHandler implements DecreeParameterHandler { - @Override - public KList getPossibilities() { - KMap p = new KMap<>(); - - //noinspection ConstantConditions - for (File i : Iris.instance.getDataFolder("packs").listFiles()) { - if (i.isDirectory()) { - IrisData data = IrisData.get(i); - for (IrisJigsawStructure j : data.getJigsawStructureLoader().loadAll(data.getJigsawStructureLoader().getPossibleKeys())) { - p.putIfAbsent(j.getLoadKey(), j); - } - - data.close(); - } - } - - return p.v(); - } - - @Override - public String toString(IrisJigsawStructure dim) { - return dim.getLoadKey(); - } - - @Override - public IrisJigsawStructure parse(String in, boolean force) throws DecreeParsingException { - if (in.equals("null")) { - return null; - } - KList options = getPossibilities(in); - - if (options.isEmpty()) { - throw new DecreeParsingException("Unable to find Jigsaw Structure \"" + in + "\""); - } - try { - return options.stream().filter((i) -> toString(i).equalsIgnoreCase(in)).collect(Collectors.toList()).get(0); - } catch (Throwable e) { - throw new DecreeParsingException("Unable to filter which Jigsaw Structure \"" + in + "\""); - } - } - - @Override - public boolean supports(Class type) { - return type.equals(IrisJigsawStructure.class); +public class JigsawStructureHandler extends RegistrantHandler { + public JigsawStructureHandler() { + super(IrisJigsawStructure.class, true); } @Override diff --git a/core/src/main/java/com/volmit/iris/util/decree/handlers/RegionHandler.java b/core/src/main/java/com/volmit/iris/util/decree/handlers/RegionHandler.java index c12c5be09..5b47af457 100644 --- a/core/src/main/java/com/volmit/iris/util/decree/handlers/RegionHandler.java +++ b/core/src/main/java/com/volmit/iris/util/decree/handlers/RegionHandler.java @@ -18,62 +18,12 @@ package com.volmit.iris.util.decree.handlers; -import com.volmit.iris.Iris; -import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.engine.object.IrisRegion; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.decree.DecreeParameterHandler; -import com.volmit.iris.util.decree.exceptions.DecreeParsingException; +import com.volmit.iris.util.decree.specialhandlers.RegistrantHandler; -import java.io.File; -import java.util.stream.Collectors; - -public class RegionHandler implements DecreeParameterHandler { - @Override - public KList getPossibilities() { - KMap p = new KMap<>(); - - //noinspection ConstantConditions - for (File i : Iris.instance.getDataFolder("packs").listFiles()) { - if (i.isDirectory()) { - IrisData data = IrisData.get(i); - for (IrisRegion j : data.getRegionLoader().loadAll(data.getRegionLoader().getPossibleKeys())) { - p.putIfAbsent(j.getLoadKey(), j); - } - - data.close(); - } - } - - return p.v(); - } - - @Override - public String toString(IrisRegion dim) { - return dim.getLoadKey(); - } - - @Override - public IrisRegion parse(String in, boolean force) throws DecreeParsingException { - if (in.equals("null")) { - return null; - } - KList options = getPossibilities(in); - - if (options.isEmpty()) { - throw new DecreeParsingException("Unable to find Region \"" + in + "\""); - } - try { - return options.stream().filter((i) -> toString(i).equalsIgnoreCase(in)).collect(Collectors.toList()).get(0); - } catch (Throwable e) { - throw new DecreeParsingException("Unable to filter which Region \"" + in + "\""); - } - } - - @Override - public boolean supports(Class type) { - return type.equals(IrisRegion.class); +public class RegionHandler extends RegistrantHandler { + public RegionHandler() { + super(IrisRegion.class, true); } @Override diff --git a/core/src/main/java/com/volmit/iris/util/decree/handlers/ScriptHandler.java b/core/src/main/java/com/volmit/iris/util/decree/handlers/ScriptHandler.java index a32670cf6..f5eaf01a4 100644 --- a/core/src/main/java/com/volmit/iris/util/decree/handlers/ScriptHandler.java +++ b/core/src/main/java/com/volmit/iris/util/decree/handlers/ScriptHandler.java @@ -18,59 +18,12 @@ package com.volmit.iris.util.decree.handlers; -import com.volmit.iris.Iris; -import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.engine.object.IrisScript; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.decree.DecreeParameterHandler; -import com.volmit.iris.util.decree.exceptions.DecreeParsingException; +import com.volmit.iris.util.decree.specialhandlers.RegistrantHandler; -import java.io.File; -import java.util.stream.Collectors; - -public class ScriptHandler implements DecreeParameterHandler { - @Override - public KList getPossibilities() { - KMap p = new KMap<>(); - - //noinspection ConstantConditions - for (File i : Iris.instance.getDataFolder("packs").listFiles()) { - if (i.isDirectory()) { - IrisData data = IrisData.get(i); - for (IrisScript j : data.getScriptLoader().loadAll(data.getScriptLoader().getPossibleKeys())) { - p.putIfAbsent(j.getLoadKey(), j); - } - - data.close(); - } - } - - return p.v(); - } - - @Override - public String toString(IrisScript script) { - return script.getLoadKey(); - } - - @Override - public IrisScript parse(String in, boolean force) throws DecreeParsingException { - KList options = getPossibilities(in); - - if (options.isEmpty()) { - throw new DecreeParsingException("Unable to find Script \"" + in + "\""); - } - try { - return options.stream().filter((i) -> toString(i).equalsIgnoreCase(in)).collect(Collectors.toList()).get(0); - } catch (Throwable e) { - throw new DecreeParsingException("Unable to filter which Script \"" + in + "\""); - } - } - - @Override - public boolean supports(Class type) { - return type.equals(IrisScript.class); +public class ScriptHandler extends RegistrantHandler { + public ScriptHandler() { + super(IrisScript.class, false); } @Override diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisJigsawPlacement.java b/core/src/main/java/com/volmit/iris/util/decree/specialhandlers/NullableDimensionHandler.java similarity index 50% rename from core/src/main/java/com/volmit/iris/engine/object/IrisJigsawPlacement.java rename to core/src/main/java/com/volmit/iris/util/decree/specialhandlers/NullableDimensionHandler.java index 8494550e7..c8278558b 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisJigsawPlacement.java +++ b/core/src/main/java/com/volmit/iris/util/decree/specialhandlers/NullableDimensionHandler.java @@ -16,28 +16,27 @@ * along with this program. If not, see . */ -package com.volmit.iris.engine.object; +package com.volmit.iris.util.decree.specialhandlers; -import com.volmit.iris.engine.object.annotations.*; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.Accessors; +import com.volmit.iris.core.IrisSettings; +import com.volmit.iris.engine.object.IrisDimension; +import com.volmit.iris.util.decree.exceptions.DecreeParsingException; -@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 = ""; +public class NullableDimensionHandler extends RegistrantHandler { + public NullableDimensionHandler() { + super(IrisDimension.class, true); + } - @Required - @MinNumber(1) - @Desc("The rarity for this jigsaw structure to place on a per chunk basis") - private int rarity = 29; + @Override + public IrisDimension parse(String in, boolean force) throws DecreeParsingException { + if (in.equalsIgnoreCase("default")) { + return parse(IrisSettings.get().getGenerator().getDefaultWorldType()); + } + return super.parse(in, force); + } + + @Override + public String getRandomDefault() { + return "dimension"; + } } diff --git a/core/src/main/java/com/volmit/iris/util/decree/specialhandlers/ObjectHandler.java b/core/src/main/java/com/volmit/iris/util/decree/specialhandlers/ObjectHandler.java index ee7d30901..84d3dceb0 100644 --- a/core/src/main/java/com/volmit/iris/util/decree/specialhandlers/ObjectHandler.java +++ b/core/src/main/java/com/volmit/iris/util/decree/specialhandlers/ObjectHandler.java @@ -31,11 +31,15 @@ public class ObjectHandler implements DecreeParameterHandler { @Override public KList getPossibilities() { KList p = new KList<>(); + IrisData data = data(); + if (data != null) { + return new KList<>(data.getObjectLoader().getPossibleKeys()); + } //noinspection ConstantConditions for (File i : Iris.instance.getDataFolder("packs").listFiles()) { if (i.isDirectory()) { - IrisData data = IrisData.get(i); + data = IrisData.get(i); p.add(data.getObjectLoader().getPossibleKeys()); } } diff --git a/core/src/main/java/com/volmit/iris/util/decree/specialhandlers/RegistrantHandler.java b/core/src/main/java/com/volmit/iris/util/decree/specialhandlers/RegistrantHandler.java new file mode 100644 index 000000000..090407bba --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/decree/specialhandlers/RegistrantHandler.java @@ -0,0 +1,76 @@ +package com.volmit.iris.util.decree.specialhandlers; + +import com.volmit.iris.Iris; +import com.volmit.iris.core.loader.IrisData; +import com.volmit.iris.core.loader.IrisRegistrant; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.decree.DecreeParameterHandler; +import com.volmit.iris.util.decree.exceptions.DecreeParsingException; + +import java.io.File; +import java.util.HashSet; +import java.util.Set; + +public abstract class RegistrantHandler implements DecreeParameterHandler { + private final Class type; + private final String name; + private final boolean nullable; + + public RegistrantHandler(Class type, boolean nullable) { + this.type = type; + this.name = type.getSimpleName().replaceFirst("Iris", ""); + this.nullable = nullable; + } + + @Override + public KList getPossibilities() { + KList p = new KList<>(); + Set known = new HashSet<>(); + IrisData data = data(); + if (data != null) { + for (T j : data.getLoader(type).loadAll(data.getLoader(type).getPossibleKeys())) { + known.add(j.getLoadKey()); + p.add(j); + } + } + + //noinspection ConstantConditions + for (File i : Iris.instance.getDataFolder("packs").listFiles()) { + if (i.isDirectory()) { + data = IrisData.get(i); + for (T j : data.getLoader(type).loadAll(data.getLoader(type).getPossibleKeys())) { + if (known.add(j.getLoadKey())) + p.add(j); + } + } + } + + return p; + } + + @Override + public String toString(T t) { + return t != null ? t.getLoadKey() : "null"; + } + + @Override + public T parse(String in, boolean force) throws DecreeParsingException { + if (in.equals("null") && nullable) { + return null; + } + KList options = getPossibilities(in); + if (options.isEmpty()) { + throw new DecreeParsingException("Unable to find " + name + " \"" + in + "\""); + } + + return options.stream() + .filter((i) -> toString(i).equalsIgnoreCase(in)) + .findFirst() + .orElseThrow(() -> new DecreeParsingException("Unable to filter which " + name + " \"" + in + "\"")); + } + + @Override + public boolean supports(Class type) { + return type.equals(this.type); + } +} diff --git a/core/src/main/java/com/volmit/iris/util/decree/virtual/VirtualDecreeCommand.java b/core/src/main/java/com/volmit/iris/util/decree/virtual/VirtualDecreeCommand.java index f1eeab8b7..4bd5e9168 100644 --- a/core/src/main/java/com/volmit/iris/util/decree/virtual/VirtualDecreeCommand.java +++ b/core/src/main/java/com/volmit/iris/util/decree/virtual/VirtualDecreeCommand.java @@ -478,21 +478,27 @@ public class VirtualDecreeCommand { } DecreeContext.touch(sender); - Runnable rx = () -> { - try { + try { + Runnable rx = () -> { DecreeContext.touch(sender); - getNode().getMethod().setAccessible(true); - getNode().getMethod().invoke(getNode().getInstance(), params); - } catch (Throwable e) { - e.printStackTrace(); - throw new RuntimeException("Failed to execute "); // TODO: - } - }; + try { + getNode().getMethod().setAccessible(true); + getNode().getMethod().invoke(getNode().getInstance(), params); + } catch (Throwable e) { + e.printStackTrace(); + throw new RuntimeException("Failed to execute "); // TODO: + } finally { + DecreeContext.remove(); + } + }; - if (getNode().isSync()) { - J.s(rx); - } else { - rx.run(); + if (getNode().isSync()) { + J.s(rx); + } else { + rx.run(); + } + } finally { + DecreeContext.remove(); } return true; diff --git a/core/src/main/java/com/volmit/iris/util/io/IO.java b/core/src/main/java/com/volmit/iris/util/io/IO.java index 7506c5651..b4bfe489d 100644 --- a/core/src/main/java/com/volmit/iris/util/io/IO.java +++ b/core/src/main/java/com/volmit/iris/util/io/IO.java @@ -179,6 +179,8 @@ public class IO { JsonElement json; try (FileReader reader = new FileReader(file)) { json = JsonParser.parseReader(reader); + } catch (Throwable e) { + throw new IOException("Failed to read json file " + file, e); } var queue = new LinkedList(); @@ -1705,6 +1707,7 @@ public class IO { action.accept(out); } Files.copy(temp.toPath(), Channels.newOutputStream(target)); + target.truncate(temp.length()); } finally { temp.delete(); } diff --git a/core/src/main/java/com/volmit/iris/util/io/ReactiveFolder.java b/core/src/main/java/com/volmit/iris/util/io/ReactiveFolder.java index b05e38760..5bf0d6eb6 100644 --- a/core/src/main/java/com/volmit/iris/util/io/ReactiveFolder.java +++ b/core/src/main/java/com/volmit/iris/util/io/ReactiveFolder.java @@ -46,8 +46,8 @@ public class ReactiveFolder { if (checkCycle % 3 == 0 ? fw.checkModified() : fw.checkModifiedFast()) { for (File i : fw.getCreated()) { - if (i.getName().endsWith(".iob") || i.getName().endsWith(".json") || i.getName().endsWith(".js")) { - if (i.getPath().contains(".iris")) { + if (i.getName().endsWith(".iob") || i.getName().endsWith(".json") || i.getName().endsWith(".kts")) { + if (i.getPath().contains(".iris") || i.getName().endsWith(".gradle.kts")) { continue; } @@ -58,11 +58,11 @@ public class ReactiveFolder { if (!modified) { for (File i : fw.getChanged()) { - if (i.getPath().contains(".iris")) { + if (i.getPath().contains(".iris") || i.getName().endsWith(".gradle.kts")) { continue; } - if (i.getName().endsWith(".iob") || i.getName().endsWith(".json") || i.getName().endsWith(".js")) { + if (i.getName().endsWith(".iob") || i.getName().endsWith(".json") || i.getName().endsWith(".kts")) { modified = true; break; } @@ -71,11 +71,11 @@ public class ReactiveFolder { if (!modified) { for (File i : fw.getDeleted()) { - if (i.getPath().contains(".iris")) { + if (i.getPath().contains(".iris") || i.getName().endsWith(".gradle.kts")) { continue; } - if (i.getName().endsWith(".iob") || i.getName().endsWith(".json") || i.getName().endsWith(".js")) { + if (i.getName().endsWith(".iob") || i.getName().endsWith(".json") || i.getName().endsWith(".kts")) { modified = true; break; } diff --git a/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java b/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java index 01153403c..f7cf935cc 100644 --- a/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java +++ b/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java @@ -181,6 +181,11 @@ public class MantleChunk { } } + public void raiseFlagUnchecked(MantleFlag flag, Runnable r) { + if (closed.get()) throw new IllegalStateException("Chunk is closed!"); + if (flags.compareAndSet(flag.ordinal(), false, true)) r.run(); + } + public boolean isFlagged(MantleFlag flag) { return flags.get(flag.ordinal()); } @@ -266,7 +271,7 @@ public class MantleChunk { dos.writeByte(x); dos.writeByte(z); dos.writeByte(sections.length()); - Varint.writeUnsignedVarInt(Math.ceilDiv(flags.length(), Byte.SIZE), dos); + Varint.writeUnsignedVarInt(flags.length(), dos); int count = flags.length(); for (int i = 0; i < count;) { diff --git a/core/src/main/java/com/volmit/iris/util/misc/Bindings.java b/core/src/main/java/com/volmit/iris/util/misc/Bindings.java index 1a82c55c4..d48879151 100644 --- a/core/src/main/java/com/volmit/iris/util/misc/Bindings.java +++ b/core/src/main/java/com/volmit/iris/util/misc/Bindings.java @@ -1,5 +1,7 @@ package com.volmit.iris.util.misc; +import com.google.gson.JsonSyntaxException; +import com.volmit.iris.BuildConstants; import com.volmit.iris.Iris; import com.volmit.iris.core.IrisSettings; import com.volmit.iris.core.nms.INMS; @@ -19,19 +21,12 @@ import net.kyori.adventure.platform.bukkit.BukkitAudiences; import net.kyori.adventure.text.serializer.ComponentSerializer; import org.bstats.bukkit.Metrics; import org.bstats.charts.DrilldownPie; -import org.bstats.charts.SimplePie; import org.bstats.charts.SingleLineChart; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; -import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.entity.Player; -import oshi.SystemInfo; -import java.io.InputStreamReader; -import java.math.RoundingMode; -import java.text.NumberFormat; import java.util.HashMap; -import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; @@ -47,8 +42,6 @@ public class Bindings { if (settings.disableAutoReporting || Sentry.isEnabled() || Boolean.getBoolean("iris.suppressReporting")) return; Iris.info("Enabling Sentry for anonymous error reporting. You can disable this in the settings."); Iris.info("Your server ID is: " + ServerID.ID); - var resource = Iris.instance.getResource("plugin.yml"); - YamlConfiguration desc = resource != null ? YamlConfiguration.loadConfiguration(new InputStreamReader(resource)) : new YamlConfiguration(); Sentry.init(options -> { options.setDsn("http://4cdbb9ac953306529947f4ca1e8e6b26@sentry.volmit.com:8080/2"); @@ -60,7 +53,7 @@ public class Bindings { options.setAttachServerName(false); options.setEnableUncaughtExceptionHandler(false); options.setRelease(Iris.instance.getDescription().getVersion()); - options.setEnvironment(desc.getString("environment", "production")); + options.setEnvironment(BuildConstants.ENVIRONMENT); options.setBeforeSend((event, hint) -> { if (suppress(event.getThrowable())) return null; event.setTag("iris.safeguard", IrisSafeguard.mode()); @@ -77,12 +70,14 @@ public class Bindings { scope.setTag("server", Bukkit.getVersion()); scope.setTag("server.type", Bukkit.getName()); scope.setTag("server.api", Bukkit.getBukkitVersion()); - scope.setTag("iris.commit", desc.getString("commit", "unknown")); + scope.setTag("iris.commit", BuildConstants.COMMIT); }); } private static boolean suppress(Throwable e) { - return (e instanceof IllegalStateException ex && "zip file closed".equals(ex.getMessage())) || e instanceof JSONException; + return (e instanceof IllegalStateException ex && "zip file closed".equals(ex.getMessage())) + || e instanceof JSONException + || e instanceof JsonSyntaxException; } @@ -99,6 +94,7 @@ public class Bindings { .map(IrisToolbelt::access) .filter(Objects::nonNull) .map(PlatformChunkGenerator::getEngine) + .filter(Objects::nonNull) .collect(Collectors.toMap(engine -> engine.getDimension().getLoadKey(), engine -> { var hash32 = engine.getHash32().getNow(null); if (hash32 == null) return Map.of(); @@ -111,23 +107,7 @@ public class Bindings { b.forEach((k, v) -> merged.merge(k, v, Integer::sum)); return merged; })))); - - - var info = new SystemInfo().getHardware(); - var cpu = info.getProcessor().getProcessorIdentifier(); - var mem = info.getMemory(); - metrics.addCustomChart(new SimplePie("cpu_model", cpu::getName)); - - var nf = NumberFormat.getInstance(Locale.ENGLISH); - nf.setMinimumFractionDigits(0); - nf.setMaximumFractionDigits(2); - nf.setRoundingMode(RoundingMode.HALF_UP); - - metrics.addCustomChart(new DrilldownPie("memory", () -> { - double total = mem.getTotal() * 1E-9; - double alloc = Math.min(total, Runtime.getRuntime().maxMemory() * 1E-9); - return Map.of(nf.format(alloc), Map.of(nf.format(total), 1)); - })); + metrics.addCustomChart(new DrilldownPie("environment", () -> Map.of(BuildConstants.ENVIRONMENT, Map.of(BuildConstants.COMMIT, 1)))); plugin.postShutdown(metrics::shutdown); }); diff --git a/core/src/main/resources/plugin.yml b/core/src/main/resources/plugin.yml index d57c39913..5506ae2f1 100644 --- a/core/src/main/resources/plugin.yml +++ b/core/src/main/resources/plugin.yml @@ -5,8 +5,6 @@ load: STARTUP authors: [ cyberpwn, NextdoorPsycho, Vatuu ] website: volmit.com description: More than a Dimension! -environment: '${environment}' -commit: '${commit}' commands: iris: aliases: [ ir, irs ] diff --git a/core/src/main/templates/BuildConstants.java b/core/src/main/templates/BuildConstants.java new file mode 100644 index 000000000..0bdbbf1cc --- /dev/null +++ b/core/src/main/templates/BuildConstants.java @@ -0,0 +1,7 @@ +package com.volmit.iris; + +// The constants are replaced before compilation +public interface BuildConstants { + String ENVIRONMENT = "${environment}"; + String COMMIT = "${commit}"; +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d313675bb..c99bd4b2b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -27,6 +27,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 @@ -78,6 +79,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 7c1e3d541..0963f9b64 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; @@ -58,6 +61,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; @@ -78,7 +83,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; @@ -89,6 +93,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 { @@ -703,6 +708,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 d6553ee5d..872f9929e 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 f242783d6..2fa74ee99 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 2c0cc00f5..149929802 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 it.unimi.dsi.fastutil.objects.Object2IntMap; import net.bytebuddy.ByteBuddy; @@ -58,6 +73,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.*; @@ -725,6 +742,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 7b3a88026..a644c01c6 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 4db50fe93..1248635eb 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 252cb0ebe..377859162 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; @@ -60,6 +65,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.*; @@ -88,10 +96,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<>(); @@ -178,14 +192,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); } @@ -242,7 +256,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)); } @@ -496,13 +510,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); @@ -600,7 +614,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(); @@ -733,6 +747,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 c3e167746..7ed97c3f1 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; @@ -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!"); 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 35d970f32..de7639c72 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!");