diff --git a/build.gradle b/build.gradle index 42fbe6d58..22fe06be5 100644 --- a/build.gradle +++ b/build.gradle @@ -32,7 +32,7 @@ plugins { id "de.undercouch.download" version "5.0.1" } -version '3.4.1-1.19.2-1.21.1' +version '3.5.0-1.19.2-1.21.3' // ADD YOURSELF AS A NEW LINE IF YOU WANT YOUR OWN BUILD TASK GENERATED // ======================== WINDOWS ============================= @@ -43,7 +43,8 @@ registerCustomOutputTask('Coco', 'D://mcsm/plugins') registerCustomOutputTask('Strange', 'D://Servers/1.17 Test Server/plugins') registerCustomOutputTask('Vatuu', 'D://Minecraft/Servers/1.19.4/plugins') registerCustomOutputTask('CrazyDev22', 'C://Users/Julian/Desktop/server/plugins') -registerCustomOutputTask('Pixel', 'C://Users/repix/Iris Dimension Engine/1.20.4 - Development/plugins') +registerCustomOutputTask('PixelFury', 'C://Users/repix/workplace/Iris/1.21.3 - Development-Public-v3/plugins') +registerCustomOutputTask('PixelFuryDev', 'C://Users/repix/workplace/Iris/1.21 - Development-v3/plugins') // ========================== UNIX ============================== registerCustomOutputTaskUnix('CyberpwnLT', '/Users/danielmills/development/server/plugins') registerCustomOutputTaskUnix('PsychoLT', '/Users/brianfopiano/Developer/RemoteGit/Server/plugins') @@ -52,7 +53,8 @@ registerCustomOutputTaskUnix('CrazyDev22LT', '/home/julian/Desktop/server/plugin // ============================================================== def NMS_BINDINGS = Map.of( - "v1_21_R1", "1.21-R0.1-SNAPSHOT", + "v1_21_R2", "1.21.3-R0.1-SNAPSHOT", + "v1_21_R1", "1.21.1-R0.1-SNAPSHOT", "v1_20_R4", "1.20.6-R0.1-SNAPSHOT", "v1_20_R3", "1.20.4-R0.1-SNAPSHOT", "v1_20_R2", "1.20.2-R0.1-SNAPSHOT", @@ -61,17 +63,14 @@ def NMS_BINDINGS = Map.of( "v1_19_R2", "1.19.3-R0.1-SNAPSHOT", "v1_19_R1", "1.19.2-R0.1-SNAPSHOT" ) -def JVM_VERSION = Map.of( - "v1_21_R1", 21, - "v1_20_R4", 21, -) +def JVM_VERSION = Map.of() NMS_BINDINGS.each { nms -> project(":nms:${nms.key}") { apply plugin: 'java' apply plugin: 'com.volmit.nmstools' nmsTools { - it.jvm = JVM_VERSION.getOrDefault(nms.key, 17) + it.jvm = JVM_VERSION.getOrDefault(nms.key, 21) it.version = nms.value } @@ -92,6 +91,7 @@ shadowJar { relocate 'com.dfsek.paralithic', 'com.volmit.iris.util.paralithic' relocate 'io.papermc.lib', 'com.volmit.iris.util.paper' relocate 'net.kyori', 'com.volmit.iris.util.kyori' + relocate 'org.bstats', 'com.volmit.util.metrics' archiveFileName.set("Iris-${project.version}.jar") } @@ -119,23 +119,26 @@ allprojects { maven { url "https://repo.triumphteam.dev/snapshots" } maven { url "https://repo.mineinabyss.com/releases" } maven { url 'https://hub.jeff-media.com/nexus/repository/jeff-media-public/' } - maven { url "https://repo.oraxen.com/releases" } + maven { url "https://repo.nexomc.com/snapshots/" } + maven { url "https://libraries.minecraft.net" } } dependencies { // Provided or Classpath - compileOnly 'org.projectlombok:lombok:1.18.24' - annotationProcessor 'org.projectlombok:lombok:1.18.24' + compileOnly 'org.projectlombok:lombok:1.18.36' + annotationProcessor 'org.projectlombok:lombok:1.18.36' // Shaded implementation 'com.dfsek:Paralithic:0.4.0' implementation 'io.papermc:paperlib:1.0.5' - implementation "net.kyori:adventure-text-minimessage:4.13.1" - implementation 'net.kyori:adventure-platform-bukkit:4.3.2' - implementation 'net.kyori:adventure-api:4.13.1' + implementation "net.kyori:adventure-text-minimessage:4.17.0" + implementation 'net.kyori:adventure-platform-bukkit:4.3.4' + implementation 'net.kyori:adventure-api:4.17.0' + implementation 'org.bstats:bstats-bukkit:3.1.0' //implementation 'org.bytedeco:javacpp:1.5.10' //implementation 'org.bytedeco:cuda-platform:12.3-8.9-1.5.10' compileOnly 'io.lumine:Mythic-Dist:5.2.1' + compileOnly 'io.lumine:MythicCrucible-Dist:2.0.0' // Dynamically Loaded compileOnly 'io.timeandspace:smoothie-map:2.0.2' @@ -149,6 +152,7 @@ allprojects { compileOnly 'rhino:js:1.7R2' compileOnly 'com.github.ben-manes.caffeine:caffeine:3.0.6' compileOnly 'org.apache.commons:commons-lang3:3.12.0' + compileOnly 'com.github.oshi:oshi-core:6.6.5' } /** @@ -175,18 +179,18 @@ allprojects { } } -if (JavaVersion.current().toString() != "17") { +if (JavaVersion.current().toString() != "21") { System.err.println() System.err.println("=========================================================================================================") - System.err.println("You must run gradle on Java 17. You are using " + JavaVersion.current()) + System.err.println("You must run gradle on Java 21. You are using " + JavaVersion.current()) System.err.println() System.err.println("=== For IDEs ===") - System.err.println("1. Configure the project for Java 17") - System.err.println("2. Configure the bundled gradle to use Java 17 in settings") + System.err.println("1. Configure the project for Java 21") + System.err.println("2. Configure the bundled gradle to use Java 21 in settings") System.err.println() System.err.println("=== For Command Line (gradlew) ===") - System.err.println("1. Install JDK 17 from https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html") - System.err.println("2. Set JAVA_HOME environment variable to the new jdk installation folder such as C:\\Program Files\\Java\\jdk-17.0.1") + System.err.println("1. Install JDK 21 from https://www.oracle.com/java/technologies/javase/jdk21-archive-downloads.html") + System.err.println("2. Set JAVA_HOME environment variable to the new jdk installation folder such as C:\\Program Files\\Java\\jdk-21.0.4") System.err.println("3. Open a new command prompt window to get the new environment variables if need be.") System.err.println("=========================================================================================================") System.err.println() diff --git a/core/build.gradle b/core/build.gradle index 70d2adbc7..dddec4037 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -62,7 +62,7 @@ dependencies { // Third Party Integrations compileOnly 'com.ticxo.playeranimator:PlayerAnimator:R1.2.7' - compileOnly 'io.th0rgal:oraxen:1.173.0' + compileOnly 'com.nexomc:nexo:0.6.0-dev.0' compileOnly 'com.github.LoneDev6:api-itemsadder:3.4.1-r4' compileOnly 'com.github.PlaceholderAPI:placeholderapi:2.11.3' compileOnly 'com.github.Ssomar-Developement:SCore:4.23.10.8' @@ -71,6 +71,9 @@ dependencies { //implementation files('libs/CustomItems.jar') } +java { + disableAutoTargetJvm() +} /** * Gradle is weird sometimes, we need to delete the plugin yml from the build folder to actually filter properly. diff --git a/core/src/main/java/com/volmit/iris/Iris.java b/core/src/main/java/com/volmit/iris/Iris.java index c39154b13..81ad04fdd 100644 --- a/core/src/main/java/com/volmit/iris/Iris.java +++ b/core/src/main/java/com/volmit/iris/Iris.java @@ -40,6 +40,7 @@ import com.volmit.iris.engine.platform.BukkitChunkGenerator; import com.volmit.iris.engine.platform.DummyChunkGenerator; import com.volmit.iris.core.safeguard.IrisSafeguard; import com.volmit.iris.core.safeguard.UtilsSFG; +import com.volmit.iris.engine.platform.PlatformChunkGenerator; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.exceptions.IrisException; @@ -55,7 +56,6 @@ import com.volmit.iris.util.math.RNG; import com.volmit.iris.util.misc.getHardware; import com.volmit.iris.util.parallel.MultiBurst; import com.volmit.iris.util.plugin.IrisService; -import com.volmit.iris.util.plugin.Metrics; import com.volmit.iris.util.plugin.VolmitPlugin; import com.volmit.iris.util.plugin.VolmitSender; import com.volmit.iris.util.reflect.ShadeFix; @@ -63,13 +63,13 @@ import com.volmit.iris.util.scheduling.J; import com.volmit.iris.util.scheduling.Queue; import com.volmit.iris.util.scheduling.ShurikenQueue; import io.papermc.lib.PaperLib; -import lombok.Getter; import net.kyori.adventure.platform.bukkit.BukkitAudiences; import net.kyori.adventure.text.serializer.ComponentSerializer; -import org.bukkit.Bukkit; -import org.bukkit.GameMode; -import org.bukkit.Location; -import org.bukkit.WorldCreator; +import org.bstats.bukkit.Metrics; +import org.bstats.charts.DrilldownPie; +import org.bstats.charts.SimplePie; +import org.bstats.charts.SingleLineChart; +import org.bukkit.*; import org.bukkit.block.data.BlockData; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; @@ -77,33 +77,31 @@ import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.entity.Player; -import org.bukkit.event.Event; -import org.bukkit.event.HandlerList; -import org.bukkit.event.Listener; +import org.bukkit.event.*; import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.ChunkGenerator; import org.bukkit.plugin.IllegalPluginAccessException; import org.bukkit.plugin.Plugin; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import oshi.SystemInfo; import java.io.*; import java.lang.annotation.Annotation; -import java.lang.management.ManagementFactory; -import java.lang.management.OperatingSystemMXBean; +import java.math.RoundingMode; import java.net.URL; -import java.util.Date; -import java.util.Map; +import java.text.NumberFormat; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; import static com.volmit.iris.core.safeguard.IrisSafeguard.*; import static com.volmit.iris.core.safeguard.ServerBootSFG.passedserversoftware; -import static com.volmit.iris.util.misc.getHardware.getCPUModel; @SuppressWarnings("CanBeFinal") public class Iris extends VolmitPlugin implements Listener { - public static final String OVERWORLD_TAG = "3800"; + public static final String OVERWORLD_TAG = "31000"; private static final Queue syncJobs = new ShurikenQueue<>(); @@ -512,11 +510,10 @@ public class Iris extends VolmitPlugin implements Listener { continue; } - Iris.info("2 World: %s | Generator: %s", s, generator); - - if (Bukkit.getWorlds().stream().anyMatch(w -> w.getName().equals(s))) { + if (Bukkit.getWorld(s) != null) continue; - } + + Iris.info("Loading World: %s | Generator: %s", s, generator); Iris.info(C.LIGHT_PURPLE + "Preparing Spawn for " + s + "' using Iris:" + generator + "..."); new WorldCreator(s) @@ -664,7 +661,48 @@ public class Iris extends VolmitPlugin implements Listener { private void bstats() { if (IrisSettings.get().getGeneral().isPluginMetrics()) { - J.s(() -> new Metrics(Iris.instance, 8757)); + J.s(() -> { + var metrics = new Metrics(Iris.instance, 24220); + metrics.addCustomChart(new SingleLineChart("custom_dimensions", () -> Bukkit.getWorlds() + .stream() + .filter(IrisToolbelt::isIrisWorld) + .mapToInt(w -> 1) + .sum())); + + metrics.addCustomChart(new DrilldownPie("used_packs", () -> Bukkit.getWorlds().stream() + .map(IrisToolbelt::access) + .filter(Objects::nonNull) + .map(PlatformChunkGenerator::getTarget) + .collect(Collectors.toMap(target -> target.getDimension().getLoadKey(), target -> { + int version = target.getDimension().getVersion(); + String checksum = IO.hashRecursive(target.getData().getDataFolder()); + + return Map.of("v" + version + " (" + checksum + ")", 1); + }, (a, b) -> { + Map merged = new HashMap<>(a); + 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)); + })); + + postShutdown.add(metrics::shutdown); + }); } } 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 9eccc80e1..8a687b564 100644 --- a/core/src/main/java/com/volmit/iris/core/IrisSettings.java +++ b/core/src/main/java/com/volmit/iris/core/IrisSettings.java @@ -25,7 +25,9 @@ import com.volmit.iris.util.json.JSONException; import com.volmit.iris.util.json.JSONObject; import com.volmit.iris.util.plugin.VolmitSender; import com.volmit.iris.util.scheduling.ChronoLatch; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; import java.io.File; import java.io.IOException; @@ -42,6 +44,7 @@ public class IrisSettings { private IrisSettingsConcurrency concurrency = new IrisSettingsConcurrency(); private IrisSettingsStudio studio = new IrisSettingsStudio(); private IrisSettingsPerformance performance = new IrisSettingsPerformance(); + private IrisSettingsUpdater updater = new IrisSettingsUpdater(); public static int getThreadCount(int c) { return switch (c) { @@ -144,6 +147,30 @@ public class IrisSettings { public int scriptLoaderCacheSize = 512; } + @Data + public static class IrisSettingsUpdater { + public double threadMultiplier = 2; + public double chunkLoadSensitivity = 0.7; + public MsRange emptyMsRange = new MsRange(80, 100); + public MsRange defaultMsRange = new MsRange(20, 40); + + public double getThreadMultiplier() { + return Math.min(Math.abs(threadMultiplier), 0.1); + } + + public double getChunkLoadSensitivity() { + return Math.min(chunkLoadSensitivity, 0.9); + } + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class MsRange { + public int min = 20; + public int max = 40; + } + @Data public static class IrisSettingsGeneral { public boolean DoomsdayAnnihilationSelfDestructMode = false; @@ -171,6 +198,7 @@ public class IrisSettings { public static class IrisSettingsGUI { public boolean useServerLaunchedGuis = true; public boolean maximumPregenGuiFPS = false; + public boolean colorMode = true; } @Data 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 da46c93f4..f2991e75a 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 @@ -21,34 +21,25 @@ package com.volmit.iris.core.commands; 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.nms.v1X.NMSBinding1X; -import com.volmit.iris.core.pregenerator.ChunkUpdater; 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.mantle.components.MantleObjectComponent; -import com.volmit.iris.engine.object.IrisBiome; -import com.volmit.iris.engine.object.IrisCave; import com.volmit.iris.engine.object.IrisDimension; -import com.volmit.iris.engine.object.IrisEntity; -import com.volmit.iris.util.data.Dimension; 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.format.C; import com.volmit.iris.util.format.Form; +import com.volmit.iris.util.io.CountingDataInputStream; import com.volmit.iris.util.io.IO; import com.volmit.iris.util.mantle.TectonicPlate; -import com.volmit.iris.util.math.Spiraler; -import com.volmit.iris.util.math.Vector3d; 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 io.lumine.mythic.bukkit.adapters.BukkitEntity; import net.jpountz.lz4.LZ4BlockInputStream; import net.jpountz.lz4.LZ4BlockOutputStream; import net.jpountz.lz4.LZ4FrameInputStream; @@ -56,10 +47,7 @@ import net.jpountz.lz4.LZ4FrameOutputStream; import org.apache.commons.lang.RandomStringUtils; import org.bukkit.Bukkit; import org.bukkit.Chunk; -import org.bukkit.Location; import org.bukkit.World; -import org.bukkit.entity.Creeper; -import org.bukkit.entity.EntityType; import java.io.*; import java.net.InetAddress; @@ -150,12 +138,14 @@ public class CommandDeveloper implements DecreeExecutor { @Decree(description = "Test") public void packBenchmark( - @Param(description = "The pack to bench", aliases = {"pack"}) - IrisDimension dimension + @Param(description = "The pack to bench", aliases = {"pack"}, defaultValue = "overworld") + IrisDimension dimension, + @Param(description = "Radius in regions", defaultValue = "5") + int radius, + @Param(description = "Open GUI while benchmarking", defaultValue = "false") + boolean gui ) { - Iris.info("test"); - IrisPackBenchmarking benchmark = new IrisPackBenchmarking(dimension, 1); - + new IrisPackBenchmarking(dimension, radius, gui); } @Decree(description = "Upgrade to another Minecraft version") @@ -206,6 +196,23 @@ public class CommandDeveloper implements DecreeExecutor { } + @Decree + public void objects(@Param(defaultValue = "overworld") IrisDimension dimension) { + var loader = dimension.getLoader().getObjectLoader(); + var sender = sender(); + var keys = loader.getPossibleKeys(); + var burst = MultiBurst.burst.burst(keys.length); + AtomicInteger failed = new AtomicInteger(); + for (String key : keys) { + burst.queue(() -> { + if (loader.load(key) == null) + failed.incrementAndGet(); + }); + } + burst.complete(); + sender.sendMessage(C.RED + "Failed to load " + failed.get() + " of " + keys.length + " objects"); + } + @Decree(description = "Test", aliases = {"ip"}) public void network() { try { @@ -243,7 +250,7 @@ public class CommandDeveloper implements DecreeExecutor { VolmitSender sender = sender(); service.submit(() -> { try { - DataInputStream raw = new DataInputStream(new FileInputStream(file)); + CountingDataInputStream raw = CountingDataInputStream.wrap(new FileInputStream(file)); TectonicPlate plate = new TectonicPlate(height, raw); raw.close(); @@ -262,7 +269,7 @@ public class CommandDeveloper implements DecreeExecutor { if (size == 0) size = tmp.length(); start = System.currentTimeMillis(); - DataInputStream din = createInput(tmp, algorithm); + CountingDataInputStream din = createInput(tmp, algorithm); new TectonicPlate(height, din); din.close(); d2 += System.currentTimeMillis() - start; @@ -282,10 +289,10 @@ public class CommandDeveloper implements DecreeExecutor { } } - private DataInputStream createInput(File file, String algorithm) throws Throwable { + private CountingDataInputStream createInput(File file, String algorithm) throws Throwable { FileInputStream in = new FileInputStream(file); - return new DataInputStream(switch (algorithm) { + return CountingDataInputStream.wrap(switch (algorithm) { case "gzip" -> new GZIPInputStream(in); case "lz4f" -> new LZ4FrameInputStream(in); case "lz4b" -> new LZ4BlockInputStream(in); 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 710184027..3022b866c 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 @@ -60,7 +60,7 @@ public class CommandJigsaw implements DecreeExecutor { try { var world = world(); WorldObjectPlacer placer = new WorldObjectPlacer(world); - PlannedStructure ps = new PlannedStructure(structure, new IrisPosition(player().getLocation().add(0, world.getMinHeight(), 0)), new RNG()); + PlannedStructure ps = new PlannedStructure(structure, new IrisPosition(player().getLocation().add(0, world.getMinHeight(), 0)), new RNG(), true); VolmitSender sender = sender(); sender.sendMessage(C.GREEN + "Generated " + ps.getPieces().size() + " pieces in " + Form.duration(p.getMilliseconds(), 2)); ps.place(placer, failed -> sender.sendMessage(failed ? C.GREEN + "Placed the structure!" : C.RED + "Failed to place the structure!")); 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 830068de7..cd8ef6058 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 @@ -24,6 +24,7 @@ import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.core.service.ObjectSVC; import com.volmit.iris.core.service.StudioSVC; import com.volmit.iris.core.service.WandSVC; +import com.volmit.iris.core.tools.IrisConverter; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.object.*; import com.volmit.iris.util.data.Cuboid; @@ -117,10 +118,8 @@ public class CommandObject implements DecreeExecutor { } @Override - public void setTile(int xx, int yy, int zz, TileData tile) { - BlockState state = world.getBlockAt(xx, yy, zz).getState(); - tile.toBukkitTry(state); - state.update(); + public void setTile(int xx, int yy, int zz, TileData tile) { + tile.toBukkitTry(world.getBlockAt(xx, yy, zz)); } @Override @@ -212,6 +211,16 @@ public class CommandObject implements DecreeExecutor { } } + @Decree(description = "Convert .schem files in the 'convert' folder to .iob files.") + public void convert () { + try { + IrisConverter.convertSchematics(sender()); + } catch (Exception e) { + e.printStackTrace(); + } + + } + @Decree(description = "Get a powder that reveals objects", studio = true, aliases = "d") public void dust() { player().getInventory().addItem(WandSVC.createDust()); @@ -367,9 +376,11 @@ public class CommandObject implements DecreeExecutor { @Param(description = "The file to store it in, can use / for subfolders") String name, @Param(description = "Overwrite existing object files", defaultValue = "false", aliases = "force") - boolean overwrite + boolean overwrite, + @Param(description = "Use legacy TileState serialization if possible", defaultValue = "true") + boolean legacy ) { - IrisObject o = WandSVC.createSchematic(player()); + IrisObject o = WandSVC.createSchematic(player(), legacy); if (o == null) { sender().sendMessage(C.YELLOW + "You need to hold your wand!"); @@ -383,7 +394,7 @@ public class CommandObject implements DecreeExecutor { return; } try { - o.write(file); + o.write(file, sender()); } catch (IOException e) { sender().sendMessage(C.RED + "Failed to save object because of an IOException: " + e.getMessage()); Iris.reportError(e); 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 774e760b8..0ee273246 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 @@ -26,7 +26,6 @@ import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.core.project.IrisProject; import com.volmit.iris.core.service.ConversionSVC; import com.volmit.iris.core.service.StudioSVC; -import com.volmit.iris.core.tools.IrisConverter; import com.volmit.iris.core.tools.IrisToolbelt; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.object.*; @@ -303,7 +302,7 @@ public class CommandStudio implements DecreeExecutor { Inventory inv = Bukkit.createInventory(null, 27 * 2); try { - engine().addItems(true, inv, RNG.r, tables, InventorySlotType.STORAGE, player().getLocation().getBlockX(), player().getLocation().getBlockY(), player().getLocation().getBlockZ(), 1); + engine().addItems(true, inv, RNG.r, tables, InventorySlotType.STORAGE, player().getWorld(), player().getLocation().getBlockX(), player().getLocation().getBlockY(), player().getLocation().getBlockZ(), 1); } catch (Throwable e) { Iris.reportError(e); sender().sendMessage(C.RED + "Cannot add items to virtual inventory because of: " + e.getMessage()); @@ -326,7 +325,7 @@ public class CommandStudio implements DecreeExecutor { inv.clear(); } - engine().addItems(true, inv, new RNG(RNG.r.imax()), tables, InventorySlotType.STORAGE, player().getLocation().getBlockX(), player().getLocation().getBlockY(), player().getLocation().getBlockZ(), 1); + engine().addItems(true, inv, new RNG(RNG.r.imax()), tables, InventorySlotType.STORAGE, player().getWorld(), player().getLocation().getBlockX(), player().getLocation().getBlockY(), player().getLocation().getBlockZ(), 1); }, 0, fast ? 5 : 35)); sender().sendMessage(C.GREEN + "Opening inventory now!"); @@ -335,29 +334,38 @@ public class CommandStudio implements DecreeExecutor { @Decree(description = "Get all structures in a radius of chunks", aliases = "dist", origin = DecreeOrigin.PLAYER) - public void distances(@Param(description = "The radius") int radius) { + public void distances(@Param(description = "The radius in chunks") int radius) { var engine = engine(); if (engine == null) { sender().sendMessage(C.RED + "Only works in an Iris world!"); return; } var sender = sender(); - int d = radius*2; + int d = radius * 2; KMap> data = new KMap<>(); var multiBurst = new MultiBurst("Distance Sampler", Thread.MIN_PRIORITY); var executor = multiBurst.burst(radius * radius); sender.sendMessage(C.GRAY + "Generating data..."); var loc = player().getLocation(); + int totalTasks = d * d; + AtomicInteger completedTasks = new AtomicInteger(0); + int c = J.ar(() -> { + sender.sendProgress((double) completedTasks.get() / totalTasks, "Finding structures"); + }, 0); + new Spiraler(d, d, (x, z) -> executor.queue(() -> { var struct = engine.getStructureAt(x, z); if (struct != null) { data.computeIfAbsent(struct.getLoadKey(), (k) -> new KList<>()).add(new Position2(x, z)); } + completedTasks.incrementAndGet(); })).setOffset(loc.getBlockX(), loc.getBlockZ()).drain(); executor.complete(); multiBurst.close(); + J.car(c); + for (var key : data.keySet()) { var list = data.get(key); KList distances = new KList<>(list.size() - 1); @@ -390,6 +398,7 @@ public class CommandStudio implements DecreeExecutor { } } + @Decree(description = "Render a world map (External GUI)", aliases = "render") public void map( @Param(name = "world", description = "The world to open the generator for", contextual = true) diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandUpdater.java b/core/src/main/java/com/volmit/iris/core/commands/CommandUpdater.java index bd910ad5d..38aba40a5 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandUpdater.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandUpdater.java @@ -43,6 +43,10 @@ public class CommandUpdater implements DecreeExecutor { sender().sendMessage(C.GOLD + "This is not an Iris world"); return; } + if (chunkUpdater != null) { + chunkUpdater.stop(); + } + chunkUpdater = new ChunkUpdater(world); if (sender().isPlayer()) { sender().sendMessage(C.GREEN + "Updating " + world.getName() + C.GRAY + " Total chunks: " + Form.f(chunkUpdater.getChunks())); @@ -53,14 +57,7 @@ public class CommandUpdater implements DecreeExecutor { } @Decree(description = "Pause the updater") - public void pause( - @Param(description = "World to pause the Updater at") - World world - ) { - if (!IrisToolbelt.isIrisWorld(world)) { - sender().sendMessage(C.GOLD + "This is not an Iris world"); - return; - } + public void pause( ) { if (chunkUpdater == null) { sender().sendMessage(C.GOLD + "You cant pause something that doesnt exist?"); return; @@ -68,40 +65,32 @@ public class CommandUpdater implements DecreeExecutor { boolean status = chunkUpdater.pause(); if (sender().isPlayer()) { if (status) { - sender().sendMessage(C.IRIS + "Paused task for: " + C.GRAY + world.getName()); + sender().sendMessage(C.IRIS + "Paused task for: " + C.GRAY + chunkUpdater.getName()); } else { - sender().sendMessage(C.IRIS + "Unpause task for: " + C.GRAY + world.getName()); + sender().sendMessage(C.IRIS + "Unpause task for: " + C.GRAY + chunkUpdater.getName()); } } else { if (status) { - Iris.info(C.IRIS + "Paused task for: " + C.GRAY + world.getName()); + Iris.info(C.IRIS + "Paused task for: " + C.GRAY + chunkUpdater.getName()); } else { - Iris.info(C.IRIS + "Unpause task for: " + C.GRAY + world.getName()); + Iris.info(C.IRIS + "Unpause task for: " + C.GRAY + chunkUpdater.getName()); } } } @Decree(description = "Stops the updater") - public void stop( - @Param(description = "World to stop the Updater at") - World world - ) { - if (!IrisToolbelt.isIrisWorld(world)) { - sender().sendMessage(C.GOLD + "This is not an Iris world"); - return; - } + public void stop() { if (chunkUpdater == null) { sender().sendMessage(C.GOLD + "You cant stop something that doesnt exist?"); return; } if (sender().isPlayer()) { - sender().sendMessage("Stopping Updater for: " + C.GRAY + world.getName()); + sender().sendMessage("Stopping Updater for: " + C.GRAY + chunkUpdater.getName()); } else { - Iris.info("Stopping Updater for: " + C.GRAY + world.getName()); + Iris.info("Stopping Updater for: " + C.GRAY + chunkUpdater.getName()); } chunkUpdater.stop(); } - } diff --git a/core/src/main/java/com/volmit/iris/core/events/IrisLootEvent.java b/core/src/main/java/com/volmit/iris/core/events/IrisLootEvent.java new file mode 100644 index 000000000..067e874ce --- /dev/null +++ b/core/src/main/java/com/volmit/iris/core/events/IrisLootEvent.java @@ -0,0 +1,113 @@ +package com.volmit.iris.core.events; + +import com.volmit.iris.Iris; +import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.engine.object.InventorySlotType; +import com.volmit.iris.engine.object.IrisLootTable; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.scheduling.J; +import lombok.Getter; +import org.bukkit.*; +import org.bukkit.block.Block; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.bukkit.event.world.LootGenerateEvent; + +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.ItemStack; +import org.bukkit.loot.LootContext; +import org.bukkit.loot.LootTable; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.List; +import java.util.Random; + +@Getter +public class IrisLootEvent extends Event { + private static final HandlerList handlers = new HandlerList(); + private static final LootTable EMPTY = new LootTable() { + @NotNull + @Override + public NamespacedKey getKey() { + return new NamespacedKey(Iris.instance, "empty"); + } + + @NotNull + @Override + public Collection populateLoot(@Nullable Random random, @NotNull LootContext context) { + return List.of(); + } + + @Override + public void fillInventory(@NotNull Inventory inventory, @Nullable Random random, @NotNull LootContext context) { + } + }; + + private final Engine engine; + private final Block block; + private final InventorySlotType slot; + private final KList tables; + + /** + * Constructor for IrisLootEvent with mode selection. + * + * @param engine The engine instance. + * @param block The block associated with the event. + * @param slot The inventory slot type. + * @param tables The list of IrisLootTables. (mutable*) + */ + public IrisLootEvent(Engine engine, Block block, InventorySlotType slot, KList tables) { + this.engine = engine; + this.block = block; + this.slot = slot; + this.tables = tables; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + /** + * Required method to get the HandlerList for this event. + * + * @return The HandlerList. + */ + public static HandlerList getHandlerList() { + return handlers; + } + + /** + * Triggers the corresponding Bukkit loot event. + * This method integrates your custom IrisLootTables with Bukkit's LootGenerateEvent, + * allowing other plugins to modify or cancel the loot generation. + * + * @return true when the event was canceled + */ + public static boolean callLootEvent(KList loot, Inventory inv, World world, int x, int y, int z) { + InventoryHolder holder = inv.getHolder(); + Location loc = new Location(world, x, y, z); + if (holder == null) { + holder = new InventoryHolder() { + @NotNull + @Override + public Inventory getInventory() { + return inv; + } + }; + } + + LootContext context = new LootContext.Builder(loc).build(); + LootGenerateEvent event = new LootGenerateEvent(world, null, holder, EMPTY, context, loot, true); + if (!Bukkit.isPrimaryThread()) { + Iris.warn("LootGenerateEvent was not called on the main thread, please report this issue."); + Thread.dumpStack(); + J.sfut(() -> Bukkit.getPluginManager().callEvent(event)).join(); + } else Bukkit.getPluginManager().callEvent(event); + + return event.isCancelled(); + } +} \ No newline at end of file diff --git a/core/src/main/java/com/volmit/iris/core/gui/NoiseExplorerGUI.java b/core/src/main/java/com/volmit/iris/core/gui/NoiseExplorerGUI.java index 6f816f6f3..19fa5cd71 100644 --- a/core/src/main/java/com/volmit/iris/core/gui/NoiseExplorerGUI.java +++ b/core/src/main/java/com/volmit/iris/core/gui/NoiseExplorerGUI.java @@ -19,6 +19,7 @@ package com.volmit.iris.core.gui; import com.volmit.iris.Iris; +import com.volmit.iris.core.IrisSettings; import com.volmit.iris.core.events.IrisEngineHotloadEvent; import com.volmit.iris.engine.object.NoiseStyle; import com.volmit.iris.util.collection.KList; @@ -61,7 +62,7 @@ public class NoiseExplorerGUI extends JPanel implements MouseWheelListener, List @SuppressWarnings("CanBeFinal") RollingSequence r = new RollingSequence(20); @SuppressWarnings("CanBeFinal") - boolean colorMode = true; + boolean colorMode = IrisSettings.get().getGui().colorMode; double scale = 1; CNG cng = NoiseStyle.STATIC.create(new RNG(RNG.r.nextLong())); @SuppressWarnings("CanBeFinal") @@ -274,7 +275,8 @@ public class NoiseExplorerGUI extends JPanel implements MouseWheelListener, List n = n > 1 ? 1 : n < 0 ? 0 : n; try { - Color color = colorMode ? Color.getHSBColor((float) (n), 1f - (float) (n * n * n * n * n * n), 1f - (float) n) : Color.getHSBColor(0f, 0f, (float) n); + //Color color = colorMode ? Color.getHSBColor((float) (n), 1f - (float) (n * n * n * n * n * n), 1f - (float) n) : Color.getHSBColor(0f, 0f, (float) n); + Color color = colorMode ? Color.getHSBColor((float) (0.666f - n * 0.666f), 1f, (float) (1f - n * 0.8f)) : Color.getHSBColor(0f, 0f, (float) n); int rgb = color.getRGB(); img.setRGB(xx, z, rgb); } catch (Throwable ignored) { diff --git a/core/src/main/java/com/volmit/iris/core/gui/PregeneratorJob.java b/core/src/main/java/com/volmit/iris/core/gui/PregeneratorJob.java index 6d3534971..35569f6f6 100644 --- a/core/src/main/java/com/volmit/iris/core/gui/PregeneratorJob.java +++ b/core/src/main/java/com/volmit/iris/core/gui/PregeneratorJob.java @@ -93,7 +93,12 @@ public class PregeneratorJob implements PregenListener { open(); } - J.a(this.pregenerator::start, 20); + var t = new Thread(() -> { + J.sleep(1000); + this.pregenerator.start(); + }, "Iris Pregenerator"); + t.setPriority(Thread.MIN_PRIORITY); + t.start(); } public static boolean shutdownInstance() { diff --git a/core/src/main/java/com/volmit/iris/core/link/EcoItemsDataProvider.java b/core/src/main/java/com/volmit/iris/core/link/EcoItemsDataProvider.java index d42684e7e..81eb9669a 100644 --- a/core/src/main/java/com/volmit/iris/core/link/EcoItemsDataProvider.java +++ b/core/src/main/java/com/volmit/iris/core/link/EcoItemsDataProvider.java @@ -9,6 +9,7 @@ import com.willfp.ecoitems.items.EcoItems; import org.bukkit.NamespacedKey; import org.bukkit.block.data.BlockData; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; import java.util.MissingResourceException; @@ -33,23 +34,27 @@ public class EcoItemsDataProvider extends ExternalDataProvider { } } + @NotNull @Override - public BlockData getBlockData(Identifier blockId, KMap state) throws MissingResourceException { + public BlockData getBlockData(@NotNull Identifier blockId, @NotNull KMap state) throws MissingResourceException { throw new MissingResourceException("Failed to find BlockData!", blockId.namespace(), blockId.key()); } + @NotNull @Override - public ItemStack getItemStack(Identifier itemId, KMap customNbt) throws MissingResourceException { + public ItemStack getItemStack(@NotNull Identifier itemId, @NotNull KMap customNbt) throws MissingResourceException { EcoItem item = EcoItems.INSTANCE.getByID(itemId.key()); if (item == null) throw new MissingResourceException("Failed to find Item!", itemId.namespace(), itemId.key()); return itemStack.get(item).clone(); } + @NotNull @Override public Identifier[] getBlockTypes() { return new Identifier[0]; } + @NotNull @Override public Identifier[] getItemTypes() { KList names = new KList<>(); @@ -66,7 +71,7 @@ public class EcoItemsDataProvider extends ExternalDataProvider { } @Override - public boolean isValidProvider(Identifier id, boolean isItem) { + public boolean isValidProvider(@NotNull Identifier id, boolean isItem) { return id.namespace().equalsIgnoreCase("ecoitems") && isItem; } } diff --git a/core/src/main/java/com/volmit/iris/core/link/ExecutableItemsDataProvider.java b/core/src/main/java/com/volmit/iris/core/link/ExecutableItemsDataProvider.java index 27a3251b2..80fc25860 100644 --- a/core/src/main/java/com/volmit/iris/core/link/ExecutableItemsDataProvider.java +++ b/core/src/main/java/com/volmit/iris/core/link/ExecutableItemsDataProvider.java @@ -6,6 +6,7 @@ import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import org.bukkit.block.data.BlockData; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; import java.util.MissingResourceException; import java.util.Optional; @@ -20,23 +21,27 @@ public class ExecutableItemsDataProvider extends ExternalDataProvider { Iris.info("Setting up ExecutableItems Link..."); } + @NotNull @Override - public BlockData getBlockData(Identifier blockId, KMap state) throws MissingResourceException { + public BlockData getBlockData(@NotNull Identifier blockId, @NotNull KMap state) throws MissingResourceException { throw new MissingResourceException("Failed to find BlockData!", blockId.namespace(), blockId.key()); } + @NotNull @Override - public ItemStack getItemStack(Identifier itemId, KMap customNbt) throws MissingResourceException { + public ItemStack getItemStack(@NotNull Identifier itemId, @NotNull KMap customNbt) throws MissingResourceException { return ExecutableItemsAPI.getExecutableItemsManager().getExecutableItem(itemId.key()) .map(item -> item.buildItem(1, Optional.empty())) .orElseThrow(() -> new MissingResourceException("Failed to find ItemData!", itemId.namespace(), itemId.key())); } + @NotNull @Override public Identifier[] getBlockTypes() { return new Identifier[0]; } + @NotNull @Override public Identifier[] getItemTypes() { KList names = new KList<>(); @@ -53,7 +58,7 @@ public class ExecutableItemsDataProvider extends ExternalDataProvider { } @Override - public boolean isValidProvider(Identifier key, boolean isItem) { + public boolean isValidProvider(@NotNull Identifier key, boolean isItem) { return key.namespace().equalsIgnoreCase("executable_items") && isItem; } } diff --git a/core/src/main/java/com/volmit/iris/core/link/ExternalDataProvider.java b/core/src/main/java/com/volmit/iris/core/link/ExternalDataProvider.java index 77e2b9c90..19c3b0935 100644 --- a/core/src/main/java/com/volmit/iris/core/link/ExternalDataProvider.java +++ b/core/src/main/java/com/volmit/iris/core/link/ExternalDataProvider.java @@ -2,22 +2,28 @@ package com.volmit.iris.core.link; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.data.IrisBlockData; import lombok.Getter; +import lombok.NonNull; import lombok.RequiredArgsConstructor; import org.bukkit.Bukkit; import org.bukkit.block.Block; import org.bukkit.block.data.BlockData; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.MissingResourceException; +@Getter @RequiredArgsConstructor public abstract class ExternalDataProvider { - @Getter + @NonNull private final String pluginId; + @Nullable public Plugin getPlugin() { return Bukkit.getPluginManager().getPlugin(pluginId); } @@ -28,23 +34,60 @@ public abstract class ExternalDataProvider { public abstract void init(); - public BlockData getBlockData(Identifier blockId) throws MissingResourceException { + /** + * @see ExternalDataProvider#getBlockData(Identifier, KMap) + */ + @NotNull + public BlockData getBlockData(@NotNull Identifier blockId) throws MissingResourceException { return getBlockData(blockId, new KMap<>()); } - public abstract BlockData getBlockData(Identifier blockId, KMap state) throws MissingResourceException; + /** + * This method returns a {@link BlockData} corresponding to the blockID + * it is used in any place Iris accepts {@link BlockData} + * + * @param blockId The id of the block to get + * @param state The state of the block to get + * @return Corresponding {@link BlockData} to the blockId + * may return {@link IrisBlockData} for blocks that need a world for placement + * @throws MissingResourceException when the blockId is invalid + */ + @NotNull + public abstract BlockData getBlockData(@NotNull Identifier blockId, @NotNull KMap state) throws MissingResourceException; - public ItemStack getItemStack(Identifier itemId) throws MissingResourceException { + /** + * @see ExternalDataProvider#getItemStack(Identifier) + */ + @NotNull + public ItemStack getItemStack(@NotNull Identifier itemId) throws MissingResourceException { return getItemStack(itemId, new KMap<>()); } - public abstract ItemStack getItemStack(Identifier itemId, KMap customNbt) throws MissingResourceException; + /** + * This method returns a {@link ItemStack} corresponding to the itemID + * it is used in loot tables + * + * @param itemId The id of the item to get + * @param customNbt Custom nbt to apply to the item + * @return Corresponding {@link ItemStack} to the itemId + * @throws MissingResourceException when the itemId is invalid + */ + @NotNull + public abstract ItemStack getItemStack(@NotNull Identifier itemId, @NotNull KMap customNbt) throws MissingResourceException; - public void processUpdate(Engine engine, Block block, Identifier blockId) {} + /** + * This method is used for placing blocks that need to use the plugins api + * it will only be called when the {@link ExternalDataProvider#getBlockData(Identifier, KMap)} returned a {@link IrisBlockData} + * + * @param engine The engine of the world the block is being placed in + * @param block The block where the block should be placed + * @param blockId The blockId to place + */ + public void processUpdate(@NotNull Engine engine, @NotNull Block block, @NotNull Identifier blockId) {} - public abstract Identifier[] getBlockTypes(); + public abstract @NotNull Identifier[] getBlockTypes(); - public abstract Identifier[] getItemTypes(); + public abstract @NotNull Identifier[] getItemTypes(); - public abstract boolean isValidProvider(Identifier id, boolean isItem); + public abstract boolean isValidProvider(@NotNull Identifier id, boolean isItem); } diff --git a/core/src/main/java/com/volmit/iris/core/link/HMCLeavesDataProvider.java b/core/src/main/java/com/volmit/iris/core/link/HMCLeavesDataProvider.java index 21207a22f..84f0a7a44 100644 --- a/core/src/main/java/com/volmit/iris/core/link/HMCLeavesDataProvider.java +++ b/core/src/main/java/com/volmit/iris/core/link/HMCLeavesDataProvider.java @@ -16,6 +16,7 @@ import org.bukkit.block.Block; import org.bukkit.block.data.BlockData; import org.bukkit.block.data.type.Leaves; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; import java.util.Map; import java.util.MissingResourceException; @@ -51,8 +52,9 @@ public class HMCLeavesDataProvider extends ExternalDataProvider { } } + @NotNull @Override - public BlockData getBlockData(Identifier blockId, KMap state) throws MissingResourceException { + public BlockData getBlockData(@NotNull Identifier blockId, @NotNull KMap state) throws MissingResourceException { Object o = blockDataMap.get(blockId.key()); if (o == null) throw new MissingResourceException("Failed to find BlockData!", blockId.namespace(), blockId.key()); @@ -65,15 +67,16 @@ public class HMCLeavesDataProvider extends ExternalDataProvider { return new IrisBlockData(blockData, ExternalDataSVC.buildState(blockId, state)); } + @NotNull @Override - public ItemStack getItemStack(Identifier itemId, KMap customNbt) throws MissingResourceException { + public ItemStack getItemStack(@NotNull Identifier itemId, @NotNull KMap customNbt) throws MissingResourceException { if (!itemDataField.containsKey(itemId.key())) throw new MissingResourceException("Failed to find ItemData!", itemId.namespace(), itemId.key()); return itemDataField.get(itemId.key()).get(); } @Override - public void processUpdate(Engine engine, Block block, Identifier blockId) { + public void processUpdate(@NotNull Engine engine, @NotNull Block block, @NotNull Identifier blockId) { var pair = ExternalDataSVC.parseState(blockId); blockId = pair.getA(); Boolean result = setCustomBlock.invoke(apiInstance, new Object[]{block.getLocation(), blockId.key(), false}); @@ -86,6 +89,7 @@ public class HMCLeavesDataProvider extends ExternalDataProvider { } } + @NotNull @Override public Identifier[] getBlockTypes() { KList names = new KList<>(); @@ -101,6 +105,7 @@ public class HMCLeavesDataProvider extends ExternalDataProvider { return names.toArray(new Identifier[0]); } + @NotNull @Override public Identifier[] getItemTypes() { KList names = new KList<>(); @@ -117,7 +122,7 @@ public class HMCLeavesDataProvider extends ExternalDataProvider { } @Override - public boolean isValidProvider(Identifier id, boolean isItem) { + public boolean isValidProvider(@NotNull Identifier id, boolean isItem) { return (isItem ? itemDataField.keySet() : blockDataMap.keySet()).contains(id.key()); } diff --git a/core/src/main/java/com/volmit/iris/core/link/ItemAdderDataProvider.java b/core/src/main/java/com/volmit/iris/core/link/ItemAdderDataProvider.java index e08e8fd7d..d7e891da0 100644 --- a/core/src/main/java/com/volmit/iris/core/link/ItemAdderDataProvider.java +++ b/core/src/main/java/com/volmit/iris/core/link/ItemAdderDataProvider.java @@ -7,6 +7,7 @@ import dev.lone.itemsadder.api.CustomBlock; import dev.lone.itemsadder.api.CustomStack; import org.bukkit.block.data.BlockData; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; import java.util.MissingResourceException; @@ -32,13 +33,15 @@ public class ItemAdderDataProvider extends ExternalDataProvider { } } + @NotNull @Override - public BlockData getBlockData(Identifier blockId, KMap state) throws MissingResourceException { + public BlockData getBlockData(@NotNull Identifier blockId, @NotNull KMap state) throws MissingResourceException { return CustomBlock.getBaseBlockData(blockId.toString()); } + @NotNull @Override - public ItemStack getItemStack(Identifier itemId, KMap customNbt) throws MissingResourceException { + public ItemStack getItemStack(@NotNull Identifier itemId, @NotNull KMap customNbt) throws MissingResourceException { CustomStack stack = CustomStack.getInstance(itemId.toString()); if (stack == null) { throw new MissingResourceException("Failed to find ItemData!", itemId.namespace(), itemId.key()); @@ -46,6 +49,7 @@ public class ItemAdderDataProvider extends ExternalDataProvider { return stack.getItemStack(); } + @NotNull @Override public Identifier[] getBlockTypes() { KList keys = new KList<>(); @@ -55,6 +59,7 @@ public class ItemAdderDataProvider extends ExternalDataProvider { return keys.toArray(new Identifier[0]); } + @NotNull @Override public Identifier[] getItemTypes() { KList keys = new KList<>(); @@ -65,7 +70,7 @@ public class ItemAdderDataProvider extends ExternalDataProvider { } @Override - public boolean isValidProvider(Identifier id, boolean isItem) { + public boolean isValidProvider(@NotNull Identifier id, boolean isItem) { return isItem ? this.itemNamespaces.contains(id.namespace()) : this.blockNamespaces.contains(id.namespace()); } } diff --git a/core/src/main/java/com/volmit/iris/core/link/MMOItemsDataProvider.java b/core/src/main/java/com/volmit/iris/core/link/MMOItemsDataProvider.java index b402f32ab..251665a54 100644 --- a/core/src/main/java/com/volmit/iris/core/link/MMOItemsDataProvider.java +++ b/core/src/main/java/com/volmit/iris/core/link/MMOItemsDataProvider.java @@ -5,11 +5,13 @@ import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.scheduling.J; import net.Indyuce.mmoitems.MMOItems; +import net.Indyuce.mmoitems.api.ItemTier; import net.Indyuce.mmoitems.api.Type; import net.Indyuce.mmoitems.api.block.CustomBlock; import org.bukkit.Bukkit; import org.bukkit.block.data.BlockData; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; import java.util.MissingResourceException; import java.util.concurrent.CompletableFuture; @@ -26,8 +28,9 @@ public class MMOItemsDataProvider extends ExternalDataProvider { Iris.info("Setting up MMOItems Link..."); } + @NotNull @Override - public BlockData getBlockData(Identifier blockId, KMap state) throws MissingResourceException { + public BlockData getBlockData(@NotNull Identifier blockId, @NotNull KMap state) throws MissingResourceException { int id = -1; try { id = Integer.parseInt(blockId.key()); @@ -37,8 +40,9 @@ public class MMOItemsDataProvider extends ExternalDataProvider { return block.getState().getBlockData(); } + @NotNull @Override - public ItemStack getItemStack(Identifier itemId, KMap customNbt) throws MissingResourceException { + public ItemStack getItemStack(@NotNull Identifier itemId, @NotNull KMap customNbt) throws MissingResourceException { String[] parts = itemId.namespace().split("_", 2); if (parts.length != 2) throw new MissingResourceException("Failed to find ItemData!", itemId.namespace(), itemId.key()); @@ -46,8 +50,13 @@ public class MMOItemsDataProvider extends ExternalDataProvider { Runnable run = () -> { try { var type = api().getTypes().get(parts[1]); - int level = customNbt.containsKey("level") ? (int) customNbt.get("level") : -1; - var tier = api().getTiers().get(String.valueOf(customNbt.get("tier"))); + int level = -1; + ItemTier tier = null; + + if (customNbt != null) { + level = (int) customNbt.getOrDefault("level", -1); + tier = api().getTiers().get(String.valueOf(customNbt.get("tier"))); + } ItemStack itemStack; if (type == null) { @@ -76,6 +85,7 @@ public class MMOItemsDataProvider extends ExternalDataProvider { return item; } + @NotNull @Override public Identifier[] getBlockTypes() { KList names = new KList<>(); @@ -90,6 +100,7 @@ public class MMOItemsDataProvider extends ExternalDataProvider { return names.toArray(new Identifier[0]); } + @NotNull @Override public Identifier[] getItemTypes() { KList names = new KList<>(); @@ -118,7 +129,7 @@ public class MMOItemsDataProvider extends ExternalDataProvider { } @Override - public boolean isValidProvider(Identifier id, boolean isItem) { + public boolean isValidProvider(@NotNull Identifier id, boolean isItem) { return isItem ? id.namespace().split("_", 2).length == 2 : id.namespace().equals("mmoitems"); } diff --git a/core/src/main/java/com/volmit/iris/core/link/MythicCrucibleDataProvider.java b/core/src/main/java/com/volmit/iris/core/link/MythicCrucibleDataProvider.java new file mode 100644 index 000000000..4c7a3cd88 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/core/link/MythicCrucibleDataProvider.java @@ -0,0 +1,171 @@ +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2022 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.volmit.iris.core.link; + +import com.volmit.iris.Iris; +import com.volmit.iris.core.nms.INMS; +import com.volmit.iris.core.nms.container.BiomeColor; +import com.volmit.iris.core.service.ExternalDataSVC; +import com.volmit.iris.engine.data.cache.Cache; +import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.data.B; +import com.volmit.iris.util.data.IrisBlockData; +import com.volmit.iris.util.math.RNG; +import io.lumine.mythic.bukkit.BukkitAdapter; +import io.lumine.mythic.bukkit.utils.serialize.Chroma; +import io.lumine.mythiccrucible.MythicCrucible; +import io.lumine.mythiccrucible.items.CrucibleItem; +import io.lumine.mythiccrucible.items.ItemManager; +import io.lumine.mythiccrucible.items.blocks.CustomBlockItemContext; +import io.lumine.mythiccrucible.items.furniture.FurnitureItemContext; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.data.BlockData; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.MissingResourceException; +import java.util.Optional; + +public class MythicCrucibleDataProvider extends ExternalDataProvider { + + private ItemManager itemManager; + + public MythicCrucibleDataProvider() { + super("MythicCrucible"); + } + + @Override + public void init() { + Iris.info("Setting up MythicCrucible Link..."); + try { + this.itemManager = MythicCrucible.inst().getItemManager(); + } catch (Exception e) { + Iris.error("Failed to set up MythicCrucible Link: Unable to fetch MythicCrucible instance!"); + } + } + + @NotNull + @Override + public BlockData getBlockData(@NotNull Identifier blockId, @NotNull KMap state) throws MissingResourceException { + CrucibleItem crucibleItem = this.itemManager.getItem(blockId.key()) + .orElseThrow(() -> new MissingResourceException("Failed to find BlockData!", blockId.namespace(), blockId.key())); + CustomBlockItemContext blockItemContext = crucibleItem.getBlockData(); + FurnitureItemContext furnitureItemContext = crucibleItem.getFurnitureData(); + if (furnitureItemContext != null) { + return new IrisBlockData(B.getAir(), ExternalDataSVC.buildState(blockId, state)); + } else if (blockItemContext != null) { + return blockItemContext.getBlockData(); + } + throw new MissingResourceException("Failed to find BlockData!", blockId.namespace(), blockId.key()); + } + + @NotNull + @Override + public ItemStack getItemStack(@NotNull Identifier itemId, @NotNull KMap customNbt) throws MissingResourceException { + Optional opt = this.itemManager.getItem(itemId.key()); + return BukkitAdapter.adapt(opt.orElseThrow(() -> + new MissingResourceException("Failed to find ItemData!", itemId.namespace(), itemId.key())) + .getMythicItem() + .generateItemStack(1)); + } + + @NotNull + @Override + public Identifier[] getBlockTypes() { + KList names = new KList<>(); + for (CrucibleItem item : this.itemManager.getItems()) { + if (item.getBlockData() == null) continue; + try { + Identifier key = new Identifier("crucible", item.getInternalName()); + if (getBlockData(key) != null) { + Iris.info("getBlockTypes: Block loaded '" + item.getInternalName() + "'"); + names.add(key); + } + } catch (MissingResourceException ignored) {} + } + return names.toArray(new Identifier[0]); + } + + @NotNull + @Override + public Identifier[] getItemTypes() { + KList names = new KList<>(); + for (CrucibleItem item : this.itemManager.getItems()) { + try { + Identifier key = new Identifier("crucible", item.getInternalName()); + if (getItemStack(key) != null) { + Iris.info("getItemTypes: Item loaded '" + item.getInternalName() + "'"); + names.add(key); + } + } catch (MissingResourceException ignored) {} + } + return names.toArray(new Identifier[0]); + } + + @Override + public void processUpdate(@NotNull Engine engine, @NotNull Block block, @NotNull Identifier blockId) { + var pair = ExternalDataSVC.parseState(blockId); + var state = pair.getB(); + blockId = pair.getA(); + + Optional item = itemManager.getItem(blockId.key()); + if (item.isEmpty()) return; + FurnitureItemContext furniture = item.get().getFurnitureData(); + if (furniture == null) return; + + float yaw = 0; + BlockFace face = BlockFace.NORTH; + long seed = engine.getSeedManager().getSeed() + Cache.key(block.getX(), block.getZ()) + block.getY(); + RNG rng = new RNG(seed); + if ("true".equals(state.get("randomYaw"))) { + yaw = rng.f(0, 360); + } else if (state.containsKey("yaw")) { + yaw = Float.parseFloat(state.get("yaw")); + } + if ("true".equals(state.get("randomFace"))) { + BlockFace[] faces = BlockFace.values(); + face = faces[rng.i(0, faces.length - 1)]; + } else if (state.containsKey("face")) { + face = BlockFace.valueOf(state.get("face").toUpperCase()); + } + if (face == BlockFace.SELF) { + face = BlockFace.NORTH; + } + + BiomeColor type = null; + Chroma color = null; + try { + type = BiomeColor.valueOf(state.get("matchBiome").toUpperCase()); + } catch (NullPointerException | IllegalArgumentException ignored) {} + if (type != null) { + var biomeColor = INMS.get().getBiomeColor(block.getLocation(), type); + if (biomeColor == null) return; + color = Chroma.of(biomeColor.getRGB()); + } + furniture.place(block, face, yaw, color); + } + + @Override + public boolean isValidProvider(@NotNull Identifier key, boolean isItem) { + return key.namespace().equalsIgnoreCase("crucible"); + } +} diff --git a/core/src/main/java/com/volmit/iris/core/link/NexoDataProvider.java b/core/src/main/java/com/volmit/iris/core/link/NexoDataProvider.java new file mode 100644 index 000000000..82463bb60 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/core/link/NexoDataProvider.java @@ -0,0 +1,164 @@ +package com.volmit.iris.core.link; + +import com.nexomc.nexo.api.NexoBlocks; +import com.nexomc.nexo.api.NexoFurniture; +import com.nexomc.nexo.api.NexoItems; +import com.nexomc.nexo.items.ItemBuilder; +import com.volmit.iris.core.nms.INMS; +import com.volmit.iris.core.nms.container.BiomeColor; +import com.volmit.iris.core.service.ExternalDataSVC; +import com.volmit.iris.engine.data.cache.Cache; +import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.data.B; +import com.volmit.iris.util.data.IrisBlockData; +import com.volmit.iris.util.math.RNG; +import org.bukkit.Color; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.data.BlockData; +import org.bukkit.entity.ItemDisplay; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.PotionMeta; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.MissingResourceException; +import java.util.concurrent.atomic.AtomicBoolean; + +public class NexoDataProvider extends ExternalDataProvider { + private final AtomicBoolean failed = new AtomicBoolean(false); + + public NexoDataProvider() { + super("Nexo"); + } + + @Override + public void init() { + } + + @NotNull + @Override + public BlockData getBlockData(@NotNull Identifier blockId, @NotNull KMap state) throws MissingResourceException { + if (!NexoItems.exists(blockId.key())) { + throw new MissingResourceException("Failed to find BlockData!", blockId.namespace(), blockId.key()); + } + + Identifier blockState = ExternalDataSVC.buildState(blockId, state); + if (NexoBlocks.isCustomBlock(blockId.key())) { + BlockData data = NexoBlocks.blockData(blockId.key()); + if (data == null) + throw new MissingResourceException("Failed to find BlockData!", blockId.namespace(), blockId.key()); + return new IrisBlockData(data, blockState); + } else if (NexoFurniture.isFurniture(blockId.key())) { + return new IrisBlockData(B.getAir(), blockState); + } + + throw new MissingResourceException("Failed to find BlockData!", blockId.namespace(), blockId.key()); + } + + @NotNull + @Override + public ItemStack getItemStack(@NotNull Identifier itemId, @NotNull KMap customNbt) throws MissingResourceException { + ItemBuilder builder = NexoItems.itemFromId(itemId.key()); + if (builder == null) { + throw new MissingResourceException("Failed to find ItemData!", itemId.namespace(), itemId.key()); + } + return builder.build(); + } + + @Override + public void processUpdate(@NotNull Engine engine, @NotNull Block block, @NotNull Identifier blockId) { + var pair = ExternalDataSVC.parseState(blockId); + var state = pair.getB(); + blockId = pair.getA(); + + if (NexoBlocks.isCustomBlock(blockId.key())) { + NexoBlocks.place(blockId.key(), block.getLocation()); + return; + } + + if (!NexoFurniture.isFurniture(blockId.key())) + return; + + float yaw = 0; + BlockFace face = BlockFace.NORTH; + + long seed = engine.getSeedManager().getSeed() + Cache.key(block.getX(), block.getZ()) + block.getY(); + RNG rng = new RNG(seed); + if ("true".equals(state.get("randomYaw"))) { + yaw = rng.f(0, 360); + } else if (state.containsKey("yaw")) { + yaw = Float.parseFloat(state.get("yaw")); + } + if ("true".equals(state.get("randomFace"))) { + BlockFace[] faces = BlockFace.values(); + face = faces[rng.i(0, faces.length - 1)]; + } else if (state.containsKey("face")) { + face = BlockFace.valueOf(state.get("face").toUpperCase()); + } + if (face == BlockFace.SELF) { + face = BlockFace.NORTH; + } + ItemDisplay display = NexoFurniture.place(blockId.key(), block.getLocation(), yaw, face); + if (display == null) return; + ItemStack itemStack = display.getItemStack(); + if (itemStack == null) return; + + BiomeColor type = null; + try { + type = BiomeColor.valueOf(state.get("matchBiome").toUpperCase()); + } catch (NullPointerException | IllegalArgumentException ignored) {} + + if (type != null) { + var biomeColor = INMS.get().getBiomeColor(block.getLocation(), type); + if (biomeColor == null) return; + var potionColor = Color.fromARGB(biomeColor.getAlpha(), biomeColor.getRed(), biomeColor.getGreen(), biomeColor.getBlue()); + if (itemStack.getItemMeta() instanceof PotionMeta meta) { + meta.setColor(potionColor); + itemStack.setItemMeta(meta); + } + } + display.setItemStack(itemStack); + } + + @NotNull + @Override + public Identifier[] getBlockTypes() { + return Arrays.stream(NexoItems.itemNames()) + .map(i -> new Identifier("nexo", i)) + .filter(i -> { + try { + return getBlockData(i) != null; + } catch (MissingResourceException e) { + return false; + } + }) + .toArray(Identifier[]::new); + } + + @NotNull + @Override + public Identifier[] getItemTypes() { + return Arrays.stream(NexoItems.itemNames()) + .map(i -> new Identifier("nexo", i)) + .filter(i -> { + try { + return getItemStack(i) != null; + } catch (MissingResourceException e) { + return false; + } + }) + .toArray(Identifier[]::new); + } + + @Override + public boolean isValidProvider(@NotNull Identifier id, boolean isItem) { + return "nexo".equalsIgnoreCase(id.namespace()); + } + + @Override + public boolean isReady() { + return super.isReady() && !failed.get(); + } +} diff --git a/core/src/main/java/com/volmit/iris/core/link/OraxenDataProvider.java b/core/src/main/java/com/volmit/iris/core/link/OraxenDataProvider.java deleted file mode 100644 index 233701866..000000000 --- a/core/src/main/java/com/volmit/iris/core/link/OraxenDataProvider.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.volmit.iris.core.link; - -import com.volmit.iris.Iris; -import com.volmit.iris.core.nms.INMS; -import com.volmit.iris.core.nms.container.BiomeColor; -import com.volmit.iris.core.service.ExternalDataSVC; -import com.volmit.iris.engine.data.cache.Cache; -import com.volmit.iris.engine.framework.Engine; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.data.B; -import com.volmit.iris.util.data.IrisBlockData; -import com.volmit.iris.util.math.RNG; -import com.volmit.iris.util.reflect.WrappedField; -import io.th0rgal.oraxen.api.OraxenItems; -import io.th0rgal.oraxen.items.ItemBuilder; -import io.th0rgal.oraxen.mechanics.Mechanic; -import io.th0rgal.oraxen.mechanics.MechanicFactory; -import io.th0rgal.oraxen.mechanics.MechanicsManager; -import io.th0rgal.oraxen.mechanics.provided.gameplay.block.BlockMechanic; -import io.th0rgal.oraxen.mechanics.provided.gameplay.block.BlockMechanicFactory; -import io.th0rgal.oraxen.mechanics.provided.gameplay.furniture.FurnitureFactory; -import io.th0rgal.oraxen.mechanics.provided.gameplay.furniture.FurnitureMechanic; -import io.th0rgal.oraxen.mechanics.provided.gameplay.noteblock.NoteBlockMechanicFactory; -import io.th0rgal.oraxen.mechanics.provided.gameplay.stringblock.StringBlockMechanicFactory; -import org.bukkit.Bukkit; -import org.bukkit.Color; -import org.bukkit.Material; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; -import org.bukkit.block.data.BlockData; -import org.bukkit.block.data.MultipleFacing; -import org.bukkit.entity.Entity; -import org.bukkit.entity.ItemDisplay; -import org.bukkit.entity.ItemFrame; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.PotionMeta; - -import java.util.Map; -import java.util.MissingResourceException; -import java.util.Optional; -import java.util.function.Consumer; - -public class OraxenDataProvider extends ExternalDataProvider { - - private static final String FIELD_FACTORIES_MAP = "FACTORIES_BY_MECHANIC_ID"; - - private WrappedField> factories; - - public OraxenDataProvider() { - super("Oraxen"); - } - - @Override - public void init() { - Iris.info("Setting up Oraxen Link..."); - this.factories = new WrappedField<>(MechanicsManager.class, FIELD_FACTORIES_MAP); - if (this.factories.hasFailed()) { - Iris.error("Failed to set up Oraxen Link: Unable to fetch MechanicFactoriesMap!"); - } - } - - @Override - public BlockData getBlockData(Identifier blockId, KMap state) throws MissingResourceException { - MechanicFactory factory = getFactory(blockId); - if (factory instanceof NoteBlockMechanicFactory f) - return f.createNoteBlockData(blockId.key()); - else if (factory instanceof BlockMechanicFactory f) { - MultipleFacing newBlockData = (MultipleFacing) Bukkit.createBlockData(Material.MUSHROOM_STEM); - BlockMechanic.setBlockFacing(newBlockData, ((BlockMechanic) f.getMechanic(blockId.key())).getCustomVariation()); - return newBlockData; - } else if (factory instanceof StringBlockMechanicFactory f) { - return f.createTripwireData(blockId.key()); - } else if (factory instanceof FurnitureFactory) { - return new IrisBlockData(B.getAir(), ExternalDataSVC.buildState(blockId, state)); - } else - throw new MissingResourceException("Failed to find BlockData!", blockId.namespace(), blockId.key()); - } - - @Override - public ItemStack getItemStack(Identifier itemId, KMap customNbt) throws MissingResourceException { - Optional opt = OraxenItems.getOptionalItemById(itemId.key()); - return opt.orElseThrow(() -> new MissingResourceException("Failed to find ItemData!", itemId.namespace(), itemId.key())).build(); - } - - @Override - public void processUpdate(Engine engine, Block block, Identifier blockId) { - var pair = ExternalDataSVC.parseState(blockId); - var state = pair.getB(); - blockId = pair.getA(); - Mechanic mechanic = getFactory(blockId).getMechanic(blockId.key()); - if (mechanic instanceof FurnitureMechanic f) { - float yaw = 0; - BlockFace face = BlockFace.NORTH; - - long seed = engine.getSeedManager().getSeed() + Cache.key(block.getX(), block.getZ()) + block.getY(); - RNG rng = new RNG(seed); - if ("true".equals(state.get("randomYaw"))) { - yaw = rng.f(0, 360); - } else if (state.containsKey("yaw")) { - yaw = Float.parseFloat(state.get("yaw")); - } - if ("true".equals(state.get("randomFace"))) { - BlockFace[] faces = BlockFace.values(); - face = faces[rng.i(0, faces.length - 1)]; - } else if (state.containsKey("face")) { - face = BlockFace.valueOf(state.get("face").toUpperCase()); - } - if (face == BlockFace.SELF) { - face = BlockFace.NORTH; - } - ItemStack itemStack = OraxenItems.getItemById(f.getItemID()).build(); - Entity entity = f.place(block.getLocation(), itemStack, yaw, face, false); - - Consumer setter = null; - if (entity instanceof ItemFrame frame) { - itemStack = frame.getItem(); - setter = frame::setItem; - } else if (entity instanceof ItemDisplay display) { - itemStack = display.getItemStack(); - setter = display::setItemStack; - } - if (setter == null || itemStack == null) return; - - BiomeColor type = null; - try { - type = BiomeColor.valueOf(state.get("matchBiome").toUpperCase()); - } catch (NullPointerException | IllegalArgumentException ignored) {} - - if (type != null) { - var biomeColor = INMS.get().getBiomeColor(block.getLocation(), type); - if (biomeColor == null) return; - var potionColor = Color.fromARGB(biomeColor.getAlpha(), biomeColor.getRed(), biomeColor.getGreen(), biomeColor.getBlue()); - if (itemStack.getItemMeta() instanceof PotionMeta meta) { - meta.setColor(potionColor); - itemStack.setItemMeta(meta); - } - } - setter.accept(itemStack); - } - } - - @Override - public Identifier[] getBlockTypes() { - KList names = new KList<>(); - for (String name : OraxenItems.getItemNames()) { - try { - Identifier key = new Identifier("oraxen", name); - if (getBlockData(key) != null) - names.add(key); - } catch (MissingResourceException ignored) { - } - } - - return names.toArray(new Identifier[0]); - } - - @Override - public Identifier[] getItemTypes() { - KList names = new KList<>(); - for (String name : OraxenItems.getItemNames()) { - try { - Identifier key = new Identifier("oraxen", name); - if (getItemStack(key) != null) - names.add(key); - } catch (MissingResourceException ignored) { - } - } - - return names.toArray(new Identifier[0]); - } - - @Override - public boolean isReady() { - if (super.isReady()) { - if (factories == null) { - this.factories = new WrappedField<>(MechanicsManager.class, FIELD_FACTORIES_MAP); - } - return super.isReady() && !factories.hasFailed(); - } - return false; - } - - @Override - public boolean isValidProvider(Identifier key, boolean isItem) { - return key.namespace().equalsIgnoreCase("oraxen"); - } - - private MechanicFactory getFactory(Identifier key) throws MissingResourceException { - return factories.get().values().stream() - .filter(i -> i.getItems().contains(key.key())) - .findFirst() - .orElseThrow(() -> new MissingResourceException("Failed to find BlockData!", key.namespace(), key.key())); - } -} 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 7a2c55b19..e51c93176 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 @@ -36,6 +36,7 @@ import com.volmit.iris.util.format.C; import com.volmit.iris.util.math.RNG; import com.volmit.iris.util.parallel.BurstExecutor; import com.volmit.iris.util.parallel.MultiBurst; +import com.volmit.iris.util.reflect.OldEnum; import com.volmit.iris.util.scheduling.ChronoLatch; import com.volmit.iris.util.scheduling.J; import lombok.Data; @@ -337,6 +338,15 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { this.imageLoader = registerLoader(IrisImage.class); this.scriptLoader = registerLoader(IrisScript.class); this.matterObjectLoader = registerLoader(IrisMatterObject.class); + if (OldEnum.exists()) { + builder.registerTypeAdapterFactory(new TypeAdapterFactory() { + @Override + public TypeAdapter create(Gson gson, TypeToken type) { + return (TypeAdapter) OldEnum.create(type.getRawType()); + } + }); + } + gson = builder.create(); } @@ -434,6 +444,7 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { return adapter.read(reader); } catch (Throwable e) { Iris.error("Failed to read " + typeToken.getRawType().getCanonicalName() + "... faking objects a little to load the file at least."); + Iris.reportError(e); try { return (T) typeToken.getRawType().getConstructor().newInstance(); } catch (Throwable ignored) { diff --git a/core/src/main/java/com/volmit/iris/core/loader/ObjectResourceLoader.java b/core/src/main/java/com/volmit/iris/core/loader/ObjectResourceLoader.java index 37f3f43b5..3fa92bc46 100644 --- a/core/src/main/java/com/volmit/iris/core/loader/ObjectResourceLoader.java +++ b/core/src/main/java/com/volmit/iris/core/loader/ObjectResourceLoader.java @@ -50,10 +50,10 @@ public class ObjectResourceLoader extends ResourceLoader { try { PrecisionStopwatch p = PrecisionStopwatch.start(); IrisObject t = new IrisObject(0, 0, 0); - t.read(j); t.setLoadKey(name); t.setLoader(manager); t.setLoadFile(j); + t.read(j); logLoad(j, t); tlt.addAndGet(p.getMilliseconds()); return t; diff --git a/core/src/main/java/com/volmit/iris/core/nms/INMS.java b/core/src/main/java/com/volmit/iris/core/nms/INMS.java index 741018d43..a6af03c88 100644 --- a/core/src/main/java/com/volmit/iris/core/nms/INMS.java +++ b/core/src/main/java/com/volmit/iris/core/nms/INMS.java @@ -30,7 +30,9 @@ public class INMS { "1.20.5", "v1_20_R4", "1.20.6", "v1_20_R4", "1.21", "v1_21_R1", - "1.21.1", "v1_21_R1" + "1.21.1", "v1_21_R1", + "1.21.2", "v1_21_R2", + "1.21.3", "v1_21_R2" ); //@done private static final INMSBinding binding = bind(); 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 b2441dfd6..d75c87672 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 @@ -28,26 +28,27 @@ import com.volmit.iris.util.math.Vector3d; import com.volmit.iris.util.nbt.mca.palette.MCABiomeContainer; import com.volmit.iris.util.nbt.mca.palette.MCAPaletteAccess; import com.volmit.iris.util.nbt.tag.CompoundTag; -import org.bukkit.Chunk; -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.WorldCreator; +import org.bukkit.*; import org.bukkit.block.Biome; import org.bukkit.entity.Dolphin; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.generator.ChunkGenerator; +import org.bukkit.generator.structure.Structure; import org.bukkit.inventory.ItemStack; import java.awt.*; +import java.awt.Color; public interface INMSBinding { + boolean hasTile(Material material); + boolean hasTile(Location l); - CompoundTag serializeTile(Location location); + KMap serializeTile(Location location); - void deserializeTile(CompoundTag s, Location newPosition); + void deserializeTile(KMap s, Location newPosition); CompoundTag serializeEntity(Entity location); @@ -107,8 +108,6 @@ public interface INMSBinding { ItemStack applyCustomNbt(ItemStack itemStack, KMap customNbt) throws IllegalArgumentException; - void setTreasurePos(Dolphin dolphin, com.volmit.iris.core.nms.container.BlockPos pos); - void inject(long seed, Engine engine, World world) throws NoSuchFieldException, IllegalAccessException; Vector3d getBoundingbox(org.bukkit.entity.EntityType entity); @@ -124,4 +123,6 @@ public interface INMSBinding { default int getSpawnChunkCount(World world) { return 441; } + + KList getStructureKeys(); } diff --git a/core/src/main/java/com/volmit/iris/core/nms/datapack/DataVersion.java b/core/src/main/java/com/volmit/iris/core/nms/datapack/DataVersion.java index 7185b693d..f73850b0c 100644 --- a/core/src/main/java/com/volmit/iris/core/nms/datapack/DataVersion.java +++ b/core/src/main/java/com/volmit/iris/core/nms/datapack/DataVersion.java @@ -3,6 +3,7 @@ package com.volmit.iris.core.nms.datapack; import com.volmit.iris.core.nms.INMS; import com.volmit.iris.core.nms.datapack.v1192.DataFixerV1192; import com.volmit.iris.core.nms.datapack.v1206.DataFixerV1206; +import com.volmit.iris.core.nms.datapack.v1213.DataFixerV1213; import com.volmit.iris.util.collection.KMap; import lombok.AccessLevel; import lombok.Getter; @@ -13,7 +14,8 @@ import java.util.function.Supplier; @Getter public enum DataVersion { V1192("1.19.2", 10, DataFixerV1192::new), - V1205("1.20.6", 41, DataFixerV1206::new); + V1205("1.20.6", 41, DataFixerV1206::new), + V1213("1.21.3", 57, DataFixerV1213::new); private static final KMap cache = new KMap<>(); @Getter(AccessLevel.NONE) private final Supplier constructor; diff --git a/core/src/main/java/com/volmit/iris/core/nms/datapack/v1206/DataFixerV1206.java b/core/src/main/java/com/volmit/iris/core/nms/datapack/v1206/DataFixerV1206.java index 48883bfa5..d4c6e54a4 100644 --- a/core/src/main/java/com/volmit/iris/core/nms/datapack/v1206/DataFixerV1206.java +++ b/core/src/main/java/com/volmit/iris/core/nms/datapack/v1206/DataFixerV1206.java @@ -16,6 +16,8 @@ public class DataFixerV1206 implements IDataFixer { int spawnRarity = biome.getSpawnRarity(); if (spawnRarity > 0) { json.put("creature_spawn_probability", Math.min(spawnRarity/20d, 0.9999999)); + } else { + json.remove("creature_spawn_probability"); } var spawns = biome.getSpawns(); @@ -26,10 +28,10 @@ public class DataFixerV1206 implements IDataFixer { for (IrisBiomeCustomSpawn i : spawns) { JSONArray g = groups.computeIfAbsent(i.getGroup(), (k) -> new JSONArray()); JSONObject o = new JSONObject(); - o.put("type", "minecraft:" + i.getType().name().toLowerCase()); + o.put("type", i.getType().getKey()); o.put("weight", i.getWeight()); - o.put("minCount", Math.min(i.getMinCount()/20d, 0)); - o.put("maxCount", Math.min(i.getMaxCount()/20d, 0.9999999)); + o.put("minCount", i.getMinCount()); + o.put("maxCount", i.getMaxCount()); g.put(o); } diff --git a/core/src/main/java/com/volmit/iris/core/nms/datapack/v1213/DataFixerV1213.java b/core/src/main/java/com/volmit/iris/core/nms/datapack/v1213/DataFixerV1213.java new file mode 100644 index 000000000..7a762e77d --- /dev/null +++ b/core/src/main/java/com/volmit/iris/core/nms/datapack/v1213/DataFixerV1213.java @@ -0,0 +1,16 @@ +package com.volmit.iris.core.nms.datapack.v1213; + +import com.volmit.iris.core.nms.datapack.v1206.DataFixerV1206; +import com.volmit.iris.engine.object.IrisBiomeCustom; +import com.volmit.iris.util.json.JSONArray; +import com.volmit.iris.util.json.JSONObject; + +public class DataFixerV1213 extends DataFixerV1206 { + + @Override + public JSONObject fixCustomBiome(IrisBiomeCustom biome, JSONObject json) { + json = super.fixCustomBiome(biome, json); + json.put("carvers", new JSONArray()); + return json; + } +} 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 8ce6caba8..fa722e688 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 @@ -21,7 +21,6 @@ package com.volmit.iris.core.nms.v1X; 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.BlockPos; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; @@ -30,18 +29,17 @@ import com.volmit.iris.util.math.Vector3d; import com.volmit.iris.util.nbt.mca.palette.MCABiomeContainer; import com.volmit.iris.util.nbt.mca.palette.MCAPaletteAccess; import com.volmit.iris.util.nbt.tag.CompoundTag; -import org.bukkit.Chunk; -import org.bukkit.Location; -import org.bukkit.World; +import org.bukkit.*; import org.bukkit.block.Biome; -import org.bukkit.entity.Dolphin; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.generator.ChunkGenerator; +import org.bukkit.generator.structure.Structure; import org.bukkit.inventory.ItemStack; -import java.awt.*; +import java.awt.Color; +import java.util.stream.StreamSupport; public class NMSBinding1X implements INMSBinding { private static final boolean supportsCustomHeight = testCustomHeight(); @@ -61,16 +59,27 @@ public class NMSBinding1X implements INMSBinding { return false; } + @Override + public boolean hasTile(Material material) { + return false; + } + @Override public boolean hasTile(Location l) { return false; } @Override - public CompoundTag serializeTile(Location location) { + public KMap serializeTile(Location location) { return null; } + @Override + public void deserializeTile(KMap s, Location newPosition) { + + } + + @Override public void injectBiomesFromMantle(Chunk e, Mantle mantle) { @@ -81,11 +90,6 @@ public class NMSBinding1X implements INMSBinding { return itemStack; } - @Override - public void setTreasurePos(Dolphin dolphin, BlockPos pos) { - - } - @Override public void inject(long seed, Engine engine, World world) throws NoSuchFieldException, IllegalAccessException { @@ -106,8 +110,12 @@ public class NMSBinding1X implements INMSBinding { } @Override - public void deserializeTile(CompoundTag s, Location newPosition) { - + public KList getStructureKeys() { + var list = StreamSupport.stream(Registry.STRUCTURE.spliterator(), false) + .map(Structure::getKey) + .map(NamespacedKey::toString) + .toList(); + return new KList<>(list); } @Override diff --git a/core/src/main/java/com/volmit/iris/core/pregenerator/ChunkUpdater.java b/core/src/main/java/com/volmit/iris/core/pregenerator/ChunkUpdater.java index d89ad3340..e8c00d3df 100644 --- a/core/src/main/java/com/volmit/iris/core/pregenerator/ChunkUpdater.java +++ b/core/src/main/java/com/volmit/iris/core/pregenerator/ChunkUpdater.java @@ -1,16 +1,21 @@ package com.volmit.iris.core.pregenerator; import com.volmit.iris.Iris; +import com.volmit.iris.core.IrisSettings; +import com.volmit.iris.core.nms.container.Pair; import com.volmit.iris.core.tools.IrisToolbelt; +import com.volmit.iris.engine.data.cache.Cache; import com.volmit.iris.engine.framework.Engine; -import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.format.Form; +import com.volmit.iris.util.mantle.MantleFlag; import com.volmit.iris.util.math.M; +import com.volmit.iris.util.math.Position2; import com.volmit.iris.util.math.RollingSequence; -import com.volmit.iris.util.math.Spiraler; +import com.volmit.iris.util.profile.LoadBalancer; import com.volmit.iris.util.scheduling.J; import io.papermc.lib.PaperLib; +import org.bukkit.Bukkit; import org.bukkit.Chunk; import org.bukkit.World; @@ -23,53 +28,40 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; public class ChunkUpdater { - private AtomicBoolean paused; - private AtomicBoolean cancelled; - private KMap lastUse; - private final RollingSequence chunksPerSecond; - private final AtomicInteger worldheightsize; - private final AtomicInteger worldwidthsize; - private final AtomicInteger totalChunks; - private final AtomicInteger totalMaxChunks; - private final AtomicInteger totalMcaregions; - private final AtomicInteger position; - private AtomicInteger chunksProcessed; - private AtomicInteger chunksUpdated; - private AtomicLong startTime; - private ExecutorService executor; - private ExecutorService chunkExecutor; - private ScheduledExecutorService scheduler; - private CompletableFuture future; - private CountDownLatch latch; - private final Object pauseLock; + private final AtomicBoolean paused = new AtomicBoolean(); + private final AtomicBoolean cancelled = new AtomicBoolean(); + private final KMap> lastUse = new KMap<>(); + private final RollingSequence chunksPerSecond = new RollingSequence(5); + private final AtomicInteger totalMaxChunks = new AtomicInteger(); + private final AtomicInteger chunksProcessed = new AtomicInteger(); + private final AtomicInteger chunksProcessedLast = new AtomicInteger(); + private final AtomicInteger chunksUpdated = new AtomicInteger(); + private final AtomicBoolean serverEmpty = new AtomicBoolean(true); + private final AtomicLong lastCpsTime = new AtomicLong(M.ms()); + private final int coreLimit = (int) Math.max(Runtime.getRuntime().availableProcessors() * IrisSettings.get().getUpdater().getThreadMultiplier(), 1); + private final Semaphore semaphore = new Semaphore(256); + private final LoadBalancer loadBalancer = new LoadBalancer(semaphore, 256, IrisSettings.get().getUpdater().emptyMsRange); + private final AtomicLong startTime = new AtomicLong(); + private final Dimensions dimensions; + private final PregenTask task; + private final ExecutorService executor = Executors.newFixedThreadPool(coreLimit); + private final ExecutorService chunkExecutor = Executors.newFixedThreadPool(coreLimit); + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + private final CountDownLatch latch; private final Engine engine; private final World world; public ChunkUpdater(World world) { this.engine = IrisToolbelt.access(world).getEngine(); - this.chunksPerSecond = new RollingSequence(5); this.world = world; - this.lastUse = new KMap(); - this.worldheightsize = new AtomicInteger(calculateWorldDimensions(new File(world.getWorldFolder(), "region"), 1)); - this.worldwidthsize = new AtomicInteger(calculateWorldDimensions(new File(world.getWorldFolder(), "region"), 0)); - int m = Math.max(worldheightsize.get(), worldwidthsize.get()); - this.executor = Executors.newFixedThreadPool(Math.max(Runtime.getRuntime().availableProcessors() / 3, 1)); - this.chunkExecutor = Executors.newFixedThreadPool(Math.max(Runtime.getRuntime().availableProcessors() / 3, 1)); - this.scheduler = Executors.newScheduledThreadPool(1); - this.future = new CompletableFuture<>(); - this.startTime = new AtomicLong(); - this.worldheightsize.set(m); - this.worldwidthsize.set(m); - this.totalMaxChunks = new AtomicInteger((worldheightsize.get() / 16) * (worldwidthsize.get() / 16)); - this.chunksProcessed = new AtomicInteger(); - this.chunksUpdated = new AtomicInteger(); - this.position = new AtomicInteger(0); + this.dimensions = calculateWorldDimensions(new File(world.getWorldFolder(), "region")); + this.task = dimensions.task(); + this.totalMaxChunks.set(dimensions.count * 1024); this.latch = new CountDownLatch(totalMaxChunks.get()); - this.paused = new AtomicBoolean(false); - this.pauseLock = new Object(); - this.cancelled = new AtomicBoolean(false); - this.totalChunks = new AtomicInteger(0); - this.totalMcaregions = new AtomicInteger(0); + } + + public String getName() { + return world.getName(); } public int getChunks() { @@ -97,7 +89,6 @@ public class ChunkUpdater { cancelled.set(true); } - private void update() { Iris.info("Updating.."); try { @@ -106,11 +97,11 @@ public class ChunkUpdater { try { if (!paused.get()) { long eta = computeETA(); - long elapsedSeconds = (System.currentTimeMillis() - startTime.get()) / 1000; int processed = chunksProcessed.get(); - double cps = elapsedSeconds > 0 ? processed / (double) elapsedSeconds : 0; + double last = processed - chunksProcessedLast.getAndSet(processed); + double cps = last / ((M.ms() - lastCpsTime.getAndSet(M.ms())) / 1000d); chunksPerSecond.put(cps); - double percentage = ((double) chunksProcessed.get() / (double) totalMaxChunks.get()) * 100; + double percentage = ((double) processed / (double) totalMaxChunks.get()) * 100; if (!cancelled.get()) { Iris.info("Updated: " + Form.f(processed) + " of " + Form.f(totalMaxChunks.get()) + " (%.0f%%) " + Form.f(chunksPerSecond.getAverage()) + "/s, ETA: " + Form.duration(eta, 2), percentage); @@ -120,35 +111,20 @@ public class ChunkUpdater { e.printStackTrace(); } }, 0, 3, TimeUnit.SECONDS); + scheduler.scheduleAtFixedRate(this::unloadChunks, 0, 1, TimeUnit.SECONDS); + scheduler.scheduleAtFixedRate(() -> { + boolean empty = Bukkit.getOnlinePlayers().isEmpty(); + if (serverEmpty.getAndSet(empty) == empty) + return; + loadBalancer.setRange(empty ? IrisSettings.get().getUpdater().emptyMsRange : IrisSettings.get().getUpdater().defaultMsRange); + }, 0, 10, TimeUnit.SECONDS); - CompletableFuture.runAsync(() -> { - for (int i = 0; i < totalMaxChunks.get(); i++) { - if (paused.get()) { - synchronized (pauseLock) { - try { - pauseLock.wait(); - } catch (InterruptedException e) { - Iris.error("Interrupted while waiting for executor: "); - e.printStackTrace(); - break; - } - } - } - executor.submit(() -> { - if (!cancelled.get()) { - processNextChunk(); - } - latch.countDown(); - }); - } - }).thenRun(() -> { - try { - latch.await(); - close(); - } catch (Exception e) { - Thread.currentThread().interrupt(); - } - }); + var t = new Thread(() -> { + run(); + close(); + }, "Iris Chunk Updater - " + world.getName()); + t.setPriority(Thread.MAX_PRIORITY); + t.start(); } catch (Exception e) { e.printStackTrace(); @@ -157,14 +133,16 @@ public class ChunkUpdater { public void close() { try { - unloadAndSaveAllChunks(); + loadBalancer.close(); + semaphore.acquire(256); + executor.shutdown(); executor.awaitTermination(5, TimeUnit.SECONDS); chunkExecutor.shutdown(); chunkExecutor.awaitTermination(5, TimeUnit.SECONDS); scheduler.shutdownNow(); - } catch (Exception ignored) { - } + unloadAndSaveAllChunks(); + } catch (Exception ignored) {} if (cancelled.get()) { Iris.info("Updated: " + Form.f(chunksUpdated.get()) + " Chunks"); Iris.info("Irritated: " + Form.f(chunksProcessed.get()) + " of " + Form.f(totalMaxChunks.get())); @@ -175,18 +153,69 @@ public class ChunkUpdater { } } - private void processNextChunk() { - int pos = position.getAndIncrement(); - int[] coords = getChunk(pos); - if (loadChunksIfGenerated(coords[0], coords[1])) { - Chunk c = world.getChunkAt(coords[0], coords[1]); - engine.updateChunk(c); - chunksUpdated.incrementAndGet(); + private void run() { + task.iterateRegions((rX, rZ) -> { + if (cancelled.get()) + return; + + while (paused.get()) { + J.sleep(50); + } + + if (rX < dimensions.min.getX() || rX > dimensions.max.getX() || rZ < dimensions.min.getZ() || rZ > dimensions.max.getZ()) { + return; + } + + PregenTask.iterateRegion(rX, rZ, (x, z) -> { + while (paused.get() && !cancelled.get()) { + J.sleep(50); + } + + try { + semaphore.acquire(); + } catch (InterruptedException ignored) { + return; + } + chunkExecutor.submit(() -> { + try { + if (!cancelled.get()) + processChunk(x, z); + } finally { + latch.countDown(); + semaphore.release(); + } + }); + }); + }); + } + + private void processChunk(int x, int z) { + if (!loadChunksIfGenerated(x, z)) { + chunksProcessed.getAndIncrement(); + return; + } + + try { + Chunk c = world.getChunkAt(x, z); + engine.getMantle().getMantle().getChunk(c); + engine.updateChunk(c); + + for (int xx = -1; xx <= 1; xx++) { + for (int zz = -1; zz <= 1; zz++) { + var counter = lastUse.get(Cache.key(x + xx, z + zz)); + if (counter != null) counter.getB().decrementAndGet(); + } + } + } finally { + chunksUpdated.incrementAndGet(); + chunksProcessed.getAndIncrement(); } - chunksProcessed.getAndIncrement(); } private boolean loadChunksIfGenerated(int x, int z) { + if (engine.getMantle().getMantle().hasFlag(x, z, MantleFlag.ETCHED)) + return false; + for (int dx = -1; dx <= 1; dx++) { for (int dz = -1; dz <= 1; dz++) { if (!PaperLib.isChunkGenerated(world, x + dx, z + dz)) { @@ -196,45 +225,79 @@ public class ChunkUpdater { } AtomicBoolean generated = new AtomicBoolean(true); - KList> futures = new KList<>(9); + CountDownLatch latch = new CountDownLatch(9); for (int dx = -1; dx <= 1; dx++) { for (int dz = -1; dz <= 1; dz++) { int xx = x + dx; int zz = z + dz; - futures.add(chunkExecutor.submit(() -> { - Chunk c; + executor.submit(() -> { try { - c = PaperLib.getChunkAtAsync(world, xx, zz, false).get(); - } catch (InterruptedException | ExecutionException e) { - generated.set(false); - return; - } - if (!c.isLoaded()) { - CountDownLatch latch = new CountDownLatch(1); - J.s(() -> { - c.load(false); - latch.countDown(); - }); + Chunk c; try { - latch.await(); - } catch (InterruptedException ignored) {} + c = PaperLib.getChunkAtAsync(world, xx, zz, false, true) + .thenApply(chunk -> { + if (chunk != null) + chunk.addPluginChunkTicket(Iris.instance); + return chunk; + }).get(); + } catch (InterruptedException | ExecutionException e) { + generated.set(false); + return; + } + + if (c == null) { + generated.set(false); + return; + } + + if (!c.isLoaded()) { + var future = J.sfut(() -> c.load(false)); + if (future != null) future.join(); + } + + if (!PaperLib.isChunkGenerated(c.getWorld(), xx, zz)) + generated.set(false); + + var pair = lastUse.computeIfAbsent(Cache.key(c), k -> new Pair<>(0L, new AtomicInteger(-1))); + pair.setA(M.ms()); + pair.getB().updateAndGet(i -> i == -1 ? 1 : ++i); + } finally { + latch.countDown(); } - if (!c.isGenerated()) { - generated.set(false); - } - lastUse.put(c, M.ms()); - })); + }); } } - while (!futures.isEmpty()) { - futures.removeIf(Future::isDone); - try { - Thread.sleep(50); - } catch (InterruptedException ignored) {} + + try { + latch.await(); + } catch (InterruptedException e) { + Iris.info("Interrupted while waiting for chunks to load"); } return generated.get(); } + private synchronized void unloadChunks() { + for (var key : new ArrayList<>(lastUse.keySet())) { + if (key == null) continue; + var pair = lastUse.get(key); + if (pair == null) continue; + var lastUseTime = pair.getA(); + var counter = pair.getB(); + if (lastUseTime == null || counter == null) + continue; + + if (M.ms() - lastUseTime >= 5000 && counter.get() == 0) { + int x = Cache.keyX(key); + int z = Cache.keyZ(key); + J.s(() -> { + world.removePluginChunkTicket(x, z, Iris.instance); + world.unloadChunk(x, z); + lastUse.remove(key); + }); + } + } + } + private void unloadAndSaveAllChunks() { try { J.sfut(() -> { @@ -243,13 +306,7 @@ public class ChunkUpdater { return; } - for (Chunk i : new ArrayList<>(lastUse.keySet())) { - Long lastUseTime = lastUse.get(i); - if (lastUseTime != null && M.ms() - lastUseTime >= 5000) { - i.unload(); - lastUse.remove(i); - } - } + unloadChunks(); world.save(); }).get(); } catch (Throwable e) { @@ -266,7 +323,7 @@ public class ChunkUpdater { ); } - public int calculateWorldDimensions(File regionDir, Integer o) { + private Dimensions calculateWorldDimensions(File regionDir) { File[] files = regionDir.listFiles((dir, name) -> name.endsWith(".mca")); int minX = Integer.MAX_VALUE; @@ -279,40 +336,23 @@ public class ChunkUpdater { int x = Integer.parseInt(parts[1]); int z = Integer.parseInt(parts[2]); - if (x < minX) minX = x; - if (x > maxX) maxX = x; - if (z < minZ) minZ = z; - if (z > maxZ) maxZ = z; + minX = Math.min(minX, x); + maxX = Math.max(maxX, x); + minZ = Math.min(minZ, z); + maxZ = Math.max(maxZ, z); } + int oX = minX + ((maxX - minX) / 2); + int oZ = minZ + ((maxZ - minZ) / 2); - int height = (maxX - minX + 1) * 32 * 16; - int width = (maxZ - minZ + 1) * 32 * 16; + int height = maxX - minX + 1; + int width = maxZ - minZ + 1; - if (o == 1) { - return height; - } - if (o == 0) { - return width; - } - return 0; + return new Dimensions(new Position2(minX, minZ), new Position2(maxX, maxZ), height * width, PregenTask.builder() + .width((int) Math.ceil(width / 2d)) + .height((int) Math.ceil(height / 2d)) + .center(new Position2(oX, oZ)) + .build()); } - public int[] getChunk(int position) { - int p = -1; - AtomicInteger xx = new AtomicInteger(); - AtomicInteger zz = new AtomicInteger(); - Spiraler s = new Spiraler(worldheightsize.get() * 2, worldwidthsize.get() * 2, (x, z) -> { - xx.set(x); - zz.set(z); - }); - - while (s.hasNext() && p++ < position) { - s.next(); - } - int[] coords = new int[2]; - coords[0] = xx.get(); - coords[1] = zz.get(); - - return coords; - } + private record Dimensions(Position2 min, Position2 max, int count, PregenTask task) { } } diff --git a/core/src/main/java/com/volmit/iris/core/pregenerator/DeepSearchPregenerator.java b/core/src/main/java/com/volmit/iris/core/pregenerator/DeepSearchPregenerator.java index c60724d2a..e9d2b8f74 100644 --- a/core/src/main/java/com/volmit/iris/core/pregenerator/DeepSearchPregenerator.java +++ b/core/src/main/java/com/volmit/iris/core/pregenerator/DeepSearchPregenerator.java @@ -15,7 +15,6 @@ import com.volmit.iris.util.math.RollingSequence; import com.volmit.iris.util.math.Spiraler; import com.volmit.iris.util.scheduling.ChronoLatch; import com.volmit.iris.util.scheduling.J; -import lombok.Builder; import lombok.Data; import lombok.Getter; import org.bukkit.Bukkit; @@ -261,14 +260,14 @@ public class DeepSearchPregenerator extends Thread implements Listener { } @Data - @Builder + @lombok.Builder public static class DeepSearchJob { private World world; - @Builder.Default + @lombok.Builder.Default private int radiusBlocks = 5000; - @Builder.Default + @lombok.Builder.Default private int position = 0; - @Builder.Default + @lombok.Builder.Default boolean paused = false; } } diff --git a/core/src/main/java/com/volmit/iris/core/pregenerator/LazyPregenerator.java b/core/src/main/java/com/volmit/iris/core/pregenerator/LazyPregenerator.java index 20e3ace96..dc58f2ed5 100644 --- a/core/src/main/java/com/volmit/iris/core/pregenerator/LazyPregenerator.java +++ b/core/src/main/java/com/volmit/iris/core/pregenerator/LazyPregenerator.java @@ -12,7 +12,6 @@ import com.volmit.iris.util.math.Spiraler; import com.volmit.iris.util.scheduling.ChronoLatch; import com.volmit.iris.util.scheduling.J; import io.papermc.lib.PaperLib; -import lombok.Builder; import lombok.Data; import lombok.Getter; import org.bukkit.Bukkit; @@ -264,22 +263,22 @@ public class LazyPregenerator extends Thread implements Listener { } @Data - @Builder + @lombok.Builder public static class LazyPregenJob { private String world; - @Builder.Default + @lombok.Builder.Default private int healingPosition = 0; - @Builder.Default + @lombok.Builder.Default private boolean healing = false; - @Builder.Default + @lombok.Builder.Default private int chunksPerMinute = 32; - @Builder.Default + @lombok.Builder.Default private int radiusBlocks = 5000; - @Builder.Default + @lombok.Builder.Default private int position = 0; - @Builder.Default + @lombok.Builder.Default boolean silent = false; - @Builder.Default + @lombok.Builder.Default boolean paused = false; } } diff --git a/core/src/main/java/com/volmit/iris/core/pregenerator/TurboPregenerator.java b/core/src/main/java/com/volmit/iris/core/pregenerator/TurboPregenerator.java index ba0c901bb..4588597c3 100644 --- a/core/src/main/java/com/volmit/iris/core/pregenerator/TurboPregenerator.java +++ b/core/src/main/java/com/volmit/iris/core/pregenerator/TurboPregenerator.java @@ -18,7 +18,6 @@ import com.volmit.iris.util.scheduling.ChronoLatch; import com.volmit.iris.util.scheduling.J; import com.volmit.iris.util.scheduling.PrecisionStopwatch; import io.papermc.lib.PaperLib; -import lombok.Builder; import lombok.Data; import lombok.Getter; import org.apache.logging.log4j.core.util.ExecutorServices; @@ -328,14 +327,14 @@ public class TurboPregenerator extends Thread implements Listener { } @Data - @Builder + @lombok.Builder public static class TurboPregenJob { private String world; - @Builder.Default + @lombok.Builder.Default private int radiusBlocks = 5000; - @Builder.Default + @lombok.Builder.Default private int position = 0; - @Builder.Default + @lombok.Builder.Default boolean paused = false; } } diff --git a/core/src/main/java/com/volmit/iris/core/pregenerator/methods/AsyncPregenMethod.java b/core/src/main/java/com/volmit/iris/core/pregenerator/methods/AsyncPregenMethod.java index 80b0a3dfd..8496f924c 100644 --- a/core/src/main/java/com/volmit/iris/core/pregenerator/methods/AsyncPregenMethod.java +++ b/core/src/main/java/com/volmit/iris/core/pregenerator/methods/AsyncPregenMethod.java @@ -22,28 +22,23 @@ import com.volmit.iris.Iris; import com.volmit.iris.core.pregenerator.PregenListener; import com.volmit.iris.core.pregenerator.PregeneratorMethod; import com.volmit.iris.core.tools.IrisToolbelt; -import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.mantle.Mantle; import com.volmit.iris.util.math.M; import com.volmit.iris.util.parallel.MultiBurst; import com.volmit.iris.util.scheduling.J; import io.papermc.lib.PaperLib; -import org.bukkit.Bukkit; import org.bukkit.Chunk; import org.bukkit.World; import java.util.ArrayList; -import java.util.HashMap; import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.Future; +import java.util.concurrent.Semaphore; public class AsyncPregenMethod implements PregeneratorMethod { private final World world; private final MultiBurst burst; - private final KList> future; + private final Semaphore semaphore; private final Map lastUse; public AsyncPregenMethod(World world, int threads) { @@ -52,8 +47,8 @@ public class AsyncPregenMethod implements PregeneratorMethod { } this.world = world; - burst = MultiBurst.burst; - future = new KList<>(1024); + burst = new MultiBurst("Iris Async Pregen", Thread.MIN_PRIORITY); + semaphore = new Semaphore(256); this.lastUse = new KMap<>(); } @@ -67,7 +62,7 @@ public class AsyncPregenMethod implements PregeneratorMethod { for (Chunk i : new ArrayList<>(lastUse.keySet())) { Long lastUseTime = lastUse.get(i); - if (lastUseTime != null && M.ms() - lastUseTime >= 10000) { + if (!i.isLoaded() || (lastUseTime != null && M.ms() - lastUseTime >= 10000)) { i.unload(); lastUse.remove(i); } @@ -81,56 +76,19 @@ public class AsyncPregenMethod implements PregeneratorMethod { private void completeChunk(int x, int z, PregenListener listener) { try { - future.add(PaperLib.getChunkAtAsync(world, x, z, true).thenApply((i) -> { - if (i == null) { - - } - Chunk c = Bukkit.getWorld(world.getUID()).getChunkAt(x, z); - lastUse.put(c, M.ms()); + PaperLib.getChunkAtAsync(world, x, z, true).thenAccept((i) -> { + lastUse.put(i, M.ms()); listener.onChunkGenerated(x, z); listener.onChunkCleaned(x, z); - return 0; - })); + }).get(); + } catch (InterruptedException ignored) { } catch (Throwable e) { e.printStackTrace(); + } finally { + semaphore.release(); } } - private void waitForChunksPartial(int maxWaiting) { - future.removeWhere(Objects::isNull); - - while (future.size() > maxWaiting) { - try { - Future i = future.remove(0); - - if (i == null) { - continue; - } - - i.get(); - } catch (Throwable e) { - e.printStackTrace(); - } - } - } - - private void waitForChunks() { - for (Future i : future.copy()) { - if (i == null) { - continue; - } - - try { - i.get(); - future.remove(i); - } catch (Throwable e) { - e.printStackTrace(); - } - } - - future.removeWhere(Objects::isNull); - } - @Override public void init() { unloadAndSaveAllChunks(); @@ -143,13 +101,13 @@ public class AsyncPregenMethod implements PregeneratorMethod { @Override public void close() { - waitForChunks(); + semaphore.acquireUninterruptibly(256); unloadAndSaveAllChunks(); + burst.close(); } @Override public void save() { - waitForChunksPartial(256); unloadAndSaveAllChunks(); } @@ -166,10 +124,12 @@ public class AsyncPregenMethod implements PregeneratorMethod { @Override public void generateChunk(int x, int z, PregenListener listener) { listener.onChunkGenerating(x, z); - if (future.size() > 256) { - waitForChunksPartial(256); + try { + semaphore.acquire(); + } catch (InterruptedException e) { + return; } - future.add(burst.complete(() -> completeChunk(x, z, listener))); + burst.complete(() -> completeChunk(x, z, listener)); } @Override diff --git a/core/src/main/java/com/volmit/iris/core/project/SchemaBuilder.java b/core/src/main/java/com/volmit/iris/core/project/SchemaBuilder.java index 7cdf4dd76..398d1630b 100644 --- a/core/src/main/java/com/volmit/iris/core/project/SchemaBuilder.java +++ b/core/src/main/java/com/volmit/iris/core/project/SchemaBuilder.java @@ -28,14 +28,17 @@ import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.data.B; import com.volmit.iris.util.json.JSONArray; import com.volmit.iris.util.json.JSONObject; +import com.volmit.iris.util.reflect.OldEnum; import org.bukkit.enchantments.Enchantment; import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; import java.awt.*; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.List; import java.util.Map; +import java.util.function.Function; import java.util.stream.Collectors; public class SchemaBuilder { @@ -43,7 +46,6 @@ public class SchemaBuilder { private static final String SYMBOL_TYPE__N = ""; private static final JSONArray POTION_TYPES = getPotionTypes(); private static final JSONArray ENCHANT_TYPES = getEnchantTypes(); - private static final JSONArray ITEM_TYPES = new JSONArray(B.getItemTypes()); private static final JSONArray FONT_TYPES = new JSONArray(GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames()); private final KMap definitions; private final Class root; @@ -261,7 +263,7 @@ public class SchemaBuilder { if (!definitions.containsKey(key)) { JSONObject j = new JSONObject(); - j.put("enum", ITEM_TYPES); + j.put("enum", B.getItemTypes()); definitions.put(key, j); } @@ -309,6 +311,24 @@ public class SchemaBuilder { fancyType = "Enchantment Type"; prop.put("$ref", "#/definitions/" + key); description.add(SYMBOL_TYPE__N + " Must be a valid Enchantment Type (use ctrl+space for auto complete!)"); + } else if (k.isAnnotationPresent(RegistryListFunction.class)) { + var functionClass = k.getDeclaredAnnotation(RegistryListFunction.class).value(); + try { + var instance = functionClass.getDeclaredConstructor().newInstance(); + String key = instance.key(); + fancyType = instance.fancyName(); + + if (!definitions.containsKey(key)) { + JSONObject j = new JSONObject(); + j.put("enum", instance.apply(data)); + definitions.put(key, j); + } + + prop.put("$ref", "#/definitions/" + key); + description.add(SYMBOL_TYPE__N + " Must be a valid " + fancyType + " (use ctrl+space for auto complete!)"); + } catch (Throwable e) { + Iris.error("Could not execute apply method in " + functionClass.getName()); + } } else if (k.getType().equals(PotionEffectType.class)) { String key = "enum-potion-effect-type"; @@ -323,38 +343,9 @@ public class SchemaBuilder { description.add(SYMBOL_TYPE__N + " Must be a valid Potion Effect Type (use ctrl+space for auto complete!)"); } else if (k.getType().isEnum()) { - fancyType = k.getType().getSimpleName().replaceAll("\\QIris\\E", ""); - JSONArray a = new JSONArray(); - boolean advanced = k.getType().isAnnotationPresent(Desc.class); - for (Object gg : k.getType().getEnumConstants()) { - if (advanced) { - try { - JSONObject j = new JSONObject(); - String name = ((Enum) gg).name(); - j.put("const", name); - Desc dd = k.getType().getField(name).getAnnotation(Desc.class); - j.put("description", dd == null ? ("No Description for " + name) : dd.value()); - a.put(j); - } catch (Throwable e) { - Iris.reportError(e); - e.printStackTrace(); - } - } else { - a.put(((Enum) gg).name()); - } - } - - String key = (advanced ? "oneof-" : "") + "enum-" + k.getType().getCanonicalName().replaceAll("\\Q.\\E", "-").toLowerCase(); - - if (!definitions.containsKey(key)) { - JSONObject j = new JSONObject(); - j.put(advanced ? "oneOf" : "enum", a); - definitions.put(key, j); - } - - prop.put("$ref", "#/definitions/" + key); - description.add(SYMBOL_TYPE__N + " Must be a valid " + k.getType().getSimpleName().replaceAll("\\QIris\\E", "") + " (use ctrl+space for auto complete!)"); - + fancyType = addEnum(k.getType(), prop, description, k.getType().getEnumConstants(), o -> ((Enum) o).name()); + } else if (OldEnum.isOldEnum(k.getType())) { + fancyType = addEnum(k.getType(), prop, description, OldEnum.values(k.getType()), OldEnum::name); } } case "object" -> { @@ -449,7 +440,7 @@ public class SchemaBuilder { if (!definitions.containsKey(key)) { JSONObject j = new JSONObject(); - j.put("enum", ITEM_TYPES); + j.put("enum", B.getItemTypes()); definitions.put(key, j); } @@ -500,39 +491,9 @@ public class SchemaBuilder { prop.put("items", items); description.add(SYMBOL_TYPE__N + " Must be a valid Potion Effect Type (use ctrl+space for auto complete!)"); } else if (t.type().isEnum()) { - fancyType = "List of " + t.type().getSimpleName().replaceAll("\\QIris\\E", "") + "s"; - JSONArray a = new JSONArray(); - boolean advanced = t.type().isAnnotationPresent(Desc.class); - for (Object gg : t.type().getEnumConstants()) { - if (advanced) { - try { - JSONObject j = new JSONObject(); - String name = ((Enum) gg).name(); - j.put("const", name); - Desc dd = t.type().getField(name).getAnnotation(Desc.class); - j.put("description", dd == null ? ("No Description for " + name) : dd.value()); - a.put(j); - } catch (Throwable e) { - Iris.reportError(e); - e.printStackTrace(); - } - } else { - a.put(((Enum) gg).name()); - } - } - - String key = (advanced ? "oneof-" : "") + "enum-" + t.type().getCanonicalName().replaceAll("\\Q.\\E", "-").toLowerCase(); - - if (!definitions.containsKey(key)) { - JSONObject j = new JSONObject(); - j.put(advanced ? "oneOf" : "enum", a); - definitions.put(key, j); - } - - JSONObject items = new JSONObject(); - items.put("$ref", "#/definitions/" + key); - prop.put("items", items); - description.add(SYMBOL_TYPE__N + " Must be a valid " + t.type().getSimpleName().replaceAll("\\QIris\\E", "") + " (use ctrl+space for auto complete!)"); + fancyType = addEnumList(prop, description, t, t.type().getEnumConstants(), o -> ((Enum) o).name()); + } else if (OldEnum.isOldEnum(t.type())) { + fancyType = addEnumList(prop, description, t, OldEnum.values(t.type()), OldEnum::name); } } } @@ -565,7 +526,7 @@ public class SchemaBuilder { if (value instanceof List) { d.add(" "); d.add("* Default Value is an empty list"); - } else if (!cl.isPrimitive() && !(value instanceof Number) && !(value instanceof String) && !(cl.isEnum())) { + } else if (!cl.isPrimitive() && !(value instanceof Number) && !(value instanceof String) && !(cl.isEnum()) && !OldEnum.isOldEnum(cl)) { d.add(" "); d.add("* Default Value is a default object (create this object to see default properties)"); } else { @@ -611,6 +572,50 @@ public class SchemaBuilder { return prop; } + @NotNull + private String addEnumList(JSONObject prop, KList description, ArrayType t, Object[] values, Function function) { + JSONObject items = new JSONObject(); + var s = addEnum(t.type(), items, description, values, function); + prop.put("items", items); + + return "List of " + s + "s"; + } + + @NotNull + private String addEnum(Class type, JSONObject prop, KList description, Object[] values, Function function) { + JSONArray a = new JSONArray(); + boolean advanced = type.isAnnotationPresent(Desc.class); + for (Object gg : values) { + if (advanced) { + try { + JSONObject j = new JSONObject(); + String name = function.apply(gg); + j.put("const", name); + Desc dd = type.getField(name).getAnnotation(Desc.class); + j.put("description", dd == null ? ("No Description for " + name) : dd.value()); + a.put(j); + } catch (Throwable e) { + Iris.reportError(e); + e.printStackTrace(); + } + } else { + a.put(function.apply(gg)); + } + } + + String key = (advanced ? "oneof-" : "") + "enum-" + type.getCanonicalName().replaceAll("\\Q.\\E", "-").toLowerCase(); + + if (!definitions.containsKey(key)) { + JSONObject j = new JSONObject(); + j.put(advanced ? "oneOf" : "enum", a); + definitions.put(key, j); + } + + prop.put("$ref", "#/definitions/" + key); + description.add(SYMBOL_TYPE__N + " Must be a valid " + type.getSimpleName().replaceAll("\\QIris\\E", "") + " (use ctrl+space for auto complete!)"); + return type.getSimpleName().replaceAll("\\QIris\\E", ""); + } + private String getType(Class c) { if (c.equals(int.class) || c.equals(Integer.class) || c.equals(long.class) || c.equals(Long.class)) { return "integer"; @@ -624,7 +629,7 @@ public class SchemaBuilder { return "boolean"; } - if (c.equals(String.class) || c.isEnum() || c.equals(Enchantment.class) || c.equals(PotionEffectType.class)) { + if (c.equals(String.class) || c.isEnum() || OldEnum.isOldEnum(c) || c.equals(Enchantment.class) || c.equals(PotionEffectType.class)) { return "string"; } diff --git a/core/src/main/java/com/volmit/iris/core/safeguard/ServerBootSFG.java b/core/src/main/java/com/volmit/iris/core/safeguard/ServerBootSFG.java index 6e88835be..a1de18768 100644 --- a/core/src/main/java/com/volmit/iris/core/safeguard/ServerBootSFG.java +++ b/core/src/main/java/com/volmit/iris/core/safeguard/ServerBootSFG.java @@ -20,12 +20,11 @@ import java.util.Map; import java.util.StringJoiner; import static com.volmit.iris.Iris.getJavaVersion; -import static com.volmit.iris.Iris.instance; import static com.volmit.iris.core.safeguard.IrisSafeguard.*; public class ServerBootSFG { public static final Map incompatibilities = new HashMap<>(); - public static boolean isJDK17 = true; + public static boolean isCorrectJDK = true; public static boolean hasEnoughDiskSpace = true; public static boolean isJRE = false; public static boolean hasPrivileges = true; @@ -87,8 +86,8 @@ public class ServerBootSFG { severityHigh++; } - if (!List.of(17, 21).contains(getJavaVersion())) { - isJDK17 = false; + if (!List.of(21).contains(getJavaVersion())) { + isCorrectJDK = false; joiner.add("Unsupported Java version"); severityMedium++; } diff --git a/core/src/main/java/com/volmit/iris/core/safeguard/UtilsSFG.java b/core/src/main/java/com/volmit/iris/core/safeguard/UtilsSFG.java index a10152651..c9284681c 100644 --- a/core/src/main/java/com/volmit/iris/core/safeguard/UtilsSFG.java +++ b/core/src/main/java/com/volmit/iris/core/safeguard/UtilsSFG.java @@ -37,7 +37,7 @@ public class UtilsSFG { } if (ServerBootSFG.unsuportedversion) { Iris.safeguard(C.RED + "Server Version"); - Iris.safeguard(C.RED + "- Iris only supports 1.19.2 > 1.21.1"); + Iris.safeguard(C.RED + "- Iris only supports 1.19.2 > 1.21.3"); } if (!ServerBootSFG.passedserversoftware) { Iris.safeguard(C.YELLOW + "Unsupported Server Software"); @@ -51,13 +51,13 @@ public class UtilsSFG { Iris.safeguard(C.YELLOW + "Insufficient Disk Space"); Iris.safeguard(C.YELLOW + "- The server has insufficient Free DiskSpace to run iris required 3GB+."); } - if (!ServerBootSFG.isJDK17) { + if (!ServerBootSFG.isCorrectJDK) { Iris.safeguard(C.YELLOW + "Unsupported java version"); - Iris.safeguard(C.YELLOW + "- Please consider using JDK 17 (or 21 for 1.20.6) Instead of JDK " + Iris.getJavaVersion()); + Iris.safeguard(C.YELLOW + "- Please consider using JDK 21 Instead of JDK " + Iris.getJavaVersion()); } if (ServerBootSFG.isJRE) { Iris.safeguard(C.YELLOW + "Unsupported Server JDK"); - Iris.safeguard(C.YELLOW + "- Please consider using JDK 17 (or 21 for 1.20.6) Instead of JRE " + Iris.getJavaVersion()); + Iris.safeguard(C.YELLOW + "- Please consider using JDK 21 Instead of JRE " + Iris.getJavaVersion()); } } } diff --git a/core/src/main/java/com/volmit/iris/core/service/DolphinSVC.java b/core/src/main/java/com/volmit/iris/core/service/DolphinSVC.java deleted file mode 100644 index 0f4aa53dc..000000000 --- a/core/src/main/java/com/volmit/iris/core/service/DolphinSVC.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.volmit.iris.core.service; - -import com.volmit.iris.core.nms.INMS; -import com.volmit.iris.core.nms.container.BlockPos; -import com.volmit.iris.core.tools.IrisToolbelt; -import com.volmit.iris.engine.framework.Engine; -import com.volmit.iris.util.documentation.ChunkCoordinates; -import com.volmit.iris.util.function.Consumer4; -import com.volmit.iris.util.math.Spiraler; -import com.volmit.iris.util.matter.MatterStructurePOI; -import com.volmit.iris.util.plugin.IrisService; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.SoundCategory; -import org.bukkit.entity.Dolphin; -import org.bukkit.entity.EntityType; -import org.bukkit.event.EventHandler; -import org.bukkit.event.player.PlayerInteractEntityEvent; -import org.bukkit.generator.structure.StructureType; - -import java.util.concurrent.atomic.AtomicReference; - -public class DolphinSVC implements IrisService { - - @Override - public void onEnable() { - - } - - @Override - public void onDisable() { - - } - - @EventHandler - public void on(PlayerInteractEntityEvent event) { - if (!IrisToolbelt.isIrisWorld(event.getPlayer().getWorld())) { - return; - } - - Material hand = event.getPlayer().getInventory().getItem(event.getHand()).getType(); - if (event.getRightClicked().getType().equals(EntityType.DOLPHIN) && (hand.equals(Material.TROPICAL_FISH) || hand.equals(Material.PUFFERFISH) || hand.equals(Material.COD) || hand.equals(Material.SALMON))) { - Engine e = IrisToolbelt.access(event.getPlayer().getWorld()).getEngine(); - searchNearestTreasure(e, event.getPlayer().getLocation().getBlockX() >> 4, event.getPlayer().getLocation().getBlockZ() >> 4, e.getMantle().getRadius() - 1, StructureType.BURIED_TREASURE, (x, y, z, p) -> { - event.setCancelled(true); - Dolphin d = (Dolphin) event.getRightClicked(); - INMS.get().setTreasurePos(d, new BlockPos(x, y, z)); - d.getWorld().playSound(d, Sound.ENTITY_DOLPHIN_EAT, SoundCategory.NEUTRAL, 1, 1); - }); - - } - } - - @ChunkCoordinates - public void findTreasure(Engine engine, int chunkX, int chunkY, StructureType type, Consumer4 consumer) { - AtomicReference ref = new AtomicReference<>(); - engine.getMantle().getMantle().iterateChunk(chunkX, chunkY, MatterStructurePOI.class, ref.get() == null ? (x, y, z, d) -> { - if (d.getType().equals(type.getKey().getKey())) { - ref.set(d); - consumer.accept(x, y, z, d); - } - } : (x, y, z, d) -> { - }); - } - - @ChunkCoordinates - public void searchNearestTreasure(Engine engine, int chunkX, int chunkY, int radius, StructureType type, Consumer4 consumer) { - AtomicReference ref = new AtomicReference<>(); - new Spiraler(radius * 2, radius * 2, (x, z) -> findTreasure(engine, x, z, type, ref.get() == null ? (i, d, g, a) -> { - ref.set(a); - consumer.accept(i, d, g, a); - } : (i, d, g, a) -> { - })).setOffset(chunkX, chunkY).drain(); - } -} diff --git a/core/src/main/java/com/volmit/iris/core/service/ExternalDataSVC.java b/core/src/main/java/com/volmit/iris/core/service/ExternalDataSVC.java index 4c31f2312..c71224a99 100644 --- a/core/src/main/java/com/volmit/iris/core/service/ExternalDataSVC.java +++ b/core/src/main/java/com/volmit/iris/core/service/ExternalDataSVC.java @@ -26,6 +26,7 @@ import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.plugin.IrisService; import lombok.Data; +import lombok.NonNull; import org.bukkit.Bukkit; import org.bukkit.block.Block; import org.bukkit.block.data.BlockData; @@ -46,9 +47,13 @@ public class ExternalDataSVC implements IrisService { Iris.info("Loading ExternalDataProvider..."); Bukkit.getPluginManager().registerEvents(this, Iris.instance); - providers.add(new OraxenDataProvider()); - if (Bukkit.getPluginManager().getPlugin("Oraxen") != null) { - Iris.info("Oraxen found, loading OraxenDataProvider..."); + providers.add(new NexoDataProvider()); + if (Bukkit.getPluginManager().getPlugin("Nexo") != null) { + Iris.info("Nexo found, loading NexoDataProvider..."); + } + providers.add(new MythicCrucibleDataProvider()); + if (Bukkit.getPluginManager().getPlugin("MythicCrucible") != null) { + Iris.info("MythicCrucible found, loading MythicCrucibleDataProvider..."); } providers.add(new ItemAdderDataProvider()); if (Bukkit.getPluginManager().getPlugin("ItemAdder") != null) { @@ -95,6 +100,18 @@ public class ExternalDataSVC implements IrisService { } } + public void registerProvider(@NonNull ExternalDataProvider provider) { + String plugin = provider.getPluginId(); + if (providers.stream().map(ExternalDataProvider::getPluginId).anyMatch(plugin::equals)) + throw new IllegalArgumentException("A provider with the same plugin id already exists."); + + providers.add(provider); + if (provider.isReady()) { + activeProviders.add(provider); + provider.init(); + } + } + public Optional getBlockData(final Identifier key) { var pair = parseState(key); Identifier mod = pair.getA(); 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 a4ad38bd9..b2d67fd02 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 @@ -63,7 +63,12 @@ public class StudioSVC implements IrisService { if (!f.exists()) { Iris.info("Downloading Default Pack " + pack); - downloadSearch(Iris.getSender(), pack, false); + if (pack.equals("overworld")) { + String url = "https://github.com/IrisDimensions/overworld/releases/download/" + Iris.OVERWORLD_TAG + "/overworld.zip"; + Iris.service(StudioSVC.class).downloadRelease(Iris.getSender(), url, false, false); + } else { + downloadSearch(Iris.getSender(), pack, false); + } } }); } diff --git a/core/src/main/java/com/volmit/iris/core/service/TreeSVC.java b/core/src/main/java/com/volmit/iris/core/service/TreeSVC.java index 72b5593c4..c9ee3c62f 100644 --- a/core/src/main/java/com/volmit/iris/core/service/TreeSVC.java +++ b/core/src/main/java/com/volmit/iris/core/service/TreeSVC.java @@ -183,7 +183,7 @@ public class TreeSVC implements IrisService { } @Override - public void setTile(int xx, int yy, int zz, TileData tile) { + public void setTile(int xx, int yy, int zz, TileData tile) { } diff --git a/core/src/main/java/com/volmit/iris/core/service/VillageSVC.java b/core/src/main/java/com/volmit/iris/core/service/VillageSVC.java deleted file mode 100644 index 820f40833..000000000 --- a/core/src/main/java/com/volmit/iris/core/service/VillageSVC.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.volmit.iris.core.service; - -import com.volmit.iris.Iris; -import com.volmit.iris.core.IrisSettings; -import com.volmit.iris.core.tools.IrisToolbelt; -import com.volmit.iris.engine.object.IrisDimension; -import com.volmit.iris.util.format.C; -import com.volmit.iris.util.plugin.IrisService; -import com.volmit.iris.util.plugin.VolmitSender; -import org.bukkit.Location; -import org.bukkit.entity.Player; -import org.bukkit.entity.Villager; -import org.bukkit.event.EventHandler; -import org.bukkit.event.entity.VillagerCareerChangeEvent; - -import java.util.List; - -public class VillageSVC implements IrisService { - @Override - public void onEnable() { - - } - - @Override - public void onDisable() { - - } - - @EventHandler - public void on(VillagerCareerChangeEvent event) { - - if (!IrisToolbelt.isIrisWorld(event.getEntity().getWorld())) { - return; - } - - IrisDimension dim = IrisToolbelt.access(event.getEntity().getWorld()) - .getEngine().getDimension(); - - if (!dim.isRemoveCartographersDueToCrash()) { - return; - } - - if (event.getProfession().equals(Villager.Profession.CARTOGRAPHER)) { - event.setCancelled(true); - - Location eventLocation = event.getEntity().getLocation(); - - int radius = dim.getNotifyPlayersOfCartographerCancelledRadius(); - - if (radius == -1) { - return; - } - - List playersInWorld = event.getEntity().getWorld().getPlayers(); - - String message = C.GOLD + IrisSettings.get().getGeneral().cartographerMessage; - - Iris.info("Cancelled Cartographer Villager to prevent server crash at " + eventLocation + "!"); - - if (radius == -2) { - playersInWorld.stream().map(VolmitSender::new).forEach(v -> v.sendMessage(message)); - } else { - playersInWorld.forEach(p -> { - if (p.getLocation().distance(eventLocation) < radius) { - new VolmitSender(p).sendMessage(message); - } - }); - } - - } - } - - /* - * Replace or disable villager trade add event to prevent explorer map - */ - /* Removed due to MC breaking stuff again. This event is now called after the cartographer maps are made, - so it can fuck right off. - @EventHandler - public void on(VillagerAcquireTradeEvent event) { - if(!IrisToolbelt.isIrisWorld((event.getEntity().getWorld()))) { - return; - } - - // Iris.info("Trade event: type " + event.getRecipe().getResult().getType() + " / meta " + event.getRecipe().getResult().getItemMeta() + " / data " + event.getRecipe().getResult().getData()); - if(!event.getRecipe().getResult().getType().equals(Material.FILLED_MAP)) { - return; - } - - IrisVillagerOverride override = IrisToolbelt.access(event.getEntity().getWorld()).getEngine() - .getDimension().getPatchCartographers(); - - if(override.isDisableTrade()) { - event.setCancelled(true); - Iris.debug("Cancelled cartographer trade @ " + event.getEntity().getLocation()); - return; - } - - if(override.getValidItems() == null) { - event.setCancelled(true); - Iris.debug("Cancelled cartographer trade because no override items are valid @ " + event.getEntity().getLocation()); - return; - } - - IrisVillagerTrade trade = override.getValidItems().getRandom(); - event.setRecipe(trade.convert()); - Iris.debug("Overrode cartographer trade with: " + trade + " to prevent allowing cartography map trades"); - } - */ -} diff --git a/core/src/main/java/com/volmit/iris/core/service/WandSVC.java b/core/src/main/java/com/volmit/iris/core/service/WandSVC.java index 559d3629f..f87bb3ef6 100644 --- a/core/src/main/java/com/volmit/iris/core/service/WandSVC.java +++ b/core/src/main/java/com/volmit/iris/core/service/WandSVC.java @@ -35,6 +35,8 @@ import com.volmit.iris.util.plugin.IrisService; import com.volmit.iris.util.plugin.VolmitSender; import com.volmit.iris.util.scheduling.J; import com.volmit.iris.util.scheduling.S; +import com.volmit.iris.util.scheduling.SR; +import com.volmit.iris.util.scheduling.jobs.Job; import org.bukkit.*; import org.bukkit.block.Block; import org.bukkit.enchantments.Enchantment; @@ -53,10 +55,13 @@ import org.bukkit.util.Vector; import java.awt.Color; import java.util.ArrayList; import java.util.Objects; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; public class WandSVC implements IrisService { private static final Particle CRIT_MAGIC = E.getOrDefault(Particle.class, "CRIT_MAGIC", "CRIT"); private static final Particle REDSTONE = E.getOrDefault(Particle.class, "REDSTONE", "DUST"); + private static final int MS_PER_TICK = Integer.parseInt(System.getProperty("iris.ms_per_tick", "30")); private static ItemStack dust; private static ItemStack wand; @@ -71,7 +76,7 @@ public class WandSVC implements IrisService { * @param p The wand player * @return The new object */ - public static IrisObject createSchematic(Player p) { + public static IrisObject createSchematic(Player p, boolean legacy) { if (!isHoldingWand(p)) { return null; } @@ -80,14 +85,81 @@ public class WandSVC implements IrisService { Location[] f = getCuboid(p); Cuboid c = new Cuboid(f[0], f[1]); IrisObject s = new IrisObject(c.getSizeX(), c.getSizeY(), c.getSizeZ()); - for (Block b : c) { - if (b.getType().equals(Material.AIR)) { - continue; + + var it = c.chunkedIterator(); + + int total = c.getSizeX() * c.getSizeY() * c.getSizeZ(); + var latch = new CountDownLatch(1); + new Job() { + private int i; + private Chunk chunk; + + @Override + public String getName() { + return "Scanning Selection"; } - BlockVector bv = b.getLocation().subtract(c.getLowerNE().toVector()).toVector().toBlockVector(); - s.setUnsigned(bv.getBlockX(), bv.getBlockY(), bv.getBlockZ(), b); - } + @Override + public void execute() { + new SR() { + @Override + public void run() { + var time = M.ms() + MS_PER_TICK; + while (time > M.ms()) { + if (!it.hasNext()) { + if (chunk != null) { + chunk.removePluginChunkTicket(Iris.instance); + chunk = null; + } + + cancel(); + latch.countDown(); + return; + } + + try { + var b = it.next(); + var bChunk = b.getChunk(); + if (chunk == null) { + chunk = bChunk; + chunk.addPluginChunkTicket(Iris.instance); + } else if (chunk != bChunk) { + chunk.removePluginChunkTicket(Iris.instance); + chunk = bChunk; + } + + if (b.getType().equals(Material.AIR)) + continue; + + BlockVector bv = b.getLocation().subtract(c.getLowerNE().toVector()).toVector().toBlockVector(); + s.setUnsigned(bv.getBlockX(), bv.getBlockY(), bv.getBlockZ(), b, legacy); + } finally { + i++; + } + } + } + }; + try { + latch.await(); + } catch (InterruptedException ignored) {} + } + + @Override + public void completeWork() {} + + @Override + public int getTotalWork() { + return total; + } + + @Override + public int getWorkCompleted() { + return i; + } + }.execute(new VolmitSender(p), true, () -> {}); + try { + latch.await(); + } catch (InterruptedException ignored) {} return s; } catch (Throwable e) { diff --git a/core/src/main/java/com/volmit/iris/core/tools/IrisConverter.java b/core/src/main/java/com/volmit/iris/core/tools/IrisConverter.java index fb4d344eb..f58cc5c69 100644 --- a/core/src/main/java/com/volmit/iris/core/tools/IrisConverter.java +++ b/core/src/main/java/com/volmit/iris/core/tools/IrisConverter.java @@ -12,9 +12,11 @@ import com.volmit.iris.util.reflect.V; import com.volmit.iris.util.scheduling.J; import com.volmit.iris.util.scheduling.PrecisionStopwatch; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import org.apache.commons.io.FileUtils; import org.bukkit.Bukkit; import org.bukkit.block.Block; import org.bukkit.block.data.BlockData; +import org.bukkit.util.FileUtil; import org.bukkit.util.Vector; import java.io.File; @@ -33,11 +35,15 @@ public class IrisConverter { FilenameFilter filter = (dir, name) -> name.endsWith(".schem"); File[] fileList = folder.listFiles(filter); + if (fileList == null) { + sender.sendMessage("No schematic files to convert found in " + folder.getAbsolutePath()); + return; + } ExecutorService executorService = Executors.newFixedThreadPool(1); executorService.submit(() -> { for (File schem : fileList) { try { - PrecisionStopwatch p = new PrecisionStopwatch(); + PrecisionStopwatch p = PrecisionStopwatch.start(); boolean largeObject = false; NamedTag tag = null; try { @@ -52,21 +58,17 @@ public class IrisConverter { int objW = ((ShortTag) compound.get("Width")).getValue(); int objH = ((ShortTag) compound.get("Height")).getValue(); int objD = ((ShortTag) compound.get("Length")).getValue(); + int i = -1; int mv = objW * objH * objD; AtomicInteger v = new AtomicInteger(0); - AtomicInteger fv = new AtomicInteger(0); if (mv > 500_000) { largeObject = true; Iris.info(C.GRAY + "Converting.. "+ schem.getName() + " -> " + schem.getName().replace(".schem", ".iob")); Iris.info(C.GRAY + "- It may take a while"); if (sender.isPlayer()) { - J.a(() -> { -// while (v.get() != mv) { -// double pr = ((double) v.get() / (double ) mv); -// sender.sendProgress(pr, "Converting"); -// J.sleep(16); -// } - }); + i = J.ar(() -> { + sender.sendProgress((double) v.get() / mv, "Converting"); + }, 0); } } @@ -82,165 +84,8 @@ public class IrisConverter { ByteArrayTag byteArray = (ByteArrayTag) compound.get("BlockData"); byte[] originalBlockArray = byteArray.getValue(); - int b = 0; - int a = 0; - Map y = new HashMap<>(); - Map x = new HashMap<>(); - Map z = new HashMap<>(); - // Height adjustments - for (int h = 0; h < objH; h++) { - if (b == 0) { - y.put(h, (byte) 0); - } - if (b > 0) { - y.put(h, (byte) 1); - } - a = 0; - b = 0; - for (int d = 0; d < objD; d++) { - for (int w = 0; w < objW; w++) { - BlockData db = blockmap.get((int) originalBlockArray[fv.get()]); - if(db.getAsString().contains("minecraft:air")) { - a++; - } else { - b++; - } - fv.getAndAdd(1); - } - } - } - fv.set(0); - - // Width adjustments - for (int w = 0; w < objW; w++) { - if (b == 0) { - x.put(w, (byte) 0); - } - if (b > 0) { - x.put(w, (byte) 1); - } - a = 0; - b = 0; - for (int h = 0; h < objH; h++) { - for (int d = 0; d < objD; d++) { - BlockData db = blockmap.get((int) originalBlockArray[fv.get()]); - if(db.getAsString().contains("minecraft:air")) { - a++; - } else { - b++; - } - fv.getAndAdd(1); - } - } - } - fv.set(0); - - // Depth adjustments - for (int d = 0; d < objD; d++) { - if (b == 0) { - z.put(d, (byte) 0); - } - if (b > 0) { - z.put(d, (byte) 1); - } - a = 0; - b = 0; - for (int h = 0; h < objH; h++) { - for (int w = 0; w < objW; w++) { - BlockData db = blockmap.get((int) originalBlockArray[fv.get()]); - if(db.getAsString().contains("minecraft:air")) { - a++; - } else { - b++; - } - fv.getAndAdd(1); - } - } - } - fv.set(0); - int CorrectObjH = getCorrectY(y, objH); - int CorrectObjW = getCorrectX(x, objW); - int CorrectObjD = getCorrectZ(z, objD); - - //IrisObject object = new IrisObject(CorrectObjW, CorrectObjH, CorrectObjH); IrisObject object = new IrisObject(objW, objH, objD); - Vector originalVector = new Vector(objW,objH,objD); - - - int[] yc = null; - int[] xc = null; - int[] zc = null; - - - int fo = 0; - int so = 0; - int o = 0; - int c = 0; - for (Integer i : y.keySet()) { - if (y.get(i) == 0) { - o++; - } - if (y.get(i) == 1) { - c++; - if (c == 1) { - fo = o; - } - o = 0; - } - } - so = o; - yc = new int[]{fo, so}; - - fo = 0; - so = 0; - o = 0; - c = 0; - for (Integer i : x.keySet()) { - if (x.get(i) == 0) { - o++; - } - if (x.get(i) == 1) { - c++; - if (c == 1) { - fo = o; - } - o = 0; - } - } - so = o; - xc = new int[]{fo, so}; - - fo = 0; - so = 0; - o = 0; - c = 0; - for (Integer i : z.keySet()) { - if (z.get(i) == 0) { - o++; - } - if (z.get(i) == 1) { - c++; - if (c == 1) { - fo = o; - } - o = 0; - } - } - so = o; - zc = new int[]{fo, so}; - - int h1, h2, w1, w2, v1 = 0, volume = objW * objH * objD; - Map blockLocationMap = new LinkedHashMap<>(); - boolean hasAir = false; - int pos = 0; - for (int i : originalBlockArray) { - blockLocationMap.put(pos, i); - pos++; - } - - - for (int h = 0; h < objH; h++) { for (int d = 0; d < objD; d++) { for (int w = 0; w < objW; w++) { @@ -252,9 +97,9 @@ public class IrisConverter { } } } - - + if (i != -1) J.car(i); try { + object.shrinkwrap(); object.write(new File(folder, schem.getName().replace(".schem", ".iob"))); } catch (IOException e) { Iris.info(C.RED + "Failed to save: " + schem.getName()); @@ -272,7 +117,7 @@ public class IrisConverter { } else { Iris.info(C.GRAY + "Converted " + schem.getName() + " -> " + schem.getName().replace(".schem", ".iob")); } - // schem.delete(); + FileUtils.delete(schem); } } catch (Exception e) { Iris.info(C.RED + "Failed to convert: " + schem.getName()); @@ -283,112 +128,10 @@ public class IrisConverter { Iris.reportError(e); } } + sender.sendMessage(C.GRAY + "converted: " + fileList.length); }); } - public static boolean isNewPointFurther(int[] originalPoint, int[] oldPoint, int[] newPoint) { - int oX = oldPoint[1]; - int oY = oldPoint[2]; - int oZ = oldPoint[3]; - - int nX = newPoint[1]; - int nY = newPoint[2]; - int nZ = newPoint[3]; - - int orX = originalPoint[1]; - int orY = originalPoint[2]; - int orZ = originalPoint[3]; - - double oldDistance = Math.sqrt(Math.pow(oX - orX, 2) + Math.pow(oY - orY, 2) + Math.pow(oZ - orZ, 2)); - double newDistance = Math.sqrt(Math.pow(nX - orX, 2) + Math.pow(nY - orY, 2) + Math.pow(nZ - orZ, 2)); - - if (newDistance > oldDistance) { - return true; - } - return false; - } - - public static int[] getCoordinates(int pos, int obX, int obY, int obZ) { - int z = 0; - int[] coords = new int[4]; - for (int h = 0; h < obY; h++) { - for (int d = 0; d < obZ; d++) { - for (int w = 0; w < obX; w++) { - if (z == pos) { - coords[1] = w; - coords[2] = h; - coords[3] = d; - return coords; - } - z++; - } - } - } - return null; - } - - public static int getCorrectY(Map y, int H) { - int fo = 0; - int so = 0; - int o = 0; - int c = 0; - for (Integer i : y.keySet()) { - if (y.get(i) == 0) { - o++; - } - if (y.get(i) == 1) { - c++; - if(c == 1){ - fo = o; - } - o = 0; - } - } - so = o; - return H = H - (fo + so); - } - - public static int getCorrectX(Map x, int W) { - int fo = 0; - int so = 0; - int o = 0; - int c = 0; - for (Integer i : x.keySet()) { - if (x.get(i) == 0) { - o++; - } - if (x.get(i) == 1) { - c++; - if(c == 1){ - fo = o; - } - o = 0; - } - } - so = o; - return W = W - (fo + so); - } - - public static int getCorrectZ(Map z, int D) { - int fo = 0; - int so = 0; - int o = 0; - int c = 0; - for (Integer i : z.keySet()) { - if (z.get(i) == 0) { - o++; - } - if (z.get(i) == 1) { - c++; - if(c == 1){ - fo = o; - } - o = 0; - } - } - so = o; - return D = D - (fo + so); - } } 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 ac29ac007..1ffccd09f 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 @@ -24,10 +24,8 @@ import com.volmit.iris.core.IrisSettings; import com.volmit.iris.core.ServerConfigurator; import com.volmit.iris.core.pregenerator.PregenTask; import com.volmit.iris.core.service.StudioSVC; -import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.object.IrisDimension; import com.volmit.iris.engine.platform.PlatformChunkGenerator; -import com.volmit.iris.core.safeguard.UtilsSFG; import com.volmit.iris.util.exceptions.IrisException; import com.volmit.iris.util.format.C; import com.volmit.iris.util.format.Form; @@ -46,7 +44,6 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; -import static com.volmit.iris.core.safeguard.IrisSafeguard.unstablemode; /** * Makes it a lot easier to setup an engine, world, studio or whatever @@ -126,14 +123,11 @@ public class IrisCreator { if (sender == null) sender = Iris.getSender(); - if (!studio()) { - Iris.service(StudioSVC.class).installIntoWorld(sender, d.getLoadKey(), new File(Bukkit.getWorldContainer(), name())); - } - if (benchmark) { + if (!studio() || benchmark) { Iris.service(StudioSVC.class).installIntoWorld(sender, d.getLoadKey(), new File(Bukkit.getWorldContainer(), name())); } - PlatformChunkGenerator access = null; + PlatformChunkGenerator access; AtomicReference world = new AtomicReference<>(); AtomicDouble pp = new AtomicDouble(0); O done = new O<>(); diff --git a/core/src/main/java/com/volmit/iris/core/tools/IrisPackBenchmarking.java b/core/src/main/java/com/volmit/iris/core/tools/IrisPackBenchmarking.java index 7993cf6e7..9d41279c9 100644 --- a/core/src/main/java/com/volmit/iris/core/tools/IrisPackBenchmarking.java +++ b/core/src/main/java/com/volmit/iris/core/tools/IrisPackBenchmarking.java @@ -9,8 +9,6 @@ import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.exceptions.IrisException; import com.volmit.iris.util.format.Form; -import com.volmit.iris.util.math.Position2; - import com.volmit.iris.util.scheduling.J; import com.volmit.iris.util.scheduling.PrecisionStopwatch; import lombok.Getter; @@ -27,44 +25,44 @@ import java.nio.file.attribute.BasicFileAttributes; import java.time.Clock; import java.time.LocalDateTime; import java.util.Collections; -import java.util.concurrent.*; public class IrisPackBenchmarking { @Getter public static IrisPackBenchmarking instance; - public static boolean benchmarkInProgress = false; - private IrisDimension IrisDimension; - private int radius; - private boolean finished = false; - PrecisionStopwatch stopwatch; + public static boolean benchmarkInProgress = false; + private final PrecisionStopwatch stopwatch = new PrecisionStopwatch(); + private final IrisDimension dimension; + private final int radius; + private final boolean gui; - public IrisPackBenchmarking(IrisDimension dimension, int r) { + public IrisPackBenchmarking(IrisDimension dimension, int radius, boolean gui) { instance = this; - this.IrisDimension = dimension; - this.radius = r; + this.dimension = dimension; + this.radius = radius; + this.gui = gui; runBenchmark(); } private void runBenchmark() { - this.stopwatch = new PrecisionStopwatch(); - ExecutorService service = Executors.newSingleThreadExecutor(); - service.submit(() -> { - Iris.info("Setting up benchmark environment "); - benchmarkInProgress = true; - File file = new File("benchmark"); - if (file.exists()) { - deleteDirectory(file.toPath()); - } - createBenchmark(); - while (!IrisToolbelt.isIrisWorld(Bukkit.getWorld("benchmark"))) { - J.sleep(1000); - Iris.debug("Iris PackBenchmark: Waiting..."); - } - Iris.info("Starting Benchmark!"); - stopwatch.begin(); - startBenchmark(); - }); + Thread.ofVirtual() + .name("PackBenchmarking") + .start(() -> { + Iris.info("Setting up benchmark environment "); + benchmarkInProgress = true; + File file = new File("benchmark"); + if (file.exists()) { + deleteDirectory(file.toPath()); + } + createBenchmark(); + while (!IrisToolbelt.isIrisWorld(Bukkit.getWorld("benchmark"))) { + J.sleep(1000); + Iris.debug("Iris PackBenchmark: Waiting..."); + } + Iris.info("Starting Benchmark!"); + stopwatch.begin(); + startBenchmark(); + }); } @@ -88,14 +86,14 @@ public class IrisPackBenchmarking { File profilers = new File("plugins" + File.separator + "Iris" + File.separator + "packbenchmarks"); profilers.mkdir(); - File results = new File("plugins " + File.separator + "Iris", IrisDimension.getName() + LocalDateTime.now(Clock.systemDefaultZone()) + ".txt"); - results.createNewFile(); + File results = new File(profilers, dimension.getName() + " " + LocalDateTime.now(Clock.systemDefaultZone()).toString().replace(':', '-') + ".txt"); + results.getParentFile().mkdirs(); KMap metrics = engine.getMetrics().pull(); try (FileWriter writer = new FileWriter(results)) { writer.write("-----------------\n"); writer.write("Results:\n"); - writer.write("Dimension: " + IrisDimension.getName() + "\n"); - writer.write("- Date of Benchmark: " + LocalDateTime.now(Clock.systemDefaultZone()) + "\n"); + writer.write("Dimension: " + dimension.getName() + "\n"); + writer.write("- Date of Benchmark: " + LocalDateTime.now(Clock.systemDefaultZone()) + "\n"); writer.write("\n"); writer.write("Metrics"); for (String m : metrics.k()) { @@ -103,7 +101,7 @@ public class IrisPackBenchmarking { writer.write("- " + m + ": " + i); } writer.write("- " + metrics); - writer.write("Benchmark: " + LocalDateTime.now(Clock.systemDefaultZone()) + "\n"); + writer.write("Benchmark: " + LocalDateTime.now(Clock.systemDefaultZone()) + "\n"); writer.write("- Total time: " + time + "\n"); writer.write("- Average CPS: " + calculateAverage(cps) + "\n"); writer.write(" - Median CPS: " + calculateMedian(cps) + "\n"); @@ -116,17 +114,24 @@ public class IrisPackBenchmarking { e.printStackTrace(); } - Bukkit.getServer().unloadWorld("benchmark", true); + J.s(() -> { + var world = Bukkit.getWorld("benchmark"); + if (world == null) return; + IrisToolbelt.evacuate(world); + Bukkit.unloadWorld(world, true); + }); + stopwatch.end(); } catch (Exception e) { Iris.error("Something has gone wrong!"); e.printStackTrace(); } } - private void createBenchmark(){ + + private void createBenchmark() { try { IrisToolbelt.createWorld() - .dimension(IrisDimension.getName()) + .dimension(dimension.getLoadKey()) .name("benchmark") .seed(1337) .studio(false) @@ -137,17 +142,14 @@ public class IrisPackBenchmarking { } } - private void startBenchmark(){ - int x = 0; - int z = 0; - IrisToolbelt.pregenerate(PregenTask - .builder() - .gui(false) - .center(new Position2(x, z)) - .width(5) - .height(5) - .build(), Bukkit.getWorld("benchmark") - ); + private void startBenchmark() { + IrisToolbelt.pregenerate(PregenTask + .builder() + .gui(gui) + .width(radius) + .height(radius) + .build(), Bukkit.getWorld("benchmark") + ); } private double calculateAverage(KList list) { @@ -179,7 +181,7 @@ public class IrisPackBenchmarking { private boolean deleteDirectory(Path dir) { try { - Files.walkFileTree(dir, new SimpleFileVisitor() { + Files.walkFileTree(dir, new SimpleFileVisitor<>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Files.delete(file); diff --git a/core/src/main/java/com/volmit/iris/core/wand/WandSelection.java b/core/src/main/java/com/volmit/iris/core/wand/WandSelection.java index e2b99633b..d6f3d4aac 100644 --- a/core/src/main/java/com/volmit/iris/core/wand/WandSelection.java +++ b/core/src/main/java/com/volmit/iris/core/wand/WandSelection.java @@ -30,9 +30,10 @@ import org.bukkit.util.Vector; import java.awt.*; public class WandSelection { - private static final Particle REDSTONE = E.getOrDefault(Particle.class, "REDSTONE", "DUST"); + private static final Particle REDSTONE = E.getOrDefault(Particle.class, "REDSTONE", "DUST"); private final Cuboid c; private final Player p; + private static final double STEP = 0.10; public WandSelection(Cuboid c, Player p) { this.c = c; @@ -40,77 +41,58 @@ public class WandSelection { } public void draw() { - double accuracy; - double dist; + Location playerLoc = p.getLocation(); + double maxDistanceSquared = 256 * 256; + int particleCount = 0; - for (double i = c.getLowerX() - 1; i < c.getUpperX() + 1; i += 0.25) { - for (double j = c.getLowerY() - 1; j < c.getUpperY() + 1; j += 0.25) { - for (double k = c.getLowerZ() - 1; k < c.getUpperZ() + 1; k += 0.25) { - boolean ii = i == c.getLowerX() || i == c.getUpperX(); - boolean jj = j == c.getLowerY() || j == c.getUpperY(); - boolean kk = k == c.getLowerZ() || k == c.getUpperZ(); + // cube! + Location[][] edges = { + {c.getLowerNE(), new Location(c.getWorld(), c.getUpperX() + 1, c.getLowerY(), c.getLowerZ())}, + {c.getLowerNE(), new Location(c.getWorld(), c.getLowerX(), c.getUpperY() + 1, c.getLowerZ())}, + {c.getLowerNE(), new Location(c.getWorld(), c.getLowerX(), c.getLowerY(), c.getUpperZ() + 1)}, + {new Location(c.getWorld(), c.getUpperX() + 1, c.getLowerY(), c.getLowerZ()), new Location(c.getWorld(), c.getUpperX() + 1, c.getUpperY() + 1, c.getLowerZ())}, + {new Location(c.getWorld(), c.getUpperX() + 1, c.getLowerY(), c.getLowerZ()), new Location(c.getWorld(), c.getUpperX() + 1, c.getLowerY(), c.getUpperZ() + 1)}, + {new Location(c.getWorld(), c.getLowerX(), c.getUpperY() + 1, c.getLowerZ()), new Location(c.getWorld(), c.getUpperX() + 1, c.getUpperY() + 1, c.getLowerZ())}, + {new Location(c.getWorld(), c.getLowerX(), c.getUpperY() + 1, c.getLowerZ()), new Location(c.getWorld(), c.getLowerX(), c.getUpperY() + 1, c.getUpperZ() + 1)}, + {new Location(c.getWorld(), c.getLowerX(), c.getLowerY(), c.getUpperZ() + 1), new Location(c.getWorld(), c.getUpperX() + 1, c.getLowerY(), c.getUpperZ() + 1)}, + {new Location(c.getWorld(), c.getLowerX(), c.getLowerY(), c.getUpperZ() + 1), new Location(c.getWorld(), c.getLowerX(), c.getUpperY() + 1, c.getUpperZ() + 1)}, + {new Location(c.getWorld(), c.getUpperX() + 1, c.getUpperY() + 1, c.getLowerZ()), new Location(c.getWorld(), c.getUpperX() + 1, c.getUpperY() + 1, c.getUpperZ() + 1)}, + {new Location(c.getWorld(), c.getLowerX(), c.getUpperY() + 1, c.getUpperZ() + 1), new Location(c.getWorld(), c.getUpperX() + 1, c.getUpperY() + 1, c.getUpperZ() + 1)}, + {new Location(c.getWorld(), c.getUpperX() + 1, c.getLowerY(), c.getUpperZ() + 1), new Location(c.getWorld(), c.getUpperX() + 1, c.getUpperY() + 1, c.getUpperZ() + 1)} + }; - if ((ii && jj) || (ii && kk) || (kk && jj)) { - Vector push = new Vector(0, 0, 0); + for (Location[] edge : edges) { + Vector direction = edge[1].toVector().subtract(edge[0].toVector()); + double length = direction.length(); + direction.normalize(); - if (i == c.getLowerX()) { - push.add(new Vector(-0.55, 0, 0)); - } + for (double d = 0; d <= length; d += STEP) { + Location particleLoc = edge[0].clone().add(direction.clone().multiply(d)); - if (j == c.getLowerY()) { - push.add(new Vector(0, -0.55, 0)); - } - - if (k == c.getLowerZ()) { - push.add(new Vector(0, 0, -0.55)); - } - - if (i == c.getUpperX()) { - push.add(new Vector(0.55, 0, 0)); - } - - if (j == c.getUpperY()) { - push.add(new Vector(0, 0.55, 0)); - } - - if (k == c.getUpperZ()) { - push.add(new Vector(0, 0, 0.55)); - } - - Location a = new Location(c.getWorld(), i, j, k).add(0.5, 0.5, 0.5).add(push); - accuracy = M.lerpInverse(0, 64 * 64, p.getLocation().distanceSquared(a)); - dist = M.lerp(0.125, 3.5, accuracy); - - if (M.r(M.min(dist * 5, 0.9D) * 0.995)) { - continue; - } - - if (ii && jj) { - a.add(0, 0, RNG.r.d(-0.3, 0.3)); - } - - if (kk && jj) { - a.add(RNG.r.d(-0.3, 0.3), 0, 0); - } - - if (ii && kk) { - a.add(0, RNG.r.d(-0.3, 0.3), 0); - } - - if (p.getLocation().distanceSquared(a) < 256 * 256) { - Color color = Color.getHSBColor((float) (0.5f + (Math.sin((i + j + k + (p.getTicksLived() / 2f)) / (20f)) / 2)), 1, 1); - int r = color.getRed(); - int g = color.getGreen(); - int b = color.getBlue(); - - p.spawnParticle(REDSTONE, a.getX(), a.getY(), a.getZ(), - 1, 0, 0, 0, 0, - new Particle.DustOptions(org.bukkit.Color.fromRGB(r, g, b), - (float) dist * 3f)); - } - } + if (playerLoc.distanceSquared(particleLoc) > maxDistanceSquared) { + continue; } + + spawnParticle(particleLoc, playerLoc); + particleCount++; } } } + + private void spawnParticle(Location particleLoc, Location playerLoc) { + double accuracy = M.lerpInverse(0, 64 * 64, playerLoc.distanceSquared(particleLoc)); + double dist = M.lerp(0.125, 3.5, accuracy); + + if (M.r(Math.min(dist * 5, 0.9D) * 0.995)) { + return; + } + + float hue = (float) (0.5f + (Math.sin((particleLoc.getX() + particleLoc.getY() + particleLoc.getZ() + (p.getTicksLived() / 2f)) / 20f) / 2)); + Color color = Color.getHSBColor(hue, 1, 1); + + p.spawnParticle(REDSTONE, particleLoc, + 0, 0, 0, 0, 1, + new Particle.DustOptions(org.bukkit.Color.fromRGB(color.getRed(), color.getGreen(), color.getBlue()), + (float) dist * 3f)); + } } diff --git a/core/src/main/java/com/volmit/iris/engine/IrisComplex.java b/core/src/main/java/com/volmit/iris/engine/IrisComplex.java index a0da316aa..e92e6da84 100644 --- a/core/src/main/java/com/volmit/iris/engine/IrisComplex.java +++ b/core/src/main/java/com/volmit/iris/engine/IrisComplex.java @@ -29,6 +29,7 @@ import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.collection.KSet; import com.volmit.iris.util.context.IrisContext; import com.volmit.iris.util.data.DataProvider; +import com.volmit.iris.util.interpolation.IrisInterpolation.NoiseKey; import com.volmit.iris.util.math.M; import com.volmit.iris.util.math.RNG; import com.volmit.iris.util.noise.CNG; @@ -292,9 +293,11 @@ public class IrisComplex implements DataProvider { return 0; } + KMap cache = new KMap<>(); double hi = interpolator.interpolate(x, z, (xx, zz) -> { try { IrisBiome bx = baseBiomeStream.get(xx, zz); + cache.put(new NoiseKey(xx, zz), bx); double b = 0; for (IrisGenerator gen : generators) { @@ -313,7 +316,11 @@ public class IrisComplex implements DataProvider { double lo = interpolator.interpolate(x, z, (xx, zz) -> { try { - IrisBiome bx = baseBiomeStream.get(xx, zz); + IrisBiome bx = cache.get(new NoiseKey(xx, zz)); + if (bx == null) { + bx = baseBiomeStream.get(xx, zz); + cache.put(new NoiseKey(xx, zz), bx); + } double b = 0; for (IrisGenerator gen : generators) { 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 cfaa4383a..11f7e0d55 100644 --- a/core/src/main/java/com/volmit/iris/engine/IrisWorldManager.java +++ b/core/src/main/java/com/volmit/iris/engine/IrisWorldManager.java @@ -76,6 +76,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager { private final ChronoLatch ecl; private final ChronoLatch cln; private final ChronoLatch chunkUpdater; + private final ChronoLatch chunkDiscovery; private double energy = 25; private int entityCount = 0; private long charge = 0; @@ -92,12 +93,14 @@ public class IrisWorldManager extends EngineAssignedWorldManager { clw = null; looper = null; chunkUpdater = null; + chunkDiscovery = null; id = -1; } public IrisWorldManager(Engine engine) { super(engine); chunkUpdater = new ChronoLatch(3000); + chunkDiscovery = new ChronoLatch(5000); cln = new ChronoLatch(60000); cl = new ChronoLatch(3000); ecl = new ChronoLatch(250); @@ -128,6 +131,10 @@ public class IrisWorldManager extends EngineAssignedWorldManager { updateChunks(); } + if (chunkDiscovery.flip()) { + discoverChunks(); + } + if (getDimension().isInfiniteEnergy()) { energy += 1000; @@ -174,6 +181,19 @@ public class IrisWorldManager extends EngineAssignedWorldManager { looper.start(); } + private void discoverChunks() { + var mantle = getEngine().getMantle().getMantle(); + for (Player i : getEngine().getWorld().realWorld().getPlayers()) { + int r = 1; + + for (int x = -r; x <= r; x++) { + for (int z = -r; z <= r; z++) { + mantle.getChunk(i.getLocation().getChunk()).flag(MantleFlag.DISCOVERED, true); + } + } + } + } + private void updateChunks() { for (Player i : getEngine().getWorld().realWorld().getPlayers()) { int r = 1; @@ -439,7 +459,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager { IrisEngineData ed = getEngine().getEngineData(); IrisEngineSpawnerCooldown cd = null; - for (IrisEngineSpawnerCooldown j : ed.getSpawnerCooldowns()) { + for (IrisEngineSpawnerCooldown j : ed.getSpawnerCooldowns().copy()) { if (j.getSpawner().equals(i.getLoadKey())) { cd = j; } diff --git a/core/src/main/java/com/volmit/iris/engine/decorator/IrisCeilingDecorator.java b/core/src/main/java/com/volmit/iris/engine/decorator/IrisCeilingDecorator.java index 81867bccc..362a3ea69 100644 --- a/core/src/main/java/com/volmit/iris/engine/decorator/IrisCeilingDecorator.java +++ b/core/src/main/java/com/volmit/iris/engine/decorator/IrisCeilingDecorator.java @@ -43,15 +43,7 @@ public class IrisCeilingDecorator extends IrisEngineDecorator { IrisDecorator decorator = getDecorator(biome, realX, realZ); if (decorator != null) { if (!decorator.isStacking()) { - if (height >= 0 || height < getEngine().getHeight()) { - if (null != decorator.getBlockDataForTop(biome, getRng(), realX, height, realZ, getData())) { - data.set(x, height, z, decorator.getBlockData100(biome, getRng(), realX, height, realZ, getData())); - height--; - data.set(x, height, z, decorator.getBlockDataForTop(biome, getRng(), realX, height, realZ, getData())); - } else { - data.set(x, height, z, decorator.getBlockData100(biome, getRng(), realX, height, realZ, getData())); - } - } + data.set(x, height, z, fixFaces(decorator.getBlockData100(biome, getRng(), realX, height, realZ, getData()), realX, height, realZ)); } else { int stack = decorator.getHeight(getRng().nextParallelRNG(Cache.key(realX, realZ)), realX, realZ, getData()); if (decorator.isScaleStack()) { diff --git a/core/src/main/java/com/volmit/iris/engine/decorator/IrisSeaFloorDecorator.java b/core/src/main/java/com/volmit/iris/engine/decorator/IrisSeaFloorDecorator.java index 4042a5f63..e1b2fac7b 100644 --- a/core/src/main/java/com/volmit/iris/engine/decorator/IrisSeaFloorDecorator.java +++ b/core/src/main/java/com/volmit/iris/engine/decorator/IrisSeaFloorDecorator.java @@ -44,15 +44,7 @@ public class IrisSeaFloorDecorator extends IrisEngineDecorator { return; } if (height >= 0 || height < getEngine().getHeight()) { - if (null != decorator.getBlockDataForTop(biome, getRng(), realX, height, realZ, getData())) { - if (height == getDimension().getFluidHeight() - 1) { - data.set(x, height, z, decorator.getBlockData100(biome, getRng(), realX, height, realZ, getData())); - height++; - data.set(x, height, z, decorator.getBlockDataForTop(biome, getRng(), realX, height, realZ, getData())); - } - } else { - data.set(x, height, z, decorator.getBlockData100(biome, getRng(), realX, height, realZ, getData())); - } + data.set(x, height, z, decorator.getBlockData100(biome, getRng(), realX, height, realZ, getData())); } } else { int stack = decorator.getHeight(getRng().nextParallelRNG(Cache.key(realX, realZ)), realX, realZ, getData()); diff --git a/core/src/main/java/com/volmit/iris/engine/decorator/IrisSeaSurfaceDecorator.java b/core/src/main/java/com/volmit/iris/engine/decorator/IrisSeaSurfaceDecorator.java index 6d86af362..8bf9b6e49 100644 --- a/core/src/main/java/com/volmit/iris/engine/decorator/IrisSeaSurfaceDecorator.java +++ b/core/src/main/java/com/volmit/iris/engine/decorator/IrisSeaSurfaceDecorator.java @@ -40,13 +40,7 @@ public class IrisSeaSurfaceDecorator extends IrisEngineDecorator { if (decorator != null) { if (!decorator.isStacking()) { if (height >= 0 || height < getEngine().getHeight()) { - if (null != decorator.getBlockDataForTop(biome, getRng(), realX, height, realZ, getData())) { - data.set(x, height, z, decorator.getBlockData100(biome, getRng(), realX, height, realZ, getData())); - height++; - data.set(x, height, z, decorator.getBlockDataForTop(biome, getRng(), realX, height, realZ, getData())); - } else { - data.set(x, height, z, decorator.getBlockData100(biome, getRng(), realX, height, realZ, getData())); - } + data.set(x, height + 1, z, decorator.getBlockData100(biome, getRng(), realX, height, realZ, getData())); } } else { int stack = decorator.getHeight(getRng().nextParallelRNG(Cache.key(realX, realZ)), realX, realZ, getData()); diff --git a/core/src/main/java/com/volmit/iris/engine/decorator/IrisShoreLineDecorator.java b/core/src/main/java/com/volmit/iris/engine/decorator/IrisShoreLineDecorator.java index 6585f0a40..88223b017 100644 --- a/core/src/main/java/com/volmit/iris/engine/decorator/IrisShoreLineDecorator.java +++ b/core/src/main/java/com/volmit/iris/engine/decorator/IrisShoreLineDecorator.java @@ -51,13 +51,7 @@ public class IrisShoreLineDecorator extends IrisEngineDecorator { } if (!decorator.isStacking()) { - if (null != decorator.getBlockDataForTop(biome, getRng(), realX, height, realZ, getData())) { - data.set(x, height, z, decorator.getBlockData100(biome, getRng(), realX, height, realZ, getData())); - height++; - data.set(x, height, z, decorator.getBlockDataForTop(biome, getRng(), realX, height, realZ, getData())); - } else { - data.set(x, height, z, decorator.getBlockData100(biome, getRng(), realX, height, realZ, getData())); - } + data.set(x, height + 1, z, decorator.getBlockData100(biome, getRng(), realX, height, realZ, getData())); } else { int stack = decorator.getHeight(getRng().nextParallelRNG(Cache.key(realX, realZ)), realX, realZ, getData()); if (decorator.isScaleStack()) { 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 13e1d2b6a..145fdd4e7 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 @@ -20,6 +20,7 @@ package com.volmit.iris.engine.framework; import com.volmit.iris.Iris; import com.volmit.iris.core.IrisSettings; +import com.volmit.iris.core.events.IrisLootEvent; import com.volmit.iris.core.gui.components.RenderType; import com.volmit.iris.core.gui.components.Renderer; import com.volmit.iris.core.link.Identifier; @@ -27,6 +28,7 @@ import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.core.loader.IrisRegistrant; import com.volmit.iris.core.nms.container.BlockPos; import com.volmit.iris.core.nms.container.Pair; +import com.volmit.iris.core.pregenerator.ChunkUpdater; import com.volmit.iris.core.service.ExternalDataSVC; import com.volmit.iris.engine.IrisComplex; import com.volmit.iris.engine.data.cache.Cache; @@ -57,15 +59,13 @@ import com.volmit.iris.util.matter.TileWrapper; import com.volmit.iris.util.matter.slices.container.JigsawPieceContainer; import com.volmit.iris.util.parallel.BurstExecutor; import com.volmit.iris.util.parallel.MultiBurst; +import com.volmit.iris.util.reflect.W; import com.volmit.iris.util.scheduling.ChronoLatch; import com.volmit.iris.util.scheduling.J; import com.volmit.iris.util.scheduling.PrecisionStopwatch; import com.volmit.iris.util.stream.ProceduralStream; import io.papermc.lib.PaperLib; -import org.bukkit.Bukkit; -import org.bukkit.Chunk; -import org.bukkit.Location; -import org.bukkit.Material; +import org.bukkit.*; import org.bukkit.block.Biome; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; @@ -76,10 +76,11 @@ import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; -import java.awt.*; +import java.awt.Color; import java.util.Arrays; import java.util.Set; import java.util.UUID; +import java.util.concurrent.Semaphore; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; @@ -273,33 +274,43 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat for (int z = -1; z <= 1; z++) { if (c.getWorld().isChunkLoaded(c.getX() + x, c.getZ() + z)) continue; - Iris.debug("Chunk %s, %s [%s, %s] is not loaded".formatted(c.getX() + x, c.getZ() + z, x, z)); + var msg = "Chunk %s, %s [%s, %s] is not loaded".formatted(c.getX() + x, c.getZ() + z, x, z); + if (W.getStack().getCallerClass().equals(ChunkUpdater.class)) Iris.warn(msg); + else Iris.debug(msg); return; } } - if (!getMantle().getMantle().isLoaded(c)) { - Iris.debug("Mantle Chunk " + c.getX() + c.getX() + " is not loaded"); + var mantle = getMantle().getMantle(); + if (!mantle.isLoaded(c)) { + var msg = "Mantle Chunk " + c.getX() + c.getX() + " is not loaded"; + if (W.getStack().getCallerClass().equals(ChunkUpdater.class)) Iris.warn(msg); + else Iris.debug(msg); return; } - getMantle().getMantle().raiseFlag(c.getX(), c.getZ(), MantleFlag.TILE, () -> J.s(() -> { - getMantle().getMantle().iterateChunk(c.getX(), c.getZ(), TileWrapper.class, (x, y, z, tile) -> { + var chunk = mantle.getChunk(c); + if (chunk.isFlagged(MantleFlag.ETCHED)) return; + chunk.flag(MantleFlag.ETCHED, true); + + Semaphore semaphore = new Semaphore(3); + chunk.raiseFlag(MantleFlag.TILE, run(semaphore, () -> J.s(() -> { + mantle.iterateChunk(c.getX(), c.getZ(), TileWrapper.class, (x, y, z, v) -> { int betterY = y + getWorld().minHeight(); - if (!TileData.setTileState(c.getBlock(x, betterY, z), tile.getData())) - Iris.warn("Failed to set tile entity data at [%d %d %d | %s] for tile %s!", x, betterY, z, c.getBlock(x, betterY, z).getBlockData().getMaterial().getKey(), tile.getData().getTileId()); + if (!TileData.setTileState(c.getBlock(x, betterY, z), v.getData())) + Iris.warn("Failed to set tile entity data at [%d %d %d | %s] for tile %s!", x, betterY, z, c.getBlock(x, betterY, z).getBlockData().getMaterial().getKey(), v.getData().getMaterial().name()); }); - })); - getMantle().getMantle().raiseFlag(c.getX(), c.getZ(), MantleFlag.CUSTOM, () -> J.s(() -> { - getMantle().getMantle().iterateChunk(c.getX(), c.getZ(), Identifier.class, (x, y, z, v) -> { + }))); + chunk.raiseFlag(MantleFlag.CUSTOM, run(semaphore, () -> J.s(() -> { + mantle.iterateChunk(c.getX(), c.getZ(), Identifier.class, (x, y, z, v) -> { Iris.service(ExternalDataSVC.class).processUpdate(this, c.getBlock(x & 15, y + getWorld().minHeight(), z & 15), v); }); - })); + }))); - getMantle().getMantle().raiseFlag(c.getX(), c.getZ(), MantleFlag.UPDATE, () -> J.s(() -> { + chunk.raiseFlag(MantleFlag.UPDATE, run(semaphore, () -> J.s(() -> { PrecisionStopwatch p = PrecisionStopwatch.start(); KMap updates = new KMap<>(); RNG r = new RNG(Cache.key(c.getX(), c.getZ())); - getMantle().getMantle().iterateChunk(c.getX(), c.getZ(), MatterCavern.class, (x, yf, z, v) -> { + mantle.iterateChunk(c.getX(), c.getZ(), MatterCavern.class, (x, yf, z, v) -> { int y = yf + getWorld().minHeight(); if (!B.isFluid(c.getBlock(x & 15, y, z & 15).getBlockData())) { return; @@ -329,7 +340,7 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat }); updates.forEach((k, v) -> update(Cache.keyX(k), v, Cache.keyZ(k), c, r)); - getMantle().getMantle().iterateChunk(c.getX(), c.getZ(), MatterUpdate.class, (x, yf, z, v) -> { + mantle.iterateChunk(c.getX(), c.getZ(), MatterUpdate.class, (x, yf, z, v) -> { int y = yf + getWorld().minHeight(); if (v != null && v.isUpdate()) { int vx = x & 15; @@ -340,9 +351,25 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat } } }); - getMantle().getMantle().deleteChunkSlice(c.getX(), c.getZ(), MatterUpdate.class); + mantle.deleteChunkSlice(c.getX(), c.getZ(), MatterUpdate.class); getMetrics().getUpdates().put(p.getMilliseconds()); - }, RNG.r.i(0, 20))); + }, RNG.r.i(0, 20)))); + + try { + semaphore.acquire(3); + } catch (InterruptedException ignored) {} + } + + private static Runnable run(Semaphore semaphore, Runnable runnable) { + return () -> { + if (!semaphore.tryAcquire()) + return; + try { + runnable.run(); + } finally { + semaphore.release(); + } + }; } @BlockCoordinates @@ -388,7 +415,7 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat if (tables.isEmpty()) return; InventoryHolder m = (InventoryHolder) block.getState(); - addItems(false, m.getInventory(), rx, tables, slot, x, y, z, 15); + addItems(false, m.getInventory(), rx, tables, slot, c.getWorld(), x, y, z, 15); } catch (Throwable e) { Iris.reportError(e); @@ -441,7 +468,10 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat } @Override - default void injectTables(KList list, IrisLootReference r) { + default void injectTables(KList list, IrisLootReference r, boolean fallback) { + if (r.getMode().equals(IrisLootMode.FALLBACK) && !fallback) + return; + if (r.getMode().equals(IrisLootMode.CLEAR) || r.getMode().equals(IrisLootMode.REPLACE)) { list.clear(); } @@ -476,10 +506,11 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat IrisBiome biomeUnder = ry < he ? getComplex().getCaveBiomeStream().get(rx, rz) : biomeSurface; double multiplier = 1D * getDimension().getLoot().getMultiplier() * region.getLoot().getMultiplier() * biomeSurface.getLoot().getMultiplier() * biomeUnder.getLoot().getMultiplier(); - injectTables(tables, getDimension().getLoot()); - injectTables(tables, region.getLoot()); - injectTables(tables, biomeSurface.getLoot()); - injectTables(tables, biomeUnder.getLoot()); + boolean fallback = tables.isEmpty(); + injectTables(tables, getDimension().getLoot(), fallback); + injectTables(tables, region.getLoot(), fallback); + injectTables(tables, biomeSurface.getLoot(), fallback); + injectTables(tables, biomeUnder.getLoot(), fallback); if (tables.isNotEmpty()) { int target = (int) Math.round(tables.size() * multiplier); @@ -497,16 +528,16 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat } @Override - default void addItems(boolean debug, Inventory inv, RNG rng, KList tables, InventorySlotType slot, int x, int y, int z, int mgf) { + default void addItems(boolean debug, Inventory inv, RNG rng, KList tables, InventorySlotType slot, World world, int x, int y, int z, int mgf) { KList items = new KList<>(); - int b = 4; for (IrisLootTable i : tables) { if (i == null) continue; - b++; - items.addAll(i.getLoot(debug, rng, slot, x, y, z)); + items.addAll(i.getLoot(debug, rng, slot, world, x, y, z)); } + if (IrisLootEvent.callLootEvent(items, inv, world, x, y, z)) + return; if (PaperLib.isPaper() && getWorld().hasRealWorld()) { PaperLib.getChunkAtAsync(getWorld().realWorld(), x >> 4, z >> 4).thenAccept((c) -> { @@ -840,7 +871,7 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat default void gotoJigsaw(IrisJigsawStructure s, Player player, boolean teleport) { if (s.getLoadKey().equals(getDimension().getStronghold())) { - KList p = getDimension().getStrongholds(getSeedManager().getSpawn()); + KList p = getDimension().getStrongholds(getSeedManager().getMantle()); if (p.isEmpty()) { player.sendMessage(C.GOLD + "No strongholds in world."); diff --git a/core/src/main/java/com/volmit/iris/engine/framework/EngineAssignedWorldManager.java b/core/src/main/java/com/volmit/iris/engine/framework/EngineAssignedWorldManager.java index 5219c5e6f..5f801033f 100644 --- a/core/src/main/java/com/volmit/iris/engine/framework/EngineAssignedWorldManager.java +++ b/core/src/main/java/com/volmit/iris/engine/framework/EngineAssignedWorldManager.java @@ -110,7 +110,7 @@ public abstract class EngineAssignedWorldManager extends EngineAssignedComponent return; } - KList positions = getEngine().getDimension().getStrongholds(getEngine().getSeedManager().getSpawn()); + KList positions = getEngine().getDimension().getStrongholds(getEngine().getSeedManager().getMantle()); if (positions.isEmpty()) { return; } diff --git a/core/src/main/java/com/volmit/iris/engine/framework/IrisLootEvent.java b/core/src/main/java/com/volmit/iris/engine/framework/IrisLootEvent.java deleted file mode 100644 index 057763893..000000000 --- a/core/src/main/java/com/volmit/iris/engine/framework/IrisLootEvent.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.volmit.iris.engine.framework; - -import com.volmit.iris.engine.object.InventorySlotType; -import com.volmit.iris.engine.object.IrisLootTable; -import com.volmit.iris.util.collection.KList; -import lombok.Getter; -import org.bukkit.block.Block; -import org.bukkit.event.Event; -import org.bukkit.event.HandlerList; - -@Getter -public class IrisLootEvent extends Event { - private static final HandlerList handlers = new HandlerList(); - private final Engine engine; - private final Block block; - private final InventorySlotType slot; - private final KList tables; - - public IrisLootEvent(Engine engine, Block block, InventorySlotType slot, KList tables) { - this.engine = engine; - this.block = block; - this.slot = slot; - this.tables = tables; - } - @Override - public HandlerList getHandlers() { - return handlers; - } - public static HandlerList getHandlerList() { - return handlers; - } -} diff --git a/core/src/main/java/com/volmit/iris/engine/framework/ListFunction.java b/core/src/main/java/com/volmit/iris/engine/framework/ListFunction.java new file mode 100644 index 000000000..a99341603 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/engine/framework/ListFunction.java @@ -0,0 +1,10 @@ +package com.volmit.iris.engine.framework; + +import com.volmit.iris.core.loader.IrisData; + +import java.util.function.Function; + +public interface ListFunction extends Function { + String key(); + String fancyName(); +} diff --git a/core/src/main/java/com/volmit/iris/engine/framework/LootProvider.java b/core/src/main/java/com/volmit/iris/engine/framework/LootProvider.java index b24f2e3ae..3192748b4 100644 --- a/core/src/main/java/com/volmit/iris/engine/framework/LootProvider.java +++ b/core/src/main/java/com/volmit/iris/engine/framework/LootProvider.java @@ -23,15 +23,16 @@ import com.volmit.iris.engine.object.IrisLootReference; import com.volmit.iris.engine.object.IrisLootTable; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.math.RNG; +import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.inventory.Inventory; public interface LootProvider { void scramble(Inventory inventory, RNG rng); - void injectTables(KList list, IrisLootReference r); + void injectTables(KList list, IrisLootReference r, boolean fallback); KList getLootTables(RNG rng, Block b); - void addItems(boolean debug, Inventory inv, RNG rng, KList tables, InventorySlotType slot, int x, int y, int z, int mgf); + void addItems(boolean debug, Inventory inv, RNG rng, KList tables, InventorySlotType slot, World world, int x, int y, int z, int mgf); } diff --git a/core/src/main/java/com/volmit/iris/engine/framework/ResultLocator.java b/core/src/main/java/com/volmit/iris/engine/framework/ResultLocator.java new file mode 100644 index 000000000..93d698347 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/engine/framework/ResultLocator.java @@ -0,0 +1,121 @@ +package com.volmit.iris.engine.framework; + +import com.volmit.iris.core.IrisSettings; +import com.volmit.iris.engine.object.IrisJigsawStructure; +import com.volmit.iris.engine.object.IrisObject; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.math.Position2; +import com.volmit.iris.util.math.Spiraler; +import com.volmit.iris.util.parallel.BurstExecutor; +import com.volmit.iris.util.parallel.MultiBurst; +import com.volmit.iris.util.scheduling.PrecisionStopwatch; +import org.apache.commons.lang3.function.TriFunction; + +import java.util.Collection; +import java.util.Set; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; + +@FunctionalInterface +public interface ResultLocator { + static void cancelSearch() { + if (LocatorCanceller.cancel != null) { + LocatorCanceller.cancel.run(); + LocatorCanceller.cancel = null; + } + } + + static ResultLocator locateStructure(Collection keys) { + return (e, pos) -> { + var structure = e.getStructureAt(pos.getX(), pos.getZ()); + return structure != null && keys.contains(structure.getLoadKey()) ? structure : null; + }; + } + + static ResultLocator locateObject(Collection keys) { + return (e, pos) -> { + Set objects = e.getObjectsAt(pos.getX(), pos.getZ()); + for (String object : objects) { + if (!keys.contains(object)) continue; + return e.getData().getObjectLoader().load(object); + } + return null; + }; + } + + T find(Engine e, Position2 chunkPos); + + default ResultLocator then(TriFunction filter) { + return (e, pos) -> { + var t = find(e, pos); + return t != null ? filter.apply(e, pos, t) : null; + }; + } + + default Future> find(Engine engine, Position2 pos, long timeout, Consumer checks, boolean cancelable) throws WrongEngineBroException { + if (engine.isClosed()) { + throw new WrongEngineBroException(); + } + + cancelSearch(); + + return MultiBurst.burst.completeValue(() -> { + int tc = IrisSettings.getThreadCount(IrisSettings.get().getConcurrency().getParallelism()) * 17; + MultiBurst burst = MultiBurst.burst; + AtomicBoolean found = new AtomicBoolean(false); + AtomicReference> foundObj = new AtomicReference<>(); + Position2 cursor = pos; + AtomicInteger searched = new AtomicInteger(); + AtomicBoolean stop = new AtomicBoolean(false); + PrecisionStopwatch px = PrecisionStopwatch.start(); + if (cancelable) LocatorCanceller.cancel = () -> stop.set(true); + AtomicReference next = new AtomicReference<>(cursor); + Spiraler s = new Spiraler(100000, 100000, (x, z) -> next.set(new Position2(x, z))); + s.setOffset(cursor.getX(), cursor.getZ()); + s.next(); + while (!found.get() && !stop.get() && px.getMilliseconds() < timeout) { + BurstExecutor e = burst.burst(tc); + + for (int i = 0; i < tc; i++) { + Position2 p = next.get(); + s.next(); + e.queue(() -> { + var o = find(engine, p); + if (o != null) { + if (foundObj.get() == null) { + foundObj.set(new Result<>(o, p)); + } + + found.set(true); + } + searched.incrementAndGet(); + }); + } + + e.complete(); + checks.accept(searched.get()); + } + + LocatorCanceller.cancel = null; + + if (found.get() && foundObj.get() != null) { + return foundObj.get(); + } + + return null; + }); + } + + record Result(T obj, Position2 pos) { + public int getBlockX() { + return (pos.getX() << 4) + 8; + } + + public int getBlockZ() { + return (pos.getZ() << 4) + 8; + } + } +} diff --git a/core/src/main/java/com/volmit/iris/engine/framework/placer/HeightmapObjectPlacer.java b/core/src/main/java/com/volmit/iris/engine/framework/placer/HeightmapObjectPlacer.java index c2c7e0f30..b228eb00f 100644 --- a/core/src/main/java/com/volmit/iris/engine/framework/placer/HeightmapObjectPlacer.java +++ b/core/src/main/java/com/volmit/iris/engine/framework/placer/HeightmapObjectPlacer.java @@ -79,7 +79,7 @@ public class HeightmapObjectPlacer implements IObjectPlacer { return oplacer.isDebugSmartBore(); } - public void setTile(int param1Int1, int param1Int2, int param1Int3, TileData param1TileData) { + public void setTile(int param1Int1, int param1Int2, int param1Int3, TileData param1TileData) { oplacer.setTile(param1Int1, param1Int2, param1Int3, param1TileData); } diff --git a/core/src/main/java/com/volmit/iris/engine/framework/placer/WorldObjectPlacer.java b/core/src/main/java/com/volmit/iris/engine/framework/placer/WorldObjectPlacer.java index 16968eee5..1d334e14b 100644 --- a/core/src/main/java/com/volmit/iris/engine/framework/placer/WorldObjectPlacer.java +++ b/core/src/main/java/com/volmit/iris/engine/framework/placer/WorldObjectPlacer.java @@ -5,7 +5,7 @@ import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.core.tools.IrisToolbelt; import com.volmit.iris.engine.data.cache.Cache; import com.volmit.iris.engine.framework.Engine; -import com.volmit.iris.engine.framework.IrisLootEvent; +import com.volmit.iris.core.events.IrisLootEvent; import com.volmit.iris.engine.mantle.EngineMantle; import com.volmit.iris.engine.object.IObjectPlacer; import com.volmit.iris.engine.object.InventorySlotType; @@ -20,8 +20,6 @@ import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.World; import org.bukkit.block.Block; -import org.bukkit.block.BlockState; -import org.bukkit.block.TileState; import org.bukkit.block.data.BlockData; import org.bukkit.inventory.InventoryHolder; @@ -74,7 +72,7 @@ public class WorldObjectPlacer implements IObjectPlacer { if (tables.isEmpty()) return; InventoryHolder m = (InventoryHolder) block.getState(); - engine.addItems(false, m.getInventory(), rx, tables, slot, x, y, z, 15); + engine.addItems(false, m.getInventory(), rx, tables, slot, world, x, y, z, 15); } catch (Throwable e) { Iris.reportError(e); } @@ -119,9 +117,7 @@ public class WorldObjectPlacer implements IObjectPlacer { } @Override - public void setTile(int xx, int yy, int zz, TileData tile) { - BlockState state = world.getBlockAt(xx, yy + world.getMinHeight(), zz).getState(); - tile.toBukkitTry(state); - state.update(); + public void setTile(int xx, int yy, int zz, TileData tile) { + tile.toBukkitTry(world.getBlockAt(xx, yy + world.getMinHeight(), zz)); } } diff --git a/core/src/main/java/com/volmit/iris/engine/jigsaw/PlannedStructure.java b/core/src/main/java/com/volmit/iris/engine/jigsaw/PlannedStructure.java index 9ee15169b..374e5f1bb 100644 --- a/core/src/main/java/com/volmit/iris/engine/jigsaw/PlannedStructure.java +++ b/core/src/main/java/com/volmit/iris/engine/jigsaw/PlannedStructure.java @@ -50,16 +50,18 @@ public class PlannedStructure { private IrisPosition position; private IrisData data; private RNG rng; + private boolean forcePlace; private boolean verbose; private boolean terminating; - public PlannedStructure(IrisJigsawStructure structure, IrisPosition position, RNG rng) { + public PlannedStructure(IrisJigsawStructure structure, IrisPosition position, RNG rng, boolean forcePlace) { terminating = false; verbose = true; this.pieces = new KList<>(); this.structure = structure; this.position = position; this.rng = rng; + this.forcePlace = forcePlace || structure.isForcePlace(); this.data = structure.getLoader(); generateStartPiece(); @@ -108,6 +110,9 @@ public class PlannedStructure { } else { options.setMode(i.getPiece().getPlaceMode()); } + if (forcePlace) { + options.setForcePlace(true); + } IrisObject v = i.getObject(); int sx = (v.getW() / 2); diff --git a/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java b/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java index 187b41685..d8969636b 100644 --- a/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java +++ b/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java @@ -107,7 +107,7 @@ public interface EngineMantle extends IObjectPlacer { } @Override - default void setTile(int x, int y, int z, TileData d) { + default void setTile(int x, int y, int z, TileData d) { getMantle().set(x, y, z, new TileWrapper(d)); } diff --git a/core/src/main/java/com/volmit/iris/engine/mantle/MantleWriter.java b/core/src/main/java/com/volmit/iris/engine/mantle/MantleWriter.java index 265e29c56..b43b72f27 100644 --- a/core/src/main/java/com/volmit/iris/engine/mantle/MantleWriter.java +++ b/core/src/main/java/com/volmit/iris/engine/mantle/MantleWriter.java @@ -206,7 +206,7 @@ public class MantleWriter implements IObjectPlacer { } @Override - public void setTile(int xx, int yy, int zz, TileData tile) { + public void setTile(int xx, int yy, int zz, TileData tile) { getEngineMantle().setTile(xx, yy, zz, tile); } 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 4f48245bf..ac091e346 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 @@ -57,7 +57,7 @@ public class MantleJigsawComponent extends IrisMantleComponent { @ChunkCoordinates private void generateJigsaw(MantleWriter writer, int x, int z, IrisBiome biome, IrisRegion region) { - long seed = cng.fit(Integer.MIN_VALUE, Integer.MIN_VALUE, x, z); + long seed = cng.fit(Integer.MIN_VALUE, Integer.MAX_VALUE, x, z); if (getDimension().getStronghold() != null) { List poss = getDimension().getStrongholds(seed()); @@ -66,7 +66,7 @@ public class MantleJigsawComponent extends IrisMantleComponent { for (Position2 pos : poss) { if (x == pos.getX() >> 4 && z == pos.getZ() >> 4) { IrisJigsawStructure structure = getData().getJigsawStructureLoader().load(getDimension().getStronghold()); - place(writer, pos.toIris(), structure, new RNG(seed)); + place(writer, pos.toIris(), structure, new RNG(seed), true); return; } } @@ -92,7 +92,7 @@ public class MantleJigsawComponent extends IrisMantleComponent { RNG rng = new RNG(seed); IrisPosition position = new IrisPosition((x << 4) + rng.nextInt(15), 0, (z << 4) + rng.nextInt(15)); IrisJigsawStructure structure = getData().getJigsawStructureLoader().load(i.getStructure()); - return place(writer, position, structure, rng); + return place(writer, position, structure, rng, false); } @ChunkCoordinates @@ -130,7 +130,7 @@ public class MantleJigsawComponent extends IrisMantleComponent { public IrisJigsawStructure guess(int x, int z) { // todo The guess doesnt bring into account that the placer may return -1 // todo doesnt bring skipped placements into account - long seed = cng.fit(Integer.MIN_VALUE, Integer.MIN_VALUE, x, z); + long seed = cng.fit(Integer.MIN_VALUE, Integer.MAX_VALUE, x, z); IrisBiome biome = getEngineMantle().getEngine().getSurfaceBiome((x << 4) + 8, (z << 4) + 8); IrisRegion region = getEngineMantle().getEngine().getRegion((x << 4) + 8, (z << 4) + 8); @@ -161,8 +161,8 @@ public class MantleJigsawComponent extends IrisMantleComponent { } @BlockCoordinates - private boolean place(MantleWriter writer, IrisPosition position, IrisJigsawStructure structure, RNG rng) { - return new PlannedStructure(structure, position, rng).place(writer, getMantle(), writer.getEngine()); + private boolean place(MantleWriter writer, IrisPosition position, IrisJigsawStructure structure, RNG rng, boolean forcePlace) { + return new PlannedStructure(structure, position, rng, forcePlace).place(writer, getMantle(), writer.getEngine()); } private long jigsaw() { diff --git a/core/src/main/java/com/volmit/iris/engine/object/IObjectLoot.java b/core/src/main/java/com/volmit/iris/engine/object/IObjectLoot.java new file mode 100644 index 000000000..6ee2ec882 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/engine/object/IObjectLoot.java @@ -0,0 +1,13 @@ +package com.volmit.iris.engine.object; + +import com.volmit.iris.core.loader.IrisData; +import com.volmit.iris.util.collection.KList; +import org.bukkit.block.data.BlockData; + +public interface IObjectLoot { + KList getFilter(); + KList getFilter(IrisData manager); + boolean isExact(); + String getName(); + int getWeight(); +} diff --git a/core/src/main/java/com/volmit/iris/engine/object/IObjectPlacer.java b/core/src/main/java/com/volmit/iris/engine/object/IObjectPlacer.java index d14f1e2f6..3a7a89a38 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IObjectPlacer.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IObjectPlacer.java @@ -44,7 +44,7 @@ public interface IObjectPlacer { boolean isDebugSmartBore(); - void setTile(int xx, int yy, int zz, TileData tile); + void setTile(int xx, int yy, int zz, TileData tile); Engine getEngine(); } diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisBiomeCustom.java b/core/src/main/java/com/volmit/iris/engine/object/IrisBiomeCustom.java index dd3e16af6..aa3859c83 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisBiomeCustom.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisBiomeCustom.java @@ -104,7 +104,7 @@ public class IrisBiomeCustom { JSONObject po = new JSONObject(); po.put("type", ambientParticle.getParticle().name().toLowerCase()); particle.put("options", po); - particle.put("probability", ambientParticle.getRarity()); + particle.put("probability", 1f/ambientParticle.getRarity()); effects.put("particle", particle); } diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisBiomeCustomSpawn.java b/core/src/main/java/com/volmit/iris/engine/object/IrisBiomeCustomSpawn.java index f064c775a..fd88885d1 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisBiomeCustomSpawn.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisBiomeCustomSpawn.java @@ -37,12 +37,10 @@ public class IrisBiomeCustomSpawn { private EntityType type = EntityType.COW; @MinNumber(1) - @MaxNumber(20) @Desc("The min to spawn") private int minCount = 2; @MinNumber(1) - @MaxNumber(20) @Desc("The max to spawn") private int maxCount = 5; diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisBlockData.java b/core/src/main/java/com/volmit/iris/engine/object/IrisBlockData.java index b926883a1..65a9a59a8 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisBlockData.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisBlockData.java @@ -21,6 +21,7 @@ package com.volmit.iris.engine.object; import com.volmit.iris.Iris; import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.core.loader.IrisRegistrant; +import com.volmit.iris.core.nms.INMS; import com.volmit.iris.engine.data.cache.AtomicCache; import com.volmit.iris.engine.object.annotations.*; import com.volmit.iris.util.collection.KList; @@ -61,6 +62,8 @@ public class IrisBlockData extends IrisRegistrant { private IrisBlockData backup = null; @Desc("Optional properties for this block data such as 'waterlogged': true") private KMap data = new KMap<>(); + @Desc("Optional tile data for this block data") + private KMap tileData = new KMap<>(); public IrisBlockData(String b) { this.block = b; @@ -196,17 +199,12 @@ public class IrisBlockData extends IrisRegistrant { }); } - public TileData tryGetTile() { + public TileData tryGetTile(IrisData data) { //TODO Do like a registry thing with the tile data registry. Also update the parsing of data to include **block** entities. - if (data.containsKey("entitySpawn")) { - TileSpawner spawner = new TileSpawner(); - String name = (String) data.get("entitySpawn"); - if (name.contains(":")) - name = name.split(":")[1]; - spawner.setEntityType(EntityType.fromName(name)); - return spawner; - } - return null; + var type = getBlockData(data).getMaterial(); + if (!INMS.get().hasTile(type) || tileData == null || tileData.isEmpty()) + return null; + return new TileData(type, this.tileData); } private String keyify(String dat) { 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 7206b2a3d..82ca54137 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 @@ -21,6 +21,7 @@ package com.volmit.iris.engine.object; import com.volmit.iris.Iris; import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.core.loader.IrisRegistrant; +import com.volmit.iris.core.nms.INMS; import com.volmit.iris.core.nms.datapack.IDataFixer; import com.volmit.iris.engine.data.cache.AtomicCache; import com.volmit.iris.engine.object.annotations.*; @@ -300,10 +301,8 @@ public class IrisDimension extends IrisRegistrant { private IrisMaterialPalette rockPalette = new IrisMaterialPalette().qclear().qadd("stone"); @Desc("The palette of blocks for 'water'") private IrisMaterialPalette fluidPalette = new IrisMaterialPalette().qclear().qadd("water"); - @Desc("Remove cartographers so they do not crash the server (Iris worlds only)") - private boolean removeCartographersDueToCrash = true; - @Desc("Notify players of cancelled cartographer villager in this radius in blocks (set to -1 to disable, -2 for everyone)") - private int notifyPlayersOfCartographerCancelledRadius = 30; + @Desc("Prevent cartographers to generate explorer maps (Iris worlds only)\nONLY TOUCH IF YOUR SERVER CRASHES WHILE GENERATING EXPLORER MAPS") + private boolean disableExplorerMaps = false; @Desc("Collection of ores to be generated") @ArrayType(type = IrisOreGenerator.class, min = 1) private KList ores = new KList<>(); @@ -489,10 +488,10 @@ public class IrisDimension extends IrisRegistrant { { "pack": { "description": "Iris Data Pack. This pack contains all installed Iris Packs' resources.", - "pack_format": 10 + "pack_format": {} } } - """); + """.replace("{}", INMS.get().getDataVersion().getPackFormat() + "")); } catch (IOException e) { Iris.reportError(e); e.printStackTrace(); diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisEntity.java b/core/src/main/java/com/volmit/iris/engine/object/IrisEntity.java index 6f732f449..0ce3a08e0 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisEntity.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisEntity.java @@ -271,7 +271,7 @@ public class IrisEntity extends IrisRegistrant { for (String fi : getLoot().getTables()) { IrisLootTable i = gen.getData().getLootLoader().load(fi); - items.addAll(i.getLoot(gen.isStudio(), rng.nextParallelRNG(345911), InventorySlotType.STORAGE, finalAt.getBlockX(), finalAt.getBlockY(), finalAt.getBlockZ())); + items.addAll(i.getLoot(gen.isStudio(), rng.nextParallelRNG(345911), InventorySlotType.STORAGE, finalAt.getWorld(), finalAt.getBlockX(), finalAt.getBlockY(), finalAt.getBlockZ())); } return items; 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 9d6d28506..b610f3e8a 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 @@ -22,6 +22,7 @@ import com.volmit.iris.Iris; 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.util.collection.KList; import com.volmit.iris.util.json.JSONObject; import com.volmit.iris.util.plugin.VolmitSender; @@ -69,6 +70,13 @@ public class IrisJigsawStructure extends IrisRegistrant { @Desc("Set to true to prevent rotating the initial structure piece") private boolean disableInitialRotation = false; + @RegistryListFunction(StructureKeyFunction.class) + @Desc("The minecraft key to use when creating treasure maps") + private String structureKey = null; + + @Desc("Force Place the whole structure") + private boolean forcePlace = false; + private transient AtomicCache maxDimension = new AtomicCache<>(); private void loadPool(String p, KList pools, KList pieces) { @@ -137,7 +145,7 @@ public class IrisJigsawStructure extends IrisRegistrant { avg += getLoader().getJigsawPieceLoader().load(i).getMax2dDimension(); } - return (avg / (pieces.size() > 0 ? pieces.size() : 1)) * (((getMaxDepth() + 1) * 2) + 1); + return (avg / (!pieces.isEmpty() ? pieces.size() : 1)) * (((getMaxDepth() + 1) * 2) + 1); } }); } diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisLootMode.java b/core/src/main/java/com/volmit/iris/engine/object/IrisLootMode.java index ae205e84e..b8d6b1341 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisLootMode.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisLootMode.java @@ -27,5 +27,7 @@ public enum IrisLootMode { @Desc("Clear all loot tables then add this table") CLEAR, @Desc("Replace all loot tables with this table (same as clear)") - REPLACE + REPLACE, + @Desc("Only use when there was no loot table defined by an object") + FALLBACK } diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisLootTable.java b/core/src/main/java/com/volmit/iris/engine/object/IrisLootTable.java index 38964c72a..07e13e875 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisLootTable.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisLootTable.java @@ -33,6 +33,7 @@ import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; import org.bukkit.Material; +import org.bukkit.World; import org.bukkit.inventory.ItemStack; @Accessors(chain = true) @@ -67,7 +68,7 @@ public class IrisLootTable extends IrisRegistrant { @ArrayType(min = 1, type = IrisLoot.class) private KList loot = new KList<>(); - public KList getLoot(boolean debug, RNG rng, InventorySlotType slot, int x, int y, int z) { + public KList getLoot(boolean debug, RNG rng, InventorySlotType slot, World world, int x, int y, int z) { KList lootf = new KList<>(); int m = 0; diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisMaterialPalette.java b/core/src/main/java/com/volmit/iris/engine/object/IrisMaterialPalette.java index b5d899158..830af6457 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisMaterialPalette.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisMaterialPalette.java @@ -64,11 +64,11 @@ public class IrisMaterialPalette { return getLayerGenerator(rng, rdata).fit(getBlockData(rdata), x / zoom, y / zoom, z / zoom); } - public Optional> getTile(RNG rng, double x, double y, double z, IrisData rdata) { + public Optional getTile(RNG rng, double x, double y, double z, IrisData rdata) { if (getBlockData(rdata).isEmpty()) return Optional.empty(); - TileData tile = getBlockData(rdata).size() == 1 ? palette.get(0).tryGetTile() : palette.getRandom(rng).tryGetTile(); + TileData tile = getBlockData(rdata).size() == 1 ? palette.get(0).tryGetTile(rdata) : palette.getRandom(rng).tryGetTile(rdata); return tile != null ? Optional.of(tile) : Optional.empty(); } diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisObject.java b/core/src/main/java/com/volmit/iris/engine/object/IrisObject.java index cb85b0d66..6691720dd 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisObject.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisObject.java @@ -43,6 +43,7 @@ import com.volmit.iris.util.parallel.MultiBurst; import com.volmit.iris.util.plugin.VolmitSender; import com.volmit.iris.util.scheduling.IrisLock; import com.volmit.iris.util.scheduling.PrecisionStopwatch; +import com.volmit.iris.util.scheduling.jobs.Job; import com.volmit.iris.util.stream.ProceduralStream; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -63,7 +64,9 @@ import org.bukkit.util.Vector; import java.io.*; import java.util.*; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiConsumer; @Accessors(chain = true) @@ -84,7 +87,7 @@ public class IrisObject extends IrisRegistrant { @Setter protected transient AtomicCache aabb = new AtomicCache<>(); private KMap blocks; - private KMap> states; + private KMap states; @Getter @Setter private int w; @@ -384,15 +387,97 @@ public class IrisObject extends IrisRegistrant { } } + public void write(OutputStream o, VolmitSender sender) throws IOException { + AtomicReference ref = new AtomicReference<>(); + CountDownLatch latch = new CountDownLatch(1); + new Job() { + private int total = getBlocks().size() * 3 + getStates().size(); + private int c = 0; + + @Override + public String getName() { + return "Saving Object"; + } + + @Override + public void execute() { + try { + DataOutputStream dos = new DataOutputStream(o); + dos.writeInt(w); + dos.writeInt(h); + dos.writeInt(d); + dos.writeUTF("Iris V2 IOB;"); + + KList palette = new KList<>(); + + for (BlockData i : getBlocks().values()) { + palette.addIfMissing(i.getAsString()); + ++c; + } + total -= getBlocks().size() - palette.size(); + + dos.writeShort(palette.size()); + + for (String i : palette) { + dos.writeUTF(i); + ++c; + } + + dos.writeInt(getBlocks().size()); + + for (BlockVector i : getBlocks().keySet()) { + dos.writeShort(i.getBlockX()); + dos.writeShort(i.getBlockY()); + dos.writeShort(i.getBlockZ()); + dos.writeShort(palette.indexOf(getBlocks().get(i).getAsString())); + ++c; + } + + dos.writeInt(getStates().size()); + for (BlockVector i : getStates().keySet()) { + dos.writeShort(i.getBlockX()); + dos.writeShort(i.getBlockY()); + dos.writeShort(i.getBlockZ()); + getStates().get(i).toBinary(dos); + ++c; + } + } catch (IOException e) { + ref.set(e); + } finally { + latch.countDown(); + } + } + + @Override + public void completeWork() {} + + @Override + public int getTotalWork() { + return total; + } + + @Override + public int getWorkCompleted() { + return c; + } + }.execute(sender, true, () -> {}); + + try { + latch.await(); + } catch (InterruptedException ignored) {} + if (ref.get() != null) + throw ref.get(); + } + public void read(File file) throws IOException { - FileInputStream fin = new FileInputStream(file); + var fin = new BufferedInputStream(new FileInputStream(file)); try { read(fin); fin.close(); } catch (Throwable e) { Iris.reportError(e); fin.close(); - fin = new FileInputStream(file); + fin = new BufferedInputStream(new FileInputStream(file)); readLegacy(fin); fin.close(); } @@ -408,6 +493,16 @@ public class IrisObject extends IrisRegistrant { out.close(); } + public void write(File file, VolmitSender sender) throws IOException { + if (file == null) { + return; + } + + FileOutputStream out = new FileOutputStream(file); + write(out, sender); + out.close(); + } + public void shrinkwrap() { BlockVector min = new BlockVector(); BlockVector max = new BlockVector(); @@ -434,7 +529,7 @@ public class IrisObject extends IrisRegistrant { d.put(new BlockVector(i.getBlockX(), i.getBlockY(), i.getBlockZ()), Objects.requireNonNull(getBlocks().get(i))); } - KMap> dx = new KMap<>(); + KMap dx = new KMap<>(); for (BlockVector i : getBlocks().keySet()) { d.put(new BlockVector(i.getBlockX(), i.getBlockY(), i.getBlockZ()), Objects.requireNonNull(getBlocks().get(i))); @@ -467,7 +562,7 @@ public class IrisObject extends IrisRegistrant { } } - public void setUnsigned(int x, int y, int z, Block block) { + public void setUnsigned(int x, int y, int z, Block block, boolean legacy) { BlockVector v = getSigned(x, y, z); if (block == null) { @@ -476,9 +571,9 @@ public class IrisObject extends IrisRegistrant { } else { BlockData data = block.getBlockData(); getBlocks().put(v, data); - TileData state = TileData.getTileState(block); + TileData state = TileData.getTileState(block, legacy); if (state != null) { - Iris.info("Saved State " + v); + Iris.debug("Saved State " + v); getStates().put(v, state); } } @@ -568,7 +663,18 @@ public class IrisObject extends IrisRegistrant { yrand = yrand > 0 ? rng.i(0, yrand) : yrand < 0 ? rng.i(yrand, 0) : yrand; boolean bail = false; - if (yv < 0) { + if (config.isFromBottom()) { + // todo Convert this to a mode and make it compatible with jigsaw + y = (getH() + 1) + rty; + if (!config.isForcePlace()) { + if (placer.isCarved(x, y, z) || + placer.isCarved(x, y - 1, z) || + placer.isCarved(x, y - 2, z) || + placer.isCarved(x, y - 3, z)) { + bail = true; + } + } + } else if (yv < 0) { if (config.getMode().equals(ObjectPlaceMode.CENTER_HEIGHT) || config.getMode() == ObjectPlaceMode.CENTER_STILT) { y = (c != null ? c.getSurface() : placer.getHighest(x, z, getLoader(), config.isUnderwater())) + rty; if (!config.isForcePlace()) { @@ -802,7 +908,7 @@ public class IrisObject extends IrisRegistrant { for (BlockVector g : getBlocks().keySet()) { BlockData d; - TileData tile = null; + TileData tile = null; try { d = getBlocks().get(g); @@ -842,11 +948,9 @@ public class IrisObject extends IrisRegistrant { else data = newData; - if (newData.getMaterial() == Material.SPAWNER) { - Optional> t = j.getReplace().getTile(rng, x, y, z, rdata); - if (t.isPresent()) { - tile = t.get(); - } + Optional t = j.getReplace().getTile(rng, x, y, z, rdata); + if (t.isPresent()) { + tile = t.get(); } } } @@ -1044,7 +1148,7 @@ public class IrisObject extends IrisRegistrant { spinx, spiny, spinz)); } - KMap> dx = new KMap<>(); + KMap dx = new KMap<>(); for (BlockVector i : getStates().keySet()) { dx.put(r.rotate(i.clone(), spinx, spiny, spinz), getStates().get(i)); @@ -1062,9 +1166,7 @@ public class IrisObject extends IrisRegistrant { if (getStates().containsKey(i)) { Iris.info(Objects.requireNonNull(states.get(i)).toString()); - BlockState st = b.getState(); - Objects.requireNonNull(getStates().get(i)).toBukkitTry(st); - st.update(); + Objects.requireNonNull(getStates().get(i)).toBukkitTry(b); } } } @@ -1075,7 +1177,7 @@ public class IrisObject extends IrisRegistrant { b.setBlockData(Objects.requireNonNull(getBlocks().get(i)), false); if (getStates().containsKey(i)) { - Objects.requireNonNull(getStates().get(i)).toBukkitTry(b.getState()); + Objects.requireNonNull(getStates().get(i)).toBukkitTry(b); } } } @@ -1084,7 +1186,7 @@ public class IrisObject extends IrisRegistrant { return blocks; } - public synchronized KMap> getStates() { + public synchronized KMap getStates() { return states; } diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisObjectLoot.java b/core/src/main/java/com/volmit/iris/engine/object/IrisObjectLoot.java index eb3781a4d..da09ae2fc 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisObjectLoot.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisObjectLoot.java @@ -34,7 +34,7 @@ import org.bukkit.block.data.BlockData; @AllArgsConstructor @Desc("Represents loot within this object or jigsaw piece") @Data -public class IrisObjectLoot { +public class IrisObjectLoot implements IObjectLoot { private final transient AtomicCache> filterCache = new AtomicCache<>(); @ArrayType(min = 1, type = IrisBlockData.class) @Desc("The list of blocks this loot table should apply to") diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisObjectPlacement.java b/core/src/main/java/com/volmit/iris/engine/object/IrisObjectPlacement.java index d03248e87..adab09743 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisObjectPlacement.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisObjectPlacement.java @@ -34,9 +34,15 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; +import org.bukkit.Bukkit; import org.bukkit.Material; +import org.bukkit.NamespacedKey; import org.bukkit.TreeType; import org.bukkit.block.data.BlockData; +import org.jetbrains.annotations.Nullable; + +import java.util.Optional; +import java.util.function.Function; @Snippet("object-placer") @EqualsAndHashCode() @@ -103,6 +109,8 @@ public class IrisObjectPlacement { private boolean onwater = false; @Desc("If set to true, this object will only place parts of itself where blocks already exist. Warning: Melding is very performance intensive!") private boolean meld = false; + @Desc("If set to true, this object will get placed from the bottom of the world up") + private boolean fromBottom; @Desc("If set to true, this object will place from the ground up instead of height checks when not y locked to the surface. This is not compatable with X and Z axis rotations (it may look off)") private boolean bottom = false; @Desc("If set to true, air will be placed before the schematic places.") @@ -123,6 +131,9 @@ public class IrisObjectPlacement { @ArrayType(min = 1, type = IrisObjectLoot.class) @Desc("The loot tables to apply to these objects") private KList loot = new KList<>(); + @ArrayType(min = 1, type = IrisObjectVanillaLoot.class) + @Desc("The vanilla loot tables to apply to these objects") + private KList vanillaLoot = new KList<>(); @Desc("Whether the given loot tables override any and all other loot tables available in the dimension, region or biome.") private boolean overrideGlobalLoot = false; @Desc("This object / these objects override the following trees when they grow...") @@ -211,46 +222,63 @@ public class IrisObjectPlacement { private TableCache getCache(IrisData manager) { return cache.aquire(() -> { - TableCache tc = new TableCache(); + TableCache cache = new TableCache(); - for (IrisObjectLoot loot : getLoot()) { - if (loot == null) - continue; - IrisLootTable table = manager.getLootLoader().load(loot.getName()); - if (table == null) { - Iris.warn("Couldn't find loot table " + loot.getName()); - continue; + cache.merge(getCache(manager, getVanillaLoot(), IrisObjectPlacement::getVanillaTable)); + cache.merge(getCache(manager, getLoot(), manager.getLootLoader()::load)); + + return cache; + }); + } + + private TableCache getCache(IrisData manager, KList list, Function loader) { + TableCache tc = new TableCache(); + + for (IObjectLoot loot : list) { + if (loot == null) + continue; + IrisLootTable table = loader.apply(loot.getName()); + if (table == null) { + Iris.warn("Couldn't find loot table " + loot.getName()); + continue; + } + + if (loot.getFilter().isEmpty()) //Table applies to all containers + { + tc.global.put(table, loot.getWeight()); + } else if (!loot.isExact()) //Table is meant to be by type + { + for (BlockData filterData : loot.getFilter(manager)) { + if (!tc.basic.containsKey(filterData.getMaterial())) { + tc.basic.put(filterData.getMaterial(), new WeightedRandom<>()); + } + + tc.basic.get(filterData.getMaterial()).put(table, loot.getWeight()); } - - if (loot.getFilter().isEmpty()) //Table applies to all containers - { - tc.global.put(table, loot.getWeight()); - } else if (!loot.isExact()) //Table is meant to be by type - { - for (BlockData filterData : loot.getFilter(manager)) { - if (!tc.basic.containsKey(filterData.getMaterial())) { - tc.basic.put(filterData.getMaterial(), new WeightedRandom<>()); - } - - tc.basic.get(filterData.getMaterial()).put(table, loot.getWeight()); + } else //Filter is exact + { + for (BlockData filterData : loot.getFilter(manager)) { + if (!tc.exact.containsKey(filterData.getMaterial())) { + tc.exact.put(filterData.getMaterial(), new KMap<>()); } - } else //Filter is exact - { - for (BlockData filterData : loot.getFilter(manager)) { - if (!tc.exact.containsKey(filterData.getMaterial())) { - tc.exact.put(filterData.getMaterial(), new KMap<>()); - } - if (!tc.exact.get(filterData.getMaterial()).containsKey(filterData)) { - tc.exact.get(filterData.getMaterial()).put(filterData, new WeightedRandom<>()); - } - - tc.exact.get(filterData.getMaterial()).get(filterData).put(table, loot.getWeight()); + if (!tc.exact.get(filterData.getMaterial()).containsKey(filterData)) { + tc.exact.get(filterData.getMaterial()).put(filterData, new WeightedRandom<>()); } + + tc.exact.get(filterData.getMaterial()).get(filterData).put(table, loot.getWeight()); } } - return tc; - }); + } + return tc; + } + + @Nullable + private static IrisVanillaLootTable getVanillaTable(String name) { + return Optional.ofNullable(NamespacedKey.fromString(name)) + .map(Bukkit::getLootTable) + .map(IrisVanillaLootTable::new) + .orElse(null); } /** @@ -262,7 +290,6 @@ public class IrisObjectPlacement { */ public IrisLootTable getTable(BlockData data, IrisData dataManager) { TableCache cache = getCache(dataManager); - if (B.isStorageChest(data)) { IrisLootTable picked = null; if (cache.exact.containsKey(data.getMaterial()) && cache.exact.get(data.getMaterial()).containsKey(data)) { @@ -283,5 +310,11 @@ public class IrisObjectPlacement { final transient WeightedRandom global = new WeightedRandom<>(); final transient KMap> basic = new KMap<>(); final transient KMap>> exact = new KMap<>(); + + private void merge(TableCache other) { + global.merge(other.global); + basic.merge(other.basic, WeightedRandom::merge); + exact.merge(other.exact, (a, b) -> a.merge(b, WeightedRandom::merge)); + } } } diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisObjectRotation.java b/core/src/main/java/com/volmit/iris/engine/object/IrisObjectRotation.java index 6e8eb2a11..9ae2c3c32 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisObjectRotation.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisObjectRotation.java @@ -22,6 +22,7 @@ import com.volmit.iris.Iris; import com.volmit.iris.engine.object.annotations.Desc; import com.volmit.iris.engine.object.annotations.Snippet; import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.collection.KMap; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -30,9 +31,12 @@ import org.bukkit.Axis; import org.bukkit.Material; import org.bukkit.block.BlockFace; import org.bukkit.block.data.*; +import org.bukkit.block.data.type.Wall; +import org.bukkit.block.structure.StructureRotation; import org.bukkit.util.BlockVector; import java.util.List; +import java.util.Map; @Snippet("object-rotator") @Accessors(chain = true) @@ -41,6 +45,8 @@ import java.util.List; @Desc("Configures rotation for iris") @Data public class IrisObjectRotation { + private static final List WALL_FACES = List.of(BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST); + @Desc("If this rotator is enabled or not") private boolean enabled = true; @@ -282,6 +288,22 @@ public class IrisObjectRotation { for (BlockFace i : faces) { g.setFace(i, true); } + } else if (d instanceof Wall wall) { + KMap faces = new KMap<>(); + + for (BlockFace i : WALL_FACES) { + Wall.Height h = wall.getHeight(i); + BlockVector bv = new BlockVector(i.getModX(), i.getModY(), i.getModZ()); + bv = rotate(bv.clone(), spinx, spiny, spinz); + BlockFace r = getFace(bv); + if (WALL_FACES.contains(r)) { + faces.put(r, h); + } + } + + for (BlockFace i : WALL_FACES) { + wall.setHeight(i, faces.getOrDefault(i, Wall.Height.NONE)); + } } else if (d.getMaterial().equals(Material.NETHER_PORTAL) && d instanceof Orientable g) { //TODO: Fucks up logs BlockFace f = faceForAxis(g.getAxis()); diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisObjectVanillaLoot.java b/core/src/main/java/com/volmit/iris/engine/object/IrisObjectVanillaLoot.java new file mode 100644 index 000000000..bd93ea998 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisObjectVanillaLoot.java @@ -0,0 +1,50 @@ +package com.volmit.iris.engine.object; + +import com.volmit.iris.core.loader.IrisData; +import com.volmit.iris.engine.data.cache.AtomicCache; +import com.volmit.iris.engine.object.annotations.*; +import com.volmit.iris.engine.object.annotations.functions.LootTableKeyFunction; +import com.volmit.iris.util.collection.KList; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import org.bukkit.block.data.BlockData; + +@Snippet("object-vanilla-loot") +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor +@Desc("Represents vanilla loot within this object or jigsaw piece") +@Data +public class IrisObjectVanillaLoot implements IObjectLoot { + private final transient AtomicCache> filterCache = new AtomicCache<>(); + @ArrayType(min = 1, type = IrisBlockData.class) + @Desc("The list of blocks this loot table should apply to") + private KList filter = new KList<>(); + @Desc("Exactly match the block data or not") + private boolean exact = false; + @Desc("The vanilla loot table key") + @Required + @RegistryListFunction(LootTableKeyFunction.class) + private String name; + @Desc("The weight of this loot table being chosen") + private int weight = 1; + + public KList getFilter(IrisData rdata) { + return filterCache.aquire(() -> + { + KList b = new KList<>(); + + for (IrisBlockData i : filter) { + BlockData bx = i.getBlockData(rdata); + + if (bx != null) { + b.add(bx); + } + } + + return b; + }); + } +} diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisVanillaLootTable.java b/core/src/main/java/com/volmit/iris/engine/object/IrisVanillaLootTable.java new file mode 100644 index 000000000..ee25d447b --- /dev/null +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisVanillaLootTable.java @@ -0,0 +1,80 @@ +package com.volmit.iris.engine.object; + +import com.volmit.iris.core.loader.IrisData; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.math.RNG; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.inventory.ItemStack; +import org.bukkit.loot.LootContext; +import org.bukkit.loot.LootTable; + +import java.io.File; + +@Data +@EqualsAndHashCode(callSuper = false) +public class IrisVanillaLootTable extends IrisLootTable { + private final LootTable lootTable; + + @Override + public String getName() { + return "Vanilla " + lootTable.getKey(); + } + + @Override + public int getRarity() { + return 0; + } + + @Override + public int getMaxPicked() { + return 0; + } + + @Override + public int getMinPicked() { + return 0; + } + + @Override + public int getMaxTries() { + return 0; + } + + @Override + public KList getLoot() { + return new KList<>(); + } + + @Override + public KList getLoot(boolean debug, RNG rng, InventorySlotType slot, World world, int x, int y, int z) { + return new KList<>(lootTable.populateLoot(rng, new LootContext.Builder(new Location(world, x, y, z)).build())); + } + + @Override + public String getFolderName() { + throw new UnsupportedOperationException("VanillaLootTables do not have a folder name"); + } + + @Override + public String getTypeName() { + throw new UnsupportedOperationException("VanillaLootTables do not have a type name"); + } + + @Override + public File getLoadFile() { + throw new UnsupportedOperationException("VanillaLootTables do not have a load file"); + } + + @Override + public IrisData getLoader() { + throw new UnsupportedOperationException("VanillaLootTables do not have a loader"); + } + + @Override + public KList getPreprocessors() { + return new KList<>(); + } +} diff --git a/core/src/main/java/com/volmit/iris/engine/object/LegacyTileData.java b/core/src/main/java/com/volmit/iris/engine/object/LegacyTileData.java new file mode 100644 index 000000000..7cbcc094c --- /dev/null +++ b/core/src/main/java/com/volmit/iris/engine/object/LegacyTileData.java @@ -0,0 +1,282 @@ +package com.volmit.iris.engine.object; + +import com.volmit.iris.core.nms.container.Pair; +import com.volmit.iris.engine.data.cache.AtomicCache; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.scheduling.J; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.NonNull; +import lombok.ToString; +import org.apache.commons.io.function.IOFunction; +import org.bukkit.*; +import org.bukkit.block.*; +import org.bukkit.block.banner.Pattern; +import org.bukkit.block.banner.PatternType; +import org.bukkit.block.data.BlockData; +import org.bukkit.entity.EntityType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +@ToString +@EqualsAndHashCode(callSuper = false) +public class LegacyTileData extends TileData { + private static final Map>> legacy = Map.of( + 0, new Pair<>(SignHandler::fromBukkit, SignHandler::new), + 1, new Pair<>(SpawnerHandler::fromBukkit, SpawnerHandler::new), + 2, new Pair<>(BannerHandler::fromBukkit, BannerHandler::new)); + private static final AtomicCache> SIGNS = new AtomicCache<>(); + private final int id; + private final Handler handler; + + public LegacyTileData(DataInputStream in) throws IOException { + id = in.readShort(); + var factory = legacy.get(id); + if (factory == null) + throw new IOException("Unknown tile type: " + id); + handler = factory.getB().apply(in); + } + + private LegacyTileData(int id, Handler handler) { + this.id = id; + this.handler = handler; + } + + @Nullable + public static LegacyTileData fromBukkit(@NonNull BlockState tileState) { + var type = tileState.getType(); + for (var id : legacy.keySet()) { + var factory = legacy.get(id); + var handler = factory.getA().apply(tileState, type); + if (handler != null) + return new LegacyTileData(id, handler); + } + return null; + } + + @Override + public @NonNull KMap getProperties() { + return new KMap<>(); + } + + @Override + public @NonNull Material getMaterial() { + return handler.getMaterial(); + } + + @Override + public boolean isApplicable(BlockData data) { + return handler.isApplicable(data); + } + + @Override + public void toBukkit(Block block) { + J.s(() -> handler.toBukkit(block)); + } + + @Override + public void toBinary(DataOutputStream out) throws IOException { + out.writeShort(id); + handler.toBinary(out); + } + + @Override + public TileData clone() { + return this; + } + + private interface Handler { + Material getMaterial(); + boolean isApplicable(BlockData data); + void toBinary(DataOutputStream out) throws IOException; + void toBukkit(Block block); + } + + @FunctionalInterface + private interface Builder { + @Nullable Handler apply(@NonNull BlockState blockState, @NonNull Material type); + } + + @ToString + @EqualsAndHashCode + @AllArgsConstructor + private static class SignHandler implements Handler { + private final String line1; + private final String line2; + private final String line3; + private final String line4; + private final DyeColor dyeColor; + + private SignHandler(DataInputStream in) throws IOException { + line1 = in.readUTF(); + line2 = in.readUTF(); + line3 = in.readUTF(); + line4 = in.readUTF(); + dyeColor = DyeColor.values()[in.readByte()]; + } + + @SuppressWarnings("deprecation") + private static SignHandler fromBukkit(BlockState blockState, Material type) { + if (!signsTag().isTagged(type) || !(blockState instanceof Sign sign)) + return null; + return new SignHandler(sign.getLine(0), sign.getLine(1), sign.getLine(2), sign.getLine(3), sign.getColor()); + } + + @Override + public Material getMaterial() { + return Material.OAK_SIGN; + } + + @Override + public boolean isApplicable(BlockData data) { + return signsTag().isTagged(data.getMaterial()); + } + + @Override + public void toBinary(DataOutputStream out) throws IOException { + out.writeUTF(line1); + out.writeUTF(line2); + out.writeUTF(line3); + out.writeUTF(line4); + out.writeByte(dyeColor.ordinal()); + } + + @Override + public void toBukkit(Block block) { + Sign sign = (Sign) block.getState(); + sign.setLine(0, line1); + sign.setLine(1, line2); + sign.setLine(2, line3); + sign.setLine(3, line4); + sign.setColor(dyeColor); + sign.update(); + } + } + @ToString + @EqualsAndHashCode + @AllArgsConstructor + private static class SpawnerHandler implements Handler { + private final EntityType type; + + private SpawnerHandler(DataInputStream in) throws IOException { + type = EntityType.values()[in.readShort()]; + } + + private static SpawnerHandler fromBukkit(BlockState blockState, Material material) { + if (material != Material.SPAWNER || !(blockState instanceof CreatureSpawner spawner)) + return null; + return new SpawnerHandler(spawner.getSpawnedType()); + } + + @Override + public Material getMaterial() { + return Material.SPAWNER; + } + + @Override + public boolean isApplicable(BlockData data) { + return data.getMaterial() == Material.SPAWNER; + } + + @Override + public void toBinary(DataOutputStream out) throws IOException { + out.writeShort(type.ordinal()); + } + + @Override + public void toBukkit(Block block) { + CreatureSpawner spawner = (CreatureSpawner) block.getState(); + spawner.setSpawnedType(type); + spawner.update(); + } + } + @ToString + @EqualsAndHashCode + @AllArgsConstructor + private static class BannerHandler implements Handler { + private final KList patterns; + private final DyeColor baseColor; + + private BannerHandler(DataInputStream in) throws IOException { + baseColor = DyeColor.values()[in.readByte()]; + patterns = new KList<>(); + int listSize = in.readByte(); + for (int i = 0; i < listSize; i++) { + DyeColor color = DyeColor.values()[in.readByte()]; + PatternType pattern = PatternType.values()[in.readByte()]; + patterns.add(new Pattern(color, pattern)); + } + } + + private static BannerHandler fromBukkit(BlockState blockState, Material type) { + if (!Tag.BANNERS.isTagged(type) || !(blockState instanceof Banner banner)) + return null; + return new BannerHandler(new KList<>(banner.getPatterns()), banner.getBaseColor()); + } + + @Override + public Material getMaterial() { + return Material.WHITE_BANNER; + } + + @Override + public boolean isApplicable(BlockData data) { + return Tag.BANNERS.isTagged(data.getMaterial()); + } + + @Override + public void toBinary(DataOutputStream out) throws IOException { + out.writeByte(baseColor.ordinal()); + out.writeByte(patterns.size()); + for (Pattern i : patterns) { + out.writeByte(i.getColor().ordinal()); + out.writeByte(i.getPattern().ordinal()); + } + } + + @Override + public void toBukkit(Block block) { + Banner banner = (Banner) block.getState(); + banner.setBaseColor(baseColor); + banner.setPatterns(patterns); + banner.update(); + } + } + + private static Tag signsTag() { + return SIGNS.aquire(() -> { + var signs = Bukkit.getTag("blocks", NamespacedKey.minecraft("all_signs"), Material.class); + if (signs != null) + return signs; + return new Tag<>() { + @Override + public boolean isTagged(@NotNull Material item) { + return item.getKey().getKey().endsWith("_sign"); + } + + @NotNull + @Override + public Set getValues() { + return StreamSupport.stream(Registry.MATERIAL.spliterator(), false) + .filter(this::isTagged) + .collect(Collectors.toUnmodifiableSet()); + } + + @NotNull + @Override + public NamespacedKey getKey() { + return NamespacedKey.minecraft("all_signs"); + } + }; + }); + } +} diff --git a/core/src/main/java/com/volmit/iris/engine/object/TileBanner.java b/core/src/main/java/com/volmit/iris/engine/object/TileBanner.java deleted file mode 100644 index 5c6b486a2..000000000 --- a/core/src/main/java/com/volmit/iris/engine/object/TileBanner.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.volmit.iris.engine.object; - -import com.volmit.iris.util.nbt.tag.CompoundTag; -import com.volmit.iris.util.nbt.tag.ListTag; -import lombok.Data; -import org.bukkit.DyeColor; -import org.bukkit.Material; -import org.bukkit.block.Banner; -import org.bukkit.block.banner.Pattern; -import org.bukkit.block.banner.PatternType; -import org.bukkit.block.data.BlockData; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -@Data -public class TileBanner implements TileData { - public static final int id = 2; - - private List patterns = new ArrayList<>(); - private DyeColor baseColor; - - @Override - public String getTileId() { - return "minecraft:banner"; - } - - @Override - public boolean isApplicable(BlockData data) { - return isBanner(data.getMaterial()); - } - - @Override - public void toBukkit(Banner banner) { - banner.setPatterns(patterns); - banner.setBaseColor(baseColor); - } - - @Override - public void fromBukkit(Banner banner) { - this.patterns = banner.getPatterns(); - this.baseColor = banner.getBaseColor(); - } - - @SuppressWarnings("MethodDoesntCallSuperMethod") - @Override - public TileBanner clone() { - TileBanner ts = new TileBanner(); - ts.setBaseColor(getBaseColor()); - ts.setPatterns(getPatterns()); - return ts; - } - - @Override - public void toBinary(DataOutputStream out) throws IOException { - out.writeShort(id); - out.writeByte(baseColor.ordinal()); - out.writeByte(patterns.size()); - for (Pattern p : patterns) { - out.writeByte(p.getColor().ordinal()); - out.writeByte(p.getPattern().ordinal()); - } - } - - @Override - public void fromBinary(DataInputStream in) throws IOException { - baseColor = DyeColor.values()[in.readByte()]; - int listSize = in.readByte(); - patterns = new ArrayList<>(); - - for (int i = 0; i < listSize; i++) { - DyeColor color = DyeColor.values()[in.readByte()]; - PatternType type = PatternType.values()[in.readByte()]; - patterns.add(new Pattern(color, type)); - } - } - - @SuppressWarnings("deprecation") - @Override - public CompoundTag toNBT(CompoundTag tag) { - @SuppressWarnings("unchecked") ListTag listTag = (ListTag) ListTag.createUnchecked(CompoundTag.class); - for (Pattern p : patterns) { - CompoundTag pattern = new CompoundTag(); - pattern.putString("Pattern", p.getPattern().getIdentifier()); - pattern.putByte("Color", p.getColor().getDyeData()); - listTag.add(pattern); - } - tag.put("Patterns", listTag); - return tag; - } - - public boolean isBanner(Material material) { - return switch (material) { - case RED_BANNER, RED_WALL_BANNER, ORANGE_BANNER, ORANGE_WALL_BANNER, YELLOW_BANNER, YELLOW_WALL_BANNER, LIME_BANNER, LIME_WALL_BANNER, GREEN_BANNER, GREEN_WALL_BANNER, CYAN_BANNER, CYAN_WALL_BANNER, LIGHT_BLUE_BANNER, LIGHT_BLUE_WALL_BANNER, BLUE_BANNER, BLUE_WALL_BANNER, PURPLE_BANNER, PURPLE_WALL_BANNER, MAGENTA_BANNER, MAGENTA_WALL_BANNER, PINK_BANNER, PINK_WALL_BANNER, WHITE_BANNER, WHITE_WALL_BANNER, LIGHT_GRAY_BANNER, LIGHT_GRAY_WALL_BANNER, GRAY_BANNER, GRAY_WALL_BANNER, BLACK_BANNER, BLACK_WALL_BANNER, BROWN_BANNER, BROWN_WALL_BANNER -> - true; - default -> false; - }; - } -} diff --git a/core/src/main/java/com/volmit/iris/engine/object/TileData.java b/core/src/main/java/com/volmit/iris/engine/object/TileData.java index 7dd5722f2..094c48b00 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/TileData.java +++ b/core/src/main/java/com/volmit/iris/engine/object/TileData.java @@ -18,84 +18,104 @@ package com.volmit.iris.engine.object; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import com.volmit.iris.Iris; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.nbt.tag.CompoundTag; +import com.volmit.iris.core.nms.INMS; +import com.volmit.iris.util.collection.KMap; +import lombok.*; +import org.bukkit.Material; import org.bukkit.block.Block; -import org.bukkit.block.BlockState; import org.bukkit.block.TileState; import org.bukkit.block.data.BlockData; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -import java.lang.reflect.InvocationTargetException; @SuppressWarnings("ALL") -public interface TileData extends Cloneable { +@Getter +@ToString +@EqualsAndHashCode +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class TileData implements Cloneable { + private static final Gson gson = new GsonBuilder().disableHtmlEscaping().setLenient().create(); - static final KList> registry = setup(); + @NonNull + private Material material; + @NonNull + private KMap properties; - static KList> setup() { - KList> registry = new KList<>(); - - registry.add(new TileSign()); - registry.add(new TileSpawner()); - registry.add(new TileBanner()); - - return registry; - } - - static TileData read(DataInputStream s) throws IOException { - try { - int id = s.readShort(); - @SuppressWarnings("unchecked") TileData d = registry.get(id).getClass().getConstructor().newInstance(); - d.fromBinary(s); - return d; - } catch (InvocationTargetException | InstantiationException | IllegalAccessException | - NoSuchMethodException e) { - throw new IOException("Failed to create TileData instance due to missing type registrar!"); - } - } - - static boolean setTileState(Block block, TileData data) { + public static boolean setTileState(Block block, TileData data) { if (block.getState() instanceof TileState && data.isApplicable(block.getBlockData())) - return data.toBukkitTry(block.getState()); + return data.toBukkitTry(block); return false; } - static TileData getTileState(Block block) { - for (TileData i : registry) { - BlockData data = block.getBlockData(); - - if (i.isApplicable(data)) { - try { - @SuppressWarnings("unchecked") TileData s = i.getClass().getConstructor().newInstance(); - s.fromBukkitTry(block.getState()); - return s; - } catch (Throwable e) { - Iris.reportError(e); - e.printStackTrace(); - } - } + public static TileData getTileState(Block block, boolean useLegacy) { + if (!INMS.get().hasTile(block.getType())) + return null; + if (useLegacy) { + var legacy = LegacyTileData.fromBukkit(block.getState()); + if (legacy != null) + return legacy; } - return null; + return new TileData().fromBukkit(block); } - String getTileId(); + public static TileData read(DataInputStream in) throws IOException { + if (!in.markSupported()) + throw new IOException("Mark not supported"); + in.mark(Integer.MAX_VALUE); + try { + return new TileData( + Material.matchMaterial(in.readUTF()), + gson.fromJson(in.readUTF(), KMap.class)); + } catch (Throwable e) { + in.reset(); + return new LegacyTileData(in); + } finally { + in.mark(0); + } + } - boolean isApplicable(BlockData data); + public boolean isApplicable(BlockData data) { + return material != null && data.getMaterial() == material; + } - void toBukkit(T t); + public void toBukkit(Block block) { + if (material == null) throw new IllegalStateException("Material not set"); + if (block.getType() != material) + throw new IllegalStateException("Material mismatch: " + block.getType() + " vs " + material); + INMS.get().deserializeTile(properties, block.getLocation()); + } - void fromBukkit(T t); + public TileData fromBukkit(Block block) { + if (material != null && block.getType() != material) + throw new IllegalStateException("Material mismatch: " + block.getType() + " vs " + material); + if (material == null) material = block.getType(); + properties = INMS.get().serializeTile(block.getLocation()); + return this; + } - default boolean toBukkitTry(BlockState t) { + public boolean toBukkitTry(Block block) { try { //noinspection unchecked - toBukkit((T) t); - t.update(); + toBukkit(block); + return true; + } catch (Throwable e) { + Iris.reportError(e); + } + + return false; + } + + public boolean fromBukkitTry(Block block) { + try { + //noinspection unchecked + fromBukkit(block); return true; } catch (Throwable e) { Iris.reportError(e); @@ -105,24 +125,16 @@ public interface TileData extends Cloneable { return false; } - default boolean fromBukkitTry(BlockState t) { - try { - //noinspection unchecked - fromBukkit((T) t); - return true; - } catch (Throwable e) { - Iris.reportError(e); - - } - - return false; + public void toBinary(DataOutputStream out) throws IOException { + out.writeUTF(material == null ? "" : material.getKey().toString()); + out.writeUTF(gson.toJson(properties)); } - CompoundTag toNBT(CompoundTag parent); - - void toBinary(DataOutputStream out) throws IOException; - - void fromBinary(DataInputStream in) throws IOException; - - TileData clone(); + @Override + public TileData clone() { + var clone = new TileData(); + clone.material = material; + clone.properties = properties.copy(); //TODO make a deep copy + return clone; + } } diff --git a/core/src/main/java/com/volmit/iris/engine/object/TileSign.java b/core/src/main/java/com/volmit/iris/engine/object/TileSign.java deleted file mode 100644 index 119ec3378..000000000 --- a/core/src/main/java/com/volmit/iris/engine/object/TileSign.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.volmit.iris.engine.object; - -import com.volmit.iris.util.nbt.tag.CompoundTag; -import lombok.Data; -import org.bukkit.DyeColor; -import org.bukkit.block.Sign; -import org.bukkit.block.data.BlockData; -import org.bukkit.block.data.type.WallSign; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; - -@Data -public class TileSign implements TileData { - public static final int id = 0; - private String line1; - private String line2; - private String line3; - private String line4; - private DyeColor dyeColor; - - @Override - public String getTileId() { - return "minecraft:sign"; - } - - @Override - public boolean isApplicable(BlockData data) { - return data instanceof org.bukkit.block.data.type.Sign || data instanceof WallSign; - } - - @Override - public void toBukkit(Sign t) { - t.setLine(0, line1); - t.setLine(1, line2); - t.setLine(2, line3); - t.setLine(3, line4); - t.setColor(dyeColor); - } - - @Override - public void fromBukkit(Sign sign) { - line1 = sign.getLine(0); - line2 = sign.getLine(1); - line3 = sign.getLine(2); - line4 = sign.getLine(3); - dyeColor = sign.getColor(); - } - - @SuppressWarnings("MethodDoesntCallSuperMethod") - @Override - public TileSign clone() { - TileSign ts = new TileSign(); - ts.setDyeColor(getDyeColor()); - ts.setLine1(getLine1()); - ts.setLine2(getLine2()); - ts.setLine3(getLine3()); - ts.setLine4(getLine4()); - return ts; - } - - @Override - public void toBinary(DataOutputStream out) throws IOException { - out.writeShort(id); - out.writeUTF(line1); - out.writeUTF(line2); - out.writeUTF(line3); - out.writeUTF(line4); - out.writeByte(dyeColor.ordinal()); - } - - @Override - public void fromBinary(DataInputStream in) throws IOException { - line1 = in.readUTF(); - line2 = in.readUTF(); - line3 = in.readUTF(); - line4 = in.readUTF(); - dyeColor = DyeColor.values()[in.readByte()]; - } - - @Override - public CompoundTag toNBT(CompoundTag tag) { - tag.putString("Text1", line1); - tag.putString("Text2", line2); - tag.putString("Text3", line3); - tag.putString("Text4", line4); - tag.putString("Color", dyeColor.name().toLowerCase()); - return tag; - } -} diff --git a/core/src/main/java/com/volmit/iris/engine/object/TileSpawner.java b/core/src/main/java/com/volmit/iris/engine/object/TileSpawner.java deleted file mode 100644 index 20a6654d0..000000000 --- a/core/src/main/java/com/volmit/iris/engine/object/TileSpawner.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.volmit.iris.engine.object; - -import com.volmit.iris.util.nbt.tag.CompoundTag; -import com.volmit.iris.util.nbt.tag.ListTag; -import lombok.Data; -import org.bukkit.Material; -import org.bukkit.block.CreatureSpawner; -import org.bukkit.block.data.BlockData; -import org.bukkit.entity.EntityType; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; - -@Data -public class TileSpawner implements TileData { - - public static final int id = 1; - - private EntityType entityType; - - @Override - public String getTileId() { - return "minecraft:mob_spawner"; - } - - @Override - public boolean isApplicable(BlockData data) { - return data.getMaterial() == Material.SPAWNER; - } - - @Override - public void toBukkit(CreatureSpawner t) { - t.setSpawnedType(entityType); - } - - @Override - public void fromBukkit(CreatureSpawner sign) { - entityType = sign.getSpawnedType(); - } - - @SuppressWarnings("MethodDoesntCallSuperMethod") - @Override - public TileSpawner clone() { - TileSpawner ts = new TileSpawner(); - ts.setEntityType(getEntityType()); - return ts; - } - - @Override - public void toBinary(DataOutputStream out) throws IOException { - out.writeShort(id); - out.writeShort(entityType.ordinal()); - } - - @Override - public void fromBinary(DataInputStream in) throws IOException { - entityType = EntityType.values()[in.readShort()]; - } - - @Override - public CompoundTag toNBT(CompoundTag parent) { - @SuppressWarnings("unchecked") ListTag potentials = (ListTag) ListTag.createUnchecked(CompoundTag.class); - CompoundTag t = new CompoundTag(); - CompoundTag ent = new CompoundTag(); - ent.putString("id", entityType.getKey().toString()); - t.put("Entity", ent); - t.putInt("Weight", 1); - potentials.add(t); - parent.put("SpawnPotentials", potentials); - return parent; - } -} diff --git a/core/src/main/java/com/volmit/iris/engine/object/annotations/RegistryListFunction.java b/core/src/main/java/com/volmit/iris/engine/object/annotations/RegistryListFunction.java new file mode 100644 index 000000000..04d9fd5b2 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/engine/object/annotations/RegistryListFunction.java @@ -0,0 +1,16 @@ +package com.volmit.iris.engine.object.annotations; + +import com.volmit.iris.engine.framework.ListFunction; +import com.volmit.iris.util.collection.KList; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Retention(RUNTIME) +@Target({PARAMETER, TYPE, FIELD}) +public @interface RegistryListFunction { + Class>> value(); +} diff --git a/core/src/main/java/com/volmit/iris/engine/object/annotations/functions/LootTableKeyFunction.java b/core/src/main/java/com/volmit/iris/engine/object/annotations/functions/LootTableKeyFunction.java new file mode 100644 index 000000000..d77f49583 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/engine/object/annotations/functions/LootTableKeyFunction.java @@ -0,0 +1,33 @@ +package com.volmit.iris.engine.object.annotations.functions; + +import com.volmit.iris.core.loader.IrisData; +import com.volmit.iris.engine.framework.ListFunction; +import com.volmit.iris.util.collection.KList; +import org.bukkit.NamespacedKey; +import org.bukkit.Registry; +import org.bukkit.loot.LootTable; +import org.bukkit.loot.LootTables; + +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +public class LootTableKeyFunction implements ListFunction> { + @Override + public String key() { + return "loot-table-key"; + } + + @Override + public String fancyName() { + return "LootTable Key"; + } + + @Override + public KList apply(IrisData data) { + return StreamSupport.stream(Registry.LOOT_TABLES.spliterator(), false) + .map(LootTables::getLootTable) + .map(LootTable::getKey) + .map(NamespacedKey::toString) + .collect(Collectors.toCollection(KList::new)); + } +} 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 new file mode 100644 index 000000000..acfb7a6b5 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/engine/object/annotations/functions/StructureKeyFunction.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 StructureKeyFunction implements ListFunction> { + @Override + public String key() { + return "structure-key"; + } + + @Override + public String fancyName() { + return "Structure Key"; + } + + @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 3c0752662..237543e23 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 @@ -129,31 +129,29 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun @EventHandler public void onWorldInit(WorldInitEvent event) { try { - if (!initialized) { - world.setRawWorldSeed(event.getWorld().getSeed()); - if (world.name().equals(event.getWorld().getName())) { - Engine engine = getEngine(event.getWorld()); - if (engine == null) { - Iris.warn("Failed to get Engine!"); - J.s(() -> { - Engine engine1 = getEngine(event.getWorld()); - if (engine1 != null) { - try { - INMS.get().inject(event.getWorld().getSeed(), engine1, event.getWorld()); - Iris.info("Injected Iris Biome Source into " + event.getWorld().getName()); - initialized = true; - } catch (Throwable e) { - e.printStackTrace(); - } - } - }, 10); - } else { - INMS.get().inject(event.getWorld().getSeed(), engine, event.getWorld()); - Iris.info("Injected Iris Biome Source into " + event.getWorld().getName()); - spawnChunks.complete(INMS.get().getSpawnChunkCount(event.getWorld())); - initialized = true; + if (initialized || !world.name().equals(event.getWorld().getName())) + return; + world.setRawWorldSeed(event.getWorld().getSeed()); + Engine engine = getEngine(event.getWorld()); + if (engine == null) { + Iris.warn("Failed to get Engine!"); + J.s(() -> { + Engine engine1 = getEngine(event.getWorld()); + if (engine1 != null) { + try { + INMS.get().inject(event.getWorld().getSeed(), engine1, event.getWorld()); + Iris.info("Injected Iris Biome Source into " + event.getWorld().getName()); + initialized = true; + } catch (Throwable e) { + e.printStackTrace(); + } } - } + }, 10); + } else { + INMS.get().inject(event.getWorld().getSeed(), engine, event.getWorld()); + Iris.info("Injected Iris Biome Source into " + event.getWorld().getName()); + spawnChunks.complete(INMS.get().getSpawnChunkCount(event.getWorld())); + initialized = true; } } catch (Throwable e) { e.printStackTrace(); diff --git a/core/src/main/java/com/volmit/iris/util/collection/KList.java b/core/src/main/java/com/volmit/iris/util/collection/KList.java index 573a5e552..9e00ea20d 100644 --- a/core/src/main/java/com/volmit/iris/util/collection/KList.java +++ b/core/src/main/java/com/volmit/iris/util/collection/KList.java @@ -80,15 +80,6 @@ public class KList extends ArrayList implements List { return indexOf(v); } - /** - * Remove the last element - */ - public KList removeLast() { - remove(last()); - - return this; - } - public void addMultiple(T t, int c) { for (int i = 0; i < c; i++) { add(t); diff --git a/core/src/main/java/com/volmit/iris/util/collection/KMap.java b/core/src/main/java/com/volmit/iris/util/collection/KMap.java index 06fc14568..01baab32b 100644 --- a/core/src/main/java/com/volmit/iris/util/collection/KMap.java +++ b/core/src/main/java/com/volmit/iris/util/collection/KMap.java @@ -28,6 +28,7 @@ import java.util.Comparator; import java.util.Enumeration; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiFunction; @SuppressWarnings("ALL") public class KMap extends ConcurrentHashMap { @@ -151,6 +152,17 @@ public class KMap extends ConcurrentHashMap { return this; } + /** + * Merge with another map + * + * @param m the map to merge + * @return this map (builder) + */ + public KMap merge(KMap m, BiFunction merger) { + m.forEach((k, v) -> merge(k, v, merger)); + return this; + } + /** * Return a copy of this map * diff --git a/core/src/main/java/com/volmit/iris/util/data/Cuboid.java b/core/src/main/java/com/volmit/iris/util/data/Cuboid.java index 437f65298..ccd837eb2 100644 --- a/core/src/main/java/com/volmit/iris/util/data/Cuboid.java +++ b/core/src/main/java/com/volmit/iris/util/data/Cuboid.java @@ -20,6 +20,7 @@ package com.volmit.iris.util.data; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.math.Direction; +import com.volmit.iris.util.math.Position2; import org.bukkit.*; import org.bukkit.block.Block; import org.bukkit.configuration.serialization.ConfigurationSerializable; @@ -651,6 +652,10 @@ public class Cuboid implements Iterable, Cloneable, ConfigurationSerializ return new CuboidIterator(getWorld(), x1, y1, z1, x2, y2, z2); } + public Iterator chunkedIterator() { + return new ChunkedCuboidIterator(getWorld(), x1, y1, z1, x2, y2, z2); + } + /* * (non-Javadoc) * @@ -746,4 +751,82 @@ public class Cuboid implements Iterable, Cloneable, ConfigurationSerializ } } + public static class ChunkedCuboidIterator implements Iterator { + private final World w; + private final int minRX, minY, minRZ, maxRX, maxY, maxRZ; + private final int minCX, minCZ, maxCX, maxCZ; + private int mX, mZ, bX, rX, rZ, y; + + private Position2 chunk; + private int cX, cZ; + + public ChunkedCuboidIterator(World w, int x1, int y1, int z1, int x2, int y2, int z2) { + this.w = w; + minY = Math.min(y1, y2); + maxY = Math.max(y1, y2); + int minX = Math.min(x1, x2); + int minZ = Math.min(z1, z2); + int maxX = Math.max(x1, x2); + int maxZ = Math.max(z1, z2); + minRX = minX & 15; + minRZ = minZ & 15; + maxRX = maxX & 15; + maxRZ = maxZ & 15; + + minCX = minX >> 4; + minCZ = minZ >> 4; + maxCX = maxX >> 4; + maxCZ = maxZ >> 4; + cX = minCX; + cZ = minCZ; + + rX = minX & 15; + rZ = minZ & 15; + y = minY; + } + + @Override + public boolean hasNext() { + return chunk != null || hasNextChunk(); + } + + public boolean hasNextChunk() { + return cX <= maxCX && cZ <= maxCZ; + } + + @Override + public Block next() { + if (chunk == null) { + chunk = new Position2(cX, cZ); + if (++cX > maxCX) { + cX = minCX; + cZ++; + } + + mX = chunk.getX() == maxCX ? maxRX : 15; + mZ = chunk.getZ() == maxCZ ? maxRZ : 15; + rX = bX = chunk.getX() == minCX ? minRX : 0; + rZ = chunk.getZ() == minCZ ? minRZ : 0; + } + + var b = w.getBlockAt((chunk.getX() << 4) + rX, y, (chunk.getZ() << 4) + rZ); + if (++y > maxY) { + y = minY; + if (++rX > mX) { + if (++rZ > mZ) { + chunk = null; + return b; + } + rX = bX; + } + } + + return b; + } + + @Override + public void remove() { + // nop + } + } } \ No newline at end of file diff --git a/core/src/main/java/com/volmit/iris/util/data/WeightedRandom.java b/core/src/main/java/com/volmit/iris/util/data/WeightedRandom.java index 16cdd6b4e..ba6cf07a6 100644 --- a/core/src/main/java/com/volmit/iris/util/data/WeightedRandom.java +++ b/core/src/main/java/com/volmit/iris/util/data/WeightedRandom.java @@ -42,11 +42,18 @@ public class WeightedRandom { totalWeight += weight; } + public WeightedRandom merge(WeightedRandom other) { + weightedObjects.addAll(other.weightedObjects); + totalWeight += other.totalWeight; + return this; + } + public T pullRandom() { int pull = random.nextInt(totalWeight); int index = 0; while (pull > 0) { pull -= weightedObjects.get(index).getV(); + if (pull <= 0) break; index++; } return weightedObjects.get(index).getK(); diff --git a/core/src/main/java/com/volmit/iris/util/interpolation/IrisInterpolation.java b/core/src/main/java/com/volmit/iris/util/interpolation/IrisInterpolation.java index 41b34a0ea..b1ecd72ca 100644 --- a/core/src/main/java/com/volmit/iris/util/interpolation/IrisInterpolation.java +++ b/core/src/main/java/com/volmit/iris/util/interpolation/IrisInterpolation.java @@ -997,7 +997,10 @@ public class IrisInterpolation { return getNoise3D(method, x, y, z, rad, rad, rad, n); } - public static double getNoise(InterpolationMethod method, int x, int z, double h, NoiseProvider n) { + public static double getNoise(InterpolationMethod method, int x, int z, double h, NoiseProvider noise) { + HashMap cache = new HashMap<>(64); + NoiseProvider n = (x1, z1) -> cache.computeIfAbsent(new NoiseKey(x1, z1), k -> noise.noise(k.x, k.z)); + if (method.equals(InterpolationMethod.BILINEAR)) { return getBilinearNoise(x, z, h, n); } else if (method.equals(InterpolationMethod.STARCAST_3)) { @@ -1058,4 +1061,7 @@ public class IrisInterpolation { public static double rangeScale(double amin, double amax, double bmin, double bmax, double b) { return amin + ((amax - amin) * ((b - bmin) / (bmax - bmin))); } + + public record NoiseKey(double x, double z) { + } } \ No newline at end of file diff --git a/core/src/main/java/com/volmit/iris/util/io/CountingDataInputStream.java b/core/src/main/java/com/volmit/iris/util/io/CountingDataInputStream.java new file mode 100644 index 000000000..6239518b1 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/io/CountingDataInputStream.java @@ -0,0 +1,87 @@ +package com.volmit.iris.util.io; + +import lombok.RequiredArgsConstructor; +import org.jetbrains.annotations.NotNull; + +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class CountingDataInputStream extends DataInputStream { + private final Counter counter; + + private CountingDataInputStream(@NotNull InputStream in) { + super(in); + if (!(in instanceof Counter c)) + throw new IllegalArgumentException("Underlying stream must be a Counter"); + this.counter = c; + } + + public static CountingDataInputStream wrap(@NotNull InputStream in) { + return new CountingDataInputStream(new Counter(in)); + } + + public long count() { + return counter.count; + } + + public void skipTo(long target) throws IOException { + skipNBytes(Math.max(target - counter.count, 0)); + } + + @RequiredArgsConstructor + private static class Counter extends InputStream { + private final InputStream in; + private long count; + private long mark = -1; + private int markLimit = 0; + + @Override + public int read() throws IOException { + int i = in.read(); + if (i != -1) count(1); + return i; + } + + @Override + public int read(@NotNull byte[] b, int off, int len) throws IOException { + int i = in.read(b, off, len); + count(i); + return i; + } + + private void count(int i) { + count += i; + if (mark == -1) + return; + + markLimit -= i; + if (markLimit <= 0) + mark = -1; + } + + @Override + public boolean markSupported() { + return in.markSupported(); + } + + @Override + public synchronized void mark(int readlimit) { + if (!in.markSupported()) return; + in.mark(readlimit); + mark = count; + markLimit = readlimit; + } + + @Override + public synchronized void reset() throws IOException { + in.reset(); + count = mark; + } + + @Override + public void close() throws IOException { + in.close(); + } + } +} 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 aee6dcfbe..32983d7f7 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 @@ -28,9 +28,7 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.*; import java.util.function.Consumer; -import java.util.zip.GZIPInputStream; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; +import java.util.zip.*; @SuppressWarnings("ALL") public class IO { @@ -113,6 +111,45 @@ public class IO { return "¯\\_(ツ)_/¯"; } + public static String hashRecursive(File base) { + LinkedList files = new LinkedList<>(); + Set processed = new HashSet<>(); + files.add(base); + try { + CRC32 crc = new CRC32(); + while (!files.isEmpty()) { + File file = files.removeFirst(); + if (!processed.add(file)) + continue; + + if (file.isDirectory()) { + File[] arr = file.listFiles(); + if (arr == null) + continue; + + Arrays.parallelSort(arr, Comparator.comparing(File::getName)); + files.addAll(Arrays.asList(arr)); + continue; + } + + try (var fin = new FileInputStream(file)) { + var din = new CheckedInputStream(fin, crc); + fullTransfer(din, new VoidOutputStream(), 8192); + } catch (IOException e) { + Iris.reportError(e); + e.printStackTrace(); + } + } + + return Long.toHexString(crc.getValue()); + } catch (Throwable e) { + Iris.reportError(e); + e.printStackTrace(); + } + + return ""; + } + public static String hash(File b) { try { MessageDigest d = MessageDigest.getInstance("SHA-256"); diff --git a/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java b/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java index b89804537..4ebe1d1c3 100644 --- a/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java +++ b/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java @@ -567,9 +567,6 @@ public class Mantle { } File file = fileForRegion(dataFolder, x, z); - if (!file.exists()) - file = new File(dataFolder, file.getName().substring(".lz4b".length())); - if (file.exists()) { try { Iris.addPanic("reading.tectonic-plate", file.getAbsolutePath()); 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 e73e7d68e..816896663 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 @@ -21,12 +21,13 @@ package com.volmit.iris.util.mantle; import com.volmit.iris.Iris; import com.volmit.iris.util.documentation.ChunkCoordinates; import com.volmit.iris.util.function.Consumer4; +import com.volmit.iris.util.io.CountingDataInputStream; import com.volmit.iris.util.matter.IrisMatter; import com.volmit.iris.util.matter.Matter; import com.volmit.iris.util.matter.MatterSlice; import lombok.Getter; -import java.io.DataInputStream; +import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.util.concurrent.atomic.AtomicIntegerArray; @@ -69,7 +70,7 @@ public class MantleChunk { * @throws IOException shit happens * @throws ClassNotFoundException shit happens */ - public MantleChunk(int sectionHeight, DataInputStream din) throws IOException, ClassNotFoundException { + public MantleChunk(int sectionHeight, CountingDataInputStream din) throws IOException { this(sectionHeight, din.readByte(), din.readByte()); int s = din.readByte(); @@ -79,8 +80,23 @@ public class MantleChunk { for (int i = 0; i < s; i++) { Iris.addPanic("read.section", "Section[" + i + "]"); - if (din.readBoolean()) { + long size = din.readInt(); + if (size == 0) continue; + long start = din.count(); + + try { sections.set(i, Matter.readDin(din)); + } catch (IOException e) { + long end = start + size; + Iris.error("Failed to read chunk section, skipping it."); + Iris.addPanic("read.byte.range", start + " " + end); + Iris.addPanic("read.byte.current", din.count() + ""); + Iris.reportError(e); + e.printStackTrace(); + Iris.panic(); + + din.skipTo(end); + TectonicPlate.addError(); } } } @@ -174,15 +190,22 @@ public class MantleChunk { dos.writeBoolean(flags.get(i) == 1); } + var bytes = new ByteArrayOutputStream(8192); + var sub = new DataOutputStream(bytes); for (int i = 0; i < sections.length(); i++) { trimSlice(i); if (exists(i)) { - dos.writeBoolean(true); - Matter matter = get(i); - matter.writeDos(dos); + try { + Matter matter = get(i); + matter.writeDos(sub); + dos.writeInt(bytes.size()); + bytes.writeTo(dos); + } finally { + bytes.reset(); + } } else { - dos.writeBoolean(false); + dos.writeInt(0); } } } diff --git a/core/src/main/java/com/volmit/iris/util/mantle/MantleFlag.java b/core/src/main/java/com/volmit/iris/util/mantle/MantleFlag.java index 6bb5f1e6f..fc2b9762f 100644 --- a/core/src/main/java/com/volmit/iris/util/mantle/MantleFlag.java +++ b/core/src/main/java/com/volmit/iris/util/mantle/MantleFlag.java @@ -34,7 +34,8 @@ public enum MantleFlag { PLANNED, ETCHED, TILE, - CUSTOM; + CUSTOM, + DISCOVERED; static StateList getStateList() { return new StateList(MantleFlag.values()); diff --git a/core/src/main/java/com/volmit/iris/util/mantle/TectonicPlate.java b/core/src/main/java/com/volmit/iris/util/mantle/TectonicPlate.java index 6f3cd0f35..f2bf590d6 100644 --- a/core/src/main/java/com/volmit/iris/util/mantle/TectonicPlate.java +++ b/core/src/main/java/com/volmit/iris/util/mantle/TectonicPlate.java @@ -21,26 +21,31 @@ package com.volmit.iris.util.mantle; import com.volmit.iris.Iris; import com.volmit.iris.engine.EnginePanic; import com.volmit.iris.engine.data.cache.Cache; +import com.volmit.iris.util.collection.KSet; import com.volmit.iris.util.documentation.ChunkCoordinates; import com.volmit.iris.util.format.C; import com.volmit.iris.util.format.Form; +import com.volmit.iris.util.io.CountingDataInputStream; import com.volmit.iris.util.scheduling.PrecisionStopwatch; import lombok.Getter; import net.jpountz.lz4.LZ4BlockInputStream; import net.jpountz.lz4.LZ4BlockOutputStream; -import net.jpountz.lz4.LZ4FrameInputStream; -import net.jpountz.lz4.LZ4FrameOutputStream; import java.io.*; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; import java.util.concurrent.atomic.AtomicReferenceArray; -import java.util.zip.GZIPInputStream; -import java.util.zip.GZIPOutputStream; /** * Tectonic Plates are essentially representations of regions in minecraft. * Tectonic Plates are fully atomic & thread safe */ public class TectonicPlate { + private static final KSet errors = new KSet<>(); + private final int sectionHeight; private final AtomicReferenceArray chunks; @@ -68,33 +73,58 @@ public class TectonicPlate { * @param worldHeight the height of the world * @param din the data input * @throws IOException shit happens yo - * @throws ClassNotFoundException real shit bro */ - public TectonicPlate(int worldHeight, DataInputStream din) throws IOException, ClassNotFoundException { + public TectonicPlate(int worldHeight, CountingDataInputStream din) throws IOException { this(worldHeight, din.readInt(), din.readInt()); + if (!din.markSupported()) + throw new IOException("Mark not supported!"); + for (int i = 0; i < chunks.length(); i++) { - if (din.readBoolean()) { + long size = din.readInt(); + if (size == 0) continue; + long start = din.count(); + + try { Iris.addPanic("read-chunk", "Chunk[" + i + "]"); chunks.set(i, new MantleChunk(sectionHeight, din)); EnginePanic.saveLast(); + } catch (Throwable e) { + long end = start + size; + Iris.error("Failed to read chunk, creating a new chunk instead."); + Iris.addPanic("read.byte.range", start + " " + end); + Iris.addPanic("read.byte.current", din.count() + ""); + Iris.reportError(e); + e.printStackTrace(); + Iris.panic(); + + din.skipTo(end); + TectonicPlate.addError(); } } } - public static TectonicPlate read(int worldHeight, File file) throws IOException, ClassNotFoundException { - FileInputStream fin = new FileInputStream(file); - DataInputStream din; - if (file.getName().endsWith("ttp.lz4b")) { - LZ4BlockInputStream lz4 = new LZ4BlockInputStream(fin); - din = new DataInputStream(lz4); - } else { - GZIPInputStream gzi = new GZIPInputStream(fin); - din = new DataInputStream(gzi); - } - TectonicPlate p = new TectonicPlate(worldHeight, din); - din.close(); + public static TectonicPlate read(int worldHeight, File file) throws IOException { + try (FileChannel fc = FileChannel.open(file.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.SYNC)) { + fc.lock(); - return p; + InputStream fin = Channels.newInputStream(fc); + LZ4BlockInputStream lz4 = new LZ4BlockInputStream(fin); + BufferedInputStream bis = new BufferedInputStream(lz4); + try (CountingDataInputStream din = CountingDataInputStream.wrap(bis)) { + return new TectonicPlate(worldHeight, din); + } + } finally { + if (errors.remove(Thread.currentThread())) { + File dump = Iris.instance.getDataFolder("dump", file.getName() + ".bin"); + try (FileChannel fc = FileChannel.open(file.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.SYNC)) { + fc.lock(); + + InputStream fin = Channels.newInputStream(fc); + LZ4BlockInputStream lz4 = new LZ4BlockInputStream(fin); + Files.copy(lz4, dump.toPath(), StandardCopyOption.REPLACE_EXISTING); + } + } + } } /** @@ -173,18 +203,15 @@ public class TectonicPlate { */ public void write(File file) throws IOException { PrecisionStopwatch p = PrecisionStopwatch.start(); - FileOutputStream fos = new FileOutputStream(file); - DataOutputStream dos; - if (file.getName().endsWith("ttp.lz4b")) { - LZ4BlockOutputStream lz4 = new LZ4BlockOutputStream(fos); - dos = new DataOutputStream(lz4); - } else { - GZIPOutputStream gzo = new GZIPOutputStream(fos); - dos = new DataOutputStream(gzo); + try (FileChannel fc = FileChannel.open(file.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.SYNC)) { + fc.lock(); + + OutputStream fos = Channels.newOutputStream(fc); + try (DataOutputStream dos = new DataOutputStream(new LZ4BlockOutputStream(fos))) { + write(dos); + Iris.debug("Saved Tectonic Plate " + C.DARK_GREEN + file.getName().split("\\Q.\\E")[0] + C.RED + " in " + Form.duration(p.getMilliseconds(), 2)); + } } - write(dos); - dos.close(); - Iris.debug("Saved Tectonic Plate " + C.DARK_GREEN + file.getName().split("\\Q.\\E")[0] + C.RED + " in " + Form.duration(p.getMilliseconds(), 2)); } /** @@ -197,15 +224,26 @@ public class TectonicPlate { dos.writeInt(x); dos.writeInt(z); + var bytes = new ByteArrayOutputStream(8192); + var sub = new DataOutputStream(bytes); for (int i = 0; i < chunks.length(); i++) { MantleChunk chunk = chunks.get(i); if (chunk != null) { - dos.writeBoolean(true); - chunk.write(dos); + try { + chunk.write(sub); + dos.writeInt(bytes.size()); + bytes.writeTo(dos); + } finally { + bytes.reset(); + } } else { - dos.writeBoolean(false); + dos.writeInt(0); } } } + + public static void addError() { + errors.add(Thread.currentThread()); + } } diff --git a/core/src/main/java/com/volmit/iris/util/matter/Matter.java b/core/src/main/java/com/volmit/iris/util/matter/Matter.java index 9c07feaf6..c04fe2485 100644 --- a/core/src/main/java/com/volmit/iris/util/matter/Matter.java +++ b/core/src/main/java/com/volmit/iris/util/matter/Matter.java @@ -23,6 +23,8 @@ import com.volmit.iris.engine.object.IrisObject; import com.volmit.iris.engine.object.IrisPosition; import com.volmit.iris.util.collection.KSet; import com.volmit.iris.util.hunk.Hunk; +import com.volmit.iris.util.io.CountingDataInputStream; +import com.volmit.iris.util.mantle.TectonicPlate; import com.volmit.iris.util.math.BlockPosition; import org.bukkit.World; import org.bukkit.block.data.BlockData; @@ -96,18 +98,18 @@ public interface Matter { return m; } - static Matter read(File f) throws IOException, ClassNotFoundException { + static Matter read(File f) throws IOException { FileInputStream in = new FileInputStream(f); Matter m = read(in); in.close(); return m; } - static Matter read(InputStream in) throws IOException, ClassNotFoundException { + static Matter read(InputStream in) throws IOException { return read(in, (b) -> new IrisMatter(b.getX(), b.getY(), b.getZ())); } - static Matter readDin(DataInputStream in) throws IOException, ClassNotFoundException { + static Matter readDin(CountingDataInputStream in) throws IOException { return readDin(in, (b) -> new IrisMatter(b.getX(), b.getY(), b.getZ())); } @@ -120,11 +122,11 @@ public interface Matter { * @return the matter object * @throws IOException shit happens yo */ - static Matter read(InputStream in, Function matterFactory) throws IOException, ClassNotFoundException { - return readDin(new DataInputStream(in), matterFactory); + static Matter read(InputStream in, Function matterFactory) throws IOException { + return readDin(CountingDataInputStream.wrap(in), matterFactory); } - static Matter readDin(DataInputStream din, Function matterFactory) throws IOException, ClassNotFoundException { + static Matter readDin(CountingDataInputStream din, Function matterFactory) throws IOException { Matter matter = matterFactory.apply(new BlockPosition( din.readInt(), din.readInt(), @@ -137,17 +139,30 @@ public interface Matter { Iris.addPanic("read.matter.header", matter.getHeader().toString()); for (int i = 0; i < sliceCount; i++) { + long size = din.readInt(); + if (size == 0) continue; + long start = din.count(); + Iris.addPanic("read.matter.slice", i + ""); - String cn = din.readUTF(); - Iris.addPanic("read.matter.slice.class", cn); try { + String cn = din.readUTF(); + Iris.addPanic("read.matter.slice.class", cn); + Class type = Class.forName(cn); MatterSlice slice = matter.createSlice(type, matter); slice.read(din); matter.putSlice(type, slice); } catch (Throwable e) { + long end = start + size; + Iris.error("Failed to read matter slice, skipping it."); + Iris.addPanic("read.byte.range", start + " " + end); + Iris.addPanic("read.byte.current", din.count() + ""); + Iris.reportError(e); e.printStackTrace(); - throw new IOException("Can't read class '" + cn + "' (slice count reverse at " + sliceCount + ")"); + Iris.panic(); + + din.skipTo(end); + TectonicPlate.addError(); } } @@ -414,8 +429,16 @@ public interface Matter { dos.writeByte(getSliceTypes().size()); getHeader().write(dos); + var bytes = new ByteArrayOutputStream(1024); + var sub = new DataOutputStream(bytes); for (Class i : getSliceTypes()) { - getSlice(i).write(dos); + try { + getSlice(i).write(sub); + dos.writeInt(bytes.size()); + bytes.writeTo(dos); + } finally { + bytes.reset(); + } } } diff --git a/core/src/main/java/com/volmit/iris/util/matter/TileWrapper.java b/core/src/main/java/com/volmit/iris/util/matter/TileWrapper.java index 3758a30ca..0fa14eab6 100644 --- a/core/src/main/java/com/volmit/iris/util/matter/TileWrapper.java +++ b/core/src/main/java/com/volmit/iris/util/matter/TileWrapper.java @@ -1,6 +1,6 @@ /* * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) + * Copyright (c) 2024 Arcane Arts (Volmit Software) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,5 +23,5 @@ import lombok.Data; @Data public class TileWrapper { - private final TileData data; + private final TileData data; } diff --git a/core/src/main/java/com/volmit/iris/util/matter/WorldMatter.java b/core/src/main/java/com/volmit/iris/util/matter/WorldMatter.java index 4b5eb59fe..40ff187d0 100644 --- a/core/src/main/java/com/volmit/iris/util/matter/WorldMatter.java +++ b/core/src/main/java/com/volmit/iris/util/matter/WorldMatter.java @@ -19,6 +19,7 @@ package com.volmit.iris.util.matter; import com.volmit.iris.Iris; +import com.volmit.iris.engine.object.TileData; import com.volmit.iris.util.data.Cuboid; import org.bukkit.Location; import org.bukkit.block.data.BlockData; diff --git a/core/src/main/java/com/volmit/iris/util/matter/slices/TileMatter.java b/core/src/main/java/com/volmit/iris/util/matter/slices/TileMatter.java index dee68e1c0..c5febf50b 100644 --- a/core/src/main/java/com/volmit/iris/util/matter/slices/TileMatter.java +++ b/core/src/main/java/com/volmit/iris/util/matter/slices/TileMatter.java @@ -18,13 +18,10 @@ package com.volmit.iris.util.matter.slices; -import com.volmit.iris.Iris; -import com.volmit.iris.core.nms.INMS; import com.volmit.iris.engine.object.TileData; import com.volmit.iris.util.data.palette.Palette; import com.volmit.iris.util.matter.Sliced; import com.volmit.iris.util.matter.TileWrapper; -import com.volmit.iris.util.nbt.tag.CompoundTag; import org.bukkit.Location; import org.bukkit.World; @@ -42,17 +39,8 @@ public class TileMatter extends RawMatter { public TileMatter(int width, int height, int depth) { super(width, height, depth, TileWrapper.class); - registerWriter(World.class, (w, d, x, y, z) -> { - CompoundTag tag = commonNbt(x, y, z, d.getData().getTileId()); - INMS.get().deserializeTile(d.getData().toNBT(d.getData().toNBT(tag)), new Location(w, x, y, z)); - Iris.warn("S: " + tag); - }); - registerReader(World.class, (w, x, y, z) -> { - TileData d = TileData.getTileState(w.getBlockAt(new Location(w, x, y, z))); - if (d == null) - return null; - return new TileWrapper(d); - }); + registerWriter(World.class, (w, d, x, y, z) -> TileData.setTileState(w.getBlockAt(new Location(w, x, y, z)), d.getData())); + registerReader(World.class, (w, x, y, z) -> new TileWrapper(TileData.getTileState(w.getBlockAt(new Location(w, x, y, z)), false))); } @Override @@ -67,14 +55,4 @@ public class TileMatter extends RawMatter { public TileWrapper readNode(DataInputStream din) throws IOException { return new TileWrapper(TileData.read(din)); } - - private CompoundTag commonNbt(int x, int y, int z, String mobId) { - CompoundTag tag = new CompoundTag(); - tag.putInt("x", x); - tag.putInt("y", y); - tag.putInt("z", z); - tag.putBoolean("keepPacked", false); - tag.putString("id", mobId); - return tag; - } } diff --git a/core/src/main/java/com/volmit/iris/util/plugin/Metrics.java b/core/src/main/java/com/volmit/iris/util/plugin/Metrics.java deleted file mode 100644 index 6b02d0c72..000000000 --- a/core/src/main/java/com/volmit/iris/util/plugin/Metrics.java +++ /dev/null @@ -1,748 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.volmit.iris.util.plugin; - -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.google.gson.JsonPrimitive; -import com.volmit.iris.Iris; -import org.bukkit.Bukkit; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Player; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.RegisteredServiceProvider; -import org.bukkit.plugin.ServicePriority; - -import javax.net.ssl.HttpsURLConnection; -import java.io.*; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.concurrent.Callable; -import java.util.logging.Level; -import java.util.zip.GZIPOutputStream; - -/** - * bStats collects some data for plugin authors. - *

- * Check out https://bStats.org/ to learn more about bStats! - */ -@SuppressWarnings({"WeakerAccess", "unused"}) -public class Metrics { - - // The version of this bStats class - public static final int B_STATS_VERSION = 1; - // The url to which the data is sent - private static final String URL = "https://bStats.org/submitData/bukkit"; - // Should failed requests be logged? - private static boolean logFailedRequests; - // Should the sent data be logged? - private static boolean logSentData; - // Should the response text be logged? - private static boolean logResponseStatusText; - // The uuid of the server - private static String serverUUID; - - static { - // You can use the property to disable the check in your test environment - if (System.getProperty("bstats.relocatecheck") == null || !System.getProperty("bstats.relocatecheck").equals("false")) { - // Maven's Relocate is clever and changes strings, too. So we have to use this little "trick" ... :D - final String defaultPackage = new String( - new byte[]{'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's', '.', 'b', 'u', 'k', 'k', 'i', 't'}); - final String examplePackage = new String(new byte[]{'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'}); - // We want to make sure nobody just copy & pastes the example and use the wrong package names - if (Metrics.class.getPackage().getName().equals(defaultPackage) || Metrics.class.getPackage().getName().equals(examplePackage)) { - throw new IllegalStateException("bStats Metrics class has not been relocated correctly!"); - } - } - } - - // Is bStats enabled on this server? - private final boolean enabled; - // The plugin - private final Plugin plugin; - - // The plugin id - private final int pluginId; - - // A list with all custom charts - private final List charts = new ArrayList<>(); - - /** - * Class constructor. - * - * @param plugin The plugin which stats should be submitted. - * @param pluginId The id of the plugin. - * It can be found at What is my plugin id? - */ - public Metrics(Plugin plugin, int pluginId) { - if (plugin == null) { - throw new IllegalArgumentException("Plugin cannot be null!"); - } - this.plugin = plugin; - this.pluginId = pluginId; - - // Get the config file - File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats"); - File configFile = new File(bStatsFolder, "config.yml"); - YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); - - // Check if the config file exists - if (!config.isSet("serverUuid")) { - - // Add default values - config.addDefault("enabled", true); - // Every server gets it's unique random id. - config.addDefault("serverUuid", UUID.randomUUID().toString()); - // Should failed request be logged? - config.addDefault("logFailedRequests", false); - // Should the sent data be logged? - config.addDefault("logSentData", false); - // Should the response text be logged? - config.addDefault("logResponseStatusText", false); - - // Inform the server owners about bStats - config.options().header( - """ - bStats collects some data for plugin authors like how many servers are using their plugins. - To honor their work, you should not disable it. - This has nearly no effect on the server performance! - Check out https://bStats.org/ to learn more :)""" - ).copyDefaults(true); - try { - config.save(configFile); - } catch (IOException e) { - Iris.reportError(e); - } - } - - // Load the data - enabled = true; - serverUUID = config.getString("serverUuid"); - logFailedRequests = config.getBoolean("logFailedRequests", false); - logSentData = config.getBoolean("logSentData", false); - logResponseStatusText = config.getBoolean("logResponseStatusText", false); - - if (enabled) { - boolean found = false; - // Search for all other bStats Metrics classes to see if we are the first one - for (Class service : Bukkit.getServicesManager().getKnownServices()) { - try { - service.getField("B_STATS_VERSION"); // Our identifier :) - found = true; // We aren't the first - break; - } catch (NoSuchFieldException e) { - Iris.reportError(e); - } - } - // Register our service - Bukkit.getServicesManager().register(Metrics.class, this, plugin, ServicePriority.Normal); - if (!found) { - // We are the first! - startSubmitting(); - } - } - } - - /** - * Sends the data to the bStats server. - * - * @param plugin Any plugin. It's just used to get a logger instance. - * @param data The data to send. - * @throws Exception If the request failed. - */ - private static void sendData(Plugin plugin, JsonObject data) throws Exception { - if (data == null) { - throw new IllegalArgumentException("Data cannot be null!"); - } - if (Bukkit.isPrimaryThread()) { - throw new IllegalAccessException("This method must not be called from the main thread!"); - } - if (logSentData) { - plugin.getLogger().info("Sending data to bStats: " + data); - } - HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection(); - - // Compress the data to save bandwidth - byte[] compressedData = compress(data.toString()); - - // Add headers - connection.setRequestMethod("POST"); - connection.addRequestProperty("Accept", "application/json"); - connection.addRequestProperty("Connection", "close"); - connection.addRequestProperty("Content-Encoding", "gzip"); // We gzip our request - connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); - connection.setRequestProperty("Content-Type", "application/json"); // We send our data in JSON format - connection.setRequestProperty("User-Agent", "MC-Server/" + B_STATS_VERSION); - - // Send data - connection.setDoOutput(true); - try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) { - outputStream.write(compressedData); - } - - StringBuilder builder = new StringBuilder(); - try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { - String line; - while ((line = bufferedReader.readLine()) != null) { - builder.append(line); - } - } - - if (logResponseStatusText) { - plugin.getLogger().info("Sent data to bStats and received response: " + builder); - } - } - - /** - * Gzips the given String. - * - * @param str The string to gzip. - * @return The gzipped String. - * @throws IOException If the compression failed. - */ - private static byte[] compress(final String str) throws IOException { - if (str == null) { - return null; - } - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - try (GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) { - gzip.write(str.getBytes(StandardCharsets.UTF_8)); - } - return outputStream.toByteArray(); - } - - /** - * Checks if bStats is enabled. - * - * @return Whether bStats is enabled or not. - */ - public boolean isEnabled() { - return enabled; - } - - /** - * Adds a custom chart. - * - * @param chart The chart to add. - */ - public void addCustomChart(CustomChart chart) { - if (chart == null) { - throw new IllegalArgumentException("Chart cannot be null!"); - } - charts.add(chart); - } - - /** - * Starts the Scheduler which submits our data every 30 minutes. - */ - private void startSubmitting() { - final Timer timer = new Timer(true); // We use a timer cause the Bukkit scheduler is affected by server lags - timer.scheduleAtFixedRate(new TimerTask() { - @Override - public void run() { - if (!plugin.isEnabled()) { // Plugin was disabled - timer.cancel(); - return; - } - // Nevertheless we want our code to run in the Bukkit main thread, so we have to use the Bukkit scheduler - // Don't be afraid! The connection to the bStats server is still async, only the stats collection is sync ;) - Bukkit.getScheduler().runTask(plugin, () -> submitData()); - } - }, 1000 * 60 * 5, 1000 * 60 * 30); - // Submit the data every 30 minutes, first time after 5 minutes to give other plugins enough time to start - // WARNING: Changing the frequency has no effect but your plugin WILL be blocked/deleted! - // WARNING: Just don't do it! - } - - /** - * Gets the plugin specific data. - * This method is called using Reflection. - * - * @return The plugin specific data. - */ - public JsonObject getPluginData() { - JsonObject data = new JsonObject(); - - String pluginName = plugin.getDescription().getName(); - String pluginVersion = plugin.getDescription().getVersion(); - - data.addProperty("pluginName", pluginName); // Append the name of the plugin - data.addProperty("id", pluginId); // Append the id of the plugin - data.addProperty("pluginVersion", pluginVersion); // Append the version of the plugin - JsonArray customCharts = new JsonArray(); - for (CustomChart customChart : charts) { - // Add the data of the custom charts - JsonObject chart = customChart.getRequestJsonObject(); - if (chart == null) { // If the chart is null, we skip it - continue; - } - customCharts.add(chart); - } - data.add("customCharts", customCharts); - - return data; - } - - /** - * Gets the server specific data. - * - * @return The server specific data. - */ - private JsonObject getServerData() { - // Minecraft specific data - int playerAmount; - try { - // Around MC 1.8 the return type was changed to a collection from an array, - // This fixes java.lang.NoSuchMethodError: org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection; - Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers"); - playerAmount = onlinePlayersMethod.getReturnType().equals(Collection.class) - ? ((Collection) onlinePlayersMethod.invoke(Bukkit.getServer())).size() - : ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length; - } catch (Exception e) { - Iris.reportError(e); - playerAmount = Bukkit.getOnlinePlayers().size(); // Just use the new method if the Reflection failed - } - int onlineMode = Bukkit.getOnlineMode() ? 1 : 0; - String bukkitVersion = Bukkit.getVersion(); - String bukkitName = Bukkit.getName(); - - // OS/Java specific data - String javaVersion = System.getProperty("java.version"); - String osName = System.getProperty("os.name"); - String osArch = System.getProperty("os.arch"); - String osVersion = System.getProperty("os.version"); - int coreCount = Runtime.getRuntime().availableProcessors(); - - JsonObject data = new JsonObject(); - - data.addProperty("serverUUID", serverUUID); - - data.addProperty("playerAmount", playerAmount); - data.addProperty("onlineMode", onlineMode); - data.addProperty("bukkitVersion", bukkitVersion); - data.addProperty("bukkitName", bukkitName); - - data.addProperty("javaVersion", javaVersion); - data.addProperty("osName", osName); - data.addProperty("osArch", osArch); - data.addProperty("osVersion", osVersion); - data.addProperty("coreCount", coreCount); - - return data; - } - - /** - * Collects the data and sends it afterwards. - */ - private void submitData() { - final JsonObject data = getServerData(); - - JsonArray pluginData = new JsonArray(); - // Search for all other bStats Metrics classes to get their plugin data - for (Class service : Bukkit.getServicesManager().getKnownServices()) { - try { - service.getField("B_STATS_VERSION"); // Our identifier :) - - for (RegisteredServiceProvider provider : Bukkit.getServicesManager().getRegistrations(service)) { - try { - Object plugin = provider.getService().getMethod("getPluginData").invoke(provider.getProvider()); - if (plugin instanceof JsonObject) { - pluginData.add((JsonObject) plugin); - } else { // old bstats version compatibility - try { - Class jsonObjectJsonSimple = Class.forName("org.json.simple.JSONObject"); - if (plugin.getClass().isAssignableFrom(jsonObjectJsonSimple)) { - Method jsonStringGetter = jsonObjectJsonSimple.getDeclaredMethod("toJSONString"); - jsonStringGetter.setAccessible(true); - String jsonString = (String) jsonStringGetter.invoke(plugin); - JsonObject object = new JsonParser().parse(jsonString).getAsJsonObject(); - pluginData.add(object); - } - } catch (ClassNotFoundException e) { - Iris.reportError(e); - // minecraft version 1.14+ - - if (logFailedRequests) { - this.plugin.getLogger().log(Level.SEVERE, "Encountered unexpected exception", e); - } - } - } - } catch (NullPointerException | NoSuchMethodException | IllegalAccessException | - InvocationTargetException ignored) { - Iris.reportError(ignored); - } - } - } catch (NoSuchFieldException e) { - Iris.reportError(e); - } - } - - data.add("plugins", pluginData); - - // Create a new thread for the connection to the bStats server - new Thread(() -> { - try { - // Send the data - sendData(plugin, data); - } catch (Exception e) { - Iris.reportError(e); - // Something went wrong! :( - if (logFailedRequests) { - plugin.getLogger().log(Level.WARNING, "Could not submit plugin stats of " + plugin.getName(), e); - } - } - }).start(); - } - - /** - * Represents a custom chart. - */ - public static abstract class CustomChart { - - // The id of the chart - final String chartId; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - */ - CustomChart(String chartId) { - if (chartId == null || chartId.isEmpty()) { - throw new IllegalArgumentException("ChartId cannot be null or empty!"); - } - this.chartId = chartId; - } - - private JsonObject getRequestJsonObject() { - JsonObject chart = new JsonObject(); - chart.addProperty("chartId", chartId); - try { - JsonObject data = getChartData(); - if (data == null) { - // If the data is null we don't send the chart. - return null; - } - chart.add("data", data); - } catch (Throwable t) { - Iris.reportError(t); - if (logFailedRequests) { - Bukkit.getLogger().log(Level.WARNING, "Failed to get data for custom chart with id " + chartId, t); - } - return null; - } - return chart; - } - - protected abstract JsonObject getChartData() throws Exception; - - } - - /** - * Represents a custom simple pie. - */ - public static class SimplePie extends CustomChart { - - private final Callable callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public SimplePie(String chartId, Callable callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObject getChartData() throws Exception { - JsonObject data = new JsonObject(); - String value = callable.call(); - if (value == null || value.isEmpty()) { - // Null = skip the chart - return null; - } - data.addProperty("value", value); - return data; - } - } - - /** - * Represents a custom advanced pie. - */ - public static class AdvancedPie extends CustomChart { - - private final Callable> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public AdvancedPie(String chartId, Callable> callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObject getChartData() throws Exception { - JsonObject data = new JsonObject(); - JsonObject values = new JsonObject(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean allSkipped = true; - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue() == 0) { - continue; // Skip this invalid - } - allSkipped = false; - values.addProperty(entry.getKey(), entry.getValue()); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - data.add("values", values); - return data; - } - } - - /** - * Represents a custom drilldown pie. - */ - public static class DrilldownPie extends CustomChart { - - private final Callable>> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public DrilldownPie(String chartId, Callable>> callable) { - super(chartId); - this.callable = callable; - } - - @Override - public JsonObject getChartData() throws Exception { - JsonObject data = new JsonObject(); - JsonObject values = new JsonObject(); - Map> map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean reallyAllSkipped = true; - for (Map.Entry> entryValues : map.entrySet()) { - JsonObject value = new JsonObject(); - boolean allSkipped = true; - for (Map.Entry valueEntry : map.get(entryValues.getKey()).entrySet()) { - value.addProperty(valueEntry.getKey(), valueEntry.getValue()); - allSkipped = false; - } - if (!allSkipped) { - reallyAllSkipped = false; - values.add(entryValues.getKey(), value); - } - } - if (reallyAllSkipped) { - // Null = skip the chart - return null; - } - data.add("values", values); - return data; - } - } - - /** - * Represents a custom single line chart. - */ - public static class SingleLineChart extends CustomChart { - - private final Callable callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public SingleLineChart(String chartId, Callable callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObject getChartData() throws Exception { - JsonObject data = new JsonObject(); - int value = callable.call(); - if (value == 0) { - // Null = skip the chart - return null; - } - data.addProperty("value", value); - return data; - } - - } - - /** - * Represents a custom multi line chart. - */ - public static class MultiLineChart extends CustomChart { - - private final Callable> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public MultiLineChart(String chartId, Callable> callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObject getChartData() throws Exception { - JsonObject data = new JsonObject(); - JsonObject values = new JsonObject(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean allSkipped = true; - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue() == 0) { - continue; // Skip this invalid - } - allSkipped = false; - values.addProperty(entry.getKey(), entry.getValue()); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - data.add("values", values); - return data; - } - - } - - /** - * Represents a custom simple bar chart. - */ - public static class SimpleBarChart extends CustomChart { - - private final Callable> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public SimpleBarChart(String chartId, Callable> callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObject getChartData() throws Exception { - JsonObject data = new JsonObject(); - JsonObject values = new JsonObject(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - for (Map.Entry entry : map.entrySet()) { - JsonArray categoryValues = new JsonArray(); - categoryValues.add(new JsonPrimitive(entry.getValue())); - values.add(entry.getKey(), categoryValues); - } - data.add("values", values); - return data; - } - - } - - /** - * Represents a custom advanced bar chart. - */ - public static class AdvancedBarChart extends CustomChart { - - private final Callable> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public AdvancedBarChart(String chartId, Callable> callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObject getChartData() throws Exception { - JsonObject data = new JsonObject(); - JsonObject values = new JsonObject(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean allSkipped = true; - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue().length == 0) { - continue; // Skip this invalid - } - allSkipped = false; - JsonArray categoryValues = new JsonArray(); - for (int categoryValue : entry.getValue()) { - categoryValues.add(new JsonPrimitive(categoryValue)); - } - values.add(entry.getKey(), categoryValues); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - data.add("values", values); - return data; - } - } - -} \ No newline at end of file diff --git a/core/src/main/java/com/volmit/iris/util/plugin/MetricsLite.java b/core/src/main/java/com/volmit/iris/util/plugin/MetricsLite.java deleted file mode 100644 index 7ffe47200..000000000 --- a/core/src/main/java/com/volmit/iris/util/plugin/MetricsLite.java +++ /dev/null @@ -1,394 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.volmit.iris.util.plugin; - -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.volmit.iris.Iris; -import org.bukkit.Bukkit; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Player; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.RegisteredServiceProvider; -import org.bukkit.plugin.ServicePriority; - -import javax.net.ssl.HttpsURLConnection; -import java.io.*; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.Collection; -import java.util.Timer; -import java.util.TimerTask; -import java.util.UUID; -import java.util.logging.Level; -import java.util.zip.GZIPOutputStream; - -/** - * bStats collects some data for plugin authors. - *

- * Check out https://bStats.org/ to learn more about bStats! - */ -public class MetricsLite { - - // The version of this bStats class - public static final int B_STATS_VERSION = 1; - // The url to which the data is sent - private static final String URL = "https://bStats.org/submitData/bukkit"; - // Should failed requests be logged? - private static boolean logFailedRequests; - // Should the sent data be logged? - private static boolean logSentData; - // Should the response text be logged? - private static boolean logResponseStatusText; - // The uuid of the server - private static String serverUUID; - - static { - // You can use the property to disable the check in your test environment - if (System.getProperty("bstats.relocatecheck") == null || !System.getProperty("bstats.relocatecheck").equals("false")) { - // Maven's Relocate is clever and changes strings, too. So we have to use this - // little "trick" ... :D - final String defaultPackage = new String(new byte[]{'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's', '.', 'b', 'u', 'k', 'k', 'i', 't'}); - final String examplePackage = new String(new byte[]{'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'}); - // We want to make sure nobody just copy & pastes the example and use the wrong - // package names - if (MetricsLite.class.getPackage().getName().equals(defaultPackage) || MetricsLite.class.getPackage().getName().equals(examplePackage)) { - throw new IllegalStateException("bStats Metrics class has not been relocated correctly!"); - } - } - } - - // Is bStats enabled on this server? - private final boolean enabled; - // The plugin - private final Plugin plugin; - - // The plugin id - private final int pluginId; - - /** - * Class constructor. - * - * @param plugin The plugin which stats should be submitted. - * @param pluginId The id of the plugin. It can be found at - * What is my - * plugin id? - */ - public MetricsLite(Plugin plugin, int pluginId) { - if (plugin == null) { - throw new IllegalArgumentException("Plugin cannot be null!"); - } - this.plugin = plugin; - this.pluginId = pluginId; - - // Get the config file - File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats"); - File configFile = new File(bStatsFolder, "config.yml"); - YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); - - // Check if the config file exists - if (!config.isSet("serverUuid")) { - - // Add default values - config.addDefault("enabled", true); - // Every server gets it's unique random id. - config.addDefault("serverUuid", UUID.randomUUID().toString()); - // Should failed request be logged? - config.addDefault("logFailedRequests", false); - // Should the sent data be logged? - config.addDefault("logSentData", false); - // Should the response text be logged? - config.addDefault("logResponseStatusText", false); - - // Inform the server owners about bStats - config.options().header(""" - bStats collects some data for plugin authors like how many servers are using their plugins. - To honor their work, you should not disable it. - This has nearly no effect on the server performance! - Check out https://bStats.org/ to learn more :)""").copyDefaults(true); - try { - config.save(configFile); - } catch (IOException e) { - Iris.reportError(e); - } - } - - // Load the data - serverUUID = config.getString("serverUuid"); - logFailedRequests = config.getBoolean("logFailedRequests", false); - enabled = config.getBoolean("enabled", true); - logSentData = config.getBoolean("logSentData", false); - logResponseStatusText = config.getBoolean("logResponseStatusText", false); - if (enabled) { - boolean found = false; - // Search for all other bStats Metrics classes to see if we are the first one - for (Class service : Bukkit.getServicesManager().getKnownServices()) { - try { - service.getField("B_STATS_VERSION"); // Our identifier :) - found = true; // We aren't the first - break; - } catch (NoSuchFieldException e) { - Iris.reportError(e); - } - } - // Register our service - Bukkit.getServicesManager().register(MetricsLite.class, this, plugin, ServicePriority.Normal); - if (!found) { - // We are the first! - startSubmitting(); - } - } - } - - /** - * Sends the data to the bStats server. - * - * @param plugin Any plugin. It's just used to get a logger instance. - * @param data The data to send. - * @throws Exception If the request failed. - */ - private static void sendData(Plugin plugin, JsonObject data) throws Exception { - if (data == null) { - throw new IllegalArgumentException("Data cannot be null!"); - } - if (Bukkit.isPrimaryThread()) { - throw new IllegalAccessException("This method must not be called from the main thread!"); - } - if (logSentData) { - plugin.getLogger().info("Sending data to bStats: " + data); - } - HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection(); - - // Compress the data to save bandwidth - byte[] compressedData = compress(data.toString()); - - // Add headers - connection.setRequestMethod("POST"); - connection.addRequestProperty("Accept", "application/json"); - connection.addRequestProperty("Connection", "close"); - connection.addRequestProperty("Content-Encoding", "gzip"); // We gzip our request - connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); - connection.setRequestProperty("Content-Type", "application/json"); // We send our data in JSON format - connection.setRequestProperty("User-Agent", "MC-Server/" + B_STATS_VERSION); - - // Send data - connection.setDoOutput(true); - try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) { - outputStream.write(compressedData); - } - - StringBuilder builder = new StringBuilder(); - try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { - String line; - while ((line = bufferedReader.readLine()) != null) { - builder.append(line); - } - } - - if (logResponseStatusText) { - plugin.getLogger().info("Sent data to bStats and received response: " + builder); - } - } - - /** - * Gzips the given String. - * - * @param str The string to gzip. - * @return The gzipped String. - * @throws IOException If the compression failed. - */ - private static byte[] compress(final String str) throws IOException { - if (str == null) { - return null; - } - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - try (GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) { - gzip.write(str.getBytes(StandardCharsets.UTF_8)); - } - return outputStream.toByteArray(); - } - - /** - * Checks if bStats is enabled. - * - * @return Whether bStats is enabled or not. - */ - public boolean isEnabled() { - return enabled; - } - - /** - * Starts the Scheduler which submits our data every 30 minutes. - */ - private void startSubmitting() { - final Timer timer = new Timer(true); // We use a timer cause the Bukkit scheduler is affected by server lags - timer.scheduleAtFixedRate(new TimerTask() { - @Override - public void run() { - if (!plugin.isEnabled()) { // Plugin was disabled - timer.cancel(); - return; - } - // Nevertheless we want our code to run in the Bukkit main thread, so we have to - // use the Bukkit scheduler - // Don't be afraid! The connection to the bStats server is still async, only the - // stats collection is sync ;) - Bukkit.getScheduler().runTask(plugin, () -> submitData()); - } - }, 1000 * 60 * 5, 1000 * 60 * 30); - // Submit the data every 30 minutes, first time after 5 minutes to give other - // plugins enough time to start - // WARNING: Changing the frequency has no effect but your plugin WILL be - // blocked/deleted! - // WARNING: Just don't do it! - } - - /** - * Gets the plugin specific data. This method is called using Reflection. - * - * @return The plugin specific data. - */ - public JsonObject getPluginData() { - JsonObject data = new JsonObject(); - - String pluginName = plugin.getDescription().getName(); - String pluginVersion = plugin.getDescription().getVersion(); - - data.addProperty("pluginName", pluginName); // Append the name of the plugin - data.addProperty("id", pluginId); // Append the id of the plugin - data.addProperty("pluginVersion", pluginVersion); // Append the version of the plugin - data.add("customCharts", new JsonArray()); - - return data; - } - - /** - * Gets the server specific data. - * - * @return The server specific data. - */ - private JsonObject getServerData() { - // Minecraft specific data - int playerAmount; - try { - // Around MC 1.8 the return type was changed to a collection from an array, - // This fixes java.lang.NoSuchMethodError: - // org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection; - Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers"); - playerAmount = onlinePlayersMethod.getReturnType().equals(Collection.class) ? ((Collection) onlinePlayersMethod.invoke(Bukkit.getServer())).size() : ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length; - } catch (Exception e) { - Iris.reportError(e); - playerAmount = Bukkit.getOnlinePlayers().size(); // Just use the new method if the Reflection failed - } - int onlineMode = Bukkit.getOnlineMode() ? 1 : 0; - String bukkitVersion = Bukkit.getVersion(); - String bukkitName = Bukkit.getName(); - - // OS/Java specific data - String javaVersion = System.getProperty("java.version"); - String osName = System.getProperty("os.name"); - String osArch = System.getProperty("os.arch"); - String osVersion = System.getProperty("os.version"); - int coreCount = Runtime.getRuntime().availableProcessors(); - - JsonObject data = new JsonObject(); - - data.addProperty("serverUUID", serverUUID); - - data.addProperty("playerAmount", playerAmount); - data.addProperty("onlineMode", onlineMode); - data.addProperty("bukkitVersion", bukkitVersion); - data.addProperty("bukkitName", bukkitName); - - data.addProperty("javaVersion", javaVersion); - data.addProperty("osName", osName); - data.addProperty("osArch", osArch); - data.addProperty("osVersion", osVersion); - data.addProperty("coreCount", coreCount); - - return data; - } - - /** - * Collects the data and sends it afterwards. - */ - private void submitData() { - final JsonObject data = getServerData(); - - JsonArray pluginData = new JsonArray(); - // Search for all other bStats Metrics classes to get their plugin data - for (Class service : Bukkit.getServicesManager().getKnownServices()) { - try { - service.getField("B_STATS_VERSION"); // Our identifier :) - - for (RegisteredServiceProvider provider : Bukkit.getServicesManager().getRegistrations(service)) { - try { - Object plugin = provider.getService().getMethod("getPluginData").invoke(provider.getProvider()); - if (plugin instanceof JsonObject) { - pluginData.add((JsonObject) plugin); - } else { // old bstats version compatibility - try { - Class jsonObjectJsonSimple = Class.forName("org.json.simple.JSONObject"); - if (plugin.getClass().isAssignableFrom(jsonObjectJsonSimple)) { - Method jsonStringGetter = jsonObjectJsonSimple.getDeclaredMethod("toJSONString"); - jsonStringGetter.setAccessible(true); - String jsonString = (String) jsonStringGetter.invoke(plugin); - JsonObject object = new JsonParser().parse(jsonString).getAsJsonObject(); - pluginData.add(object); - } - } catch (ClassNotFoundException e) { - Iris.reportError(e); - // minecraft version 1.14+ - if (logFailedRequests) { - this.plugin.getLogger().log(Level.SEVERE, "Encountered unexpected exception ", e); - } - } - } - } catch (NullPointerException | NoSuchMethodException | IllegalAccessException | - InvocationTargetException ignored) { - Iris.reportError(ignored); - } - } - } catch (NoSuchFieldException e) { - Iris.reportError(e); - } - } - - data.add("plugins", pluginData); - - // Create a new thread for the connection to the bStats server - new Thread(() -> - { - try { - // Send the data - sendData(plugin, data); - } catch (Exception e) { - Iris.reportError(e); - // Something went wrong! :( - if (logFailedRequests) { - plugin.getLogger().log(Level.WARNING, "Could not submit plugin stats of " + plugin.getName(), e); - } - } - }).start(); - } - -} \ No newline at end of file diff --git a/core/src/main/java/com/volmit/iris/util/profile/LoadBalancer.java b/core/src/main/java/com/volmit/iris/util/profile/LoadBalancer.java new file mode 100644 index 000000000..c92f57a2f --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/profile/LoadBalancer.java @@ -0,0 +1,70 @@ +package com.volmit.iris.util.profile; + + +import com.volmit.iris.Iris; +import com.volmit.iris.core.IrisSettings; +import com.volmit.iris.util.math.M; +import lombok.Getter; +import lombok.Setter; + +import java.util.concurrent.Semaphore; + +@Getter +public class LoadBalancer extends MsptTimings { + private final Semaphore semaphore; + private final int maxPermits; + private final double range; + @Setter + private int minMspt, maxMspt; + private int permits, lastMspt; + private long lastTime = M.ms(); + + public LoadBalancer(Semaphore semaphore, int maxPermits, IrisSettings.MsRange range) { + this(semaphore, maxPermits, range.getMin(), range.getMax()); + } + + public LoadBalancer(Semaphore semaphore, int maxPermits, int minMspt, int maxMspt) { + this.semaphore = semaphore; + this.maxPermits = maxPermits; + this.minMspt = minMspt; + this.maxMspt = maxMspt; + this.range = maxMspt - minMspt; + setName("LoadBalancer"); + start(); + } + + @Override + protected void update(int raw) { + lastTime = M.ms(); + int mspt = raw; + if (mspt < lastMspt) { + int min = (int) Math.max(lastMspt * IrisSettings.get().getUpdater().getChunkLoadSensitivity(), 1); + mspt = Math.max(mspt, min); + } + lastMspt = mspt; + mspt = Math.max(mspt - minMspt, 0); + double percent = mspt / range; + + int target = (int) (maxPermits * percent); + target = Math.min(target, maxPermits - 20); + + int diff = target - permits; + permits = target; + + if (diff == 0) return; + Iris.debug("Adjusting load to %s (%s) permits (%s mspt, %.2f)".formatted(target, diff, raw, percent)); + + if (diff > 0) semaphore.acquireUninterruptibly(diff); + else semaphore.release(Math.abs(diff)); + } + + public void close() { + interrupt(); + semaphore.release(permits); + } + + public void setRange(IrisSettings.MsRange range) { + minMspt = range.getMin(); + maxMspt = range.getMax(); + } +} diff --git a/core/src/main/java/com/volmit/iris/util/profile/MsptTimings.java b/core/src/main/java/com/volmit/iris/util/profile/MsptTimings.java new file mode 100644 index 000000000..2d098e1e7 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/profile/MsptTimings.java @@ -0,0 +1,84 @@ +package com.volmit.iris.util.profile; + +import com.volmit.iris.util.math.M; +import com.volmit.iris.util.scheduling.J; +import com.volmit.iris.util.scheduling.Looper; +import org.bukkit.Bukkit; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; + +public abstract class MsptTimings extends Looper { + private final AtomicInteger currentTick = new AtomicInteger(0); + private int lastTick, lastMspt; + private long lastTime; + private int taskId = -1; + + public MsptTimings() { + setName("MsptTimings"); + setPriority(9); + setDaemon(true); + } + + public static MsptTimings of(Consumer update) { + return new Simple(update); + } + + @Override + protected final long loop() { + if (startTickTask()) + return 200; + + long now = M.ms(); + int tick = currentTick.get(); + int deltaTick = tick - lastTick; + if (deltaTick == 0) + return 200; + lastTick = tick; + int deltaTime = (int) (now - lastTime); + lastTime = now; + int mspt = deltaTime / deltaTick; + mspt -= 50; + mspt = Math.max(mspt, 0); + lastMspt = mspt; + update(mspt); + return 200; + } + + public final int getMspt() { + return lastMspt; + } + + protected abstract void update(int mspt); + + private boolean startTickTask() { + if (taskId != -1 && (Bukkit.getScheduler().isQueued(taskId) || Bukkit.getScheduler().isCurrentlyRunning(taskId))) + return false; + + taskId = J.sr(() -> { + if (isInterrupted()) { + J.csr(taskId); + return; + } + + currentTick.incrementAndGet(); + }, 1); + return taskId != -1; + } + + private static class Simple extends MsptTimings { + private final Consumer update; + + private Simple(Consumer update) { + this.update = update; + start(); + } + + @Override + protected void update(int mspt) { + if (update == null) + return; + update.accept(mspt); + } + } +} diff --git a/core/src/main/java/com/volmit/iris/util/reflect/OldEnum.java b/core/src/main/java/com/volmit/iris/util/reflect/OldEnum.java new file mode 100644 index 000000000..24293ad2a --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/reflect/OldEnum.java @@ -0,0 +1,93 @@ +package com.volmit.iris.util.reflect; + +import com.google.gson.TypeAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.Objects; + +public class OldEnum { + + private static final Class oldEnum; + private static final Method name; + + public static boolean exists() { + return oldEnum != null; + } + + public static boolean isOldEnum(Class c) { + return oldEnum != null && oldEnum.isAssignableFrom(c); + } + + public static T valueOf(Class c, String name) { + try { + return (T) c.getDeclaredField(name).get(null); + } catch (Throwable e) { + return null; + } + } + + public static String name(Object o) { + try { + return (String) name.invoke(o); + } catch (Throwable e) { + return null; + } + } + + public static Object[] values(Class clazz) { + if (!isOldEnum(clazz)) return new Object[0]; + return Arrays.stream(clazz.getDeclaredFields()) + .filter(f -> Modifier.isStatic(f.getModifiers())) + .filter(f -> Modifier.isFinal(f.getModifiers())) + .map(f -> { + try { + return f.get(null); + } catch (Throwable ignored) { + return null; + } + }) + .filter(Objects::nonNull) + .toArray(); + } + + public static TypeAdapter create(Class type) { + if (!isOldEnum(type)) + return null; + + return new TypeAdapter<>() { + + @Override + public void write(JsonWriter out, T value) throws IOException { + out.value(name(value)); + } + + @Override + public T read(JsonReader in) throws IOException { + return valueOf(type, in.nextString()); + } + }; + } + + static { + Class clazz = null; + Method method = null; + try { + clazz = Class.forName("org.bukkit.util.OldEnum"); + method = clazz.getDeclaredMethod("name"); + } catch (Throwable ignored) {} + + if (clazz == null || method == null) { + oldEnum = null; + name = null; + } else { + oldEnum = clazz; + name = method; + } + } +} diff --git a/core/src/main/java/com/volmit/iris/util/reflect/W.java b/core/src/main/java/com/volmit/iris/util/reflect/W.java new file mode 100644 index 000000000..7febb0611 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/reflect/W.java @@ -0,0 +1,8 @@ +package com.volmit.iris.util.reflect; + +import lombok.Getter; + +public class W { + @Getter + private static final StackWalker stack = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); +} diff --git a/core/src/main/java/com/volmit/iris/util/reflect/WrappedField.java b/core/src/main/java/com/volmit/iris/util/reflect/WrappedField.java index 3249fa07f..0aad874ad 100644 --- a/core/src/main/java/com/volmit/iris/util/reflect/WrappedField.java +++ b/core/src/main/java/com/volmit/iris/util/reflect/WrappedField.java @@ -36,6 +36,18 @@ public class WrappedField { } } + public void set(T value) throws IllegalAccessException { + set(null, value); + } + + public void set(C instance, T value) throws IllegalAccessException { + if (field == null) { + return; + } + + field.set(instance, value); + } + public boolean hasFailed() { return field == null; } diff --git a/core/src/main/resources/plugin.yml b/core/src/main/resources/plugin.yml index a54d30829..7ff73ff7e 100644 --- a/core/src/main/resources/plugin.yml +++ b/core/src/main/resources/plugin.yml @@ -19,6 +19,7 @@ libraries: - rhino:js:1.7R2 - bsf:bsf:2.4.0 - org.lz4:lz4-java:1.8.0 + - com.github.oshi:oshi-core:6.6.5 commands: iris: aliases: [ ir, irs ] diff --git a/nms/v1_19_R1/src/main/java/com/volmit/iris/core/nms/v1_19_R1/IrisChunkGenerator.java b/nms/v1_19_R1/src/main/java/com/volmit/iris/core/nms/v1_19_R1/IrisChunkGenerator.java new file mode 100644 index 000000000..df23a6d05 --- /dev/null +++ b/nms/v1_19_R1/src/main/java/com/volmit/iris/core/nms/v1_19_R1/IrisChunkGenerator.java @@ -0,0 +1,257 @@ +package com.volmit.iris.core.nms.v1_19_R1; + +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.Codec; +import com.volmit.iris.Iris; +import com.volmit.iris.engine.framework.Engine; +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.util.collection.KList; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.collection.KSet; +import com.volmit.iris.util.mantle.MantleFlag; +import com.volmit.iris.util.math.Position2; +import com.volmit.iris.util.reflect.WrappedField; +import net.minecraft.core.*; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.WorldGenRegion; +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.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.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkGenerator; +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.levelgen.blending.Blender; +import net.minecraft.world.level.levelgen.structure.Structure; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; +import org.bukkit.World; +import org.bukkit.craftbukkit.v1_19_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_19_R1.generator.CustomChunkGenerator; + +import javax.annotation.Nullable; +import java.lang.reflect.Field; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; + +public class IrisChunkGenerator extends CustomChunkGenerator { + private static final WrappedField BIOME_SOURCE; + private final ChunkGenerator delegate; + private final Engine engine; + private final KMap, KSet> structures = new KMap<>(); + + 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; + var dimension = engine.getDimension(); + + KSet placements = new KSet<>(); + addAll(dimension.getJigsawStructures(), placements); + for (var region : dimension.getAllRegions(engine)) { + addAll(region.getJigsawStructures(), placements); + for (var biome : region.getAllBiomes(engine)) + addAll(biome.getJigsawStructures(), placements); + } + var stronghold = dimension.getStronghold(); + if (stronghold != null) + placements.add(engine.getData().getJigsawStructureLoader().load(stronghold)); + placements.removeIf(Objects::isNull); + + var registry = ((CraftWorld) world).getHandle().registryAccess().registry(Registry.STRUCTURE_REGISTRY).orElseThrow(); + for (var s : placements) { + try { + String raw = s.getStructureKey(); + if (raw == null) continue; + boolean tag = raw.startsWith("#"); + if (tag) raw = raw.substring(1); + + var location = new ResourceLocation(raw); + if (!tag) { + structures.computeIfAbsent(ResourceKey.create(Registry.STRUCTURE_REGISTRY, location), k -> new KSet<>()).add(s.getLoadKey()); + continue; + } + + var key = TagKey.create(Registry.STRUCTURE_REGISTRY, location); + var set = registry.getTag(key).orElse(null); + if (set == null) { + Iris.error("Could not find structure tag: " + raw); + continue; + } + for (var holder : set) { + var resourceKey = holder.unwrapKey().orElse(null); + if (resourceKey == null) continue; + structures.computeIfAbsent(resourceKey, k -> new KSet<>()).add(s.getLoadKey()); + } + } catch (Throwable e) { + Iris.error("Failed to load structure: " + s.getLoadKey()); + e.printStackTrace(); + } + } + } + + private void addAll(KList placements, KSet structures) { + if (placements == null) return; + placements.stream() + .map(IrisJigsawStructurePlacement::getStructure) + .map(engine.getData().getJigsawStructureLoader()::load) + .filter(Objects::nonNull) + .forEach(structures::add); + } + + @Override + public @Nullable Pair> findNearestMapStructure(ServerLevel level, HolderSet holders, BlockPos pos, int radius, boolean findUnexplored) { + if (engine.getDimension().isDisableExplorerMaps()) + return null; + + KMap> structures = new KMap<>(); + for (var holder : holders) { + if (holder == null) continue; + var key = holder.unwrapKey().orElse(null); + var set = this.structures.get(key); + if (set == null) continue; + for (var structure : set) { + structures.put(structure, holder); + } + } + if (structures.isEmpty()) + return null; + + var locator = ResultLocator.locateStructure(structures.keySet()) + .then((e, p , s) -> structures.get(s.getLoadKey())); + if (findUnexplored) + locator = locator.then((e, p, s) -> e.getMantle().getMantle().getChunk(p.getX(), p.getZ()).isFlagged(MantleFlag.DISCOVERED) ? null : s); + + try { + var result = locator.find(engine, new Position2(pos.getX() >> 4, pos.getZ() >> 4), radius * 10L, i -> {}, false).get(); + if (result == null) return null; + var blockPos = new BlockPos(result.getBlockX(), 0, result.getBlockZ()); + return Pair.of(blockPos, result.obj()); + } catch (WrongEngineBroException | ExecutionException | InterruptedException e) { + return null; + } + } + + @Override + protected Codec codec() { + return Codec.unit(null); + } + + @Override + public ChunkGenerator getDelegate() { + if (delegate instanceof CustomChunkGenerator chunkGenerator) + return chunkGenerator.getDelegate(); + return delegate; + } + + @Override + public int getMinY() { + return delegate.getMinY(); + } + + @Override + public int getSeaLevel() { + return delegate.getSeaLevel(); + } + + @Override + public void createStructures(RegistryAccess iregistrycustom, RandomState randomstate, StructureManager structuremanager, ChunkAccess ichunkaccess, StructureTemplateManager structuretemplatemanager, long i) { + delegate.createStructures(iregistrycustom, randomstate, structuremanager, ichunkaccess, structuretemplatemanager, i); + } + + @Override + public void buildSurface(WorldGenRegion regionlimitedworldaccess, StructureManager structuremanager, RandomState randomstate, ChunkAccess ichunkaccess) { + delegate.buildSurface(regionlimitedworldaccess, structuremanager, randomstate, ichunkaccess); + } + + @Override + public void applyCarvers(WorldGenRegion regionlimitedworldaccess, long seed, RandomState randomstate, BiomeManager biomemanager, StructureManager structuremanager, ChunkAccess ichunkaccess, GenerationStep.Carving worldgenstage_features) { + delegate.applyCarvers(regionlimitedworldaccess, seed, randomstate, biomemanager, structuremanager, ichunkaccess, worldgenstage_features); + } + + @Override + public CompletableFuture fillFromNoise(Executor executor, Blender blender, RandomState randomstate, StructureManager structuremanager, ChunkAccess ichunkaccess) { + 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); + } + + @Override + public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) { + delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager); + } + + @Override + public void addDebugScreenInfo(List list, RandomState randomstate, BlockPos blockposition) { + delegate.addDebugScreenInfo(list, randomstate, blockposition); + } + + @Override + public void spawnOriginalMobs(WorldGenRegion regionlimitedworldaccess) { + delegate.spawnOriginalMobs(regionlimitedworldaccess); + } + + @Override + public int getSpawnHeight(LevelHeightAccessor levelheightaccessor) { + return delegate.getSpawnHeight(levelheightaccessor); + } + + @Override + public int getGenDepth() { + return delegate.getGenDepth(); + } + + @Override + public NoiseColumn getBaseColumn(int i, int j, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { + return delegate.getBaseColumn(i, j, levelheightaccessor, randomstate); + } + + static { + Field biomeSource = null; + for (Field field : ChunkGenerator.class.getDeclaredFields()) { + if (!field.getType().equals(BiomeSource.class)) + continue; + biomeSource = field; + break; + } + if (biomeSource == null) + throw new RuntimeException("Could not find biomeSource field in ChunkGenerator!"); + BIOME_SOURCE = new WrappedField<>(ChunkGenerator.class, biomeSource.getName()); + } + + private static ChunkGenerator edit(ChunkGenerator generator, BiomeSource source) { + try { + BIOME_SOURCE.set(generator, source); + if (generator instanceof CustomChunkGenerator custom) + BIOME_SOURCE.set(custom.getDelegate(), source); + + return generator; + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } +} diff --git a/nms/v1_19_R1/src/main/java/com/volmit/iris/core/nms/v1_19_R1/NMSBinding.java b/nms/v1_19_R1/src/main/java/com/volmit/iris/core/nms/v1_19_R1/NMSBinding.java index 058e4046d..935af6a14 100644 --- a/nms/v1_19_R1/src/main/java/com/volmit/iris/core/nms/v1_19_R1/NMSBinding.java +++ b/nms/v1_19_R1/src/main/java/com/volmit/iris/core/nms/v1_19_R1/NMSBinding.java @@ -10,11 +10,19 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Vector; import java.util.concurrent.atomic.AtomicInteger; +import com.mojang.datafixers.util.Pair; import com.volmit.iris.core.nms.container.BiomeColor; +import com.volmit.iris.util.scheduling.J; +import net.minecraft.nbt.*; +import net.minecraft.nbt.Tag; +import net.minecraft.server.commands.data.BlockDataAccessor; +import net.minecraft.tags.TagKey; import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.EntityBlock; import org.bukkit.*; import org.bukkit.block.Biome; import org.bukkit.block.data.BlockData; @@ -22,6 +30,8 @@ import org.bukkit.craftbukkit.v1_19_R1.CraftChunk; import org.bukkit.craftbukkit.v1_19_R1.CraftServer; import org.bukkit.craftbukkit.v1_19_R1.CraftWorld; import org.bukkit.craftbukkit.v1_19_R1.block.CraftBlock; +import org.bukkit.craftbukkit.v1_19_R1.block.CraftBlockState; +import org.bukkit.craftbukkit.v1_19_R1.block.CraftBlockStates; import org.bukkit.craftbukkit.v1_19_R1.block.data.CraftBlockData; import org.bukkit.craftbukkit.v1_19_R1.entity.CraftDolphin; import org.bukkit.craftbukkit.v1_19_R1.inventory.CraftItemStack; @@ -30,6 +40,7 @@ import org.bukkit.entity.Entity; import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.generator.ChunkGenerator; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import com.mojang.brigadier.exceptions.CommandSyntaxException; @@ -54,8 +65,6 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.core.Registry; import net.minecraft.core.RegistryAccess; -import net.minecraft.nbt.NbtIo; -import net.minecraft.nbt.TagParser; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.EntityType; @@ -127,55 +136,106 @@ public class NMSBinding implements INMSBinding { return type.getDeclaredClasses()[ordinal]; } + @Override + public boolean hasTile(Material material) { + return !CraftBlockState.class.equals(CraftBlockStates.getBlockStateType(material)); + } + @Override public boolean hasTile(Location l) { return ((CraftWorld) l.getWorld()).getHandle().getBlockEntity(new BlockPos(l.getBlockX(), l.getBlockY(), l.getBlockZ()), false) != null; } @Override - public CompoundTag serializeTile(Location location) { - BlockEntity e = ((CraftWorld) location.getWorld()).getHandle().getBlockEntity(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()), true); + @SuppressWarnings("unchecked") + public KMap serializeTile(Location location) { + BlockEntity e = ((CraftWorld) location.getWorld()).getHandle().getBlockEntity(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()), false); if (e == null) { return null; } - net.minecraft.nbt.CompoundTag tag = e.saveWithFullMetadata(); - return convert(tag); + net.minecraft.nbt.CompoundTag tag = e.saveWithoutMetadata(); + return (KMap) convertFromTag(tag, 0, 64); } - private CompoundTag convert(net.minecraft.nbt.CompoundTag tag) { - try { - ByteArrayOutputStream boas = new ByteArrayOutputStream(); - DataOutputStream dos = new DataOutputStream(boas); - tag.write(dos); - dos.close(); - return (CompoundTag) NBTUtil.read(new ByteArrayInputStream(boas.toByteArray()), false).getTag(); - } catch (Throwable ex) { - ex.printStackTrace(); + @Contract(value = "null, _, _ -> null", pure = true) + private Object convertFromTag(net.minecraft.nbt.Tag tag, int depth, int maxDepth) { + if (tag == null || depth > maxDepth) return null; + if (tag instanceof CollectionTag collection) { + KList list = new KList<>(); + + for (Object i : collection) { + if (i instanceof net.minecraft.nbt.Tag t) + list.add(convertFromTag(t, depth + 1, maxDepth)); + else list.add(i); + } + return list; } + if (tag instanceof net.minecraft.nbt.CompoundTag compound) { + KMap map = new KMap<>(); - return null; - } - - private net.minecraft.nbt.CompoundTag convert(CompoundTag tag) { - try { - ByteArrayOutputStream boas = new ByteArrayOutputStream(); - NBTUtil.write(tag, boas, false); - DataInputStream din = new DataInputStream(new ByteArrayInputStream(boas.toByteArray())); - net.minecraft.nbt.CompoundTag c = NbtIo.read(din); - din.close(); - return c; - } catch (Throwable e) { - e.printStackTrace(); + for (String key : compound.getAllKeys()) { + var child = compound.get(key); + if (child == null) continue; + var value = convertFromTag(child, depth + 1, maxDepth); + if (value == null) continue; + map.put(key, value); + } + return map; } - - return null; + if (tag instanceof NumericTag numeric) + return numeric.getAsNumber(); + return tag.getAsString(); } @Override - public void deserializeTile(CompoundTag c, Location pos) { - ((CraftWorld) pos.getWorld()).getHandle().getChunkAt(new BlockPos(pos.getBlockX(), 0, pos.getBlockZ())).setBlockEntityNbt(convert(c)); + public void deserializeTile(KMap map, Location pos) { + net.minecraft.nbt.CompoundTag tag = (net.minecraft.nbt.CompoundTag) convertToTag(map, 0, 64); + var level = ((CraftWorld) pos.getWorld()).getHandle(); + var blockPos = new BlockPos(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ()); + J.s(() -> merge(level, blockPos, tag)); + } + + private void merge(ServerLevel level, BlockPos blockPos, net.minecraft.nbt.CompoundTag tag) { + var blockEntity = level.getBlockEntity(blockPos); + if (blockEntity == null) { + Iris.warn("[NMS] BlockEntity not found at " + blockPos); + var state = level.getBlockState(blockPos); + if (!state.hasBlockEntity()) + return; + + blockEntity = ((EntityBlock) state.getBlock()) + .newBlockEntity(blockPos, state); + } + var accessor = new BlockDataAccessor(blockEntity, blockPos); + accessor.setData(tag.merge(accessor.getData())); + } + + private Tag convertToTag(Object object, int depth, int maxDepth) { + if (object == null || depth > maxDepth) return EndTag.INSTANCE; + if (object instanceof Map map) { + var tag = new net.minecraft.nbt.CompoundTag(); + for (var i : map.entrySet()) { + tag.put(i.getKey().toString(), convertToTag(i.getValue(), depth + 1, maxDepth)); + } + return tag; + } + if (object instanceof List list) { + var tag = new net.minecraft.nbt.ListTag(); + for (var i : list) { + tag.add(convertToTag(i, depth + 1, maxDepth)); + } + return tag; + } + if (object instanceof Byte number) return ByteTag.valueOf(number); + if (object instanceof Short number) return ShortTag.valueOf(number); + if (object instanceof Integer number) return IntTag.valueOf(number); + if (object instanceof Long number) return LongTag.valueOf(number); + if (object instanceof Float number) return FloatTag.valueOf(number); + if (object instanceof Double number) return DoubleTag.valueOf(number); + if (object instanceof String string) return StringTag.valueOf(string); + return EndTag.INSTANCE; } @Override @@ -473,23 +533,9 @@ public class NMSBinding implements INMSBinding { } } - public void setTreasurePos(Dolphin dolphin, com.volmit.iris.core.nms.container.BlockPos pos) { - CraftDolphin cd = (CraftDolphin)dolphin; - cd.getHandle().setTreasurePos(new BlockPos(pos.getX(), pos.getY(), pos.getZ())); - cd.getHandle().setGotFish(true); - } - - public void inject(long seed, Engine engine, World world) throws NoSuchFieldException, IllegalAccessException { - ServerLevel serverLevel = ((CraftWorld)world).getHandle(); - Class clazz = serverLevel.getChunkSource().chunkMap.generator.getClass(); - Field biomeSource = getField(clazz, BiomeSource.class); - biomeSource.setAccessible(true); - Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); - unsafeField.setAccessible(true); - Unsafe unsafe = (Unsafe)unsafeField.get(null); - CustomBiomeSource customBiomeSource = new CustomBiomeSource(seed, engine, world); - unsafe.putObject(biomeSource.get(serverLevel.getChunkSource().chunkMap.generator), unsafe.objectFieldOffset(biomeSource), customBiomeSource); - biomeSource.set(serverLevel.getChunkSource().chunkMap.generator, customBiomeSource); + public void inject(long seed, Engine engine, World world) { + var chunkMap = ((CraftWorld)world).getHandle().getChunkSource().chunkMap; + chunkMap.generator = new IrisChunkGenerator(chunkMap.generator, seed, engine, world); } public Vector3d getBoundingbox(org.bukkit.entity.EntityType entity) { @@ -544,6 +590,23 @@ public class NMSBinding implements INMSBinding { return new Color(rgba, true); } + @Override + public KList getStructureKeys() { + KList keys = new KList<>(); + + var registry = registry().registry(Registry.STRUCTURE_REGISTRY).orElse(null); + if (registry == null) return keys; + registry.keySet().stream().map(ResourceLocation::toString).forEach(keys::add); + registry.getTags() + .map(Pair::getFirst) + .map(TagKey::location) + .map(ResourceLocation::toString) + .map(s -> "#" + s) + .forEach(keys::add); + + return keys; + } + private static Field getField(Class clazz, Class fieldType) throws NoSuchFieldException { try { for (Field f : clazz.getDeclaredFields()) { diff --git a/nms/v1_19_R2/src/main/java/com/volmit/iris/core/nms/v1_19_R2/IrisChunkGenerator.java b/nms/v1_19_R2/src/main/java/com/volmit/iris/core/nms/v1_19_R2/IrisChunkGenerator.java new file mode 100644 index 000000000..92b7b2b32 --- /dev/null +++ b/nms/v1_19_R2/src/main/java/com/volmit/iris/core/nms/v1_19_R2/IrisChunkGenerator.java @@ -0,0 +1,260 @@ +package com.volmit.iris.core.nms.v1_19_R2; + +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.Codec; +import com.volmit.iris.Iris; +import com.volmit.iris.engine.framework.Engine; +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.util.collection.KList; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.collection.KSet; +import com.volmit.iris.util.mantle.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 net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.WorldGenRegion; +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.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.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.levelgen.blending.Blender; +import net.minecraft.world.level.levelgen.structure.Structure; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; +import org.bukkit.World; +import org.bukkit.craftbukkit.v1_19_R2.CraftWorld; +import org.bukkit.craftbukkit.v1_19_R2.generator.CustomChunkGenerator; + +import javax.annotation.Nullable; +import java.lang.reflect.Field; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.*; + +public class IrisChunkGenerator extends CustomChunkGenerator { + private static final WrappedField BIOME_SOURCE; + private final ChunkGenerator delegate; + private final Engine engine; + private final KMap, KSet> structures = new KMap<>(); + + 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; + var dimension = engine.getDimension(); + + KSet placements = new KSet<>(); + addAll(dimension.getJigsawStructures(), placements); + for (var region : dimension.getAllRegions(engine)) { + addAll(region.getJigsawStructures(), placements); + for (var biome : region.getAllBiomes(engine)) + addAll(biome.getJigsawStructures(), placements); + } + var stronghold = dimension.getStronghold(); + if (stronghold != null) + placements.add(engine.getData().getJigsawStructureLoader().load(stronghold)); + placements.removeIf(Objects::isNull); + + var registry = ((CraftWorld) world).getHandle().registryAccess().registry(Registries.STRUCTURE).orElseThrow(); + for (var s : placements) { + try { + String raw = s.getStructureKey(); + if (raw == null) continue; + boolean tag = raw.startsWith("#"); + if (tag) raw = raw.substring(1); + + var location = new ResourceLocation(raw); + if (!tag) { + structures.computeIfAbsent(ResourceKey.create(Registries.STRUCTURE, location), k -> new KSet<>()).add(s.getLoadKey()); + continue; + } + + var key = TagKey.create(Registries.STRUCTURE, location); + var set = registry.getTag(key).orElse(null); + if (set == null) { + Iris.error("Could not find structure tag: " + raw); + continue; + } + for (var holder : set) { + var resourceKey = holder.unwrapKey().orElse(null); + if (resourceKey == null) continue; + structures.computeIfAbsent(resourceKey, k -> new KSet<>()).add(s.getLoadKey()); + } + } catch (Throwable e) { + Iris.error("Failed to load structure: " + s.getLoadKey()); + e.printStackTrace(); + } + } + } + + private void addAll(KList placements, KSet structures) { + if (placements == null) return; + placements.stream() + .map(IrisJigsawStructurePlacement::getStructure) + .map(engine.getData().getJigsawStructureLoader()::load) + .filter(Objects::nonNull) + .forEach(structures::add); + } + + @Override + public @Nullable Pair> findNearestMapStructure(ServerLevel level, HolderSet holders, BlockPos pos, int radius, boolean findUnexplored) { + if (engine.getDimension().isDisableExplorerMaps()) + return null; + + KMap> structures = new KMap<>(); + for (var holder : holders) { + if (holder == null) continue; + var key = holder.unwrapKey().orElse(null); + var set = this.structures.get(key); + if (set == null) continue; + for (var structure : set) { + structures.put(structure, holder); + } + } + if (structures.isEmpty()) + return null; + + var locator = ResultLocator.locateStructure(structures.keySet()) + .then((e, p , s) -> structures.get(s.getLoadKey())); + if (findUnexplored) + locator = locator.then((e, p, s) -> e.getMantle().getMantle().getChunk(p.getX(), p.getZ()).isFlagged(MantleFlag.DISCOVERED) ? null : s); + + try { + var result = locator.find(engine, new Position2(pos.getX() >> 4, pos.getZ() >> 4), radius * 10L, i -> {}, false).get(); + if (result == null) return null; + var blockPos = new BlockPos(result.getBlockX(), 0, result.getBlockZ()); + return Pair.of(blockPos, result.obj()); + } catch (WrongEngineBroException | ExecutionException | InterruptedException e) { + return null; + } + } + + @Override + protected Codec codec() { + return Codec.unit(null); + } + + @Override + public ChunkGenerator getDelegate() { + if (delegate instanceof CustomChunkGenerator chunkGenerator) + return chunkGenerator.getDelegate(); + return delegate; + } + + @Override + public int getMinY() { + return delegate.getMinY(); + } + + @Override + public int getSeaLevel() { + return delegate.getSeaLevel(); + } + + @Override + public void createStructures(RegistryAccess iregistrycustom, ChunkGeneratorStructureState chunkgeneratorstructurestate, StructureManager structuremanager, ChunkAccess ichunkaccess, StructureTemplateManager structuretemplatemanager) { + delegate.createStructures(iregistrycustom, chunkgeneratorstructurestate, structuremanager, ichunkaccess, structuretemplatemanager); + } + + @Override + public void buildSurface(WorldGenRegion regionlimitedworldaccess, StructureManager structuremanager, RandomState randomstate, ChunkAccess ichunkaccess) { + delegate.buildSurface(regionlimitedworldaccess, structuremanager, randomstate, ichunkaccess); + } + + @Override + public void applyCarvers(WorldGenRegion regionlimitedworldaccess, long seed, RandomState randomstate, BiomeManager biomemanager, StructureManager structuremanager, ChunkAccess ichunkaccess, GenerationStep.Carving worldgenstage_features) { + delegate.applyCarvers(regionlimitedworldaccess, seed, randomstate, biomemanager, structuremanager, ichunkaccess, worldgenstage_features); + } + + @Override + public CompletableFuture fillFromNoise(Executor executor, Blender blender, RandomState randomstate, StructureManager structuremanager, ChunkAccess ichunkaccess) { + 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); + } + + @Override + public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) { + delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager); + } + + @Override + public void addDebugScreenInfo(List list, RandomState randomstate, BlockPos blockposition) { + delegate.addDebugScreenInfo(list, randomstate, blockposition); + } + + @Override + public void spawnOriginalMobs(WorldGenRegion regionlimitedworldaccess) { + delegate.spawnOriginalMobs(regionlimitedworldaccess); + } + + @Override + public int getSpawnHeight(LevelHeightAccessor levelheightaccessor) { + return delegate.getSpawnHeight(levelheightaccessor); + } + + @Override + public int getGenDepth() { + return delegate.getGenDepth(); + } + + @Override + public NoiseColumn getBaseColumn(int i, int j, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { + return delegate.getBaseColumn(i, j, levelheightaccessor, randomstate); + } + + static { + Field biomeSource = null; + for (Field field : ChunkGenerator.class.getDeclaredFields()) { + if (!field.getType().equals(BiomeSource.class)) + continue; + biomeSource = field; + break; + } + if (biomeSource == null) + throw new RuntimeException("Could not find biomeSource field in ChunkGenerator!"); + BIOME_SOURCE = new WrappedField<>(ChunkGenerator.class, biomeSource.getName()); + } + + private static ChunkGenerator edit(ChunkGenerator generator, BiomeSource source) { + try { + BIOME_SOURCE.set(generator, source); + if (generator instanceof CustomChunkGenerator custom) + BIOME_SOURCE.set(custom.getDelegate(), source); + + return generator; + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } +} diff --git a/nms/v1_19_R2/src/main/java/com/volmit/iris/core/nms/v1_19_R2/NMSBinding.java b/nms/v1_19_R2/src/main/java/com/volmit/iris/core/nms/v1_19_R2/NMSBinding.java index f5f778002..5d4c44d70 100644 --- a/nms/v1_19_R2/src/main/java/com/volmit/iris/core/nms/v1_19_R2/NMSBinding.java +++ b/nms/v1_19_R2/src/main/java/com/volmit/iris/core/nms/v1_19_R2/NMSBinding.java @@ -10,11 +10,19 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Vector; import java.util.concurrent.atomic.AtomicInteger; +import com.mojang.datafixers.util.Pair; import com.volmit.iris.core.nms.container.BiomeColor; +import com.volmit.iris.util.scheduling.J; +import net.minecraft.nbt.*; +import net.minecraft.nbt.Tag; +import net.minecraft.server.commands.data.BlockDataAccessor; +import net.minecraft.tags.TagKey; import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.EntityBlock; import org.bukkit.*; import org.bukkit.block.Biome; import org.bukkit.block.data.BlockData; @@ -22,6 +30,8 @@ import org.bukkit.craftbukkit.v1_19_R2.CraftChunk; import org.bukkit.craftbukkit.v1_19_R2.CraftServer; import org.bukkit.craftbukkit.v1_19_R2.CraftWorld; import org.bukkit.craftbukkit.v1_19_R2.block.CraftBlock; +import org.bukkit.craftbukkit.v1_19_R2.block.CraftBlockState; +import org.bukkit.craftbukkit.v1_19_R2.block.CraftBlockStates; import org.bukkit.craftbukkit.v1_19_R2.block.data.CraftBlockData; import org.bukkit.craftbukkit.v1_19_R2.entity.CraftDolphin; import org.bukkit.craftbukkit.v1_19_R2.inventory.CraftItemStack; @@ -30,6 +40,7 @@ import org.bukkit.entity.Entity; import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.generator.ChunkGenerator; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import com.mojang.brigadier.exceptions.CommandSyntaxException; @@ -55,8 +66,6 @@ import net.minecraft.core.Holder; import net.minecraft.core.Registry; import net.minecraft.core.RegistryAccess; import net.minecraft.core.registries.Registries; -import net.minecraft.nbt.NbtIo; -import net.minecraft.nbt.TagParser; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.EntityType; @@ -128,55 +137,106 @@ public class NMSBinding implements INMSBinding { return type.getDeclaredClasses()[ordinal]; } + @Override + public boolean hasTile(Material material) { + return !CraftBlockState.class.equals(CraftBlockStates.getBlockStateType(material)); + } + @Override public boolean hasTile(Location l) { return ((CraftWorld) l.getWorld()).getHandle().getBlockEntity(new BlockPos(l.getBlockX(), l.getBlockY(), l.getBlockZ()), false) != null; } @Override - public CompoundTag serializeTile(Location location) { - BlockEntity e = ((CraftWorld) location.getWorld()).getHandle().getBlockEntity(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()), true); + @SuppressWarnings("unchecked") + public KMap serializeTile(Location location) { + BlockEntity e = ((CraftWorld) location.getWorld()).getHandle().getBlockEntity(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()), false); if (e == null) { return null; } - net.minecraft.nbt.CompoundTag tag = e.saveWithFullMetadata(); - return convert(tag); + net.minecraft.nbt.CompoundTag tag = e.saveWithoutMetadata(); + return (KMap) convertFromTag(tag, 0, 64); } - private CompoundTag convert(net.minecraft.nbt.CompoundTag tag) { - try { - ByteArrayOutputStream boas = new ByteArrayOutputStream(); - DataOutputStream dos = new DataOutputStream(boas); - tag.write(dos); - dos.close(); - return (CompoundTag) NBTUtil.read(new ByteArrayInputStream(boas.toByteArray()), false).getTag(); - } catch (Throwable ex) { - ex.printStackTrace(); + @Contract(value = "null, _, _ -> null", pure = true) + private Object convertFromTag(net.minecraft.nbt.Tag tag, int depth, int maxDepth) { + if (tag == null || depth > maxDepth) return null; + if (tag instanceof CollectionTag collection) { + KList list = new KList<>(); + + for (Object i : collection) { + if (i instanceof net.minecraft.nbt.Tag t) + list.add(convertFromTag(t, depth + 1, maxDepth)); + else list.add(i); + } + return list; } + if (tag instanceof net.minecraft.nbt.CompoundTag compound) { + KMap map = new KMap<>(); - return null; - } - - private net.minecraft.nbt.CompoundTag convert(CompoundTag tag) { - try { - ByteArrayOutputStream boas = new ByteArrayOutputStream(); - NBTUtil.write(tag, boas, false); - DataInputStream din = new DataInputStream(new ByteArrayInputStream(boas.toByteArray())); - net.minecraft.nbt.CompoundTag c = NbtIo.read(din); - din.close(); - return c; - } catch (Throwable e) { - e.printStackTrace(); + for (String key : compound.getAllKeys()) { + var child = compound.get(key); + if (child == null) continue; + var value = convertFromTag(child, depth + 1, maxDepth); + if (value == null) continue; + map.put(key, value); + } + return map; } - - return null; + if (tag instanceof NumericTag numeric) + return numeric.getAsNumber(); + return tag.getAsString(); } @Override - public void deserializeTile(CompoundTag c, Location pos) { - ((CraftWorld) pos.getWorld()).getHandle().getChunkAt(new BlockPos(pos.getBlockX(), 0, pos.getBlockZ())).setBlockEntityNbt(convert(c)); + public void deserializeTile(KMap map, Location pos) { + net.minecraft.nbt.CompoundTag tag = (net.minecraft.nbt.CompoundTag) convertToTag(map, 0, 64); + var level = ((CraftWorld) pos.getWorld()).getHandle(); + var blockPos = new BlockPos(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ()); + J.s(() -> merge(level, blockPos, tag)); + } + + private void merge(ServerLevel level, BlockPos blockPos, net.minecraft.nbt.CompoundTag tag) { + var blockEntity = level.getBlockEntity(blockPos); + if (blockEntity == null) { + Iris.warn("[NMS] BlockEntity not found at " + blockPos); + var state = level.getBlockState(blockPos); + if (!state.hasBlockEntity()) + return; + + blockEntity = ((EntityBlock) state.getBlock()) + .newBlockEntity(blockPos, state); + } + var accessor = new BlockDataAccessor(blockEntity, blockPos); + accessor.setData(tag.merge(accessor.getData())); + } + + private Tag convertToTag(Object object, int depth, int maxDepth) { + if (object == null || depth > maxDepth) return EndTag.INSTANCE; + if (object instanceof Map map) { + var tag = new net.minecraft.nbt.CompoundTag(); + for (var i : map.entrySet()) { + tag.put(i.getKey().toString(), convertToTag(i.getValue(), depth + 1, maxDepth)); + } + return tag; + } + if (object instanceof List list) { + var tag = new net.minecraft.nbt.ListTag(); + for (var i : list) { + tag.add(convertToTag(i, depth + 1, maxDepth)); + } + return tag; + } + if (object instanceof Byte number) return ByteTag.valueOf(number); + if (object instanceof Short number) return ShortTag.valueOf(number); + if (object instanceof Integer number) return IntTag.valueOf(number); + if (object instanceof Long number) return LongTag.valueOf(number); + if (object instanceof Float number) return FloatTag.valueOf(number); + if (object instanceof Double number) return DoubleTag.valueOf(number); + if (object instanceof String string) return StringTag.valueOf(string); + return EndTag.INSTANCE; } @Override @@ -474,23 +534,9 @@ public class NMSBinding implements INMSBinding { } } - public void setTreasurePos(Dolphin dolphin, com.volmit.iris.core.nms.container.BlockPos pos) { - CraftDolphin cd = (CraftDolphin)dolphin; - cd.getHandle().setTreasurePos(new BlockPos(pos.getX(), pos.getY(), pos.getZ())); - cd.getHandle().setGotFish(true); - } - - public void inject(long seed, Engine engine, World world) throws NoSuchFieldException, IllegalAccessException { - ServerLevel serverLevel = ((CraftWorld)world).getHandle(); - Class clazz = serverLevel.getChunkSource().chunkMap.generator.getClass(); - Field biomeSource = getField(clazz, BiomeSource.class); - biomeSource.setAccessible(true); - Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); - unsafeField.setAccessible(true); - Unsafe unsafe = (Unsafe)unsafeField.get(null); - CustomBiomeSource customBiomeSource = new CustomBiomeSource(seed, engine, world); - unsafe.putObject(biomeSource.get(serverLevel.getChunkSource().chunkMap.generator), unsafe.objectFieldOffset(biomeSource), customBiomeSource); - biomeSource.set(serverLevel.getChunkSource().chunkMap.generator, customBiomeSource); + public void inject(long seed, Engine engine, World world) { + var chunkMap = ((CraftWorld)world).getHandle().getChunkSource().chunkMap; + chunkMap.generator = new IrisChunkGenerator(chunkMap.generator, seed, engine, world); } @@ -546,6 +592,23 @@ public class NMSBinding implements INMSBinding { return new Color(rgba, true); } + @Override + public KList getStructureKeys() { + KList keys = new KList<>(); + + var registry = registry().registry(Registries.STRUCTURE).orElse(null); + if (registry == null) return keys; + registry.keySet().stream().map(ResourceLocation::toString).forEach(keys::add); + registry.getTags() + .map(Pair::getFirst) + .map(TagKey::location) + .map(ResourceLocation::toString) + .map(s -> "#" + s) + .forEach(keys::add); + + return keys; + } + private static Field getField(Class clazz, Class fieldType) throws NoSuchFieldException { try { for (Field f : clazz.getDeclaredFields()) { diff --git a/nms/v1_19_R3/src/main/java/com/volmit/iris/core/nms/v1_19_R3/IrisChunkGenerator.java b/nms/v1_19_R3/src/main/java/com/volmit/iris/core/nms/v1_19_R3/IrisChunkGenerator.java new file mode 100644 index 000000000..f4f0b4502 --- /dev/null +++ b/nms/v1_19_R3/src/main/java/com/volmit/iris/core/nms/v1_19_R3/IrisChunkGenerator.java @@ -0,0 +1,262 @@ +package com.volmit.iris.core.nms.v1_19_R3; + +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.Codec; +import com.volmit.iris.Iris; +import com.volmit.iris.engine.framework.Engine; +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.util.collection.KList; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.collection.KSet; +import com.volmit.iris.util.mantle.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 net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.WorldGenRegion; +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.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.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.levelgen.blending.Blender; +import net.minecraft.world.level.levelgen.structure.Structure; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; +import org.bukkit.World; +import org.bukkit.craftbukkit.v1_19_R3.CraftWorld; +import org.bukkit.craftbukkit.v1_19_R3.generator.CustomChunkGenerator; + +import javax.annotation.Nullable; +import java.lang.reflect.Field; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; + +public class IrisChunkGenerator extends CustomChunkGenerator { + private static final WrappedField BIOME_SOURCE; + private final ChunkGenerator delegate; + private final Engine engine; + private final KMap, KSet> structures = new KMap<>(); + + 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; + var dimension = engine.getDimension(); + + KSet placements = new KSet<>(); + addAll(dimension.getJigsawStructures(), placements); + for (var region : dimension.getAllRegions(engine)) { + addAll(region.getJigsawStructures(), placements); + for (var biome : region.getAllBiomes(engine)) + addAll(biome.getJigsawStructures(), placements); + } + var stronghold = dimension.getStronghold(); + if (stronghold != null) + placements.add(engine.getData().getJigsawStructureLoader().load(stronghold)); + placements.removeIf(Objects::isNull); + + var registry = ((CraftWorld) world).getHandle().registryAccess().registry(Registries.STRUCTURE).orElseThrow(); + for (var s : placements) { + try { + String raw = s.getStructureKey(); + if (raw == null) continue; + boolean tag = raw.startsWith("#"); + if (tag) raw = raw.substring(1); + + var location = new ResourceLocation(raw); + if (!tag) { + structures.computeIfAbsent(ResourceKey.create(Registries.STRUCTURE, location), k -> new KSet<>()).add(s.getLoadKey()); + continue; + } + + var key = TagKey.create(Registries.STRUCTURE, location); + var set = registry.getTag(key).orElse(null); + if (set == null) { + Iris.error("Could not find structure tag: " + raw); + continue; + } + for (var holder : set) { + var resourceKey = holder.unwrapKey().orElse(null); + if (resourceKey == null) continue; + structures.computeIfAbsent(resourceKey, k -> new KSet<>()).add(s.getLoadKey()); + } + } catch (Throwable e) { + Iris.error("Failed to load structure: " + s.getLoadKey()); + e.printStackTrace(); + } + } + } + + private void addAll(KList placements, KSet structures) { + if (placements == null) return; + placements.stream() + .map(IrisJigsawStructurePlacement::getStructure) + .map(engine.getData().getJigsawStructureLoader()::load) + .filter(Objects::nonNull) + .forEach(structures::add); + } + + @Override + public @Nullable Pair> findNearestMapStructure(ServerLevel level, HolderSet holders, BlockPos pos, int radius, boolean findUnexplored) { + if (engine.getDimension().isDisableExplorerMaps()) + return null; + + KMap> structures = new KMap<>(); + for (var holder : holders) { + if (holder == null) continue; + var key = holder.unwrapKey().orElse(null); + var set = this.structures.get(key); + if (set == null) continue; + for (var structure : set) { + structures.put(structure, holder); + } + } + if (structures.isEmpty()) + return null; + + var locator = ResultLocator.locateStructure(structures.keySet()) + .then((e, p , s) -> structures.get(s.getLoadKey())); + if (findUnexplored) + locator = locator.then((e, p, s) -> e.getMantle().getMantle().getChunk(p.getX(), p.getZ()).isFlagged(MantleFlag.DISCOVERED) ? null : s); + + try { + var result = locator.find(engine, new Position2(pos.getX() >> 4, pos.getZ() >> 4), radius * 10L, i -> {}, false).get(); + if (result == null) return null; + var blockPos = new BlockPos(result.getBlockX(), 0, result.getBlockZ()); + return Pair.of(blockPos, result.obj()); + } catch (WrongEngineBroException | ExecutionException | InterruptedException e) { + return null; + } + } + + @Override + protected Codec codec() { + return Codec.unit(null); + } + + @Override + public ChunkGenerator getDelegate() { + if (delegate instanceof CustomChunkGenerator chunkGenerator) + return chunkGenerator.getDelegate(); + return delegate; + } + + @Override + public int getMinY() { + return delegate.getMinY(); + } + + @Override + public int getSeaLevel() { + return delegate.getSeaLevel(); + } + + @Override + public void createStructures(RegistryAccess iregistrycustom, ChunkGeneratorStructureState chunkgeneratorstructurestate, StructureManager structuremanager, ChunkAccess ichunkaccess, StructureTemplateManager structuretemplatemanager) { + delegate.createStructures(iregistrycustom, chunkgeneratorstructurestate, structuremanager, ichunkaccess, structuretemplatemanager); + } + + @Override + public void buildSurface(WorldGenRegion regionlimitedworldaccess, StructureManager structuremanager, RandomState randomstate, ChunkAccess ichunkaccess) { + delegate.buildSurface(regionlimitedworldaccess, structuremanager, randomstate, ichunkaccess); + } + + @Override + public void applyCarvers(WorldGenRegion regionlimitedworldaccess, long seed, RandomState randomstate, BiomeManager biomemanager, StructureManager structuremanager, ChunkAccess ichunkaccess, GenerationStep.Carving worldgenstage_features) { + delegate.applyCarvers(regionlimitedworldaccess, seed, randomstate, biomemanager, structuremanager, ichunkaccess, worldgenstage_features); + } + + @Override + public CompletableFuture fillFromNoise(Executor executor, Blender blender, RandomState randomstate, StructureManager structuremanager, ChunkAccess ichunkaccess) { + 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); + } + + @Override + public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) { + delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager); + } + + @Override + public void addDebugScreenInfo(List list, RandomState randomstate, BlockPos blockposition) { + delegate.addDebugScreenInfo(list, randomstate, blockposition); + } + + @Override + public void spawnOriginalMobs(WorldGenRegion regionlimitedworldaccess) { + delegate.spawnOriginalMobs(regionlimitedworldaccess); + } + + @Override + public int getSpawnHeight(LevelHeightAccessor levelheightaccessor) { + return delegate.getSpawnHeight(levelheightaccessor); + } + + @Override + public int getGenDepth() { + return delegate.getGenDepth(); + } + + @Override + public NoiseColumn getBaseColumn(int i, int j, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { + return delegate.getBaseColumn(i, j, levelheightaccessor, randomstate); + } + + static { + Field biomeSource = null; + for (Field field : ChunkGenerator.class.getDeclaredFields()) { + if (!field.getType().equals(BiomeSource.class)) + continue; + biomeSource = field; + break; + } + if (biomeSource == null) + throw new RuntimeException("Could not find biomeSource field in ChunkGenerator!"); + BIOME_SOURCE = new WrappedField<>(ChunkGenerator.class, biomeSource.getName()); + } + + private static ChunkGenerator edit(ChunkGenerator generator, BiomeSource source) { + try { + BIOME_SOURCE.set(generator, source); + if (generator instanceof CustomChunkGenerator custom) + BIOME_SOURCE.set(custom.getDelegate(), source); + + return generator; + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } +} diff --git a/nms/v1_19_R3/src/main/java/com/volmit/iris/core/nms/v1_19_R3/NMSBinding.java b/nms/v1_19_R3/src/main/java/com/volmit/iris/core/nms/v1_19_R3/NMSBinding.java index 0cbf158bd..6c151dea8 100644 --- a/nms/v1_19_R3/src/main/java/com/volmit/iris/core/nms/v1_19_R3/NMSBinding.java +++ b/nms/v1_19_R3/src/main/java/com/volmit/iris/core/nms/v1_19_R3/NMSBinding.java @@ -10,11 +10,19 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Vector; import java.util.concurrent.atomic.AtomicInteger; +import com.mojang.datafixers.util.Pair; import com.volmit.iris.core.nms.container.BiomeColor; +import com.volmit.iris.util.scheduling.J; +import net.minecraft.nbt.*; +import net.minecraft.nbt.Tag; +import net.minecraft.server.commands.data.BlockDataAccessor; +import net.minecraft.tags.TagKey; import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.EntityBlock; import org.bukkit.*; import org.bukkit.block.Biome; import org.bukkit.block.data.BlockData; @@ -22,6 +30,8 @@ import org.bukkit.craftbukkit.v1_19_R3.CraftChunk; import org.bukkit.craftbukkit.v1_19_R3.CraftServer; import org.bukkit.craftbukkit.v1_19_R3.CraftWorld; import org.bukkit.craftbukkit.v1_19_R3.block.CraftBlock; +import org.bukkit.craftbukkit.v1_19_R3.block.CraftBlockState; +import org.bukkit.craftbukkit.v1_19_R3.block.CraftBlockStates; import org.bukkit.craftbukkit.v1_19_R3.block.data.CraftBlockData; import org.bukkit.craftbukkit.v1_19_R3.entity.CraftDolphin; import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftItemStack; @@ -30,6 +40,7 @@ import org.bukkit.entity.Entity; import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.generator.ChunkGenerator; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import com.mojang.brigadier.exceptions.CommandSyntaxException; @@ -55,8 +66,6 @@ import net.minecraft.core.Holder; import net.minecraft.core.Registry; import net.minecraft.core.RegistryAccess; import net.minecraft.core.registries.Registries; -import net.minecraft.nbt.NbtIo; -import net.minecraft.nbt.TagParser; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.EntityType; @@ -129,55 +138,106 @@ public class NMSBinding implements INMSBinding { return type.getDeclaredClasses()[ordinal]; } + @Override + public boolean hasTile(Material material) { + return !CraftBlockState.class.equals(CraftBlockStates.getBlockStateType(material)); + } + @Override public boolean hasTile(Location l) { return ((CraftWorld) l.getWorld()).getHandle().getBlockEntity(new BlockPos(l.getBlockX(), l.getBlockY(), l.getBlockZ()), false) != null; } @Override - public CompoundTag serializeTile(Location location) { - BlockEntity e = ((CraftWorld) location.getWorld()).getHandle().getBlockEntity(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()), true); + @SuppressWarnings("unchecked") + public KMap serializeTile(Location location) { + BlockEntity e = ((CraftWorld) location.getWorld()).getHandle().getBlockEntity(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()), false); if (e == null) { return null; } - net.minecraft.nbt.CompoundTag tag = e.saveWithFullMetadata(); - return convert(tag); + net.minecraft.nbt.CompoundTag tag = e.saveWithoutMetadata(); + return (KMap) convertFromTag(tag, 0, 64); } - private CompoundTag convert(net.minecraft.nbt.CompoundTag tag) { - try { - ByteArrayOutputStream boas = new ByteArrayOutputStream(); - DataOutputStream dos = new DataOutputStream(boas); - tag.write(dos); - dos.close(); - return (CompoundTag) NBTUtil.read(new ByteArrayInputStream(boas.toByteArray()), false).getTag(); - } catch (Throwable ex) { - ex.printStackTrace(); + @Contract(value = "null, _, _ -> null", pure = true) + private Object convertFromTag(net.minecraft.nbt.Tag tag, int depth, int maxDepth) { + if (tag == null || depth > maxDepth) return null; + if (tag instanceof CollectionTag collection) { + KList list = new KList<>(); + + for (Object i : collection) { + if (i instanceof net.minecraft.nbt.Tag t) + list.add(convertFromTag(t, depth + 1, maxDepth)); + else list.add(i); + } + return list; } + if (tag instanceof net.minecraft.nbt.CompoundTag compound) { + KMap map = new KMap<>(); - return null; - } - - private net.minecraft.nbt.CompoundTag convert(CompoundTag tag) { - try { - ByteArrayOutputStream boas = new ByteArrayOutputStream(); - NBTUtil.write(tag, boas, false); - DataInputStream din = new DataInputStream(new ByteArrayInputStream(boas.toByteArray())); - net.minecraft.nbt.CompoundTag c = NbtIo.read(din); - din.close(); - return c; - } catch (Throwable e) { - e.printStackTrace(); + for (String key : compound.getAllKeys()) { + var child = compound.get(key); + if (child == null) continue; + var value = convertFromTag(child, depth + 1, maxDepth); + if (value == null) continue; + map.put(key, value); + } + return map; } - - return null; + if (tag instanceof NumericTag numeric) + return numeric.getAsNumber(); + return tag.getAsString(); } @Override - public void deserializeTile(CompoundTag c, Location pos) { - ((CraftWorld) pos.getWorld()).getHandle().getChunkAt(new BlockPos(pos.getBlockX(), 0, pos.getBlockZ())).setBlockEntityNbt(convert(c)); + public void deserializeTile(KMap map, Location pos) { + net.minecraft.nbt.CompoundTag tag = (net.minecraft.nbt.CompoundTag) convertToTag(map, 0, 64); + var level = ((CraftWorld) pos.getWorld()).getHandle(); + var blockPos = new BlockPos(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ()); + J.s(() -> merge(level, blockPos, tag)); + } + + private void merge(ServerLevel level, BlockPos blockPos, net.minecraft.nbt.CompoundTag tag) { + var blockEntity = level.getBlockEntity(blockPos); + if (blockEntity == null) { + Iris.warn("[NMS] BlockEntity not found at " + blockPos); + var state = level.getBlockState(blockPos); + if (!state.hasBlockEntity()) + return; + + blockEntity = ((EntityBlock) state.getBlock()) + .newBlockEntity(blockPos, state); + } + var accessor = new BlockDataAccessor(blockEntity, blockPos); + accessor.setData(tag.merge(accessor.getData())); + } + + private Tag convertToTag(Object object, int depth, int maxDepth) { + if (object == null || depth > maxDepth) return EndTag.INSTANCE; + if (object instanceof Map map) { + var tag = new net.minecraft.nbt.CompoundTag(); + for (var i : map.entrySet()) { + tag.put(i.getKey().toString(), convertToTag(i.getValue(), depth + 1, maxDepth)); + } + return tag; + } + if (object instanceof List list) { + var tag = new net.minecraft.nbt.ListTag(); + for (var i : list) { + tag.add(convertToTag(i, depth + 1, maxDepth)); + } + return tag; + } + if (object instanceof Byte number) return ByteTag.valueOf(number); + if (object instanceof Short number) return ShortTag.valueOf(number); + if (object instanceof Integer number) return IntTag.valueOf(number); + if (object instanceof Long number) return LongTag.valueOf(number); + if (object instanceof Float number) return FloatTag.valueOf(number); + if (object instanceof Double number) return DoubleTag.valueOf(number); + if (object instanceof String string) return StringTag.valueOf(string); + return EndTag.INSTANCE; } @Override @@ -482,17 +542,9 @@ public class NMSBinding implements INMSBinding { cd.getHandle().setGotFish(true); } - public void inject(long seed, Engine engine, World world) throws NoSuchFieldException, IllegalAccessException { - ServerLevel serverLevel = ((CraftWorld)world).getHandle(); - Class clazz = serverLevel.getChunkSource().chunkMap.generator.getClass(); - Field biomeSource = getField(clazz, BiomeSource.class); - biomeSource.setAccessible(true); - Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); - unsafeField.setAccessible(true); - Unsafe unsafe = (Unsafe)unsafeField.get(null); - CustomBiomeSource customBiomeSource = new CustomBiomeSource(seed, engine, world); - unsafe.putObject(biomeSource.get(serverLevel.getChunkSource().chunkMap.generator), unsafe.objectFieldOffset(biomeSource), customBiomeSource); - biomeSource.set(serverLevel.getChunkSource().chunkMap.generator, customBiomeSource); + public void inject(long seed, Engine engine, World world) { + var chunkMap = ((CraftWorld)world).getHandle().getChunkSource().chunkMap; + chunkMap.generator = new IrisChunkGenerator(chunkMap.generator, seed, engine, world); } public Vector3d getBoundingbox(org.bukkit.entity.EntityType entity) { @@ -550,6 +602,23 @@ public class NMSBinding implements INMSBinding { return new Color(rgba, true); } + @Override + public KList getStructureKeys() { + KList keys = new KList<>(); + + var registry = registry().registry(Registries.STRUCTURE).orElse(null); + if (registry == null) return keys; + registry.keySet().stream().map(ResourceLocation::toString).forEach(keys::add); + registry.getTags() + .map(Pair::getFirst) + .map(TagKey::location) + .map(ResourceLocation::toString) + .map(s -> "#" + s) + .forEach(keys::add); + + return keys; + } + private static Field getField(Class clazz, Class fieldType) throws NoSuchFieldException { try { for (Field f : clazz.getDeclaredFields()) { 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 new file mode 100644 index 000000000..30873ea31 --- /dev/null +++ b/nms/v1_20_R1/src/main/java/com/volmit/iris/core/nms/v1_20_R1/IrisChunkGenerator.java @@ -0,0 +1,262 @@ +package com.volmit.iris.core.nms.v1_20_R1; + +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.Codec; +import com.volmit.iris.Iris; +import com.volmit.iris.engine.framework.Engine; +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.util.collection.KList; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.collection.KSet; +import com.volmit.iris.util.mantle.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 net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.WorldGenRegion; +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.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.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.levelgen.blending.Blender; +import net.minecraft.world.level.levelgen.structure.Structure; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; +import org.bukkit.World; +import org.bukkit.craftbukkit.v1_20_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_20_R1.generator.CustomChunkGenerator; + +import javax.annotation.Nullable; +import java.lang.reflect.Field; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; + +public class IrisChunkGenerator extends CustomChunkGenerator { + private static final WrappedField BIOME_SOURCE; + private final ChunkGenerator delegate; + private final Engine engine; + private final KMap, KSet> structures = new KMap<>(); + + 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; + var dimension = engine.getDimension(); + + KSet placements = new KSet<>(); + addAll(dimension.getJigsawStructures(), placements); + for (var region : dimension.getAllRegions(engine)) { + addAll(region.getJigsawStructures(), placements); + for (var biome : region.getAllBiomes(engine)) + addAll(biome.getJigsawStructures(), placements); + } + var stronghold = dimension.getStronghold(); + if (stronghold != null) + placements.add(engine.getData().getJigsawStructureLoader().load(stronghold)); + placements.removeIf(Objects::isNull); + + var registry = ((CraftWorld) world).getHandle().registryAccess().registry(Registries.STRUCTURE).orElseThrow(); + for (var s : placements) { + try { + String raw = s.getStructureKey(); + if (raw == null) continue; + boolean tag = raw.startsWith("#"); + if (tag) raw = raw.substring(1); + + var location = new ResourceLocation(raw); + if (!tag) { + structures.computeIfAbsent(ResourceKey.create(Registries.STRUCTURE, location), k -> new KSet<>()).add(s.getLoadKey()); + continue; + } + + var key = TagKey.create(Registries.STRUCTURE, location); + var set = registry.getTag(key).orElse(null); + if (set == null) { + Iris.error("Could not find structure tag: " + raw); + continue; + } + for (var holder : set) { + var resourceKey = holder.unwrapKey().orElse(null); + if (resourceKey == null) continue; + structures.computeIfAbsent(resourceKey, k -> new KSet<>()).add(s.getLoadKey()); + } + } catch (Throwable e) { + Iris.error("Failed to load structure: " + s.getLoadKey()); + e.printStackTrace(); + } + } + } + + private void addAll(KList placements, KSet structures) { + if (placements == null) return; + placements.stream() + .map(IrisJigsawStructurePlacement::getStructure) + .map(engine.getData().getJigsawStructureLoader()::load) + .filter(Objects::nonNull) + .forEach(structures::add); + } + + @Override + public @Nullable Pair> findNearestMapStructure(ServerLevel level, HolderSet holders, BlockPos pos, int radius, boolean findUnexplored) { + if (engine.getDimension().isDisableExplorerMaps()) + return null; + + KMap> structures = new KMap<>(); + for (var holder : holders) { + if (holder == null) continue; + var key = holder.unwrapKey().orElse(null); + var set = this.structures.get(key); + if (set == null) continue; + for (var structure : set) { + structures.put(structure, holder); + } + } + if (structures.isEmpty()) + return null; + + var locator = ResultLocator.locateStructure(structures.keySet()) + .then((e, p , s) -> structures.get(s.getLoadKey())); + if (findUnexplored) + locator = locator.then((e, p, s) -> e.getMantle().getMantle().getChunk(p.getX(), p.getZ()).isFlagged(MantleFlag.DISCOVERED) ? null : s); + + try { + var result = locator.find(engine, new Position2(pos.getX() >> 4, pos.getZ() >> 4), radius * 10L, i -> {}, false).get(); + if (result == null) return null; + var blockPos = new BlockPos(result.getBlockX(), 0, result.getBlockZ()); + return Pair.of(blockPos, result.obj()); + } catch (WrongEngineBroException | ExecutionException | InterruptedException e) { + return null; + } + } + + @Override + protected Codec codec() { + return Codec.unit(null); + } + + @Override + public ChunkGenerator getDelegate() { + if (delegate instanceof CustomChunkGenerator chunkGenerator) + return chunkGenerator.getDelegate(); + return delegate; + } + + @Override + public int getMinY() { + return delegate.getMinY(); + } + + @Override + public int getSeaLevel() { + return delegate.getSeaLevel(); + } + + @Override + public void createStructures(RegistryAccess iregistrycustom, ChunkGeneratorStructureState chunkgeneratorstructurestate, StructureManager structuremanager, ChunkAccess ichunkaccess, StructureTemplateManager structuretemplatemanager) { + delegate.createStructures(iregistrycustom, chunkgeneratorstructurestate, structuremanager, ichunkaccess, structuretemplatemanager); + } + + @Override + public void buildSurface(WorldGenRegion regionlimitedworldaccess, StructureManager structuremanager, RandomState randomstate, ChunkAccess ichunkaccess) { + delegate.buildSurface(regionlimitedworldaccess, structuremanager, randomstate, ichunkaccess); + } + + @Override + public void applyCarvers(WorldGenRegion regionlimitedworldaccess, long seed, RandomState randomstate, BiomeManager biomemanager, StructureManager structuremanager, ChunkAccess ichunkaccess, GenerationStep.Carving worldgenstage_features) { + delegate.applyCarvers(regionlimitedworldaccess, seed, randomstate, biomemanager, structuremanager, ichunkaccess, worldgenstage_features); + } + + @Override + public CompletableFuture fillFromNoise(Executor executor, Blender blender, RandomState randomstate, StructureManager structuremanager, ChunkAccess ichunkaccess) { + 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); + } + + @Override + public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) { + delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager); + } + + @Override + public void addDebugScreenInfo(List list, RandomState randomstate, BlockPos blockposition) { + delegate.addDebugScreenInfo(list, randomstate, blockposition); + } + + @Override + public void spawnOriginalMobs(WorldGenRegion regionlimitedworldaccess) { + delegate.spawnOriginalMobs(regionlimitedworldaccess); + } + + @Override + public int getSpawnHeight(LevelHeightAccessor levelheightaccessor) { + return delegate.getSpawnHeight(levelheightaccessor); + } + + @Override + public int getGenDepth() { + return delegate.getGenDepth(); + } + + @Override + public NoiseColumn getBaseColumn(int i, int j, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { + return delegate.getBaseColumn(i, j, levelheightaccessor, randomstate); + } + + static { + Field biomeSource = null; + for (Field field : ChunkGenerator.class.getDeclaredFields()) { + if (!field.getType().equals(BiomeSource.class)) + continue; + biomeSource = field; + break; + } + if (biomeSource == null) + throw new RuntimeException("Could not find biomeSource field in ChunkGenerator!"); + BIOME_SOURCE = new WrappedField<>(ChunkGenerator.class, biomeSource.getName()); + } + + private static ChunkGenerator edit(ChunkGenerator generator, BiomeSource source) { + try { + BIOME_SOURCE.set(generator, source); + if (generator instanceof CustomChunkGenerator custom) + BIOME_SOURCE.set(custom.getDelegate(), source); + + return generator; + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } +} 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 cb8323009..d737ec6aa 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 @@ -1,6 +1,7 @@ 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.nms.INMSBinding; import com.volmit.iris.core.nms.container.BiomeColor; @@ -17,20 +18,24 @@ import com.volmit.iris.util.nbt.io.NBTUtil; 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.util.scheduling.J; import it.unimi.dsi.fastutil.objects.Object2IntMap; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.core.Registry; import net.minecraft.core.RegistryAccess; import net.minecraft.core.registries.Registries; -import net.minecraft.nbt.NbtIo; -import net.minecraft.nbt.TagParser; +import net.minecraft.nbt.*; +import net.minecraft.nbt.Tag; import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.commands.data.BlockDataAccessor; import net.minecraft.server.level.ServerLevel; +import net.minecraft.tags.TagKey; import net.minecraft.world.entity.EntityDimensions; import net.minecraft.world.level.LevelReader; import net.minecraft.world.level.biome.BiomeSource; import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.EntityBlock; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.ChunkAccess; @@ -44,6 +49,8 @@ import org.bukkit.craftbukkit.v1_20_R1.CraftChunk; import org.bukkit.craftbukkit.v1_20_R1.CraftServer; import org.bukkit.craftbukkit.v1_20_R1.CraftWorld; import org.bukkit.craftbukkit.v1_20_R1.block.CraftBlock; +import org.bukkit.craftbukkit.v1_20_R1.block.CraftBlockState; +import org.bukkit.craftbukkit.v1_20_R1.block.CraftBlockStates; import org.bukkit.craftbukkit.v1_20_R1.block.data.CraftBlockData; import org.bukkit.craftbukkit.v1_20_R1.entity.CraftDolphin; import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftItemStack; @@ -54,6 +61,7 @@ import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.entity.EntityType; import org.bukkit.generator.ChunkGenerator; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import sun.misc.Unsafe; @@ -67,6 +75,7 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Vector; import java.util.concurrent.atomic.AtomicInteger; @@ -132,55 +141,106 @@ public class NMSBinding implements INMSBinding { return type.getDeclaredClasses()[ordinal]; } + @Override + public boolean hasTile(Material material) { + return !CraftBlockState.class.equals(CraftBlockStates.getBlockStateType(material)); + } + @Override public boolean hasTile(Location l) { return ((CraftWorld) l.getWorld()).getHandle().getBlockEntity(new BlockPos(l.getBlockX(), l.getBlockY(), l.getBlockZ()), false) != null; } @Override - public CompoundTag serializeTile(Location location) { - BlockEntity e = ((CraftWorld) location.getWorld()).getHandle().getBlockEntity(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()), true); + @SuppressWarnings("unchecked") + public KMap serializeTile(Location location) { + BlockEntity e = ((CraftWorld) location.getWorld()).getHandle().getBlockEntity(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()), false); if (e == null) { return null; } - net.minecraft.nbt.CompoundTag tag = e.saveWithFullMetadata(); - return convert(tag); + net.minecraft.nbt.CompoundTag tag = e.saveWithoutMetadata(); + return (KMap) convertFromTag(tag, 0, 64); } - private CompoundTag convert(net.minecraft.nbt.CompoundTag tag) { - try { - ByteArrayOutputStream boas = new ByteArrayOutputStream(); - DataOutputStream dos = new DataOutputStream(boas); - tag.write(dos); - dos.close(); - return (CompoundTag) NBTUtil.read(new ByteArrayInputStream(boas.toByteArray()), false).getTag(); - } catch (Throwable ex) { - ex.printStackTrace(); + @Contract(value = "null, _, _ -> null", pure = true) + private Object convertFromTag(net.minecraft.nbt.Tag tag, int depth, int maxDepth) { + if (tag == null || depth > maxDepth) return null; + if (tag instanceof CollectionTag collection) { + KList list = new KList<>(); + + for (Object i : collection) { + if (i instanceof net.minecraft.nbt.Tag t) + list.add(convertFromTag(t, depth + 1, maxDepth)); + else list.add(i); + } + return list; } + if (tag instanceof net.minecraft.nbt.CompoundTag compound) { + KMap map = new KMap<>(); - return null; - } - - private net.minecraft.nbt.CompoundTag convert(CompoundTag tag) { - try { - ByteArrayOutputStream boas = new ByteArrayOutputStream(); - NBTUtil.write(tag, boas, false); - DataInputStream din = new DataInputStream(new ByteArrayInputStream(boas.toByteArray())); - net.minecraft.nbt.CompoundTag c = NbtIo.read(din); - din.close(); - return c; - } catch (Throwable e) { - e.printStackTrace(); + for (String key : compound.getAllKeys()) { + var child = compound.get(key); + if (child == null) continue; + var value = convertFromTag(child, depth + 1, maxDepth); + if (value == null) continue; + map.put(key, value); + } + return map; } - - return null; + if (tag instanceof NumericTag numeric) + return numeric.getAsNumber(); + return tag.getAsString(); } @Override - public void deserializeTile(CompoundTag c, Location pos) { - ((CraftWorld) pos.getWorld()).getHandle().getChunkAt(new BlockPos(pos.getBlockX(), 0, pos.getBlockZ())).setBlockEntityNbt(convert(c)); + public void deserializeTile(KMap map, Location pos) { + net.minecraft.nbt.CompoundTag tag = (net.minecraft.nbt.CompoundTag) convertToTag(map, 0, 64); + var level = ((CraftWorld) pos.getWorld()).getHandle(); + var blockPos = new BlockPos(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ()); + J.s(() -> merge(level, blockPos, tag)); + } + + private void merge(ServerLevel level, BlockPos blockPos, net.minecraft.nbt.CompoundTag tag) { + var blockEntity = level.getBlockEntity(blockPos); + if (blockEntity == null) { + Iris.warn("[NMS] BlockEntity not found at " + blockPos); + var state = level.getBlockState(blockPos); + if (!state.hasBlockEntity()) + return; + + blockEntity = ((EntityBlock) state.getBlock()) + .newBlockEntity(blockPos, state); + } + var accessor = new BlockDataAccessor(blockEntity, blockPos); + accessor.setData(tag.merge(accessor.getData())); + } + + private Tag convertToTag(Object object, int depth, int maxDepth) { + if (object == null || depth > maxDepth) return EndTag.INSTANCE; + if (object instanceof Map map) { + var tag = new net.minecraft.nbt.CompoundTag(); + for (var i : map.entrySet()) { + tag.put(i.getKey().toString(), convertToTag(i.getValue(), depth + 1, maxDepth)); + } + return tag; + } + if (object instanceof List list) { + var tag = new net.minecraft.nbt.ListTag(); + for (var i : list) { + tag.add(convertToTag(i, depth + 1, maxDepth)); + } + return tag; + } + if (object instanceof Byte number) return ByteTag.valueOf(number); + if (object instanceof Short number) return ShortTag.valueOf(number); + if (object instanceof Integer number) return IntTag.valueOf(number); + if (object instanceof Long number) return LongTag.valueOf(number); + if (object instanceof Float number) return FloatTag.valueOf(number); + if (object instanceof Double number) return DoubleTag.valueOf(number); + if (object instanceof String string) return StringTag.valueOf(string); + return EndTag.INSTANCE; } @Override @@ -478,12 +538,6 @@ public class NMSBinding implements INMSBinding { } } - public void setTreasurePos(Dolphin dolphin, com.volmit.iris.core.nms.container.BlockPos pos) { - CraftDolphin cd = (CraftDolphin)dolphin; - cd.getHandle().setTreasurePos(new BlockPos(pos.getX(), pos.getY(), pos.getZ())); - cd.getHandle().setGotFish(true); - } - public Vector3d getBoundingbox(org.bukkit.entity.EntityType entity) { Field[] fields = net.minecraft.world.entity.EntityType.class.getDeclaredFields(); for (Field field : fields) { @@ -512,17 +566,9 @@ public class NMSBinding implements INMSBinding { return ((CraftWorld) location.getWorld()).spawn(location, type.getEntityClass(), null, reason); } - public void inject(long seed, Engine engine, World world) throws NoSuchFieldException, IllegalAccessException { - ServerLevel serverLevel = ((CraftWorld)world).getHandle(); - Class clazz = serverLevel.getChunkSource().chunkMap.generator.getClass(); - Field biomeSource = getField(clazz, BiomeSource.class); - biomeSource.setAccessible(true); - Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); - unsafeField.setAccessible(true); - Unsafe unsafe = (Unsafe)unsafeField.get(null); - CustomBiomeSource customBiomeSource = new CustomBiomeSource(seed, engine, world); - unsafe.putObject(biomeSource.get(serverLevel.getChunkSource().chunkMap.generator), unsafe.objectFieldOffset(biomeSource), customBiomeSource); - biomeSource.set(serverLevel.getChunkSource().chunkMap.generator, customBiomeSource); + public void inject(long seed, Engine engine, World world) { + var chunkMap = ((CraftWorld)world).getHandle().getChunkSource().chunkMap; + chunkMap.generator = new IrisChunkGenerator(chunkMap.generator, seed, engine, world); } @Override @@ -549,6 +595,23 @@ public class NMSBinding implements INMSBinding { return new Color(rgba, true); } + @Override + public KList getStructureKeys() { + KList keys = new KList<>(); + + var registry = registry().registry(Registries.STRUCTURE).orElse(null); + if (registry == null) return keys; + registry.keySet().stream().map(ResourceLocation::toString).forEach(keys::add); + registry.getTags() + .map(Pair::getFirst) + .map(TagKey::location) + .map(ResourceLocation::toString) + .map(s -> "#" + s) + .forEach(keys::add); + + return keys; + } + private static Field getField(Class clazz, Class fieldType) throws NoSuchFieldException { try { for (Field f : clazz.getDeclaredFields()) { 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 new file mode 100644 index 000000000..0d44523fe --- /dev/null +++ b/nms/v1_20_R2/src/main/java/com/volmit/iris/core/nms/v1_20_R2/IrisChunkGenerator.java @@ -0,0 +1,262 @@ +package com.volmit.iris.core.nms.v1_20_R2; + +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.Codec; +import com.volmit.iris.Iris; +import com.volmit.iris.engine.framework.Engine; +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.util.collection.KList; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.collection.KSet; +import com.volmit.iris.util.mantle.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 net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.WorldGenRegion; +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.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.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.levelgen.blending.Blender; +import net.minecraft.world.level.levelgen.structure.Structure; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; +import org.bukkit.World; +import org.bukkit.craftbukkit.v1_20_R2.CraftWorld; +import org.bukkit.craftbukkit.v1_20_R2.generator.CustomChunkGenerator; + +import javax.annotation.Nullable; +import java.lang.reflect.Field; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; + +public class IrisChunkGenerator extends CustomChunkGenerator { + private static final WrappedField BIOME_SOURCE; + private final ChunkGenerator delegate; + private final Engine engine; + private final KMap, KSet> structures = new KMap<>(); + + 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; + var dimension = engine.getDimension(); + + KSet placements = new KSet<>(); + addAll(dimension.getJigsawStructures(), placements); + for (var region : dimension.getAllRegions(engine)) { + addAll(region.getJigsawStructures(), placements); + for (var biome : region.getAllBiomes(engine)) + addAll(biome.getJigsawStructures(), placements); + } + var stronghold = dimension.getStronghold(); + if (stronghold != null) + placements.add(engine.getData().getJigsawStructureLoader().load(stronghold)); + placements.removeIf(Objects::isNull); + + var registry = ((CraftWorld) world).getHandle().registryAccess().registry(Registries.STRUCTURE).orElseThrow(); + for (var s : placements) { + try { + String raw = s.getStructureKey(); + if (raw == null) continue; + boolean tag = raw.startsWith("#"); + if (tag) raw = raw.substring(1); + + var location = new ResourceLocation(raw); + if (!tag) { + structures.computeIfAbsent(ResourceKey.create(Registries.STRUCTURE, location), k -> new KSet<>()).add(s.getLoadKey()); + continue; + } + + var key = TagKey.create(Registries.STRUCTURE, location); + var set = registry.getTag(key).orElse(null); + if (set == null) { + Iris.error("Could not find structure tag: " + raw); + continue; + } + for (var holder : set) { + var resourceKey = holder.unwrapKey().orElse(null); + if (resourceKey == null) continue; + structures.computeIfAbsent(resourceKey, k -> new KSet<>()).add(s.getLoadKey()); + } + } catch (Throwable e) { + Iris.error("Failed to load structure: " + s.getLoadKey()); + e.printStackTrace(); + } + } + } + + private void addAll(KList placements, KSet structures) { + if (placements == null) return; + placements.stream() + .map(IrisJigsawStructurePlacement::getStructure) + .map(engine.getData().getJigsawStructureLoader()::load) + .filter(Objects::nonNull) + .forEach(structures::add); + } + + @Override + public @Nullable Pair> findNearestMapStructure(ServerLevel level, HolderSet holders, BlockPos pos, int radius, boolean findUnexplored) { + if (engine.getDimension().isDisableExplorerMaps()) + return null; + + KMap> structures = new KMap<>(); + for (var holder : holders) { + if (holder == null) continue; + var key = holder.unwrapKey().orElse(null); + var set = this.structures.get(key); + if (set == null) continue; + for (var structure : set) { + structures.put(structure, holder); + } + } + if (structures.isEmpty()) + return null; + + var locator = ResultLocator.locateStructure(structures.keySet()) + .then((e, p , s) -> structures.get(s.getLoadKey())); + if (findUnexplored) + locator = locator.then((e, p, s) -> e.getMantle().getMantle().getChunk(p.getX(), p.getZ()).isFlagged(MantleFlag.DISCOVERED) ? null : s); + + try { + var result = locator.find(engine, new Position2(pos.getX() >> 4, pos.getZ() >> 4), radius * 10L, i -> {}, false).get(); + if (result == null) return null; + var blockPos = new BlockPos(result.getBlockX(), 0, result.getBlockZ()); + return Pair.of(blockPos, result.obj()); + } catch (WrongEngineBroException | ExecutionException | InterruptedException e) { + return null; + } + } + + @Override + protected Codec codec() { + return Codec.unit(null); + } + + @Override + public ChunkGenerator getDelegate() { + if (delegate instanceof CustomChunkGenerator chunkGenerator) + return chunkGenerator.getDelegate(); + return delegate; + } + + @Override + public int getMinY() { + return delegate.getMinY(); + } + + @Override + public int getSeaLevel() { + return delegate.getSeaLevel(); + } + + @Override + public void createStructures(RegistryAccess iregistrycustom, ChunkGeneratorStructureState chunkgeneratorstructurestate, StructureManager structuremanager, ChunkAccess ichunkaccess, StructureTemplateManager structuretemplatemanager) { + delegate.createStructures(iregistrycustom, chunkgeneratorstructurestate, structuremanager, ichunkaccess, structuretemplatemanager); + } + + @Override + public void buildSurface(WorldGenRegion regionlimitedworldaccess, StructureManager structuremanager, RandomState randomstate, ChunkAccess ichunkaccess) { + delegate.buildSurface(regionlimitedworldaccess, structuremanager, randomstate, ichunkaccess); + } + + @Override + public void applyCarvers(WorldGenRegion regionlimitedworldaccess, long seed, RandomState randomstate, BiomeManager biomemanager, StructureManager structuremanager, ChunkAccess ichunkaccess, GenerationStep.Carving worldgenstage_features) { + delegate.applyCarvers(regionlimitedworldaccess, seed, randomstate, biomemanager, structuremanager, ichunkaccess, worldgenstage_features); + } + + @Override + public CompletableFuture fillFromNoise(Executor executor, Blender blender, RandomState randomstate, StructureManager structuremanager, ChunkAccess ichunkaccess) { + 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); + } + + @Override + public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) { + delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager); + } + + @Override + public void addDebugScreenInfo(List list, RandomState randomstate, BlockPos blockposition) { + delegate.addDebugScreenInfo(list, randomstate, blockposition); + } + + @Override + public void spawnOriginalMobs(WorldGenRegion regionlimitedworldaccess) { + delegate.spawnOriginalMobs(regionlimitedworldaccess); + } + + @Override + public int getSpawnHeight(LevelHeightAccessor levelheightaccessor) { + return delegate.getSpawnHeight(levelheightaccessor); + } + + @Override + public int getGenDepth() { + return delegate.getGenDepth(); + } + + @Override + public NoiseColumn getBaseColumn(int i, int j, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { + return delegate.getBaseColumn(i, j, levelheightaccessor, randomstate); + } + + static { + Field biomeSource = null; + for (Field field : ChunkGenerator.class.getDeclaredFields()) { + if (!field.getType().equals(BiomeSource.class)) + continue; + biomeSource = field; + break; + } + if (biomeSource == null) + throw new RuntimeException("Could not find biomeSource field in ChunkGenerator!"); + BIOME_SOURCE = new WrappedField<>(ChunkGenerator.class, biomeSource.getName()); + } + + private static ChunkGenerator edit(ChunkGenerator generator, BiomeSource source) { + try { + BIOME_SOURCE.set(generator, source); + if (generator instanceof CustomChunkGenerator custom) + BIOME_SOURCE.set(custom.getDelegate(), source); + + return generator; + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } +} 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 9d073f4a7..c14400975 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 @@ -10,17 +10,27 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Vector; import java.util.concurrent.atomic.AtomicInteger; +import com.mojang.datafixers.util.Pair; import com.volmit.iris.core.nms.container.BiomeColor; +import com.volmit.iris.util.scheduling.J; +import net.minecraft.nbt.*; +import net.minecraft.nbt.Tag; +import net.minecraft.server.commands.data.BlockDataAccessor; +import net.minecraft.tags.TagKey; import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.EntityBlock; import org.bukkit.*; import org.bukkit.block.Biome; import org.bukkit.block.data.BlockData; import org.bukkit.craftbukkit.v1_20_R2.CraftChunk; import org.bukkit.craftbukkit.v1_20_R2.CraftServer; import org.bukkit.craftbukkit.v1_20_R2.CraftWorld; +import org.bukkit.craftbukkit.v1_20_R2.block.CraftBlockState; +import org.bukkit.craftbukkit.v1_20_R2.block.CraftBlockStates; import org.bukkit.craftbukkit.v1_20_R2.block.data.CraftBlockData; import org.bukkit.craftbukkit.v1_20_R2.entity.CraftDolphin; import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftItemStack; @@ -30,6 +40,7 @@ import org.bukkit.entity.Entity; import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.generator.ChunkGenerator; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import com.mojang.brigadier.exceptions.CommandSyntaxException; @@ -55,8 +66,6 @@ import net.minecraft.core.Holder; import net.minecraft.core.Registry; import net.minecraft.core.RegistryAccess; import net.minecraft.core.registries.Registries; -import net.minecraft.nbt.NbtIo; -import net.minecraft.nbt.TagParser; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; @@ -130,55 +139,106 @@ public class NMSBinding implements INMSBinding { return type.getDeclaredClasses()[ordinal]; } + @Override + public boolean hasTile(Material material) { + return !CraftBlockState.class.equals(CraftBlockStates.getBlockStateType(material)); + } + @Override public boolean hasTile(Location l) { return ((CraftWorld) l.getWorld()).getHandle().getBlockEntity(new BlockPos(l.getBlockX(), l.getBlockY(), l.getBlockZ()), false) != null; } @Override - public CompoundTag serializeTile(Location location) { - BlockEntity e = ((CraftWorld) location.getWorld()).getHandle().getBlockEntity(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()), true); + @SuppressWarnings("unchecked") + public KMap serializeTile(Location location) { + BlockEntity e = ((CraftWorld) location.getWorld()).getHandle().getBlockEntity(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()), false); if (e == null) { return null; } - net.minecraft.nbt.CompoundTag tag = e.saveWithFullMetadata(); - return convert(tag); + net.minecraft.nbt.CompoundTag tag = e.saveWithoutMetadata(); + return (KMap) convertFromTag(tag, 0, 64); } - private CompoundTag convert(net.minecraft.nbt.CompoundTag tag) { - try { - ByteArrayOutputStream boas = new ByteArrayOutputStream(); - DataOutputStream dos = new DataOutputStream(boas); - tag.write(dos); - dos.close(); - return (CompoundTag) NBTUtil.read(new ByteArrayInputStream(boas.toByteArray()), false).getTag(); - } catch (Throwable ex) { - ex.printStackTrace(); + @Contract(value = "null, _, _ -> null", pure = true) + private Object convertFromTag(net.minecraft.nbt.Tag tag, int depth, int maxDepth) { + if (tag == null || depth > maxDepth) return null; + if (tag instanceof CollectionTag collection) { + KList list = new KList<>(); + + for (Object i : collection) { + if (i instanceof net.minecraft.nbt.Tag t) + list.add(convertFromTag(t, depth + 1, maxDepth)); + else list.add(i); + } + return list; } + if (tag instanceof net.minecraft.nbt.CompoundTag compound) { + KMap map = new KMap<>(); - return null; - } - - private net.minecraft.nbt.CompoundTag convert(CompoundTag tag) { - try { - ByteArrayOutputStream boas = new ByteArrayOutputStream(); - NBTUtil.write(tag, boas, false); - DataInputStream din = new DataInputStream(new ByteArrayInputStream(boas.toByteArray())); - net.minecraft.nbt.CompoundTag c = NbtIo.read(din); - din.close(); - return c; - } catch (Throwable e) { - e.printStackTrace(); + for (String key : compound.getAllKeys()) { + var child = compound.get(key); + if (child == null) continue; + var value = convertFromTag(child, depth + 1, maxDepth); + if (value == null) continue; + map.put(key, value); + } + return map; } - - return null; + if (tag instanceof NumericTag numeric) + return numeric.getAsNumber(); + return tag.getAsString(); } @Override - public void deserializeTile(CompoundTag c, Location pos) { - ((CraftWorld) pos.getWorld()).getHandle().getChunkAt(new BlockPos(pos.getBlockX(), 0, pos.getBlockZ())).setBlockEntityNbt(convert(c)); + public void deserializeTile(KMap map, Location pos) { + net.minecraft.nbt.CompoundTag tag = (net.minecraft.nbt.CompoundTag) convertToTag(map, 0, 64); + var level = ((CraftWorld) pos.getWorld()).getHandle(); + var blockPos = new BlockPos(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ()); + J.s(() -> merge(level, blockPos, tag)); + } + + private void merge(ServerLevel level, BlockPos blockPos, net.minecraft.nbt.CompoundTag tag) { + var blockEntity = level.getBlockEntity(blockPos); + if (blockEntity == null) { + Iris.warn("[NMS] BlockEntity not found at " + blockPos); + var state = level.getBlockState(blockPos); + if (!state.hasBlockEntity()) + return; + + blockEntity = ((EntityBlock) state.getBlock()) + .newBlockEntity(blockPos, state); + } + var accessor = new BlockDataAccessor(blockEntity, blockPos); + accessor.setData(tag.merge(accessor.getData())); + } + + private Tag convertToTag(Object object, int depth, int maxDepth) { + if (object == null || depth > maxDepth) return EndTag.INSTANCE; + if (object instanceof Map map) { + var tag = new net.minecraft.nbt.CompoundTag(); + for (var i : map.entrySet()) { + tag.put(i.getKey().toString(), convertToTag(i.getValue(), depth + 1, maxDepth)); + } + return tag; + } + if (object instanceof List list) { + var tag = new net.minecraft.nbt.ListTag(); + for (var i : list) { + tag.add(convertToTag(i, depth + 1, maxDepth)); + } + return tag; + } + if (object instanceof Byte number) return ByteTag.valueOf(number); + if (object instanceof Short number) return ShortTag.valueOf(number); + if (object instanceof Integer number) return IntTag.valueOf(number); + if (object instanceof Long number) return LongTag.valueOf(number); + if (object instanceof Float number) return FloatTag.valueOf(number); + if (object instanceof Double number) return DoubleTag.valueOf(number); + if (object instanceof String string) return StringTag.valueOf(string); + return EndTag.INSTANCE; } @Override @@ -476,23 +536,9 @@ public class NMSBinding implements INMSBinding { } } - public void setTreasurePos(Dolphin dolphin, com.volmit.iris.core.nms.container.BlockPos pos) { - CraftDolphin cd = (CraftDolphin)dolphin; - cd.getHandle().setTreasurePos(new BlockPos(pos.getX(), pos.getY(), pos.getZ())); - cd.getHandle().setGotFish(true); - } - - public void inject(long seed, Engine engine, World world) throws NoSuchFieldException, IllegalAccessException { - ServerLevel serverLevel = ((CraftWorld)world).getHandle(); - Class clazz = serverLevel.getChunkSource().chunkMap.generator.getClass(); - Field biomeSource = getField(clazz, BiomeSource.class); - biomeSource.setAccessible(true); - Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); - unsafeField.setAccessible(true); - Unsafe unsafe = (Unsafe)unsafeField.get(null); - CustomBiomeSource customBiomeSource = new CustomBiomeSource(seed, engine, world); - unsafe.putObject(biomeSource.get(serverLevel.getChunkSource().chunkMap.generator), unsafe.objectFieldOffset(biomeSource), customBiomeSource); - biomeSource.set(serverLevel.getChunkSource().chunkMap.generator, customBiomeSource); + public void inject(long seed, Engine engine, World world) { + var chunkMap = ((CraftWorld)world).getHandle().getChunkSource().chunkMap; + chunkMap.generator = new IrisChunkGenerator(chunkMap.generator, seed, engine, world); } public Vector3d getBoundingbox(org.bukkit.entity.EntityType entity) { @@ -547,6 +593,23 @@ public class NMSBinding implements INMSBinding { return new Color(rgba, true); } + @Override + public KList getStructureKeys() { + KList keys = new KList<>(); + + var registry = registry().registry(Registries.STRUCTURE).orElse(null); + if (registry == null) return keys; + registry.keySet().stream().map(ResourceLocation::toString).forEach(keys::add); + registry.getTags() + .map(Pair::getFirst) + .map(TagKey::location) + .map(ResourceLocation::toString) + .map(s -> "#" + s) + .forEach(keys::add); + + return keys; + } + private static Field getField(Class clazz, Class fieldType) throws NoSuchFieldException { try { for (Field f : clazz.getDeclaredFields()) { 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 new file mode 100644 index 000000000..26826709b --- /dev/null +++ b/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/IrisChunkGenerator.java @@ -0,0 +1,265 @@ +package com.volmit.iris.core.nms.v1_20_R3; + +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.Codec; +import com.volmit.iris.Iris; +import com.volmit.iris.engine.framework.Engine; +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.util.collection.KList; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.collection.KSet; +import com.volmit.iris.util.mantle.MantleFlag; +import com.volmit.iris.util.math.Position2; +import com.volmit.iris.util.reflect.WrappedField; +import com.volmit.iris.util.scheduling.S; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.HolderSet; +import net.minecraft.core.RegistryAccess; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.WorldGenRegion; +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.item.EnderEyeItem; +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.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.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.levelgen.blending.Blender; +import net.minecraft.world.level.levelgen.structure.Structure; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; +import org.bukkit.World; +import org.bukkit.craftbukkit.v1_20_R3.CraftWorld; +import org.bukkit.craftbukkit.v1_20_R3.generator.CustomChunkGenerator; + +import javax.annotation.Nullable; +import java.lang.reflect.Field; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; + +public class IrisChunkGenerator extends CustomChunkGenerator { + private static final WrappedField BIOME_SOURCE; + private final ChunkGenerator delegate; + private final Engine engine; + private final KMap, KSet> structures = new KMap<>(); + + 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; + var dimension = engine.getDimension(); + + KSet placements = new KSet<>(); + addAll(dimension.getJigsawStructures(), placements); + for (var region : dimension.getAllRegions(engine)) { + addAll(region.getJigsawStructures(), placements); + for (var biome : region.getAllBiomes(engine)) + addAll(biome.getJigsawStructures(), placements); + } + var stronghold = dimension.getStronghold(); + if (stronghold != null) + placements.add(engine.getData().getJigsawStructureLoader().load(stronghold)); + placements.removeIf(Objects::isNull); + + var registry = ((CraftWorld) world).getHandle().registryAccess().registry(Registries.STRUCTURE).orElseThrow(); + for (var s : placements) { + try { + String raw = s.getStructureKey(); + if (raw == null) continue; + boolean tag = raw.startsWith("#"); + if (tag) raw = raw.substring(1); + + var location = new ResourceLocation(raw); + if (!tag) { + structures.computeIfAbsent(ResourceKey.create(Registries.STRUCTURE, location), k -> new KSet<>()).add(s.getLoadKey()); + continue; + } + + var key = TagKey.create(Registries.STRUCTURE, location); + var set = registry.getTag(key).orElse(null); + if (set == null) { + Iris.error("Could not find structure tag: " + raw); + continue; + } + for (var holder : set) { + var resourceKey = holder.unwrapKey().orElse(null); + if (resourceKey == null) continue; + structures.computeIfAbsent(resourceKey, k -> new KSet<>()).add(s.getLoadKey()); + } + } catch (Throwable e) { + Iris.error("Failed to load structure: " + s.getLoadKey()); + e.printStackTrace(); + } + } + } + + private void addAll(KList placements, KSet structures) { + if (placements == null) return; + placements.stream() + .map(IrisJigsawStructurePlacement::getStructure) + .map(engine.getData().getJigsawStructureLoader()::load) + .filter(Objects::nonNull) + .forEach(structures::add); + } + + @Override + public @Nullable Pair> findNearestMapStructure(ServerLevel level, HolderSet holders, BlockPos pos, int radius, boolean findUnexplored) { + if (engine.getDimension().isDisableExplorerMaps()) + return null; + + KMap> structures = new KMap<>(); + for (var holder : holders) { + if (holder == null) continue; + var key = holder.unwrapKey().orElse(null); + var set = this.structures.get(key); + if (set == null) continue; + for (var structure : set) { + structures.put(structure, holder); + } + } + if (structures.isEmpty()) + return null; + + var locator = ResultLocator.locateStructure(structures.keySet()) + .then((e, p , s) -> structures.get(s.getLoadKey())); + if (findUnexplored) + locator = locator.then((e, p, s) -> e.getMantle().getMantle().getChunk(p.getX(), p.getZ()).isFlagged(MantleFlag.DISCOVERED) ? null : s); + + try { + var result = locator.find(engine, new Position2(pos.getX() >> 4, pos.getZ() >> 4), radius * 10L, i -> {}, false).get(); + if (result == null) return null; + var blockPos = new BlockPos(result.getBlockX(), 0, result.getBlockZ()); + return Pair.of(blockPos, result.obj()); + } catch (WrongEngineBroException | ExecutionException | InterruptedException e) { + return null; + } + } + + @Override + protected Codec codec() { + return Codec.unit(null); + } + + @Override + public ChunkGenerator getDelegate() { + if (delegate instanceof CustomChunkGenerator chunkGenerator) + return chunkGenerator.getDelegate(); + return delegate; + } + + @Override + public int getMinY() { + return delegate.getMinY(); + } + + @Override + public int getSeaLevel() { + return delegate.getSeaLevel(); + } + + @Override + public void createStructures(RegistryAccess iregistrycustom, ChunkGeneratorStructureState chunkgeneratorstructurestate, StructureManager structuremanager, ChunkAccess ichunkaccess, StructureTemplateManager structuretemplatemanager) { + delegate.createStructures(iregistrycustom, chunkgeneratorstructurestate, structuremanager, ichunkaccess, structuretemplatemanager); + } + + @Override + public void buildSurface(WorldGenRegion regionlimitedworldaccess, StructureManager structuremanager, RandomState randomstate, ChunkAccess ichunkaccess) { + delegate.buildSurface(regionlimitedworldaccess, structuremanager, randomstate, ichunkaccess); + } + + @Override + public void applyCarvers(WorldGenRegion regionlimitedworldaccess, long seed, RandomState randomstate, BiomeManager biomemanager, StructureManager structuremanager, ChunkAccess ichunkaccess, GenerationStep.Carving worldgenstage_features) { + delegate.applyCarvers(regionlimitedworldaccess, seed, randomstate, biomemanager, structuremanager, ichunkaccess, worldgenstage_features); + } + + @Override + public CompletableFuture fillFromNoise(Executor executor, Blender blender, RandomState randomstate, StructureManager structuremanager, ChunkAccess ichunkaccess) { + 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); + } + + @Override + public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) { + delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager); + } + + @Override + public void addDebugScreenInfo(List list, RandomState randomstate, BlockPos blockposition) { + delegate.addDebugScreenInfo(list, randomstate, blockposition); + } + + @Override + public void spawnOriginalMobs(WorldGenRegion regionlimitedworldaccess) { + delegate.spawnOriginalMobs(regionlimitedworldaccess); + } + + @Override + public int getSpawnHeight(LevelHeightAccessor levelheightaccessor) { + return delegate.getSpawnHeight(levelheightaccessor); + } + + @Override + public int getGenDepth() { + return delegate.getGenDepth(); + } + + @Override + public NoiseColumn getBaseColumn(int i, int j, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { + return delegate.getBaseColumn(i, j, levelheightaccessor, randomstate); + } + + static { + Field biomeSource = null; + for (Field field : ChunkGenerator.class.getDeclaredFields()) { + if (!field.getType().equals(BiomeSource.class)) + continue; + biomeSource = field; + break; + } + if (biomeSource == null) + throw new RuntimeException("Could not find biomeSource field in ChunkGenerator!"); + BIOME_SOURCE = new WrappedField<>(ChunkGenerator.class, biomeSource.getName()); + } + + private static ChunkGenerator edit(ChunkGenerator generator, BiomeSource source) { + try { + BIOME_SOURCE.set(generator, source); + if (generator instanceof CustomChunkGenerator custom) + BIOME_SOURCE.set(custom.getDelegate(), source); + + return generator; + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } +} 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 7334bb040..c69d2a66e 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 @@ -10,17 +10,27 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Vector; import java.util.concurrent.atomic.AtomicInteger; +import com.mojang.datafixers.util.Pair; import com.volmit.iris.core.nms.container.BiomeColor; +import com.volmit.iris.util.scheduling.J; +import net.minecraft.nbt.*; +import net.minecraft.nbt.Tag; +import net.minecraft.server.commands.data.BlockDataAccessor; +import net.minecraft.tags.TagKey; import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.EntityBlock; import org.bukkit.*; import org.bukkit.block.Biome; import org.bukkit.block.data.BlockData; import org.bukkit.craftbukkit.v1_20_R3.CraftChunk; import org.bukkit.craftbukkit.v1_20_R3.CraftServer; import org.bukkit.craftbukkit.v1_20_R3.CraftWorld; +import org.bukkit.craftbukkit.v1_20_R3.block.CraftBlockState; +import org.bukkit.craftbukkit.v1_20_R3.block.CraftBlockStates; import org.bukkit.craftbukkit.v1_20_R3.block.data.CraftBlockData; import org.bukkit.craftbukkit.v1_20_R3.entity.CraftDolphin; import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftItemStack; @@ -30,6 +40,7 @@ import org.bukkit.entity.Entity; import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.generator.ChunkGenerator; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import com.mojang.brigadier.exceptions.CommandSyntaxException; @@ -55,8 +66,6 @@ import net.minecraft.core.Holder; import net.minecraft.core.Registry; import net.minecraft.core.RegistryAccess; import net.minecraft.core.registries.Registries; -import net.minecraft.nbt.NbtIo; -import net.minecraft.nbt.TagParser; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; @@ -130,55 +139,106 @@ public class NMSBinding implements INMSBinding { return type.getDeclaredClasses()[ordinal]; } + @Override + public boolean hasTile(Material material) { + return !CraftBlockState.class.equals(CraftBlockStates.getBlockStateType(material)); + } + @Override public boolean hasTile(Location l) { return ((CraftWorld) l.getWorld()).getHandle().getBlockEntity(new BlockPos(l.getBlockX(), l.getBlockY(), l.getBlockZ()), false) != null; } @Override - public CompoundTag serializeTile(Location location) { - BlockEntity e = ((CraftWorld) location.getWorld()).getHandle().getBlockEntity(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()), true); + @SuppressWarnings("unchecked") + public KMap serializeTile(Location location) { + BlockEntity e = ((CraftWorld) location.getWorld()).getHandle().getBlockEntity(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()), false); if (e == null) { return null; } - net.minecraft.nbt.CompoundTag tag = e.saveWithFullMetadata(); - return convert(tag); + net.minecraft.nbt.CompoundTag tag = e.saveWithoutMetadata(); + return (KMap) convertFromTag(tag, 0, 64); } - private CompoundTag convert(net.minecraft.nbt.CompoundTag tag) { - try { - ByteArrayOutputStream boas = new ByteArrayOutputStream(); - DataOutputStream dos = new DataOutputStream(boas); - tag.write(dos); - dos.close(); - return (CompoundTag) NBTUtil.read(new ByteArrayInputStream(boas.toByteArray()), false).getTag(); - } catch (Throwable ex) { - ex.printStackTrace(); + @Contract(value = "null, _, _ -> null", pure = true) + private Object convertFromTag(net.minecraft.nbt.Tag tag, int depth, int maxDepth) { + if (tag == null || depth > maxDepth) return null; + if (tag instanceof CollectionTag collection) { + KList list = new KList<>(); + + for (Object i : collection) { + if (i instanceof net.minecraft.nbt.Tag t) + list.add(convertFromTag(t, depth + 1, maxDepth)); + else list.add(i); + } + return list; } + if (tag instanceof net.minecraft.nbt.CompoundTag compound) { + KMap map = new KMap<>(); - return null; - } - - private net.minecraft.nbt.CompoundTag convert(CompoundTag tag) { - try { - ByteArrayOutputStream boas = new ByteArrayOutputStream(); - NBTUtil.write(tag, boas, false); - DataInputStream din = new DataInputStream(new ByteArrayInputStream(boas.toByteArray())); - net.minecraft.nbt.CompoundTag c = NbtIo.read(din); - din.close(); - return c; - } catch (Throwable e) { - e.printStackTrace(); + for (String key : compound.getAllKeys()) { + var child = compound.get(key); + if (child == null) continue; + var value = convertFromTag(child, depth + 1, maxDepth); + if (value == null) continue; + map.put(key, value); + } + return map; } - - return null; + if (tag instanceof NumericTag numeric) + return numeric.getAsNumber(); + return tag.getAsString(); } @Override - public void deserializeTile(CompoundTag c, Location pos) { - ((CraftWorld) pos.getWorld()).getHandle().getChunkAt(new BlockPos(pos.getBlockX(), 0, pos.getBlockZ())).setBlockEntityNbt(convert(c)); + public void deserializeTile(KMap map, Location pos) { + net.minecraft.nbt.CompoundTag tag = (net.minecraft.nbt.CompoundTag) convertToTag(map, 0, 64); + var level = ((CraftWorld) pos.getWorld()).getHandle(); + var blockPos = new BlockPos(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ()); + J.s(() -> merge(level, blockPos, tag)); + } + + private void merge(ServerLevel level, BlockPos blockPos, net.minecraft.nbt.CompoundTag tag) { + var blockEntity = level.getBlockEntity(blockPos); + if (blockEntity == null) { + Iris.warn("[NMS] BlockEntity not found at " + blockPos); + var state = level.getBlockState(blockPos); + if (!state.hasBlockEntity()) + return; + + blockEntity = ((EntityBlock) state.getBlock()) + .newBlockEntity(blockPos, state); + } + var accessor = new BlockDataAccessor(blockEntity, blockPos); + accessor.setData(tag.merge(accessor.getData())); + } + + private Tag convertToTag(Object object, int depth, int maxDepth) { + if (object == null || depth > maxDepth) return EndTag.INSTANCE; + if (object instanceof Map map) { + var tag = new net.minecraft.nbt.CompoundTag(); + for (var i : map.entrySet()) { + tag.put(i.getKey().toString(), convertToTag(i.getValue(), depth + 1, maxDepth)); + } + return tag; + } + if (object instanceof List list) { + var tag = new net.minecraft.nbt.ListTag(); + for (var i : list) { + tag.add(convertToTag(i, depth + 1, maxDepth)); + } + return tag; + } + if (object instanceof Byte number) return ByteTag.valueOf(number); + if (object instanceof Short number) return ShortTag.valueOf(number); + if (object instanceof Integer number) return IntTag.valueOf(number); + if (object instanceof Long number) return LongTag.valueOf(number); + if (object instanceof Float number) return FloatTag.valueOf(number); + if (object instanceof Double number) return DoubleTag.valueOf(number); + if (object instanceof String string) return StringTag.valueOf(string); + return EndTag.INSTANCE; } @Override @@ -476,23 +536,9 @@ public class NMSBinding implements INMSBinding { } } - public void setTreasurePos(Dolphin dolphin, com.volmit.iris.core.nms.container.BlockPos pos) { - CraftDolphin cd = (CraftDolphin)dolphin; - cd.getHandle().setTreasurePos(new BlockPos(pos.getX(), pos.getY(), pos.getZ())); - cd.getHandle().setGotFish(true); - } - - public void inject(long seed, Engine engine, World world) throws NoSuchFieldException, IllegalAccessException { - ServerLevel serverLevel = ((CraftWorld)world).getHandle(); - Class clazz = serverLevel.getChunkSource().chunkMap.generator.getClass(); - Field biomeSource = getField(clazz, BiomeSource.class); - biomeSource.setAccessible(true); - Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); - unsafeField.setAccessible(true); - Unsafe unsafe = (Unsafe)unsafeField.get(null); - CustomBiomeSource customBiomeSource = new CustomBiomeSource(seed, engine, world); - unsafe.putObject(biomeSource.get(serverLevel.getChunkSource().chunkMap.generator), unsafe.objectFieldOffset(biomeSource), customBiomeSource); - biomeSource.set(serverLevel.getChunkSource().chunkMap.generator, customBiomeSource); + public void inject(long seed, Engine engine, World world) { + var chunkMap = ((CraftWorld)world).getHandle().getChunkSource().chunkMap; + chunkMap.generator = new IrisChunkGenerator(chunkMap.generator, seed, engine, world); } public Vector3d getBoundingbox(org.bukkit.entity.EntityType entity) { @@ -548,6 +594,23 @@ public class NMSBinding implements INMSBinding { return new Color(rgba, true); } + @Override + public KList getStructureKeys() { + KList keys = new KList<>(); + + var registry = registry().registry(Registries.STRUCTURE).orElse(null); + if (registry == null) return keys; + registry.keySet().stream().map(ResourceLocation::toString).forEach(keys::add); + registry.getTags() + .map(Pair::getFirst) + .map(TagKey::location) + .map(ResourceLocation::toString) + .map(s -> "#" + s) + .forEach(keys::add); + + return keys; + } + private static Field getField(Class clazz, Class fieldType) throws NoSuchFieldException { try { for (Field f : clazz.getDeclaredFields()) { 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 new file mode 100644 index 000000000..537acc9d9 --- /dev/null +++ b/nms/v1_20_R4/src/main/java/com/volmit/iris/core/nms/v1_20_R4/IrisChunkGenerator.java @@ -0,0 +1,260 @@ +package com.volmit.iris.core.nms.v1_20_R4; + +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.MapCodec; +import com.volmit.iris.Iris; +import com.volmit.iris.engine.framework.Engine; +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.util.collection.KList; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.collection.KSet; +import com.volmit.iris.util.mantle.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 net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.WorldGenRegion; +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.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.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.levelgen.blending.Blender; +import net.minecraft.world.level.levelgen.structure.Structure; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; +import org.bukkit.World; +import org.bukkit.craftbukkit.v1_20_R4.CraftWorld; +import org.bukkit.craftbukkit.v1_20_R4.generator.CustomChunkGenerator; + +import javax.annotation.Nullable; +import java.lang.reflect.Field; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.*; + +public class IrisChunkGenerator extends CustomChunkGenerator { + private static final WrappedField BIOME_SOURCE; + private final ChunkGenerator delegate; + private final Engine engine; + private final KMap, KSet> structures = new KMap<>(); + + 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; + var dimension = engine.getDimension(); + + KSet placements = new KSet<>(); + addAll(dimension.getJigsawStructures(), placements); + for (var region : dimension.getAllRegions(engine)) { + addAll(region.getJigsawStructures(), placements); + for (var biome : region.getAllBiomes(engine)) + addAll(biome.getJigsawStructures(), placements); + } + var stronghold = dimension.getStronghold(); + if (stronghold != null) + placements.add(engine.getData().getJigsawStructureLoader().load(stronghold)); + placements.removeIf(Objects::isNull); + + var registry = ((CraftWorld) world).getHandle().registryAccess().registry(Registries.STRUCTURE).orElseThrow(); + for (var s : placements) { + try { + String raw = s.getStructureKey(); + if (raw == null) continue; + boolean tag = raw.startsWith("#"); + if (tag) raw = raw.substring(1); + + var location = new ResourceLocation(raw); + if (!tag) { + structures.computeIfAbsent(ResourceKey.create(Registries.STRUCTURE, location), k -> new KSet<>()).add(s.getLoadKey()); + continue; + } + + var key = TagKey.create(Registries.STRUCTURE, location); + var set = registry.getTag(key).orElse(null); + if (set == null) { + Iris.error("Could not find structure tag: " + raw); + continue; + } + for (var holder : set) { + var resourceKey = holder.unwrapKey().orElse(null); + if (resourceKey == null) continue; + structures.computeIfAbsent(resourceKey, k -> new KSet<>()).add(s.getLoadKey()); + } + } catch (Throwable e) { + Iris.error("Failed to load structure: " + s.getLoadKey()); + e.printStackTrace(); + } + } + } + + private void addAll(KList placements, KSet structures) { + if (placements == null) return; + placements.stream() + .map(IrisJigsawStructurePlacement::getStructure) + .map(engine.getData().getJigsawStructureLoader()::load) + .filter(Objects::nonNull) + .forEach(structures::add); + } + + @Override + public @Nullable Pair> findNearestMapStructure(ServerLevel level, HolderSet holders, BlockPos pos, int radius, boolean findUnexplored) { + if (engine.getDimension().isDisableExplorerMaps()) + return null; + + KMap> structures = new KMap<>(); + for (var holder : holders) { + if (holder == null) continue; + var key = holder.unwrapKey().orElse(null); + var set = this.structures.get(key); + if (set == null) continue; + for (var structure : set) { + structures.put(structure, holder); + } + } + if (structures.isEmpty()) + return null; + + var locator = ResultLocator.locateStructure(structures.keySet()) + .then((e, p , s) -> structures.get(s.getLoadKey())); + if (findUnexplored) + locator = locator.then((e, p, s) -> e.getMantle().getMantle().getChunk(p.getX(), p.getZ()).isFlagged(MantleFlag.DISCOVERED) ? null : s); + + try { + var result = locator.find(engine, new Position2(pos.getX() >> 4, pos.getZ() >> 4), radius * 10L, i -> {}, false).get(); + if (result == null) return null; + var blockPos = new BlockPos(result.getBlockX(), 0, result.getBlockZ()); + return Pair.of(blockPos, result.obj()); + } catch (WrongEngineBroException | ExecutionException | InterruptedException e) { + return null; + } + } + + @Override + protected MapCodec codec() { + return MapCodec.unit(null); + } + + @Override + public ChunkGenerator getDelegate() { + if (delegate instanceof CustomChunkGenerator chunkGenerator) + return chunkGenerator.getDelegate(); + return delegate; + } + + @Override + public int getMinY() { + return delegate.getMinY(); + } + + @Override + public int getSeaLevel() { + return delegate.getSeaLevel(); + } + + @Override + public void createStructures(RegistryAccess iregistrycustom, ChunkGeneratorStructureState chunkgeneratorstructurestate, StructureManager structuremanager, ChunkAccess ichunkaccess, StructureTemplateManager structuretemplatemanager) { + delegate.createStructures(iregistrycustom, chunkgeneratorstructurestate, structuremanager, ichunkaccess, structuretemplatemanager); + } + + @Override + public void buildSurface(WorldGenRegion regionlimitedworldaccess, StructureManager structuremanager, RandomState randomstate, ChunkAccess ichunkaccess) { + delegate.buildSurface(regionlimitedworldaccess, structuremanager, randomstate, ichunkaccess); + } + + @Override + public void applyCarvers(WorldGenRegion regionlimitedworldaccess, long seed, RandomState randomstate, BiomeManager biomemanager, StructureManager structuremanager, ChunkAccess ichunkaccess, GenerationStep.Carving worldgenstage_features) { + delegate.applyCarvers(regionlimitedworldaccess, seed, randomstate, biomemanager, structuremanager, ichunkaccess, worldgenstage_features); + } + + @Override + public CompletableFuture fillFromNoise(Executor executor, Blender blender, RandomState randomstate, StructureManager structuremanager, ChunkAccess ichunkaccess) { + 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); + } + + @Override + public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) { + delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager); + } + + @Override + public void addDebugScreenInfo(List list, RandomState randomstate, BlockPos blockposition) { + delegate.addDebugScreenInfo(list, randomstate, blockposition); + } + + @Override + public void spawnOriginalMobs(WorldGenRegion regionlimitedworldaccess) { + delegate.spawnOriginalMobs(regionlimitedworldaccess); + } + + @Override + public int getSpawnHeight(LevelHeightAccessor levelheightaccessor) { + return delegate.getSpawnHeight(levelheightaccessor); + } + + @Override + public int getGenDepth() { + return delegate.getGenDepth(); + } + + @Override + public NoiseColumn getBaseColumn(int i, int j, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { + return delegate.getBaseColumn(i, j, levelheightaccessor, randomstate); + } + + static { + Field biomeSource = null; + for (Field field : ChunkGenerator.class.getDeclaredFields()) { + if (!field.getType().equals(BiomeSource.class)) + continue; + biomeSource = field; + break; + } + if (biomeSource == null) + throw new RuntimeException("Could not find biomeSource field in ChunkGenerator!"); + BIOME_SOURCE = new WrappedField<>(ChunkGenerator.class, biomeSource.getName()); + } + + private static ChunkGenerator edit(ChunkGenerator generator, BiomeSource source) { + try { + BIOME_SOURCE.set(generator, source); + if (generator instanceof CustomChunkGenerator custom) + BIOME_SOURCE.set(custom.getDelegate(), source); + + return generator; + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } +} 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 4b407ef18..9933dbbb0 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,24 +1,38 @@ package com.volmit.iris.core.nms.v1_20_R4; 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.Iterator; -import java.util.List; -import java.util.Optional; -import java.util.Vector; +import java.util.*; import java.util.concurrent.atomic.AtomicInteger; +import com.mojang.datafixers.util.Pair; import com.volmit.iris.core.nms.container.BiomeColor; import com.volmit.iris.core.nms.datapack.DataVersion; +import com.volmit.iris.util.nbt.tag.CompoundTag; +import com.volmit.iris.util.scheduling.J; +import net.minecraft.core.*; +import net.minecraft.core.Registry; import net.minecraft.core.component.DataComponents; +import net.minecraft.nbt.*; +import net.minecraft.nbt.ByteTag; +import net.minecraft.nbt.DoubleTag; +import net.minecraft.nbt.EndTag; +import net.minecraft.nbt.FloatTag; +import net.minecraft.nbt.IntTag; +import net.minecraft.nbt.LongTag; +import net.minecraft.nbt.ShortTag; +import net.minecraft.nbt.StringTag; +import net.minecraft.nbt.Tag; +import net.minecraft.server.commands.data.BlockDataAccessor; +import net.minecraft.server.commands.data.DataCommands; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.tags.TagKey; import net.minecraft.world.item.component.CustomData; import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.EntityBlock; +import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.chunk.status.ChunkStatus; import org.bukkit.*; import org.bukkit.block.Biome; @@ -26,8 +40,12 @@ import org.bukkit.block.data.BlockData; import org.bukkit.craftbukkit.v1_20_R4.CraftChunk; import org.bukkit.craftbukkit.v1_20_R4.CraftServer; import org.bukkit.craftbukkit.v1_20_R4.CraftWorld; +import org.bukkit.craftbukkit.v1_20_R4.block.CraftBlockState; +import org.bukkit.craftbukkit.v1_20_R4.block.CraftBlockStates; +import org.bukkit.craftbukkit.v1_20_R4.block.CraftBlockType; import org.bukkit.craftbukkit.v1_20_R4.block.data.CraftBlockData; import org.bukkit.craftbukkit.v1_20_R4.entity.CraftDolphin; +import org.bukkit.craftbukkit.v1_20_R4.generator.CustomChunkGenerator; import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack; import org.bukkit.craftbukkit.v1_20_R4.util.CraftNamespacedKey; import org.bukkit.entity.Dolphin; @@ -35,6 +53,7 @@ import org.bukkit.entity.Entity; import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.generator.ChunkGenerator; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import com.mojang.brigadier.exceptions.CommandSyntaxException; @@ -49,30 +68,19 @@ import com.volmit.iris.util.json.JSONObject; import com.volmit.iris.util.mantle.Mantle; import com.volmit.iris.util.math.Vector3d; import com.volmit.iris.util.matter.MatterBiomeInject; -import com.volmit.iris.util.nbt.io.NBTUtil; 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 it.unimi.dsi.fastutil.objects.Object2IntMap; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Holder; -import net.minecraft.core.Registry; -import net.minecraft.core.RegistryAccess; import net.minecraft.core.registries.Registries; -import net.minecraft.nbt.NbtIo; -import net.minecraft.nbt.TagParser; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.EntityType; -import net.minecraft.world.level.biome.BiomeSource; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.LevelChunk; -import sun.misc.Unsafe; public class NMSBinding implements INMSBinding { private final KMap baseBiomeCache = new KMap<>(); @@ -134,55 +142,109 @@ public class NMSBinding implements INMSBinding { return type.getDeclaredClasses()[ordinal]; } + @Override + public boolean hasTile(Material material) { + return !CraftBlockState.class.equals(CraftBlockStates.getBlockStateType(material)); + } + @Override public boolean hasTile(Location l) { return ((CraftWorld) l.getWorld()).getHandle().getBlockEntity(new BlockPos(l.getBlockX(), l.getBlockY(), l.getBlockZ()), false) != null; } @Override - public CompoundTag serializeTile(Location location) { - BlockEntity e = ((CraftWorld) location.getWorld()).getHandle().getBlockEntity(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()), true); + @SuppressWarnings("unchecked") + public KMap serializeTile(Location location) { + BlockEntity e = ((CraftWorld) location.getWorld()).getHandle().getBlockEntity(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()), false); if (e == null) { return null; } - net.minecraft.nbt.CompoundTag tag = e.saveWithFullMetadata(registry()); - return convert(tag); + net.minecraft.nbt.CompoundTag tag = e.saveWithoutMetadata(registry()); + return (KMap) convertFromTag(tag, 0, 64); } - private CompoundTag convert(net.minecraft.nbt.CompoundTag tag) { - try { - ByteArrayOutputStream boas = new ByteArrayOutputStream(); - DataOutputStream dos = new DataOutputStream(boas); - tag.write(dos); - dos.close(); - return (CompoundTag) NBTUtil.read(new ByteArrayInputStream(boas.toByteArray()), false).getTag(); - } catch (Throwable ex) { - ex.printStackTrace(); - } + @Contract(value = "null, _, _ -> null", pure = true) + private Object convertFromTag(net.minecraft.nbt.Tag tag, int depth, int maxDepth) { + if (tag == null || depth > maxDepth) return null; + return switch (tag) { + case CollectionTag collection -> { + KList list = new KList<>(); - return null; - } + for (Object i : collection) { + if (i instanceof net.minecraft.nbt.Tag t) + list.add(convertFromTag(t, depth + 1, maxDepth)); + else list.add(i); + } + yield list; + } + case net.minecraft.nbt.CompoundTag compound -> { + KMap map = new KMap<>(); - private net.minecraft.nbt.CompoundTag convert(CompoundTag tag) { - try { - ByteArrayOutputStream boas = new ByteArrayOutputStream(); - NBTUtil.write(tag, boas, false); - DataInputStream din = new DataInputStream(new ByteArrayInputStream(boas.toByteArray())); - net.minecraft.nbt.CompoundTag c = NbtIo.read(din); - din.close(); - return c; - } catch (Throwable e) { - e.printStackTrace(); - } - - return null; + for (String key : compound.getAllKeys()) { + var child = compound.get(key); + if (child == null) continue; + var value = convertFromTag(child, depth + 1, maxDepth); + if (value == null) continue; + map.put(key, value); + } + yield map; + } + case NumericTag numeric -> numeric.getAsNumber(); + default -> tag.getAsString(); + }; } @Override - public void deserializeTile(CompoundTag c, Location pos) { - ((CraftWorld) pos.getWorld()).getHandle().getChunkAt(new BlockPos(pos.getBlockX(), 0, pos.getBlockZ())).setBlockEntityNbt(convert(c)); + public void deserializeTile(KMap map, Location pos) { + net.minecraft.nbt.CompoundTag tag = (net.minecraft.nbt.CompoundTag) convertToTag(map, 0, 64); + var level = ((CraftWorld) pos.getWorld()).getHandle(); + var blockPos = new BlockPos(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ()); + J.s(() -> merge(level, blockPos, tag)); + } + + private void merge(ServerLevel level, BlockPos blockPos, net.minecraft.nbt.CompoundTag tag) { + var blockEntity = level.getBlockEntity(blockPos); + if (blockEntity == null) { + Iris.warn("[NMS] BlockEntity not found at " + blockPos); + var state = level.getBlockState(blockPos); + if (!state.hasBlockEntity()) + return; + + blockEntity = ((EntityBlock) state.getBlock()) + .newBlockEntity(blockPos, state); + } + var accessor = new BlockDataAccessor(blockEntity, blockPos); + accessor.setData(tag.merge(accessor.getData())); + } + + private Tag convertToTag(Object object, int depth, int maxDepth) { + if (object == null || depth > maxDepth) return EndTag.INSTANCE; + return switch (object) { + case Map map -> { + var tag = new net.minecraft.nbt.CompoundTag(); + for (var i : map.entrySet()) { + tag.put(i.getKey().toString(), convertToTag(i.getValue(), depth + 1, maxDepth)); + } + yield tag; + } + case List list -> { + var tag = new net.minecraft.nbt.ListTag(); + for (var i : list) { + tag.add(convertToTag(i, depth + 1, maxDepth)); + } + yield tag; + } + case Byte number -> ByteTag.valueOf(number); + case Short number -> ShortTag.valueOf(number); + case Integer number -> IntTag.valueOf(number); + case Long number -> LongTag.valueOf(number); + case Float number -> FloatTag.valueOf(number); + case Double number -> DoubleTag.valueOf(number); + case String string -> StringTag.valueOf(string); + default -> EndTag.INSTANCE; + }; } @Override @@ -422,13 +484,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); @@ -480,23 +542,9 @@ public class NMSBinding implements INMSBinding { } } - public void setTreasurePos(Dolphin dolphin, com.volmit.iris.core.nms.container.BlockPos pos) { - CraftDolphin cd = (CraftDolphin)dolphin; - cd.getHandle().setTreasurePos(new BlockPos(pos.getX(), pos.getY(), pos.getZ())); - cd.getHandle().setGotFish(true); - } - - public void inject(long seed, Engine engine, World world) throws NoSuchFieldException, IllegalAccessException { - ServerLevel serverLevel = ((CraftWorld)world).getHandle(); - Class clazz = serverLevel.getChunkSource().chunkMap.generator.getClass(); - Field biomeSource = getField(clazz, BiomeSource.class); - biomeSource.setAccessible(true); - Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); - unsafeField.setAccessible(true); - Unsafe unsafe = (Unsafe)unsafeField.get(null); - CustomBiomeSource customBiomeSource = new CustomBiomeSource(seed, engine, world); - unsafe.putObject(biomeSource.get(serverLevel.getChunkSource().chunkMap.generator), unsafe.objectFieldOffset(biomeSource), customBiomeSource); - biomeSource.set(serverLevel.getChunkSource().chunkMap.generator, customBiomeSource); + public void inject(long seed, Engine engine, World world) { + var chunkMap = ((CraftWorld)world).getHandle().getChunkSource().chunkMap; + chunkMap.generator = new IrisChunkGenerator(chunkMap.generator, seed, engine, world); } public Vector3d getBoundingbox(org.bukkit.entity.EntityType entity) { @@ -585,4 +633,21 @@ public class NMSBinding implements INMSBinding { if (radius == null) throw new IllegalStateException("GameRule.SPAWN_CHUNK_RADIUS is null!"); return (int) Math.pow(2 * radius + 1, 2); } + + @Override + public KList getStructureKeys() { + KList keys = new KList<>(); + + var registry = registry().registry(Registries.STRUCTURE).orElse(null); + if (registry == null) return keys; + registry.keySet().stream().map(ResourceLocation::toString).forEach(keys::add); + registry.getTags() + .map(Pair::getFirst) + .map(TagKey::location) + .map(ResourceLocation::toString) + .map(s -> "#" + s) + .forEach(keys::add); + + return keys; + } } 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 new file mode 100644 index 000000000..461446ed2 --- /dev/null +++ b/nms/v1_21_R1/src/main/java/com/volmit/iris/core/nms/v1_21_R1/IrisChunkGenerator.java @@ -0,0 +1,261 @@ +package com.volmit.iris.core.nms.v1_21_R1; + +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.MapCodec; +import com.volmit.iris.Iris; +import com.volmit.iris.engine.framework.Engine; +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.util.collection.KList; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.collection.KSet; +import com.volmit.iris.util.mantle.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 net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.WorldGenRegion; +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.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.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.levelgen.blending.Blender; +import net.minecraft.world.level.levelgen.structure.Structure; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; +import org.bukkit.World; +import org.bukkit.craftbukkit.v1_21_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_21_R1.generator.CustomChunkGenerator; + +import javax.annotation.Nullable; +import java.lang.reflect.Field; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +public class IrisChunkGenerator extends CustomChunkGenerator { + private static final WrappedField BIOME_SOURCE; + private final ChunkGenerator delegate; + private final Engine engine; + private final KMap, KSet> structures = new KMap<>(); + + 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; + var dimension = engine.getDimension(); + + KSet placements = new KSet<>(); + addAll(dimension.getJigsawStructures(), placements); + for (var region : dimension.getAllRegions(engine)) { + addAll(region.getJigsawStructures(), placements); + for (var biome : region.getAllBiomes(engine)) + addAll(biome.getJigsawStructures(), placements); + } + var stronghold = dimension.getStronghold(); + if (stronghold != null) + placements.add(engine.getData().getJigsawStructureLoader().load(stronghold)); + placements.removeIf(Objects::isNull); + + var registry = ((CraftWorld) world).getHandle().registryAccess().registry(Registries.STRUCTURE).orElseThrow(); + for (var s : placements) { + try { + String raw = s.getStructureKey(); + if (raw == null) continue; + boolean tag = raw.startsWith("#"); + if (tag) raw = raw.substring(1); + + var location = ResourceLocation.parse(raw); + if (!tag) { + structures.computeIfAbsent(ResourceKey.create(Registries.STRUCTURE, location), k -> new KSet<>()).add(s.getLoadKey()); + continue; + } + + var key = TagKey.create(Registries.STRUCTURE, location); + var set = registry.getTag(key).orElse(null); + if (set == null) { + Iris.error("Could not find structure tag: " + raw); + continue; + } + for (var holder : set) { + var resourceKey = holder.unwrapKey().orElse(null); + if (resourceKey == null) continue; + structures.computeIfAbsent(resourceKey, k -> new KSet<>()).add(s.getLoadKey()); + } + } catch (Throwable e) { + Iris.error("Failed to load structure: " + s.getLoadKey()); + e.printStackTrace(); + } + } + } + + private void addAll(KList placements, KSet structures) { + if (placements == null) return; + placements.stream() + .map(IrisJigsawStructurePlacement::getStructure) + .map(engine.getData().getJigsawStructureLoader()::load) + .filter(Objects::nonNull) + .forEach(structures::add); + } + + @Override + public @Nullable Pair> findNearestMapStructure(ServerLevel level, HolderSet holders, BlockPos pos, int radius, boolean findUnexplored) { + if (engine.getDimension().isDisableExplorerMaps()) + return null; + + KMap> structures = new KMap<>(); + for (var holder : holders) { + if (holder == null) continue; + var key = holder.unwrapKey().orElse(null); + var set = this.structures.get(key); + if (set == null) continue; + for (var structure : set) { + structures.put(structure, holder); + } + } + if (structures.isEmpty()) + return null; + + var locator = ResultLocator.locateStructure(structures.keySet()) + .then((e, p , s) -> structures.get(s.getLoadKey())); + if (findUnexplored) + locator = locator.then((e, p, s) -> e.getMantle().getMantle().getChunk(p.getX(), p.getZ()).isFlagged(MantleFlag.DISCOVERED) ? null : s); + + try { + var result = locator.find(engine, new Position2(pos.getX() >> 4, pos.getZ() >> 4), radius * 10L, i -> {}, false).get(); + if (result == null) return null; + var blockPos = new BlockPos(result.getBlockX(), 0, result.getBlockZ()); + return Pair.of(blockPos, result.obj()); + } catch (WrongEngineBroException | ExecutionException | InterruptedException e) { + return null; + } + } + + @Override + protected MapCodec codec() { + return MapCodec.unit(null); + } + + @Override + public ChunkGenerator getDelegate() { + if (delegate instanceof CustomChunkGenerator chunkGenerator) + return chunkGenerator.getDelegate(); + return delegate; + } + + @Override + public int getMinY() { + return delegate.getMinY(); + } + + @Override + public int getSeaLevel() { + return delegate.getSeaLevel(); + } + + @Override + public void createStructures(RegistryAccess iregistrycustom, ChunkGeneratorStructureState chunkgeneratorstructurestate, StructureManager structuremanager, ChunkAccess ichunkaccess, StructureTemplateManager structuretemplatemanager) { + delegate.createStructures(iregistrycustom, chunkgeneratorstructurestate, structuremanager, ichunkaccess, structuretemplatemanager); + } + + @Override + public void buildSurface(WorldGenRegion regionlimitedworldaccess, StructureManager structuremanager, RandomState randomstate, ChunkAccess ichunkaccess) { + delegate.buildSurface(regionlimitedworldaccess, structuremanager, randomstate, ichunkaccess); + } + + @Override + public void applyCarvers(WorldGenRegion regionlimitedworldaccess, long seed, RandomState randomstate, BiomeManager biomemanager, StructureManager structuremanager, ChunkAccess ichunkaccess, GenerationStep.Carving worldgenstage_features) { + delegate.applyCarvers(regionlimitedworldaccess, seed, randomstate, biomemanager, structuremanager, ichunkaccess, worldgenstage_features); + } + + @Override + public CompletableFuture fillFromNoise(Blender blender, RandomState randomstate, StructureManager structuremanager, ChunkAccess ichunkaccess) { + 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); + } + + @Override + public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) { + delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager); + } + + @Override + public void addDebugScreenInfo(List list, RandomState randomstate, BlockPos blockposition) { + delegate.addDebugScreenInfo(list, randomstate, blockposition); + } + + @Override + public void spawnOriginalMobs(WorldGenRegion regionlimitedworldaccess) { + delegate.spawnOriginalMobs(regionlimitedworldaccess); + } + + @Override + public int getSpawnHeight(LevelHeightAccessor levelheightaccessor) { + return delegate.getSpawnHeight(levelheightaccessor); + } + + @Override + public int getGenDepth() { + return delegate.getGenDepth(); + } + + @Override + public NoiseColumn getBaseColumn(int i, int j, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { + return delegate.getBaseColumn(i, j, levelheightaccessor, randomstate); + } + + static { + Field biomeSource = null; + for (Field field : ChunkGenerator.class.getDeclaredFields()) { + if (!field.getType().equals(BiomeSource.class)) + continue; + biomeSource = field; + break; + } + if (biomeSource == null) + throw new RuntimeException("Could not find biomeSource field in ChunkGenerator!"); + BIOME_SOURCE = new WrappedField<>(ChunkGenerator.class, biomeSource.getName()); + } + + private static ChunkGenerator edit(ChunkGenerator generator, BiomeSource source) { + try { + BIOME_SOURCE.set(generator, source); + if (generator instanceof CustomChunkGenerator custom) + BIOME_SOURCE.set(custom.getDelegate(), source); + + return generator; + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } +} 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 1455b09e1..7f5662d1d 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 @@ -8,18 +8,22 @@ import java.io.DataOutputStream; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.util.Iterator; -import java.util.List; -import java.util.Optional; -import java.util.Vector; +import java.util.*; import java.util.concurrent.atomic.AtomicInteger; +import com.mojang.datafixers.util.Pair; import com.volmit.iris.core.nms.container.BiomeColor; import com.volmit.iris.core.nms.datapack.DataVersion; +import com.volmit.iris.util.scheduling.J; import net.minecraft.core.component.DataComponents; +import net.minecraft.nbt.*; +import net.minecraft.nbt.Tag; +import net.minecraft.server.commands.data.BlockDataAccessor; import net.minecraft.server.level.ChunkMap; +import net.minecraft.tags.TagKey; import net.minecraft.world.item.component.CustomData; import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.EntityBlock; import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.chunk.status.WorldGenContext; import org.bukkit.*; @@ -28,6 +32,8 @@ import org.bukkit.block.data.BlockData; import org.bukkit.craftbukkit.v1_21_R1.CraftChunk; import org.bukkit.craftbukkit.v1_21_R1.CraftServer; import org.bukkit.craftbukkit.v1_21_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_21_R1.block.CraftBlockState; +import org.bukkit.craftbukkit.v1_21_R1.block.CraftBlockStates; import org.bukkit.craftbukkit.v1_21_R1.block.data.CraftBlockData; import org.bukkit.craftbukkit.v1_21_R1.entity.CraftDolphin; import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftItemStack; @@ -37,6 +43,7 @@ import org.bukkit.entity.Entity; import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.generator.ChunkGenerator; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import com.mojang.brigadier.exceptions.CommandSyntaxException; @@ -62,8 +69,6 @@ import net.minecraft.core.Holder; import net.minecraft.core.Registry; import net.minecraft.core.RegistryAccess; import net.minecraft.core.registries.Registries; -import net.minecraft.nbt.NbtIo; -import net.minecraft.nbt.TagParser; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; @@ -136,55 +141,109 @@ public class NMSBinding implements INMSBinding { return type.getDeclaredClasses()[ordinal]; } + @Override + public boolean hasTile(Material material) { + return !CraftBlockState.class.equals(CraftBlockStates.getBlockStateType(material)); + } + @Override public boolean hasTile(Location l) { return ((CraftWorld) l.getWorld()).getHandle().getBlockEntity(new BlockPos(l.getBlockX(), l.getBlockY(), l.getBlockZ()), false) != null; } @Override - public CompoundTag serializeTile(Location location) { - BlockEntity e = ((CraftWorld) location.getWorld()).getHandle().getBlockEntity(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()), true); + @SuppressWarnings("unchecked") + public KMap serializeTile(Location location) { + BlockEntity e = ((CraftWorld) location.getWorld()).getHandle().getBlockEntity(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()), false); if (e == null) { return null; } - net.minecraft.nbt.CompoundTag tag = e.saveWithFullMetadata(registry()); - return convert(tag); + net.minecraft.nbt.CompoundTag tag = e.saveWithoutMetadata(registry()); + return (KMap) convertFromTag(tag, 0, 64); } - private CompoundTag convert(net.minecraft.nbt.CompoundTag tag) { - try { - ByteArrayOutputStream boas = new ByteArrayOutputStream(); - DataOutputStream dos = new DataOutputStream(boas); - tag.write(dos); - dos.close(); - return (CompoundTag) NBTUtil.read(new ByteArrayInputStream(boas.toByteArray()), false).getTag(); - } catch (Throwable ex) { - ex.printStackTrace(); - } + @Contract(value = "null, _, _ -> null", pure = true) + private Object convertFromTag(net.minecraft.nbt.Tag tag, int depth, int maxDepth) { + if (tag == null || depth > maxDepth) return null; + return switch (tag) { + case CollectionTag collection -> { + KList list = new KList<>(); - return null; - } + for (Object i : collection) { + if (i instanceof net.minecraft.nbt.Tag t) + list.add(convertFromTag(t, depth + 1, maxDepth)); + else list.add(i); + } + yield list; + } + case net.minecraft.nbt.CompoundTag compound -> { + KMap map = new KMap<>(); - private net.minecraft.nbt.CompoundTag convert(CompoundTag tag) { - try { - ByteArrayOutputStream boas = new ByteArrayOutputStream(); - NBTUtil.write(tag, boas, false); - DataInputStream din = new DataInputStream(new ByteArrayInputStream(boas.toByteArray())); - net.minecraft.nbt.CompoundTag c = NbtIo.read(din); - din.close(); - return c; - } catch (Throwable e) { - e.printStackTrace(); - } - - return null; + for (String key : compound.getAllKeys()) { + var child = compound.get(key); + if (child == null) continue; + var value = convertFromTag(child, depth + 1, maxDepth); + if (value == null) continue; + map.put(key, value); + } + yield map; + } + case NumericTag numeric -> numeric.getAsNumber(); + default -> tag.getAsString(); + }; } @Override - public void deserializeTile(CompoundTag c, Location pos) { - ((CraftWorld) pos.getWorld()).getHandle().getChunkAt(new BlockPos(pos.getBlockX(), 0, pos.getBlockZ())).setBlockEntityNbt(convert(c)); + public void deserializeTile(KMap map, Location pos) { + net.minecraft.nbt.CompoundTag tag = (net.minecraft.nbt.CompoundTag) convertToTag(map, 0, 64); + var level = ((CraftWorld) pos.getWorld()).getHandle(); + var blockPos = new BlockPos(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ()); + J.s(() -> merge(level, blockPos, tag)); + } + + private void merge(ServerLevel level, BlockPos blockPos, net.minecraft.nbt.CompoundTag tag) { + var blockEntity = level.getBlockEntity(blockPos); + if (blockEntity == null) { + Iris.warn("[NMS] BlockEntity not found at " + blockPos); + var state = level.getBlockState(blockPos); + if (!state.hasBlockEntity()) + return; + + blockEntity = ((EntityBlock) state.getBlock()) + .newBlockEntity(blockPos, state); + } + var accessor = new BlockDataAccessor(blockEntity, blockPos); + accessor.setData(tag.merge(accessor.getData())); + } + + private Tag convertToTag(Object object, int depth, int maxDepth) { + if (object == null || depth > maxDepth) return EndTag.INSTANCE; + return switch (object) { + case Map map -> { + var tag = new net.minecraft.nbt.CompoundTag(); + for (var i : map.entrySet()) { + tag.put(i.getKey().toString(), convertToTag(i.getValue(), depth + 1, maxDepth)); + } + yield tag; + } + case List list -> { + var tag = new net.minecraft.nbt.ListTag(); + for (var i : list) { + tag.add(convertToTag(i, depth + 1, maxDepth)); + } + yield tag; + } + case Byte number -> ByteTag.valueOf(number); + case Short number -> ShortTag.valueOf(number); + case Integer number -> IntTag.valueOf(number); + case Long number -> LongTag.valueOf(number); + case Float number -> FloatTag.valueOf(number); + case Double number -> DoubleTag.valueOf(number); + case String string -> StringTag.valueOf(string); + default -> EndTag.INSTANCE; + }; } @Override @@ -482,26 +541,17 @@ public class NMSBinding implements INMSBinding { } } - public void setTreasurePos(Dolphin dolphin, com.volmit.iris.core.nms.container.BlockPos pos) { - CraftDolphin cd = (CraftDolphin)dolphin; - cd.getHandle().setTreasurePos(new BlockPos(pos.getX(), pos.getY(), pos.getZ())); - cd.getHandle().setGotFish(true); - } - public void inject(long seed, Engine engine, World world) throws NoSuchFieldException, IllegalAccessException { var chunkMap = ((CraftWorld)world).getHandle().getChunkSource().chunkMap; var worldGenContextField = getField(chunkMap.getClass(), WorldGenContext.class); worldGenContextField.setAccessible(true); var worldGenContext = (WorldGenContext) worldGenContextField.get(chunkMap); - Class clazz = worldGenContext.generator().getClass(); - Field biomeSource = getField(clazz, BiomeSource.class); - biomeSource.setAccessible(true); - Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); - unsafeField.setAccessible(true); - Unsafe unsafe = (Unsafe)unsafeField.get(null); - CustomBiomeSource customBiomeSource = new CustomBiomeSource(seed, engine, world); - unsafe.putObject(biomeSource.get(worldGenContext.generator()), unsafe.objectFieldOffset(biomeSource), customBiomeSource); - biomeSource.set(worldGenContext.generator(), customBiomeSource); + + var newContext = new WorldGenContext( + worldGenContext.level(), new IrisChunkGenerator(worldGenContext.generator(), seed, engine, world), + worldGenContext.structureManager(), worldGenContext.lightEngine(), worldGenContext.mainThreadMailBox()); + + worldGenContextField.set(chunkMap, newContext); } public Vector3d getBoundingbox(org.bukkit.entity.EntityType entity) { @@ -590,4 +640,21 @@ public class NMSBinding implements INMSBinding { if (radius == null) throw new IllegalStateException("GameRule.SPAWN_CHUNK_RADIUS is null!"); return (int) Math.pow(2 * radius + 1, 2); } + + @Override + public KList getStructureKeys() { + KList keys = new KList<>(); + + var registry = registry().registry(Registries.STRUCTURE).orElse(null); + if (registry == null) return keys; + registry.keySet().stream().map(ResourceLocation::toString).forEach(keys::add); + registry.getTags() + .map(Pair::getFirst) + .map(TagKey::location) + .map(ResourceLocation::toString) + .map(s -> "#" + s) + .forEach(keys::add); + + return keys; + } } diff --git a/nms/v1_21_R2/src/main/java/com/volmit/iris/core/nms/v1_21_R2/CustomBiomeSource.java b/nms/v1_21_R2/src/main/java/com/volmit/iris/core/nms/v1_21_R2/CustomBiomeSource.java new file mode 100644 index 000000000..ee1d212b9 --- /dev/null +++ b/nms/v1_21_R2/src/main/java/com/volmit/iris/core/nms/v1_21_R2/CustomBiomeSource.java @@ -0,0 +1,168 @@ +package com.volmit.iris.core.nms.v1_21_R2; + +import com.mojang.serialization.MapCodec; +import com.volmit.iris.Iris; +import com.volmit.iris.engine.data.cache.AtomicCache; +import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.engine.object.IrisBiome; +import com.volmit.iris.engine.object.IrisBiomeCustom; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.math.RNG; +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.core.RegistryAccess; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.BiomeSource; +import net.minecraft.world.level.biome.Climate; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.craftbukkit.v1_21_R2.CraftServer; +import org.bukkit.craftbukkit.v1_21_R2.CraftWorld; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +public class CustomBiomeSource extends BiomeSource { + + private final long seed; + private final Engine engine; + private final Registry biomeCustomRegistry; + private final Registry biomeRegistry; + private final AtomicCache registryAccess = new AtomicCache<>(); + private final RNG rng; + private final KMap> customBiomes; + + public CustomBiomeSource(long seed, Engine engine, World world) { + this.engine = engine; + this.seed = seed; + this.biomeCustomRegistry = registry().lookup(Registries.BIOME).orElse(null); + this.biomeRegistry = ((RegistryAccess) getFor(RegistryAccess.Frozen.class, ((CraftServer) Bukkit.getServer()).getHandle().getServer())).lookup(Registries.BIOME).orElse(null); + this.rng = new RNG(engine.getSeedManager().getBiome()); + this.customBiomes = fillCustomBiomes(biomeCustomRegistry, engine); + } + + private static List> getAllBiomes(Registry customRegistry, Registry registry, Engine engine) { + List> b = new ArrayList<>(); + + for (IrisBiome i : engine.getAllBiomes()) { + if (i.isCustom()) { + for (IrisBiomeCustom j : i.getCustomDerivitives()) { + b.add(customRegistry.get(customRegistry.getResourceKey(customRegistry + .getValue(ResourceLocation.fromNamespaceAndPath(engine.getDimension().getLoadKey(), j.getId()))).get()).get()); + } + } else { + b.add(NMSBinding.biomeToBiomeBase(registry, i.getVanillaDerivative())); + } + } + + return b; + } + + private static Object getFor(Class type, Object source) { + Object o = fieldFor(type, source); + + if (o != null) { + return o; + } + + return invokeFor(type, source); + } + + private static Object fieldFor(Class returns, Object in) { + return fieldForClass(returns, in.getClass(), in); + } + + private static Object invokeFor(Class returns, Object in) { + for (Method i : in.getClass().getMethods()) { + if (i.getReturnType().equals(returns)) { + i.setAccessible(true); + try { + Iris.debug("[NMS] Found " + returns.getSimpleName() + " in " + in.getClass().getSimpleName() + "." + i.getName() + "()"); + return i.invoke(in); + } catch (Throwable e) { + e.printStackTrace(); + } + } + } + + return null; + } + + @SuppressWarnings("unchecked") + private static T fieldForClass(Class returnType, Class sourceType, Object in) { + for (Field i : sourceType.getDeclaredFields()) { + if (i.getType().equals(returnType)) { + i.setAccessible(true); + try { + Iris.debug("[NMS] Found " + returnType.getSimpleName() + " in " + sourceType.getSimpleName() + "." + i.getName()); + return (T) i.get(in); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + } + return null; + } + + @Override + protected Stream> collectPossibleBiomes() { + return getAllBiomes( + ((RegistryAccess) getFor(RegistryAccess.Frozen.class, ((CraftServer) Bukkit.getServer()).getHandle().getServer())) + .lookup(Registries.BIOME).orElse(null), + ((CraftWorld) engine.getWorld().realWorld()).getHandle().registryAccess().lookup(Registries.BIOME).orElse(null), + engine).stream(); + } + private KMap> fillCustomBiomes(Registry customRegistry, Engine engine) { + KMap> m = new KMap<>(); + + for (IrisBiome i : engine.getAllBiomes()) { + if (i.isCustom()) { + for (IrisBiomeCustom j : i.getCustomDerivitives()) { + ResourceLocation resourceLocation = ResourceLocation.fromNamespaceAndPath(engine.getDimension().getLoadKey(), j.getId()); + Biome biome = customRegistry.getValue(resourceLocation); + Optional> optionalBiomeKey = customRegistry.getResourceKey(biome); + if (optionalBiomeKey.isEmpty()) { + Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName()); + continue; + } + ResourceKey biomeKey = optionalBiomeKey.get(); + Optional> optionalReferenceHolder = customRegistry.get(biomeKey); + if (optionalReferenceHolder.isEmpty()) { + Iris.error("Cannot find reference to biome " + biomeKey + " for engine " + engine.getName()); + continue; + } + m.put(j.getId(), optionalReferenceHolder.get()); + } + } + } + + return m; + } + + private RegistryAccess registry() { + return registryAccess.aquire(() -> (RegistryAccess) getFor(RegistryAccess.Frozen.class, ((CraftServer) Bukkit.getServer()).getHandle().getServer())); + } + + @Override + protected MapCodec codec() { + throw new UnsupportedOperationException("Not supported"); + } + + @Override + public Holder getNoiseBiome(int x, int y, int z, Climate.Sampler sampler) { + int m = (y - engine.getMinHeight()) << 2; + IrisBiome ib = engine.getComplex().getTrueBiomeStream().get(x << 2, z << 2); + if (ib.isCustom()) { + return customBiomes.get(ib.getCustomBiome(rng, x << 2, m, z << 2).getId()); + } else { + org.bukkit.block.Biome v = ib.getSkyBiome(rng, x << 2, m, z << 2); + return NMSBinding.biomeToBiomeBase(biomeRegistry, v); + } + } +} \ No newline at end of file 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 new file mode 100644 index 000000000..9dc702b05 --- /dev/null +++ b/nms/v1_21_R2/src/main/java/com/volmit/iris/core/nms/v1_21_R2/IrisChunkGenerator.java @@ -0,0 +1,260 @@ +package com.volmit.iris.core.nms.v1_21_R2; + +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.MapCodec; +import com.volmit.iris.Iris; +import com.volmit.iris.engine.framework.Engine; +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.util.collection.KList; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.collection.KSet; +import com.volmit.iris.util.mantle.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 net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.WorldGenRegion; +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.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.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.levelgen.blending.Blender; +import net.minecraft.world.level.levelgen.structure.Structure; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; +import org.bukkit.World; +import org.bukkit.craftbukkit.v1_21_R2.CraftWorld; +import org.bukkit.craftbukkit.v1_21_R2.generator.CustomChunkGenerator; + +import javax.annotation.Nullable; +import java.lang.reflect.Field; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +public class IrisChunkGenerator extends CustomChunkGenerator { + private static final WrappedField BIOME_SOURCE; + private final ChunkGenerator delegate; + private final Engine engine; + private final KMap, KSet> structures = new KMap<>(); + + 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; + var dimension = engine.getDimension(); + + KSet placements = new KSet<>(); + addAll(dimension.getJigsawStructures(), placements); + for (var region : dimension.getAllRegions(engine)) { + addAll(region.getJigsawStructures(), placements); + for (var biome : region.getAllBiomes(engine)) + addAll(biome.getJigsawStructures(), placements); + } + var stronghold = dimension.getStronghold(); + if (stronghold != null) + placements.add(engine.getData().getJigsawStructureLoader().load(stronghold)); + placements.removeIf(Objects::isNull); + + var registry = ((CraftWorld) world).getHandle().registryAccess().lookup(Registries.STRUCTURE).orElseThrow(); + for (var s : placements) { + try { + String raw = s.getStructureKey(); + if (raw == null) continue; + boolean tag = raw.startsWith("#"); + if (tag) raw = raw.substring(1); + + var location = ResourceLocation.parse(raw); + if (!tag) { + structures.computeIfAbsent(ResourceKey.create(Registries.STRUCTURE, location), k -> new KSet<>()).add(s.getLoadKey()); + continue; + } + + var key = TagKey.create(Registries.STRUCTURE, location); + var set = registry.get(key).orElse(null); + if (set == null) { + Iris.error("Could not find structure tag: " + raw); + continue; + } + for (var holder : set) { + var resourceKey = holder.unwrapKey().orElse(null); + if (resourceKey == null) continue; + structures.computeIfAbsent(resourceKey, k -> new KSet<>()).add(s.getLoadKey()); + } + } catch (Throwable e) { + Iris.error("Failed to load structure: " + s.getLoadKey()); + e.printStackTrace(); + } + } + } + + private void addAll(KList placements, KSet structures) { + if (placements == null) return; + placements.stream() + .map(IrisJigsawStructurePlacement::getStructure) + .map(engine.getData().getJigsawStructureLoader()::load) + .filter(Objects::nonNull) + .forEach(structures::add); + } + + @Override + public @Nullable Pair> findNearestMapStructure(ServerLevel level, HolderSet holders, BlockPos pos, int radius, boolean findUnexplored) { + if (engine.getDimension().isDisableExplorerMaps()) + return null; + + KMap> structures = new KMap<>(); + for (var holder : holders) { + if (holder == null) continue; + var key = holder.unwrapKey().orElse(null); + var set = this.structures.get(key); + if (set == null) continue; + for (var structure : set) { + structures.put(structure, holder); + } + } + if (structures.isEmpty()) + return null; + + var locator = ResultLocator.locateStructure(structures.keySet()) + .then((e, p , s) -> structures.get(s.getLoadKey())); + if (findUnexplored) + locator = locator.then((e, p, s) -> e.getMantle().getMantle().getChunk(p.getX(), p.getZ()).isFlagged(MantleFlag.DISCOVERED) ? null : s); + + try { + var result = locator.find(engine, new Position2(pos.getX() >> 4, pos.getZ() >> 4), radius * 10L, i -> {}, false).get(); + if (result == null) return null; + var blockPos = new BlockPos(result.getBlockX(), 0, result.getBlockZ()); + return Pair.of(blockPos, result.obj()); + } catch (WrongEngineBroException | ExecutionException | InterruptedException e) { + return null; + } + } + + @Override + protected MapCodec codec() { + return MapCodec.unit(null); + } + + @Override + public ChunkGenerator getDelegate() { + if (delegate instanceof CustomChunkGenerator chunkGenerator) + return chunkGenerator.getDelegate(); + return delegate; + } + + @Override + public int getMinY() { + return delegate.getMinY(); + } + + @Override + public int getSeaLevel() { + return delegate.getSeaLevel(); + } + + @Override + public void createStructures(RegistryAccess iregistrycustom, ChunkGeneratorStructureState chunkgeneratorstructurestate, StructureManager structuremanager, ChunkAccess ichunkaccess, StructureTemplateManager structuretemplatemanager) { + delegate.createStructures(iregistrycustom, chunkgeneratorstructurestate, structuremanager, ichunkaccess, structuretemplatemanager); + } + + @Override + public void buildSurface(WorldGenRegion regionlimitedworldaccess, StructureManager structuremanager, RandomState randomstate, ChunkAccess ichunkaccess) { + delegate.buildSurface(regionlimitedworldaccess, structuremanager, randomstate, ichunkaccess); + } + + @Override + public void applyCarvers(WorldGenRegion regionlimitedworldaccess, long seed, RandomState randomstate, BiomeManager biomemanager, StructureManager structuremanager, ChunkAccess ichunkaccess) { + delegate.applyCarvers(regionlimitedworldaccess, seed, randomstate, biomemanager, structuremanager, ichunkaccess); + } + + @Override + public CompletableFuture fillFromNoise(Blender blender, RandomState randomstate, StructureManager structuremanager, ChunkAccess ichunkaccess) { + 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); + } + + @Override + public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) { + delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager); + } + + @Override + public void addDebugScreenInfo(List list, RandomState randomstate, BlockPos blockposition) { + delegate.addDebugScreenInfo(list, randomstate, blockposition); + } + + @Override + public void spawnOriginalMobs(WorldGenRegion regionlimitedworldaccess) { + delegate.spawnOriginalMobs(regionlimitedworldaccess); + } + + @Override + public int getSpawnHeight(LevelHeightAccessor levelheightaccessor) { + return delegate.getSpawnHeight(levelheightaccessor); + } + + @Override + public int getGenDepth() { + return delegate.getGenDepth(); + } + + @Override + public NoiseColumn getBaseColumn(int i, int j, LevelHeightAccessor levelheightaccessor, RandomState randomstate) { + return delegate.getBaseColumn(i, j, levelheightaccessor, randomstate); + } + + static { + Field biomeSource = null; + for (Field field : ChunkGenerator.class.getDeclaredFields()) { + if (!field.getType().equals(BiomeSource.class)) + continue; + biomeSource = field; + break; + } + if (biomeSource == null) + throw new RuntimeException("Could not find biomeSource field in ChunkGenerator!"); + BIOME_SOURCE = new WrappedField<>(ChunkGenerator.class, biomeSource.getName()); + } + + private static ChunkGenerator edit(ChunkGenerator generator, BiomeSource source) { + try { + BIOME_SOURCE.set(generator, source); + if (generator instanceof CustomChunkGenerator custom) + BIOME_SOURCE.set(custom.getDelegate(), source); + + return generator; + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } +} 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 new file mode 100644 index 000000000..d5b2e82b6 --- /dev/null +++ b/nms/v1_21_R2/src/main/java/com/volmit/iris/core/nms/v1_21_R2/NMSBinding.java @@ -0,0 +1,647 @@ +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 com.volmit.iris.core.nms.container.BiomeColor; +import com.volmit.iris.core.nms.datapack.DataVersion; +import com.volmit.iris.util.scheduling.J; +import net.minecraft.core.*; +import net.minecraft.core.Registry; +import net.minecraft.core.component.DataComponents; +import net.minecraft.nbt.*; +import net.minecraft.nbt.Tag; +import net.minecraft.server.commands.data.BlockDataAccessor; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.component.CustomData; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.EntityBlock; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.chunk.status.WorldGenContext; +import org.bukkit.*; +import org.bukkit.block.Biome; +import org.bukkit.block.data.BlockData; +import org.bukkit.craftbukkit.v1_21_R2.CraftChunk; +import org.bukkit.craftbukkit.v1_21_R2.CraftServer; +import org.bukkit.craftbukkit.v1_21_R2.CraftWorld; +import org.bukkit.craftbukkit.v1_21_R2.block.CraftBlockState; +import org.bukkit.craftbukkit.v1_21_R2.block.CraftBlockStates; +import org.bukkit.craftbukkit.v1_21_R2.block.data.CraftBlockData; +import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftItemStack; +import org.bukkit.craftbukkit.v1_21_R2.util.CraftNamespacedKey; +import org.bukkit.entity.Entity; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.generator.ChunkGenerator; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.volmit.iris.Iris; +import com.volmit.iris.core.nms.INMSBinding; +import com.volmit.iris.engine.data.cache.AtomicCache; +import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.hunk.Hunk; +import com.volmit.iris.util.json.JSONObject; +import com.volmit.iris.util.mantle.Mantle; +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.util.nbt.tag.CompoundTag; + +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.LevelChunk; + +public class NMSBinding implements INMSBinding { + private final KMap baseBiomeCache = new KMap<>(); + private final BlockData AIR = Material.AIR.createBlockData(); + private final AtomicCache> biomeMapCache = new AtomicCache<>(); + private final AtomicCache> registryCache = new AtomicCache<>(); + private final AtomicCache> globalCache = new AtomicCache<>(); + private final AtomicCache registryAccess = new AtomicCache<>(); + private final AtomicCache byIdRef = new AtomicCache<>(); + private Field biomeStorageCache = null; + + private static Object getFor(Class type, Object source) { + Object o = fieldFor(type, source); + + if (o != null) { + return o; + } + + return invokeFor(type, source); + } + + private static Object invokeFor(Class returns, Object in) { + for (Method i : in.getClass().getMethods()) { + if (i.getReturnType().equals(returns)) { + i.setAccessible(true); + try { + Iris.debug("[NMS] Found " + returns.getSimpleName() + " in " + in.getClass().getSimpleName() + "." + i.getName() + "()"); + return i.invoke(in); + } catch (Throwable e) { + e.printStackTrace(); + } + } + } + + return null; + } + + private static Object fieldFor(Class returns, Object in) { + return fieldForClass(returns, in.getClass(), in); + } + + @SuppressWarnings("unchecked") + private static T fieldForClass(Class returnType, Class sourceType, Object in) { + for (Field i : sourceType.getDeclaredFields()) { + if (i.getType().equals(returnType)) { + i.setAccessible(true); + try { + Iris.debug("[NMS] Found " + returnType.getSimpleName() + " in " + sourceType.getSimpleName() + "." + i.getName()); + return (T) i.get(in); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + } + return null; + } + + private static Class getClassType(Class type, int ordinal) { + return type.getDeclaredClasses()[ordinal]; + } + + @Override + public boolean hasTile(Material material) { + return !CraftBlockState.class.equals(CraftBlockStates.getBlockStateType(material)); + } + + @Override + public boolean hasTile(Location l) { + return ((CraftWorld) l.getWorld()).getHandle().getBlockEntity(new BlockPos(l.getBlockX(), l.getBlockY(), l.getBlockZ()), false) != null; + } + + @Override + @SuppressWarnings("unchecked") + public KMap serializeTile(Location location) { + BlockEntity e = ((CraftWorld) location.getWorld()).getHandle().getBlockEntity(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()), false); + + if (e == null) { + return null; + } + + net.minecraft.nbt.CompoundTag tag = e.saveWithoutMetadata(registry()); + return (KMap) convertFromTag(tag, 0, 64); + } + + @Contract(value = "null, _, _ -> null", pure = true) + private Object convertFromTag(net.minecraft.nbt.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) + list.add(convertFromTag(t, depth + 1, maxDepth)); + else list.add(i); + } + yield list; + } + case net.minecraft.nbt.CompoundTag compound -> { + KMap map = new KMap<>(); + + for (String key : compound.getAllKeys()) { + var child = compound.get(key); + if (child == null) continue; + var value = convertFromTag(child, depth + 1, maxDepth); + if (value == null) continue; + map.put(key, value); + } + yield map; + } + case NumericTag numeric -> numeric.getAsNumber(); + default -> tag.getAsString(); + }; + } + + @Override + public void deserializeTile(KMap map, Location pos) { + net.minecraft.nbt.CompoundTag tag = (net.minecraft.nbt.CompoundTag) convertToTag(map, 0, 64); + var level = ((CraftWorld) pos.getWorld()).getHandle(); + var blockPos = new BlockPos(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ()); + J.s(() -> merge(level, blockPos, tag)); + } + + private void merge(ServerLevel level, BlockPos blockPos, net.minecraft.nbt.CompoundTag tag) { + var blockEntity = level.getBlockEntity(blockPos); + if (blockEntity == null) { + Iris.warn("[NMS] BlockEntity not found at " + blockPos); + var state = level.getBlockState(blockPos); + if (!state.hasBlockEntity()) + return; + + blockEntity = ((EntityBlock) state.getBlock()) + .newBlockEntity(blockPos, state); + } + var accessor = new BlockDataAccessor(blockEntity, blockPos); + accessor.setData(tag.merge(accessor.getData())); + } + + private Tag convertToTag(Object object, int depth, int maxDepth) { + if (object == null || depth > maxDepth) return EndTag.INSTANCE; + return switch (object) { + case Map map -> { + var tag = new net.minecraft.nbt.CompoundTag(); + for (var i : map.entrySet()) { + tag.put(i.getKey().toString(), convertToTag(i.getValue(), depth + 1, maxDepth)); + } + yield tag; + } + case List list -> { + var tag = new net.minecraft.nbt.ListTag(); + for (var i : list) { + tag.add(convertToTag(i, depth + 1, maxDepth)); + } + yield tag; + } + case Byte number -> ByteTag.valueOf(number); + case Short number -> ShortTag.valueOf(number); + case Integer number -> IntTag.valueOf(number); + case Long number -> LongTag.valueOf(number); + case Float number -> FloatTag.valueOf(number); + case Double number -> DoubleTag.valueOf(number); + case String string -> StringTag.valueOf(string); + default -> EndTag.INSTANCE; + }; + } + + @Override + public CompoundTag serializeEntity(Entity location) { + return null;// TODO: + } + + @Override + public Entity deserializeEntity(CompoundTag s, Location newPosition) { + return null;// TODO: + } + + @Override + public boolean supportsCustomHeight() { + return true; + } + + private RegistryAccess registry() { + return registryAccess.aquire(() -> (RegistryAccess) getFor(RegistryAccess.Frozen.class, ((CraftServer) Bukkit.getServer()).getHandle().getServer())); + } + + private Registry getCustomBiomeRegistry() { + return registry().lookup(Registries.BIOME).orElse(null); + } + + private Registry getBlockRegistry() { + return registry().lookup(Registries.BLOCK).orElse(null); + } + + @Override + public Object getBiomeBaseFromId(int id) { + return getCustomBiomeRegistry().get(id); + } + + @Override + public int getMinHeight(World world) { + return world.getMinHeight(); + } + + @Override + public boolean supportsCustomBiomes() { + return true; + } + + @Override + public int getTrueBiomeBaseId(Object biomeBase) { + return getCustomBiomeRegistry().getId(((Holder) biomeBase).value()); + } + + @Override + public Object getTrueBiomeBase(Location location) { + return ((CraftWorld) location.getWorld()).getHandle().getBiome(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ())); + } + + @Override + public String getTrueBiomeBaseKey(Location location) { + return getKeyForBiomeBase(getTrueBiomeBase(location)); + } + + @Override + public Object getCustomBiomeBaseFor(String mckey) { + return getCustomBiomeRegistry().getValue(ResourceLocation.parse(mckey)); + } + + @Override + public Object getCustomBiomeBaseHolderFor(String mckey) { + return getCustomBiomeRegistry().get(getTrueBiomeBaseId(getCustomBiomeRegistry().get(ResourceLocation.parse(mckey)))).orElse(null); + } + + public int getBiomeBaseIdForKey(String key) { + return getCustomBiomeRegistry().getId(getCustomBiomeRegistry().get(ResourceLocation.parse(key)).map(Holder::value).orElse(null)); + } + + @Override + public String getKeyForBiomeBase(Object biomeBase) { + return getCustomBiomeRegistry().getKey((net.minecraft.world.level.biome.Biome) biomeBase).getPath(); // something, not something:something + } + + @Override + public Object getBiomeBase(World world, Biome biome) { + return biomeToBiomeBase(((CraftWorld) world).getHandle() + .registryAccess().lookup(Registries.BIOME).orElse(null), biome); + } + + @Override + public Object getBiomeBase(Object registry, Biome biome) { + Object v = baseBiomeCache.get(biome); + + if (v != null) { + return v; + } + //noinspection unchecked + v = biomeToBiomeBase((Registry) registry, biome); + if (v == null) { + // Ok so there is this new biome name called "CUSTOM" in Paper's new releases. + // But, this does NOT exist within CraftBukkit which makes it return an error. + // So, we will just return the ID that the plains biome returns instead. + //noinspection unchecked + return biomeToBiomeBase((Registry) registry, Biome.PLAINS); + } + baseBiomeCache.put(biome, v); + return v; + } + + @Override + public KList getBiomes() { + return new KList<>(Biome.values()).qadd(Biome.CHERRY_GROVE).qdel(Biome.CUSTOM); + } + + @Override + public boolean isBukkit() { + return true; + } + + @Override + public int getBiomeId(Biome biome) { + for (World i : Bukkit.getWorlds()) { + if (i.getEnvironment().equals(World.Environment.NORMAL)) { + Registry registry = ((CraftWorld) i).getHandle().registryAccess().lookup(Registries.BIOME).orElse(null); + return registry.getId((net.minecraft.world.level.biome.Biome) getBiomeBase(registry, biome)); + } + } + + return biome.ordinal(); + } + + private MCAIdMap getBiomeMapping() { + return biomeMapCache.aquire(() -> new MCAIdMap<>() { + @NotNull + @Override + public Iterator iterator() { + return getCustomBiomeRegistry().iterator(); + } + + @Override + public int getId(net.minecraft.world.level.biome.Biome paramT) { + return getCustomBiomeRegistry().getId(paramT); + } + + @Override + public net.minecraft.world.level.biome.Biome byId(int paramInt) { + return (net.minecraft.world.level.biome.Biome) getBiomeBaseFromId(paramInt); + } + }); + } + + @NotNull + private MCABiomeContainer getBiomeContainerInterface(MCAIdMap biomeMapping, MCAChunkBiomeContainer base) { + return new MCABiomeContainer() { + @Override + public int[] getData() { + return base.writeBiomes(); + } + + @Override + public void setBiome(int x, int y, int z, int id) { + base.setBiome(x, y, z, biomeMapping.byId(id)); + } + + @Override + public int getBiome(int x, int y, int z) { + return biomeMapping.getId(base.getBiome(x, y, z)); + } + }; + } + + @Override + public MCABiomeContainer newBiomeContainer(int min, int max) { + MCAChunkBiomeContainer base = new MCAChunkBiomeContainer<>(getBiomeMapping(), min, max); + return getBiomeContainerInterface(getBiomeMapping(), base); + } + + @Override + public MCABiomeContainer newBiomeContainer(int min, int max, int[] data) { + MCAChunkBiomeContainer base = new MCAChunkBiomeContainer<>(getBiomeMapping(), min, max, data); + return getBiomeContainerInterface(getBiomeMapping(), base); + } + + @Override + public int countCustomBiomes() { + AtomicInteger a = new AtomicInteger(0); + + getCustomBiomeRegistry().keySet().forEach((i) -> { + if (i.getNamespace().equals("minecraft")) { + return; + } + + a.incrementAndGet(); + Iris.debug("Custom Biome: " + i); + }); + + return a.get(); + } + + public boolean supportsDataPacks() { + return true; + } + + public void setBiomes(int cx, int cz, World world, Hunk biomes) { + LevelChunk c = ((CraftWorld) world).getHandle().getChunk(cx, cz); + biomes.iterateSync((x, y, z, b) -> c.setBiome(x, y, z, (Holder) b)); + c.markUnsaved(); + } + + @Override + public void forceBiomeInto(int x, int y, int z, Object somethingVeryDirty, ChunkGenerator.BiomeGrid chunk) { + try { + ChunkAccess s = (ChunkAccess) getFieldForBiomeStorage(chunk).get(chunk); + Holder biome = (Holder) somethingVeryDirty; + s.setBiome(x, y, z, biome); + } catch (IllegalAccessException e) { + Iris.reportError(e); + e.printStackTrace(); + } + } + + private Field getFieldForBiomeStorage(Object storage) { + Field f = biomeStorageCache; + + if (f != null) { + return f; + } + try { + f = storage.getClass().getDeclaredField("biome"); + f.setAccessible(true); + return f; + } catch (Throwable e) { + Iris.reportError(e); + e.printStackTrace(); + Iris.error(storage.getClass().getCanonicalName()); + } + + biomeStorageCache = f; + return null; + } + + @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"); + cf.setAccessible(true); + df.setAccessible(true); + bf.setAccessible(true); + net.minecraft.core.IdMapper blockData = Block.BLOCK_STATE_REGISTRY; + int b = bf.getInt(blockData); + Object2IntMap c = (Object2IntMap) cf.get(blockData); + List d = (List) df.get(blockData); + return new MCAIdMapper(c, d, b); + }); + MCAPalette global = globalCache.aquireNasty(() -> new MCAGlobalPalette<>(registry, ((CraftBlockData) AIR).getState())); + MCAPalettedContainer container = new MCAPalettedContainer<>(global, registry, + i -> ((CraftBlockData) NBTWorld.getBlockData(i)).getState(), + i -> NBTWorld.getCompound(CraftBlockData.fromData(i)), + ((CraftBlockData) AIR).getState()); + return new MCAWrappedPalettedContainer<>(container, + i -> NBTWorld.getCompound(CraftBlockData.fromData(i)), + i -> ((CraftBlockData) NBTWorld.getBlockData(i)).getState()); + } + + @Override + public void injectBiomesFromMantle(Chunk e, Mantle mantle) { + ChunkAccess chunk = ((CraftChunk) e).getHandle(ChunkStatus.FULL); + AtomicInteger c = new AtomicInteger(); + AtomicInteger r = new AtomicInteger(); + mantle.iterateChunk(e.getX(), e.getZ(), MatterBiomeInject.class, (x, y, z, b) -> { + if (b != null) { + if (b.isCustom()) { + chunk.setBiome(x, y, z, getCustomBiomeRegistry().get(b.getBiomeId()).get()); + c.getAndIncrement(); + } else { + chunk.setBiome(x, y, z, (Holder) getBiomeBase(e.getWorld(), b.getBiome())); + r.getAndIncrement(); + } + } + }); + } + + public ItemStack applyCustomNbt(ItemStack itemStack, KMap customNbt) throws IllegalArgumentException { + if (customNbt != null && !customNbt.isEmpty()) { + net.minecraft.world.item.ItemStack s = CraftItemStack.asNMSCopy(itemStack); + + try { + net.minecraft.nbt.CompoundTag tag = TagParser.parseTag((new JSONObject(customNbt)).toString()); + tag.merge(s.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).getUnsafe()); + s.set(DataComponents.CUSTOM_DATA, CustomData.of(tag)); + } catch (CommandSyntaxException var5) { + throw new IllegalArgumentException(var5); + } + + return CraftItemStack.asBukkitCopy(s); + } else { + return itemStack; + } + } + + public void inject(long seed, Engine engine, World world) throws NoSuchFieldException, IllegalAccessException { + var chunkMap = ((CraftWorld)world).getHandle().getChunkSource().chunkMap; + var worldGenContextField = getField(chunkMap.getClass(), WorldGenContext.class); + worldGenContextField.setAccessible(true); + var worldGenContext = (WorldGenContext) worldGenContextField.get(chunkMap); + + var newContext = new WorldGenContext( + worldGenContext.level(), new IrisChunkGenerator(worldGenContext.generator(), seed, engine, world), + worldGenContext.structureManager(), worldGenContext.lightEngine(), worldGenContext.mainThreadExecutor(), worldGenContext.unsavedListener()); + + worldGenContextField.set(chunkMap, newContext); + } + + public Vector3d getBoundingbox(org.bukkit.entity.EntityType entity) { + Field[] fields = EntityType.class.getDeclaredFields(); + for (Field field : fields) { + if (Modifier.isStatic(field.getModifiers()) && field.getType().equals(EntityType.class)) { + try { + EntityType entityType = (EntityType) field.get(null); + if (entityType.getDescriptionId().equals("entity.minecraft." + entity.name().toLowerCase())) { + Vector v1 = new Vector<>(); + v1.add(entityType.getHeight()); + entityType.getDimensions(); + Vector3d box = new Vector3d( entityType.getWidth(), entityType.getHeight(), entityType.getWidth()); + //System.out.println("Entity Type: " + entityType.getDescriptionId() + ", " + "Height: " + height + ", Width: " + width); + return box; + } + } catch (IllegalAccessException e) { + Iris.error("Unable to get entity dimensions!"); + e.printStackTrace(); + } + } + } + return null; + } + + + @Override + public Entity spawnEntity(Location location, org.bukkit.entity.EntityType type, CreatureSpawnEvent.SpawnReason reason) { + return ((CraftWorld) location.getWorld()).spawn(location, type.getEntityClass(), null, reason); + } + + @Override + public java.awt.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(); + if (biome == null) throw new IllegalArgumentException("Invalid biome: " + holder.unwrapKey().orElse(null)); + + int rgba = switch (type) { + case FOG -> biome.getFogColor(); + case WATER -> biome.getWaterColor(); + case WATER_FOG -> biome.getWaterFogColor(); + case SKY -> biome.getSkyColor(); + case FOLIAGE -> biome.getFoliageColor(); + case GRASS -> biome.getGrassColor(location.getBlockX(), location.getBlockZ()); + }; + if (rgba == 0) { + if (BiomeColor.FOLIAGE == type && biome.getSpecialEffects().getFoliageColorOverride().isEmpty()) + return null; + if (BiomeColor.GRASS == type && biome.getSpecialEffects().getGrassColorOverride().isEmpty()) + return null; + } + return new Color(rgba, true); + } + + private static Field getField(Class clazz, Class fieldType) throws NoSuchFieldException { + try { + for (Field f : clazz.getDeclaredFields()) { + if (f.getType().equals(fieldType)) + return f; + } + throw new NoSuchFieldException(fieldType.getName()); + } catch (NoSuchFieldException var4) { + Class superClass = clazz.getSuperclass(); + if (superClass == null) { + throw var4; + } else { + return getField(superClass, fieldType); + } + } + } + + public static Holder biomeToBiomeBase(Registry registry, Biome biome) { + return registry.getOrThrow(ResourceKey.create(Registries.BIOME, CraftNamespacedKey.toMinecraft(biome.getKey()))); + } + + @Override + public DataVersion getDataVersion() { + return DataVersion.V1213; + } + + @Override + public int getSpawnChunkCount(World world) { + var radius = Optional.ofNullable(world.getGameRuleValue(GameRule.SPAWN_CHUNK_RADIUS)) + .orElseGet(() -> world.getGameRuleDefault(GameRule.SPAWN_CHUNK_RADIUS)); + if (radius == null) throw new IllegalStateException("GameRule.SPAWN_CHUNK_RADIUS is null!"); + return (int) Math.pow(2 * radius + 1, 2); + } + + @Override + public KList getStructureKeys() { + KList keys = new KList<>(); + + var registry = registry().lookup(Registries.STRUCTURE).orElse(null); + if (registry == null) return keys; + registry.keySet().stream().map(ResourceLocation::toString).forEach(keys::add); + registry.getTags() + .map(HolderSet.Named::key) + .map(TagKey::location) + .map(ResourceLocation::toString) + .map(s -> "#" + s) + .forEach(keys::add); + + return keys; + } +} diff --git a/settings.gradle b/settings.gradle index ce6112b89..03baf2fc4 100644 --- a/settings.gradle +++ b/settings.gradle @@ -30,6 +30,7 @@ rootProject.name = 'Iris' include(':core') include( + ':nms:v1_21_R2', ':nms:v1_21_R1', ':nms:v1_20_R4', ':nms:v1_20_R3',