diff --git a/build.gradle b/build.gradle index a797903d2..d1d8b7191 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,5 @@ +import java.util.function.Consumer + /* * Iris is a World Generator for Minecraft Bukkit Servers * Copyright (c) 2021 Arcane Arts (Volmit Software) @@ -212,6 +214,8 @@ shadowJar { dependsOn(":nms:${it.key}:build") from("${project(":nms:${it.key}").layout.buildDirectory.asFile.get()}/libs/${it.key}.jar") } + NMS_BINDINGS.each {dependsOn(":nms:${it.key}:build")} + //dependsOn(':com.volmit.gui:build') //minimize() append("plugin.yml") @@ -280,6 +284,10 @@ 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 'net.bytebuddy:byte-buddy:1.14.14' + compileOnly 'net.bytebuddy:byte-buddy-agent:1.12.8' + compileOnly 'org.bytedeco:javacpp:1.5.10' + compileOnly 'org.bytedeco:cuda-platform:12.3-8.9-1.5.10' } /** diff --git a/core/src/main/java/com/volmit/iris/Iris.java b/core/src/main/java/com/volmit/iris/Iris.java index c39154b13..240e656e7 100644 --- a/core/src/main/java/com/volmit/iris/Iris.java +++ b/core/src/main/java/com/volmit/iris/Iris.java @@ -64,12 +64,10 @@ import com.volmit.iris.util.scheduling.Queue; import com.volmit.iris.util.scheduling.ShurikenQueue; import io.papermc.lib.PaperLib; import lombok.Getter; +import net.bytebuddy.agent.ByteBuddyAgent; 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.bukkit.*; import org.bukkit.block.data.BlockData; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; @@ -94,12 +92,14 @@ import java.lang.management.OperatingSystemMXBean; import java.net.URL; import java.util.Date; import java.util.Map; +import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; 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; +import static org.bukkit.Bukkit.getServer; @SuppressWarnings("CanBeFinal") public class Iris extends VolmitPlugin implements Listener { @@ -456,36 +456,45 @@ public class Iris extends VolmitPlugin implements Listener { } private void enable() { instance = this; + InitializeSafeguard(); + ByteBuddyAgent.install(); services = new KMap<>(); setupAudience(); initialize("com.volmit.iris.core.service").forEach((i) -> services.put((Class) i.getClass(), (IrisService) i)); INMS.get(); IO.delete(new File("iris")); - IrisSafeguard.IrisSafeguardSystem(); + IrisSafeguard.instance.IrisSafeguardSystem(); getSender().setTag(getTag()); + INMS.get().injectBukkit(); + if (IrisSafeguard.instance.unstablemode && !IrisSafeguard.instance.acceptUnstable) IrisSafeguard.instance.earlySplash(); compat = IrisCompat.configured(getDataFile("compat.json")); linkMultiverseCore = new MultiverseCoreLink(); linkMythicMobs = new MythicMobsLink(); configWatcher = new FileWatcher(getDataFile("settings.json")); services.values().forEach(IrisService::onEnable); services.values().forEach(this::registerListener); - J.s(() -> { - J.a(() -> PaperLib.suggestPaper(this)); - J.a(() -> IO.delete(getTemp())); - J.a(LazyPregenerator::loadLazyGenerators, 100); - J.a(this::bstats); - J.ar(this::checkConfigHotload, 60); - J.sr(this::tickQueue, 0); - J.s(this::setupPapi); - J.a(ServerConfigurator::configure, 20); - splash(); - UtilsSFG.splash(); - - autoStartStudio(); - checkForBukkitWorlds(); - IrisToolbelt.retainMantleDataForSlice(String.class.getCanonicalName()); - IrisToolbelt.retainMantleDataForSlice(BlockData.class.getCanonicalName()); - }); + installMainDimension(); + if (!IrisSafeguard.instance.acceptUnstable && IrisSafeguard.instance.unstablemode) { + Iris.info(C.RED + "World loading has been disabled until the incompatibility is resolved."); + Iris.info(C.DARK_RED + "Alternatively, go to plugins/iris/settings.json and set ignoreBootMode to true."); + } else { + J.s(() -> { + J.a(() -> PaperLib.suggestPaper(this)); + J.a(() -> IO.delete(getTemp())); + J.a(LazyPregenerator::loadLazyGenerators, 100); + J.a(this::bstats); + J.ar(this::checkConfigHotload, 60); + J.sr(this::tickQueue, 0); + J.s(this::setupPapi); + J.a(ServerConfigurator::configure, 20); + splash(); + UtilsSFG.splash(); + autoStartStudio(); + checkForBukkitWorlds(); + IrisToolbelt.retainMantleDataForSlice(String.class.getCanonicalName()); + IrisToolbelt.retainMantleDataForSlice(BlockData.class.getCanonicalName()); + }); + } } private void checkForBukkitWorlds() { @@ -599,10 +608,10 @@ public class Iris extends VolmitPlugin implements Listener { @Override public String getTag(String subTag) { - if (unstablemode) { + if (IrisSafeguard.instance.unstablemode) { return C.BOLD + "" + C.DARK_GRAY + "[" + C.BOLD + "" + C.RED + "Iris" + C.BOLD + C.DARK_GRAY + "]" + C.RESET + "" + C.GRAY + ": "; } - if (warningmode) { + if (IrisSafeguard.instance.warningmode) { return C.BOLD + "" + C.DARK_GRAY + "[" + C.BOLD + "" + C.GOLD + "Iris" + C.BOLD + C.DARK_GRAY + "]" + C.RESET + "" + C.GRAY + ": "; } return C.BOLD + "" + C.DARK_GRAY + "[" + C.BOLD + "" + C.IRIS + "Iris" + C.BOLD + C.DARK_GRAY + "]" + C.RESET + "" + C.GRAY + ": "; @@ -611,7 +620,7 @@ public class Iris extends VolmitPlugin implements Listener { private boolean setupChecks() { boolean passed = true; - Iris.info("Version Information: " + instance.getServer().getVersion() + " | " + instance.getServer().getBukkitVersion()); + Iris.info("Server type & version: " + instance.getServer().getVersion() + " | " + instance.getServer().getBukkitVersion()); if (INMS.get() instanceof NMSBinding1X) { passed = false; Iris.warn("============================================"); @@ -677,6 +686,48 @@ public class Iris extends VolmitPlugin implements Listener { s.sendMessage(C.IRIS + "[" + C.DARK_GRAY + "Iris" + C.IRIS + "]" + C.GRAY + ": " + msg); } + private void installMainDimension() { + try { + Properties props = new Properties(); + props.load(new FileInputStream("server.properties")); + String world = props.getProperty("level-name"); + if (world == null) return; + + FileConfiguration fc = new YamlConfiguration(); + fc.load(new File("bukkit.yml")); + String id = fc.getString("worlds." + world + ".generator"); + if (id.startsWith("Iris:")) { + id = id.split("\\Q:\\E")[1]; + } else if (id.equalsIgnoreCase("Iris")) { + id = IrisSettings.get().getGenerator().getDefaultWorldType(); + } else { + return; + } + + IrisDimension dim; + if (id == null || id.isEmpty()) { + dim = IrisData.loadAnyDimension(IrisSettings.get().getGenerator().getDefaultWorldType()); + } else { + dim = IrisData.loadAnyDimension(id); + } + + File w = new File(Bukkit.getWorldContainer(), world); + File packFolder = new File(w, "/iris/pack"); + if (!packFolder.exists() || packFolder.listFiles().length == 0) { + packFolder.mkdirs(); + service(StudioSVC.class).installIntoWorld(getSender(), dim.getLoadKey(), w); + } + if (packFolder.exists()) { + IrisDimension worldDim = IrisData.get(packFolder).getDimensionLoader().load(id); + if (worldDim != null) dim = worldDim; + } + + INMS.get().registerDimension("overworld", dim); + } catch (Throwable e) { + + } + } + @Nullable @Override public BiomeProvider getDefaultBiomeProvider(@NotNull String worldName, @Nullable String id) { @@ -719,6 +770,11 @@ public class Iris extends VolmitPlugin implements Listener { Iris.info("Resolved missing dimension, proceeding with generation."); } } + File packFolder = new File(Bukkit.getWorldContainer(), worldName + "/iris/pack"); + if (packFolder.exists()) { + IrisDimension worldDim = IrisData.get(packFolder).getDimensionLoader().load(id); + if (worldDim != null) dim = worldDim; + } Iris.debug("Assuming IrisDimension: " + dim.getName()); @@ -738,6 +794,9 @@ public class Iris extends VolmitPlugin implements Listener { ff.mkdirs(); service(StudioSVC.class).installIntoWorld(getSender(), dim.getLoadKey(), w.worldFolder()); } + if (!INMS.get().registerDimension(worldName, dim)) { + throw new IllegalStateException("Unable to register dimension " + dim.getName()); + } return new BukkitChunkGenerator(w, false, ff, dim.getLoadKey(), false); } @@ -750,10 +809,10 @@ public class Iris extends VolmitPlugin implements Listener { String padd = Form.repeat(" ", 8); String padd2 = Form.repeat(" ", 4); String[] info = {"", "", "", "", "", padd2 + C.IRIS + " Iris", padd2 + C.GRAY + " by " + "Volmit Software", padd2 + C.GRAY + " v" + C.IRIS + getDescription().getVersion()}; - if (unstablemode) { + if (IrisSafeguard.instance.unstablemode) { info = new String[]{"", "", "", "", "", padd2 + C.RED + " Iris", padd2 + C.GRAY + " by " + C.DARK_RED + "Volmit Software", padd2 + C.GRAY + " v" + C.RED + getDescription().getVersion()}; } - if (warningmode) { + if (IrisSafeguard.instance.warningmode) { info = new String[]{"", "", "", "", "", padd2 + C.GOLD + " Iris", padd2 + C.GRAY + " by " + C.GOLD + "Volmit Software", padd2 + C.GRAY + " v" + C.GOLD + getDescription().getVersion()}; } @@ -798,18 +857,15 @@ public class Iris extends VolmitPlugin implements Listener { padd + C.GRAY + " " + C.DARK_GRAY + "@@@" + C.GRAY + "@@@@@@@@@@@@@@" }; String[] splash; - File freeSpace = new File(Bukkit.getWorldContainer() + "."); - if (unstablemode) { + if (IrisSafeguard.instance.unstablemode) { splash = splashunstable; - } else if (warningmode) { + } else if (IrisSafeguard.instance.warningmode) { splash = splashwarning; } else { splash = splashstable; } - if (!passedserversoftware) { - Iris.info("Server type & version: " + C.RED + Bukkit.getVersion()); - } else { Iris.info("Server type & version: " + Bukkit.getVersion()); } + setupChecks(); Iris.info("Java: " + getJava()); if (!instance.getServer().getVersion().contains("Purpur")) { if (instance.getServer().getVersion().contains("Spigot") && instance.getServer().getVersion().contains("Bukkit")) { @@ -824,7 +880,6 @@ public class Iris extends VolmitPlugin implements Listener { } Iris.info("Bukkit distro: " + Bukkit.getName()); Iris.info("Custom Biomes: " + INMS.get().countCustomBiomes()); - setupChecks(); printPacks(); for (int i = 0; i < info.length; i++) { 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 335207de5..de38220fb 100644 --- a/core/src/main/java/com/volmit/iris/core/IrisSettings.java +++ b/core/src/main/java/com/volmit/iris/core/IrisSettings.java @@ -34,6 +34,7 @@ import java.io.IOException; @Data public class IrisSettings { public static IrisSettings settings; + private IrisSafeGuard safeguard = new IrisSafeGuard(); private IrisSettingsGeneral general = new IrisSettingsGeneral(); private IrisSettingsWorld world = new IrisSettingsWorld(); private IrisSettingsGUI gui = new IrisSettingsGUI(); @@ -42,6 +43,7 @@ public class IrisSettings { private IrisSettingsConcurrency concurrency = new IrisSettingsConcurrency(); private IrisSettingsStudio studio = new IrisSettingsStudio(); private IrisSettingsPerformance performance = new IrisSettingsPerformance(); + private IrisWorldDump worldDump = new IrisWorldDump(); public static int getThreadCount(int c) { return switch (c) { @@ -102,6 +104,12 @@ public class IrisSettings { } } + @Data + public static class IrisSafeGuard { + public boolean ignoreBootMode = false; + public boolean userUnstableWarning = true; + } + @Data public static class IrisSettingsAutoconfiguration { public boolean configureSpigotTimeoutTime = true; @@ -146,7 +154,6 @@ public class IrisSettings { @Data public static class IrisSettingsGeneral { - public boolean ignoreBootMode = false; public boolean commandSounds = true; public boolean debug = false; public boolean disableNMS = false; @@ -187,4 +194,8 @@ public class IrisSettings { public boolean disableTimeAndWeather = true; public boolean autoStartDefaultStudio = false; } + @Data + public static class IrisWorldDump { + public int mcaCacheSize = 3; + } } diff --git a/core/src/main/java/com/volmit/iris/core/ServerConfigurator.java b/core/src/main/java/com/volmit/iris/core/ServerConfigurator.java index 1a760d9d7..dc75b46df 100644 --- a/core/src/main/java/com/volmit/iris/core/ServerConfigurator.java +++ b/core/src/main/java/com/volmit/iris/core/ServerConfigurator.java @@ -19,28 +19,13 @@ package com.volmit.iris.core; import com.volmit.iris.Iris; -import com.volmit.iris.core.loader.IrisData; -import com.volmit.iris.core.nms.INMS; -import com.volmit.iris.core.nms.datapack.DataVersion; -import com.volmit.iris.core.nms.datapack.IDataFixer; -import com.volmit.iris.engine.object.IrisBiome; -import com.volmit.iris.engine.object.IrisBiomeCustom; -import com.volmit.iris.engine.object.IrisDimension; -import com.volmit.iris.engine.object.IrisRange; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.collection.KSet; -import com.volmit.iris.util.format.C; -import com.volmit.iris.util.plugin.VolmitSender; import com.volmit.iris.util.scheduling.J; -import org.bukkit.Bukkit; import org.bukkit.configuration.InvalidConfigurationException; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Player; import java.io.File; import java.io.IOException; -import java.util.List; import java.util.concurrent.TimeUnit; public class ServerConfigurator { @@ -53,12 +38,10 @@ public class ServerConfigurator { if (s.isConfigurePaperWatchdogDelay()) { J.attempt(ServerConfigurator::increasePaperWatchdog); } - - installDataPacks(true); } private static void increaseKeepAliveSpigot() throws IOException, InvalidConfigurationException { - File spigotConfig = new File("config/spigot.yml"); + File spigotConfig = new File("spigot.yml"); FileConfiguration f = new YamlConfiguration(); f.load(spigotConfig); long tt = f.getLong("settings.timeout-time"); @@ -70,6 +53,7 @@ public class ServerConfigurator { f.save(spigotConfig); } } + private static void increasePaperWatchdog() throws IOException, InvalidConfigurationException { File spigotConfig = new File("config/paper-global.yml"); FileConfiguration f = new YamlConfiguration(); @@ -83,187 +67,4 @@ public class ServerConfigurator { f.save(spigotConfig); } } - - private static List getDatapacksFolder() { - if (!IrisSettings.get().getGeneral().forceMainWorld.isEmpty()) { - return new KList().qadd(new File(Bukkit.getWorldContainer(), IrisSettings.get().getGeneral().forceMainWorld + "/datapacks")); - } - KList worlds = new KList<>(); - Bukkit.getServer().getWorlds().forEach(w -> worlds.add(new File(w.getWorldFolder(), "datapacks"))); - return worlds; - } - - public static void installDataPacks(boolean fullInstall) { - installDataPacks(DataVersion.getDefault(), fullInstall); - } - - public static void installDataPacks(IDataFixer fixer, boolean fullInstall) { - Iris.info("Checking Data Packs..."); - File packs = new File("plugins/Iris/packs"); - double ultimateMaxHeight = 0; - double ultimateMinHeight = 0; - if (packs.exists() && packs.isDirectory()) { - for (File pack : packs.listFiles()) { - IrisData data = IrisData.get(pack); - if (pack.isDirectory()) { - File dimensionsFolder = new File(pack, "dimensions"); - if (dimensionsFolder.exists() && dimensionsFolder.isDirectory()) { - for (File file : dimensionsFolder.listFiles()) { - if (file.isFile() && file.getName().endsWith(".json")) { - IrisDimension dim = data.getDimensionLoader().load(file.getName().split("\\Q.\\E")[0]); - if (ultimateMaxHeight < dim.getDimensionHeight().getMax()) { - ultimateMaxHeight = dim.getDimensionHeight().getMax(); - } - if (ultimateMinHeight > dim.getDimensionHeight().getMin()) { - ultimateMinHeight = dim.getDimensionHeight().getMin(); - } - } - } - } - } - } - } - - if (packs.exists()) { - for (File i : packs.listFiles()) { - if (i.isDirectory()) { - Iris.verbose("Checking Pack: " + i.getPath()); - IrisData data = IrisData.get(i); - File dims = new File(i, "dimensions"); - - if (dims.exists()) { - for (File j : dims.listFiles()) { - if (j.getName().endsWith(".json")) { - IrisDimension dim = data.getDimensionLoader().load(j.getName().split("\\Q.\\E")[0]); - - if (dim == null) { - continue; - } - - Iris.verbose(" Checking Dimension " + dim.getLoadFile().getPath()); - for (File dpack : getDatapacksFolder()) { - dim.installDataPack(fixer, () -> data, dpack, ultimateMaxHeight, ultimateMinHeight); - } - } - } - } - } - } - } - - Iris.info("Data Packs Setup!"); - - if (fullInstall) - verifyDataPacksPost(IrisSettings.get().getAutoConfiguration().isAutoRestartOnCustomBiomeInstall()); - } - - private static void verifyDataPacksPost(boolean allowRestarting) { - File packs = new File("plugins/Iris/packs"); - - boolean bad = false; - if (packs.exists()) { - for (File i : packs.listFiles()) { - if (i.isDirectory()) { - Iris.verbose("Checking Pack: " + i.getPath()); - IrisData data = IrisData.get(i); - File dims = new File(i, "dimensions"); - - if (dims.exists()) { - for (File j : dims.listFiles()) { - if (j.getName().endsWith(".json")) { - IrisDimension dim = data.getDimensionLoader().load(j.getName().split("\\Q.\\E")[0]); - - if (dim == null) { - Iris.error("Failed to load " + j.getPath() + " "); - continue; - } - - if (!verifyDataPackInstalled(dim)) { - bad = true; - } - } - } - } - } - } - } - - if (bad) { - if (allowRestarting) { - restart(); - } else if (INMS.get().supportsDataPacks()) { - Iris.error("============================================================================"); - Iris.error(C.ITALIC + "You need to restart your server to properly generate custom biomes."); - Iris.error(C.ITALIC + "By continuing, Iris will use backup biomes in place of the custom biomes."); - Iris.error("----------------------------------------------------------------------------"); - Iris.error(C.UNDERLINE + "IT IS HIGHLY RECOMMENDED YOU RESTART THE SERVER BEFORE GENERATING!"); - Iris.error("============================================================================"); - - for (Player i : Bukkit.getOnlinePlayers()) { - if (i.isOp() || i.hasPermission("iris.all")) { - VolmitSender sender = new VolmitSender(i, Iris.instance.getTag("WARNING")); - sender.sendMessage("There are some Iris Packs that have custom biomes in them"); - sender.sendMessage("You need to restart your server to use these packs."); - } - } - - J.sleep(3000); - } - } - } - - public static void restart() { - J.s(() -> { - Iris.warn("New data pack entries have been installed in Iris! Restarting server!"); - Iris.warn("This will only happen when your pack changes (updates/first time setup)"); - Iris.warn("(You can disable this auto restart in iris settings)"); - J.s(() -> { - Iris.warn("Looks like the restart command diddn't work. Stopping the server instead!"); - Bukkit.shutdown(); - }, 100); - Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "restart"); - }); - } - - public static boolean verifyDataPackInstalled(IrisDimension dimension) { - IrisData idm = IrisData.get(Iris.instance.getDataFolder("packs", dimension.getLoadKey())); - KSet keys = new KSet<>(); - boolean warn = false; - - for (IrisBiome i : dimension.getAllBiomes(() -> idm)) { - if (i.isCustom()) { - for (IrisBiomeCustom j : i.getCustomDerivitives()) { - keys.add(dimension.getLoadKey() + ":" + j.getId()); - } - } - } - - if (!INMS.get().supportsDataPacks()) { - if (!keys.isEmpty()) { - Iris.warn("==================================================================================="); - Iris.warn("Pack " + dimension.getLoadKey() + " has " + keys.size() + " custom biome(s). "); - Iris.warn("Your server version does not yet support datapacks for iris."); - Iris.warn("The world will generate these biomes as backup biomes."); - Iris.warn("===================================================================================="); - } - - return true; - } - - for (String i : keys) { - Object o = INMS.get().getCustomBiomeBaseFor(i); - - if (o == null) { - Iris.warn("The Biome " + i + " is not registered on the server."); - warn = true; - } - } - - if (warn) { - Iris.error("The Pack " + dimension.getLoadKey() + " is INCAPABLE of generating custom biomes"); - Iris.error("If not done automatically, restart your server before generating with this pack!"); - } - - return !warn; - } } diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandDeepSearch.java b/core/src/main/java/com/volmit/iris/core/commands/CommandDeepSearch.java deleted file mode 100644 index d8cd6a260..000000000 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandDeepSearch.java +++ /dev/null @@ -1,134 +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.commands; - -import com.volmit.iris.Iris; -import com.volmit.iris.core.pregenerator.DeepSearchPregenerator; -import com.volmit.iris.core.pregenerator.PregenTask; -import com.volmit.iris.core.pregenerator.TurboPregenerator; -import com.volmit.iris.core.tools.IrisToolbelt; -import com.volmit.iris.util.data.Dimension; -import com.volmit.iris.util.decree.DecreeExecutor; -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.math.Position2; -import org.bukkit.Bukkit; -import org.bukkit.World; -import org.bukkit.util.Vector; - -import java.io.File; -import java.io.IOException; - -@Decree(name = "DeepSearch", aliases = "search", description = "Pregenerate your Iris worlds!") -public class CommandDeepSearch implements DecreeExecutor { - public String worldName; - @Decree(description = "DeepSearch a world") - public void start( - @Param(description = "The radius of the pregen in blocks", aliases = "size") - int radius, - @Param(description = "The world to pregen", contextual = true) - World world, - @Param(aliases = "middle", description = "The center location of the pregen. Use \"me\" for your current location", defaultValue = "0,0") - Vector center - ) { - - worldName = world.getName(); - File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName()); - File TurboFile = new File(worldDirectory, "DeepSearch.json"); - if (TurboFile.exists()) { - if (DeepSearchPregenerator.getInstance() != null) { - sender().sendMessage(C.BLUE + "DeepSearch is already in progress"); - Iris.info(C.YELLOW + "DeepSearch is already in progress"); - return; - } else { - try { - TurboFile.delete(); - } catch (Exception e){ - Iris.error("Failed to delete the old instance file of DeepSearch!"); - return; - } - } - } - - try { - if (sender().isPlayer() && access() == null) { - sender().sendMessage(C.RED + "The engine access for this world is null!"); - sender().sendMessage(C.RED + "Please make sure the world is loaded & the engine is initialized. Generate a new chunk, for example."); - } - - DeepSearchPregenerator.DeepSearchJob DeepSearchJob = DeepSearchPregenerator.DeepSearchJob.builder() - .world(world) - .radiusBlocks(radius) - .position(0) - .build(); - - File SearchGenFile = new File(worldDirectory, "DeepSearch.json"); - DeepSearchPregenerator pregenerator = new DeepSearchPregenerator(DeepSearchJob, SearchGenFile); - pregenerator.start(); - - String msg = C.GREEN + "DeepSearch started in " + C.GOLD + worldName + C.GREEN + " of " + C.GOLD + (radius * 2) + C.GREEN + " by " + C.GOLD + (radius * 2) + C.GREEN + " blocks from " + C.GOLD + center.getX() + "," + center.getZ(); - sender().sendMessage(msg); - Iris.info(msg); - } catch (Throwable e) { - sender().sendMessage(C.RED + "Epic fail. See console."); - Iris.reportError(e); - e.printStackTrace(); - } - } - - @Decree(description = "Stop the active DeepSearch task", aliases = "x") - public void stop(@Param(aliases = "world", description = "The world to pause") World world) throws IOException { - DeepSearchPregenerator DeepSearchInstance = DeepSearchPregenerator.getInstance(); - File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName()); - File turboFile = new File(worldDirectory, "DeepSearch.json"); - - if (DeepSearchInstance != null) { - DeepSearchInstance.shutdownInstance(world); - sender().sendMessage(C.LIGHT_PURPLE + "Closed Turbogen instance for " + world.getName()); - } else if (turboFile.exists() && turboFile.delete()) { - sender().sendMessage(C.LIGHT_PURPLE + "Closed Turbogen instance for " + world.getName()); - } else if (turboFile.exists()) { - Iris.error("Failed to delete the old instance file of Turbo Pregen!"); - } else { - sender().sendMessage(C.YELLOW + "No active pregeneration tasks to stop"); - } - } - - @Decree(description = "Pause / continue the active pregeneration task", aliases = {"t", "resume", "unpause"}) - public void pause( - @Param(aliases = "world", description = "The world to pause") - World world - ) { - if (TurboPregenerator.getInstance() != null) { - TurboPregenerator.setPausedTurbo(world); - sender().sendMessage(C.GREEN + "Paused/unpaused Turbo Pregen, now: " + (TurboPregenerator.isPausedTurbo(world) ? "Paused" : "Running") + "."); - } else { - File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName()); - File TurboFile = new File(worldDirectory, "DeepSearch.json"); - if (TurboFile.exists()){ - TurboPregenerator.loadTurboGenerator(world.getName()); - sender().sendMessage(C.YELLOW + "Started DeepSearch back up!"); - } else { - sender().sendMessage(C.YELLOW + "No active DeepSearch tasks to pause/unpause."); - } - - } - } -} 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 9439d29d6..7f6a0786f 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 @@ -28,7 +28,9 @@ 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.core.tools.IrisWorldDump; import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.engine.jvm.VMJavaFX; import com.volmit.iris.engine.mantle.components.MantleObjectComponent; import com.volmit.iris.engine.object.IrisBiome; import com.volmit.iris.engine.object.IrisCave; @@ -150,21 +152,18 @@ public class CommandDeveloper implements DecreeExecutor { @Decree(description = "Test") public void packBenchmark( @Param(description = "The pack to bench", aliases = {"pack"}) - IrisDimension dimension + IrisDimension dimension, + @Param(description = "Headless", defaultValue = "true") + boolean headless, + @Param(description = "GUI", defaultValue = "false") + boolean gui ) { Iris.info("test"); - IrisPackBenchmarking benchmark = new IrisPackBenchmarking(dimension, 1); + IrisPackBenchmarking benchmark = new IrisPackBenchmarking(dimension, 1, headless, gui); + benchmark.runBenchmark(); } - @Decree(description = "Upgrade to another Minecraft version") - public void upgrade( - @Param(description = "The version to upgrade to", defaultValue = "latest") DataVersion version) { - sender().sendMessage(C.GREEN + "Upgrading to " + version.getVersion() + "..."); - ServerConfigurator.installDataPacks(version.get(), false); - sender().sendMessage(C.GREEN + "Done upgrading! You can now update your server version to " + version.getVersion()); - } - @Decree(description = "Test") public void updater( @Param(description = "Updater for chunks") @@ -179,12 +178,22 @@ public class CommandDeveloper implements DecreeExecutor { @Decree(description = "test") public void mca ( - @Param(description = "String") String world) { + @Param(description = "String") World world) { try { - File[] McaFiles = new File(world, "region").listFiles((dir, name) -> name.endsWith(".mca")); - for (File mca : McaFiles) { - MCAFile MCARegion = MCAUtil.read(mca); - } + IrisWorldDump dump = new IrisWorldDump(world, sender()); + dump.start(); + + } catch (Exception e) { + e.printStackTrace(); + } + + } + + @Decree(description = "test") + public void javafx () { + try { + VMJavaFX javaFX = new VMJavaFX(sender()); + javaFX.start(); } catch (Exception e) { e.printStackTrace(); } diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandEdit.java b/core/src/main/java/com/volmit/iris/core/commands/CommandEdit.java index 345b3f214..f5e8d0b1c 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandEdit.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandEdit.java @@ -19,13 +19,19 @@ package com.volmit.iris.core.commands; import com.volmit.iris.Iris; +import com.volmit.iris.core.nms.INMS; import com.volmit.iris.core.service.StudioSVC; import com.volmit.iris.engine.object.*; import com.volmit.iris.util.decree.DecreeExecutor; import com.volmit.iris.util.decree.DecreeOrigin; import com.volmit.iris.util.decree.annotations.Decree; import com.volmit.iris.util.decree.annotations.Param; +import com.volmit.iris.util.decree.specialhandlers.NullableBiomeHandler; +import com.volmit.iris.util.decree.specialhandlers.NullablePlayerHandler; +import com.volmit.iris.util.decree.specialhandlers.NullableRegionHandler; import com.volmit.iris.util.format.C; +import org.bukkit.Location; +import org.bukkit.block.Biome; import java.awt.*; @@ -51,12 +57,31 @@ public class CommandEdit implements DecreeExecutor { @Decree(description = "Edit the biome you specified", aliases = {"b"}, origin = DecreeOrigin.PLAYER) - public void biome(@Param(contextual = false, description = "The biome to edit") IrisBiome biome) { + public void biome(@Param(contextual = false, description = "The biome to edit", defaultValue = "---", customHandler = NullableBiomeHandler.class) IrisBiome biome ) { if (noStudio()) { return; } + if(biome == null) { + try { + IrisBiome b = engine().getBiome(player().getLocation().getBlockX(), player().getLocation().getBlockY() - player().getWorld().getMinHeight(), player().getLocation().getBlockZ()); + Desktop.getDesktop().open(b.getLoadFile()); + sender().sendMessage(C.GREEN + "Opening " + b.getTypeName() + " " + b.getLoadFile().getName().split("\\Q.\\E")[0] + " in VSCode! "); + } catch (Throwable e) { + Iris.reportError(e); + sender().sendMessage("Non-Iris Biome: " + player().getLocation().getBlock().getBiome().name()); + if (player().getLocation().getBlock().getBiome().equals(Biome.CUSTOM)) { + try { + sender().sendMessage("Data Pack Biome: " + INMS.get().getTrueBiomeBaseKey(player().getLocation()) + " (ID: " + INMS.get().getTrueBiomeBaseId(INMS.get().getTrueBiomeBase(player().getLocation())) + ")"); + } catch (Throwable ee) { + Iris.reportError(ee); + } + } + } + return; + } + try { - if (biome == null || biome.getLoadFile() == null) { + if (biome.getLoadFile() == null) { sender().sendMessage(C.GOLD + "Cannot find the file; Perhaps it was not loaded directly from a file?"); return; } @@ -69,10 +94,20 @@ public class CommandEdit implements DecreeExecutor { } @Decree(description = "Edit the region you specified", aliases = {"r"}, origin = DecreeOrigin.PLAYER) - public void region(@Param(contextual = false, description = "The region to edit") IrisRegion region) { + public void region(@Param(contextual = false, description = "The region to edit", defaultValue = "---", customHandler = NullableRegionHandler.class) IrisRegion region) { if (noStudio()) { return; } + if(region == null) { + try { + IrisRegion r = engine().getRegion(player().getLocation().getBlockX(), player().getLocation().getBlockZ()); + Desktop.getDesktop().open(r.getLoadFile()); + sender().sendMessage(C.GREEN + "Opening " + r.getTypeName() + " " + r.getLoadFile().getName().split("\\Q.\\E")[0] + " in VSCode! "); + } catch (Throwable e) { + sender().sendMessage(C.RED + "Failed to get region."); + } + return; + } try { if (region == null || region.getLoadFile() == null) { sender().sendMessage(C.GOLD + "Cannot find the file; Perhaps it was not loaded directly from a file?"); diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandIris.java b/core/src/main/java/com/volmit/iris/core/commands/CommandIris.java index 5154ea4af..94c21710f 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandIris.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandIris.java @@ -24,6 +24,7 @@ import com.volmit.iris.core.ServerConfigurator; import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.core.nms.datapack.DataVersion; import com.volmit.iris.core.pregenerator.ChunkUpdater; +import com.volmit.iris.core.safeguard.IrisSafeguard; import com.volmit.iris.core.service.StudioSVC; import com.volmit.iris.core.tools.IrisBenchmarking; import com.volmit.iris.core.tools.IrisToolbelt; @@ -43,6 +44,7 @@ import com.volmit.iris.util.format.C; import com.volmit.iris.util.format.Form; import com.volmit.iris.util.plugin.VolmitSender; import com.volmit.iris.util.scheduling.J; +import io.lumine.mythic.bukkit.adapters.BukkitPlayer; import lombok.Getter; import org.bukkit.Bukkit; import org.bukkit.Difficulty; @@ -65,7 +67,6 @@ import java.util.List; import static com.volmit.iris.Iris.service; import static com.volmit.iris.core.service.EditSVC.deletingWorld; import static com.volmit.iris.core.tools.IrisBenchmarking.inProgress; -import static com.volmit.iris.core.safeguard.IrisSafeguard.unstablemode; import static com.volmit.iris.core.safeguard.ServerBootSFG.incompatibilities; import static org.bukkit.Bukkit.getServer; @@ -98,24 +99,7 @@ public class CommandIris implements DecreeExecutor { @Param(description = "If it should convert the dimension to match the vanilla height system.", defaultValue = "false") boolean vanillaheight ) { - if(sender() instanceof Player) { - if (incompatibilities.get("Multiverse-Core")) { - sender().sendMessage(C.RED + "Your server has an incompatibility that may corrupt all worlds on the server if not handled properly."); - sender().sendMessage(C.RED + "it is strongly advised for you to take action. see log for full detail"); - sender().sendMessage(C.RED + "----------------------------------------------------------------"); - sender().sendMessage(C.RED + "Command ran: /iris create"); - sender().sendMessage(C.RED + UtilsSFG.MSGIncompatibleWarnings()); - sender().sendMessage(C.RED + "----------------------------------------------------------------"); - } - if (unstablemode && !incompatibilities.get("Multiverse-Core")) { - sender().sendMessage(C.RED + "Your server is experiencing an incompatibility with the Iris plugin."); - sender().sendMessage(C.RED + "Please rectify this problem to avoid further complications."); - sender().sendMessage(C.RED + "----------------------------------------------------------------"); - sender().sendMessage(C.RED + "Command ran: /iris create"); - sender().sendMessage(C.RED + UtilsSFG.MSGIncompatibleWarnings()); - sender().sendMessage(C.RED + "----------------------------------------------------------------"); - } - } + if (name.equals("iris")) { sender().sendMessage(C.RED + "You cannot use the world name \"iris\" for creating worlds as Iris uses this directory for studio worlds."); sender().sendMessage(C.RED + "May we suggest the name \"IrisWorld\" instead?"); 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..095e28406 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 @@ -22,11 +22,10 @@ import com.volmit.iris.Iris; import com.volmit.iris.core.IrisSettings; import com.volmit.iris.core.gui.NoiseExplorerGUI; import com.volmit.iris.core.gui.VisionGUI; -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.IrisNoiseBenchmark; import com.volmit.iris.core.tools.IrisToolbelt; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.object.*; @@ -42,22 +41,17 @@ 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.function.Function2; -import com.volmit.iris.util.function.NoiseProvider; -import com.volmit.iris.util.interpolation.InterpolationMethod; -import com.volmit.iris.util.io.IO; import com.volmit.iris.util.json.JSONArray; import com.volmit.iris.util.json.JSONObject; import com.volmit.iris.util.math.M; import com.volmit.iris.util.math.Position2; import com.volmit.iris.util.math.RNG; import com.volmit.iris.util.math.Spiraler; -import com.volmit.iris.util.noise.CNG; import com.volmit.iris.util.parallel.BurstExecutor; import com.volmit.iris.util.parallel.MultiBurst; import com.volmit.iris.util.plugin.VolmitSender; import com.volmit.iris.util.scheduling.J; import com.volmit.iris.util.scheduling.O; -import com.volmit.iris.util.scheduling.PrecisionStopwatch; import com.volmit.iris.util.scheduling.jobs.QueueJob; import io.papermc.lib.PaperLib; import org.bukkit.*; @@ -79,14 +73,12 @@ import java.util.Date; import java.util.Objects; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; -import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; @Decree(name = "studio", aliases = {"std", "s"}, description = "Studio Commands", studio = true) public class CommandStudio implements DecreeExecutor { private CommandFind find; private CommandEdit edit; - //private CommandDeepSearch deepSearch; public static String hrf(Duration duration) { return duration.toString().substring(2).replaceAll("(\\d[HMS])(?!$)", "$1 ").toLowerCase(); @@ -286,7 +278,8 @@ public class CommandStudio implements DecreeExecutor { sender().sendMessage(C.RED + "No studio world open!"); return; } - Iris.service(StudioSVC.class).getActiveProject().getActiveProvider().getEngine().hotload(); + var provider = Iris.service(StudioSVC.class).getActiveProject().getActiveProvider(); + provider.getEngine().hotload(); sender().sendMessage(C.GREEN + "Hotloaded"); } @@ -391,18 +384,10 @@ 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) - World world - ) { + public void map() { if (noGUI()) return; - - if (!IrisToolbelt.isIrisWorld(world)) { - sender().sendMessage(C.RED + "You need to be in or specify an Iris-generated world!"); - return; - } - - VisionGUI.launch(IrisToolbelt.access(world).getEngine(), 0); + if (noStudio()) return; + VisionGUI.launch(IrisToolbelt.access(player().getWorld()).getEngine(), 0); sender().sendMessage(C.GREEN + "Opening map!"); } @@ -423,188 +408,8 @@ public class CommandStudio implements DecreeExecutor { @Param(description = "The dimension to profile", contextual = true, defaultValue = "default") IrisDimension dimension ) { - // Todo: Make this more accurate - File pack = dimension.getLoadFile().getParentFile().getParentFile(); - File report = Iris.instance.getDataFile("profile.txt"); - IrisProject project = new IrisProject(pack); - IrisData data = IrisData.get(pack); - - KList fileText = new KList<>(); - - KMap styleTimings = new KMap<>(); - KMap interpolatorTimings = new KMap<>(); - KMap generatorTimings = new KMap<>(); - KMap biomeTimings = new KMap<>(); - KMap regionTimings = new KMap<>(); - - sender().sendMessage("Calculating Performance Metrics for Noise generators"); - - for (NoiseStyle i : NoiseStyle.values()) { - CNG c = i.create(new RNG(i.hashCode())); - - for (int j = 0; j < 3000; j++) { - c.noise(j, j + 1000, j * j); - c.noise(j, -j); - } - - PrecisionStopwatch px = PrecisionStopwatch.start(); - - for (int j = 0; j < 100000; j++) { - c.noise(j, j + 1000, j * j); - c.noise(j, -j); - } - - styleTimings.put(i, px.getMilliseconds()); - } - - fileText.add("Noise Style Performance Impacts: "); - - for (NoiseStyle i : styleTimings.sortKNumber()) { - fileText.add(i.name() + ": " + styleTimings.get(i)); - } - - fileText.add(""); - - sender().sendMessage("Calculating Interpolator Timings..."); - - for (InterpolationMethod i : InterpolationMethod.values()) { - IrisInterpolator in = new IrisInterpolator(); - in.setFunction(i); - in.setHorizontalScale(8); - - NoiseProvider np = (x, z) -> Math.random(); - - for (int j = 0; j < 3000; j++) { - in.interpolate(j, -j, np); - } - - PrecisionStopwatch px = PrecisionStopwatch.start(); - - for (int j = 0; j < 100000; j++) { - in.interpolate(j + 10000, -j - 100000, np); - } - - interpolatorTimings.put(i, px.getMilliseconds()); - } - - fileText.add("Noise Interpolator Performance Impacts: "); - - for (InterpolationMethod i : interpolatorTimings.sortKNumber()) { - fileText.add(i.name() + ": " + interpolatorTimings.get(i)); - } - - fileText.add(""); - - sender().sendMessage("Processing Generator Scores: "); - - KMap> btx = new KMap<>(); - - for (String i : data.getGeneratorLoader().getPossibleKeys()) { - KList vv = new KList<>(); - IrisGenerator g = data.getGeneratorLoader().load(i); - KList composites = g.getAllComposites(); - double score = 0; - int m = 0; - for (IrisNoiseGenerator j : composites) { - m++; - score += styleTimings.get(j.getStyle().getStyle()); - vv.add("Composite Noise Style " + m + " " + j.getStyle().getStyle().name() + ": " + styleTimings.get(j.getStyle().getStyle())); - } - - score += interpolatorTimings.get(g.getInterpolator().getFunction()); - vv.add("Interpolator " + g.getInterpolator().getFunction().name() + ": " + interpolatorTimings.get(g.getInterpolator().getFunction())); - generatorTimings.put(i, score); - btx.put(i, vv); - } - - fileText.add("Project Generator Performance Impacts: "); - - for (String i : generatorTimings.sortKNumber()) { - fileText.add(i + ": " + generatorTimings.get(i)); - - btx.get(i).forEach((ii) -> fileText.add(" " + ii)); - } - - fileText.add(""); - - KMap> bt = new KMap<>(); - - for (String i : data.getBiomeLoader().getPossibleKeys()) { - KList vv = new KList<>(); - IrisBiome b = data.getBiomeLoader().load(i); - double score = 0; - - int m = 0; - for (IrisBiomePaletteLayer j : b.getLayers()) { - m++; - score += styleTimings.get(j.getStyle().getStyle()); - vv.add("Palette Layer " + m + ": " + styleTimings.get(j.getStyle().getStyle())); - } - - score += styleTimings.get(b.getBiomeStyle().getStyle()); - vv.add("Biome Style: " + styleTimings.get(b.getBiomeStyle().getStyle())); - score += styleTimings.get(b.getChildStyle().getStyle()); - vv.add("Child Style: " + styleTimings.get(b.getChildStyle().getStyle())); - biomeTimings.put(i, score); - bt.put(i, vv); - } - - fileText.add("Project Biome Performance Impacts: "); - - for (String i : biomeTimings.sortKNumber()) { - fileText.add(i + ": " + biomeTimings.get(i)); - - bt.get(i).forEach((ff) -> fileText.add(" " + ff)); - } - - fileText.add(""); - - for (String i : data.getRegionLoader().getPossibleKeys()) { - IrisRegion b = data.getRegionLoader().load(i); - double score = 0; - - score += styleTimings.get(b.getLakeStyle().getStyle()); - score += styleTimings.get(b.getRiverStyle().getStyle()); - regionTimings.put(i, score); - } - - fileText.add("Project Region Performance Impacts: "); - - for (String i : regionTimings.sortKNumber()) { - fileText.add(i + ": " + regionTimings.get(i)); - } - - fileText.add(""); - - double m = 0; - for (double i : biomeTimings.v()) { - m += i; - } - m /= biomeTimings.size(); - double mm = 0; - for (double i : generatorTimings.v()) { - mm += i; - } - mm /= generatorTimings.size(); - m += mm; - double mmm = 0; - for (double i : regionTimings.v()) { - mmm += i; - } - mmm /= regionTimings.size(); - m += mmm; - - fileText.add("Average Score: " + m); - sender().sendMessage("Score: " + Form.duration(m, 0)); - - try { - IO.writeAll(report, fileText.toString("\n")); - } catch (IOException e) { - Iris.reportError(e); - e.printStackTrace(); - } - - sender().sendMessage(C.GREEN + "Done! " + report.getPath()); + IrisNoiseBenchmark noiseBenchmark = new IrisNoiseBenchmark(dimension, sender()); + noiseBenchmark.runAll(); } @Decree(description = "Spawn an Iris entity", aliases = "summon", origin = DecreeOrigin.PLAYER) diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandWhat.java b/core/src/main/java/com/volmit/iris/core/commands/CommandWhat.java index 48d8c726e..87ee13ba3 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandWhat.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandWhat.java @@ -88,8 +88,7 @@ public class CommandWhat implements DecreeExecutor { @Decree(description = "What region am i in?", origin = DecreeOrigin.PLAYER) public void region() { try { - Chunk chunk = world().getChunkAt(player().getLocation().getBlockZ() / 16, player().getLocation().getBlockZ() / 16); - IrisRegion r = engine().getRegion(chunk); + IrisRegion r = engine().getRegion(player().getLocation()); sender().sendMessage("IRegion: " + r.getLoadKey() + " (" + r.getName() + ")"); } catch (Throwable e) { diff --git a/core/src/main/java/com/volmit/iris/core/gui/VisionGUI.java b/core/src/main/java/com/volmit/iris/core/gui/VisionGUI.java index 93c7c2009..0342dbc41 100644 --- a/core/src/main/java/com/volmit/iris/core/gui/VisionGUI.java +++ b/core/src/main/java/com/volmit/iris/core/gui/VisionGUI.java @@ -80,6 +80,7 @@ public class VisionGUI extends JPanel implements MouseWheelListener, KeyListener private boolean lowtile = false; private boolean follow = false; private boolean alt = false; + private boolean dragging = false; private IrisRenderer renderer; private IrisWorld world; private double velocity = 0; @@ -201,6 +202,7 @@ public class VisionGUI extends JPanel implements MouseWheelListener, KeyListener @Override public void mouseDragged(MouseEvent e) { + dragging = true; Point cp = e.getPoint(); ox += (lx - cp.getX()) * scale; oz += (lz - cp.getY()) * scale; @@ -413,7 +415,7 @@ public class VisionGUI extends JPanel implements MouseWheelListener, KeyListener private double getWorldX(double screenX) { //return (mscale * screenX) + ((oxp / scale) * mscale); - return (mscale * screenX) + ((oxp / scale)); + return (mscale * screenX) + ((oxp / scale) * mscale); } private double getWorldZ(double screenZ) { diff --git a/core/src/main/java/com/volmit/iris/core/nms/IHeadless.java b/core/src/main/java/com/volmit/iris/core/nms/IHeadless.java new file mode 100644 index 000000000..2b7accdc1 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/core/nms/IHeadless.java @@ -0,0 +1,22 @@ +package com.volmit.iris.core.nms; + +import com.volmit.iris.core.pregenerator.PregenListener; +import com.volmit.iris.util.documentation.ChunkCoordinates; +import com.volmit.iris.util.documentation.RegionCoordinates; +import com.volmit.iris.util.parallel.MultiBurst; + +import java.io.Closeable; + +public interface IHeadless extends Closeable { + + void save(); + + @ChunkCoordinates + boolean exists(int x, int z); + + @RegionCoordinates + void generateRegion(MultiBurst burst, int x, int z, PregenListener listener); + + @ChunkCoordinates + void generateChunk(int x, int z); +} 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 da90e6fb2..7f72c84d7 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 @@ -20,6 +20,8 @@ package com.volmit.iris.core.nms; import com.volmit.iris.core.nms.datapack.DataVersion; import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.engine.object.IrisBiomeCustom; +import com.volmit.iris.engine.object.IrisDimension; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.mantle.Mantle; @@ -115,4 +117,14 @@ public interface INMSBinding { default DataVersion getDataVersion() { return DataVersion.V1192; } + + boolean registerDimension(String name, IrisDimension dimension); + + boolean registerBiome(String dimensionId, IrisBiomeCustom biome, boolean replace); + + void injectBukkit(); + + default IHeadless createHeadless(Engine engine) { + throw new IllegalStateException("Headless mode not supported"); + } } 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 e99478753..d54ee7f6a 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 @@ -18,20 +18,29 @@ package com.volmit.iris.core.nms.v1X; +import com.google.common.base.Preconditions; import com.volmit.iris.Iris; import com.volmit.iris.core.nms.INMSBinding; import com.volmit.iris.core.nms.container.BlockPos; import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.engine.object.IrisBiomeCustom; +import com.volmit.iris.engine.object.IrisDimension; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.format.C; import com.volmit.iris.util.mantle.Mantle; 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 net.bytebuddy.ByteBuddy; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.dynamic.loading.ClassReloadingStrategy; +import net.bytebuddy.matcher.ElementMatchers; import org.bukkit.Chunk; import org.bukkit.Location; import org.bukkit.World; +import org.bukkit.WorldCreator; import org.bukkit.block.Biome; import org.bukkit.entity.Dolphin; import org.bukkit.entity.Entity; @@ -40,6 +49,8 @@ import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.generator.ChunkGenerator; import org.bukkit.inventory.ItemStack; +import java.io.File; + public class NMSBinding1X implements INMSBinding { private static final boolean supportsCustomHeight = testCustomHeight(); @@ -97,6 +108,16 @@ public class NMSBinding1X implements INMSBinding { return location.getWorld().spawnEntity(location, type); } + @Override + public boolean registerDimension(String name, IrisDimension dimension) { + return false; + } + + @Override + public boolean registerBiome(String dimensionId, IrisBiomeCustom biome, boolean replace) { + return false; + } + @Override public void deserializeTile(CompoundTag s, Location newPosition) { @@ -225,4 +246,39 @@ public class NMSBinding1X implements INMSBinding { Iris.error("Cannot use the global data palette! Iris is incapable of using MCA generation on this version of minecraft!"); return null; } + + public void injectBukkit() { + try { + Iris.info("Injecting Bukkit"); + new ByteBuddy() + .redefine(WorldCreator.class) + .visit(Advice.to(WorldCreatorAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(String.class)))) + .make() + .load(WorldCreator.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); + Iris.info("Injected Bukkit Successfully!"); + } catch (Exception e) { + Iris.info(C.RED + "Failed to Inject Bukkit!"); + e.printStackTrace(); + Iris.reportError(e); + } + + } + + private static class WorldCreatorAdvice { + @Advice.OnMethodEnter + static void enter(@Advice.Argument(0) String name) { + File isIrisWorld = new File(name, "iris"); + boolean isFromIris = false; + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + for (StackTraceElement stack : stackTrace) { + if (stack.getClassName().contains("Iris")) { + isFromIris = true; + break; + } + } + if (!isFromIris) { + Preconditions.checkArgument(!isIrisWorld.exists(), "Only Iris can load Iris Worlds!"); + } + } + } } 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..29c590fa2 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 @@ -106,7 +106,7 @@ public class ChunkUpdater { try { if (!paused.get()) { long eta = computeETA(); - long elapsedSeconds = (System.currentTimeMillis() - startTime.get()) / 1000; + long elapsedSeconds = (System.currentTimeMillis() - startTime.get()) / 3000; int processed = chunksProcessed.get(); double cps = elapsedSeconds > 0 ? processed / (double) elapsedSeconds : 0; chunksPerSecond.put(cps); 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 deleted file mode 100644 index c60724d2a..000000000 --- a/core/src/main/java/com/volmit/iris/core/pregenerator/DeepSearchPregenerator.java +++ /dev/null @@ -1,275 +0,0 @@ -package com.volmit.iris.core.pregenerator; - -import com.google.gson.Gson; -import com.volmit.iris.Iris; -import com.volmit.iris.core.tools.IrisToolbelt; -import com.volmit.iris.engine.framework.Engine; -import com.volmit.iris.engine.object.IrisBiome; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.format.C; -import com.volmit.iris.util.format.Form; -import com.volmit.iris.util.io.IO; -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.scheduling.ChronoLatch; -import com.volmit.iris.util.scheduling.J; -import lombok.Builder; -import lombok.Data; -import lombok.Getter; -import org.bukkit.Bukkit; -import org.bukkit.World; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.world.WorldUnloadEvent; -import org.bukkit.scheduler.BukkitRunnable; - -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.locks.ReentrantLock; - -public class DeepSearchPregenerator extends Thread implements Listener { - @Getter - private static DeepSearchPregenerator instance; - private final DeepSearchJob job; - private final File destination; - private final int maxPosition; - private World world; - private final ChronoLatch latch; - private static AtomicInteger foundChunks; - private final AtomicInteger foundLast; - private final AtomicInteger foundTotalChunks; - private final AtomicLong startTime; - private final RollingSequence chunksPerSecond; - private final RollingSequence chunksPerMinute; - private final AtomicInteger chunkCachePos; - private final AtomicInteger chunkCacheSize; - private int pos; - private final AtomicInteger foundCacheLast; - private final AtomicInteger foundCache; - private LinkedHashMap chunkCache; - private KList chunkQueue; - private final ReentrantLock cacheLock; - - private static final Map jobs = new HashMap<>(); - - public DeepSearchPregenerator(DeepSearchJob job, File destination) { - this.job = job; - this.chunkCacheSize = new AtomicInteger(); // todo - this.chunkCachePos = new AtomicInteger(1000); - this.foundCacheLast = new AtomicInteger(); - this.foundCache = new AtomicInteger(); - this.cacheLock = new ReentrantLock(); - this.destination = destination; - this.chunkCache = new LinkedHashMap<>(); - this.maxPosition = new Spiraler(job.getRadiusBlocks() * 2, job.getRadiusBlocks() * 2, (x, z) -> { - }).count(); - this.world = Bukkit.getWorld(job.getWorld().getUID()); - this.chunkQueue = new KList<>(); - this.latch = new ChronoLatch(3000); - this.startTime = new AtomicLong(M.ms()); - this.chunksPerSecond = new RollingSequence(10); - this.chunksPerMinute = new RollingSequence(10); - foundChunks = new AtomicInteger(0); - this.foundLast = new AtomicInteger(0); - this.foundTotalChunks = new AtomicInteger((int) Math.ceil(Math.pow((2.0 * job.getRadiusBlocks()) / 16, 2))); - - this.pos = 0; - jobs.put(job.getWorld().getName(), job); - DeepSearchPregenerator.instance = this; - } - - @EventHandler - public void on(WorldUnloadEvent e) { - if (e.getWorld().equals(world)) { - interrupt(); - } - } - - public void run() { - while (!interrupted()) { - tick(); - } - try { - saveNow(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - public void tick() { - DeepSearchJob job = jobs.get(world.getName()); - // chunkCache(); //todo finish this - if (latch.flip() && !job.paused) { - if (cacheLock.isLocked()) { - Iris.info("DeepFinder: Caching: " + chunkCachePos.get() + " Of " + chunkCacheSize.get()); - } else { - long eta = computeETA(); - save(); - int secondGenerated = foundChunks.get() - foundLast.get(); - foundLast.set(foundChunks.get()); - secondGenerated = secondGenerated / 3; - chunksPerSecond.put(secondGenerated); - chunksPerMinute.put(secondGenerated * 60); - Iris.info("DeepFinder: " + C.IRIS + world.getName() + C.RESET + " Searching: " + Form.f(foundChunks.get()) + " of " + Form.f(foundTotalChunks.get()) + " " + Form.f((int) chunksPerSecond.getAverage()) + "/s ETA: " + Form.duration((double) eta, 2)); - } - - } - if (foundChunks.get() >= foundTotalChunks.get()) { - Iris.info("Completed DeepSearch!"); - interrupt(); - } - } - - private long computeETA() { - return (long) ((foundTotalChunks.get() - foundChunks.get()) / chunksPerSecond.getAverage()) * 1000; - // todo broken - } - - private final ExecutorService executorService = Executors.newSingleThreadExecutor(); - - private void queueSystem(Position2 chunk) { - if (chunkQueue.isEmpty()) { - for (int limit = 512; limit != 0; limit--) { - pos = job.getPosition() + 1; - chunkQueue.add(getChunk(pos)); - } - } else { - //MCAUtil.read(); - - } - - - } - - private void findInChunk(World world, int x, int z) throws IOException { - int xx = x * 16; - int zz = z * 16; - Engine engine = IrisToolbelt.access(world).getEngine(); - for (int i = 0; i < 16; i++) { - for (int j = 0; j < 16; j++) { - int height = engine.getHeight(xx + i, zz + j); - if (height > 300) { - File found = new File("plugins" + "iris" + "found.txt"); - FileWriter writer = new FileWriter(found); - if (!found.exists()) { - found.createNewFile(); - } - IrisBiome biome = engine.getBiome(xx, engine.getHeight(), zz); - Iris.info("Found at! " + xx + ", " + zz + "Biome ID: " + biome.getName() + ", "); - writer.write("Biome at: X: " + xx + " Z: " + zz + "Biome ID: " + biome.getName() + ", "); - return; - } - } - } - } - - public Position2 getChunk(int position) { - int p = -1; - AtomicInteger xx = new AtomicInteger(); - AtomicInteger zz = new AtomicInteger(); - Spiraler s = new Spiraler(job.getRadiusBlocks() * 2, job.getRadiusBlocks() * 2, (x, z) -> { - xx.set(x); - zz.set(z); - }); - - while (s.hasNext() && p++ < position) { - s.next(); - } - - return new Position2(xx.get(), zz.get()); - } - - public void save() { - J.a(() -> { - try { - saveNow(); - } catch (Throwable e) { - e.printStackTrace(); - } - }); - } - - public static void setPausedDeep(World world) { - DeepSearchJob job = jobs.get(world.getName()); - if (isPausedDeep(world)){ - job.paused = false; - } else { - job.paused = true; - } - - if ( job.paused) { - Iris.info(C.BLUE + "DeepSearch: " + C.IRIS + world.getName() + C.BLUE + " Paused"); - } else { - Iris.info(C.BLUE + "DeepSearch: " + C.IRIS + world.getName() + C.BLUE + " Resumed"); - } - } - - public static boolean isPausedDeep(World world) { - DeepSearchJob job = jobs.get(world.getName()); - return job != null && job.isPaused(); - } - - public void shutdownInstance(World world) throws IOException { - Iris.info("DeepSearch: " + C.IRIS + world.getName() + C.BLUE + " Shutting down.."); - DeepSearchJob job = jobs.get(world.getName()); - File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName()); - File deepFile = new File(worldDirectory, "DeepSearch.json"); - - if (job == null) { - Iris.error("No DeepSearch job found for world: " + world.getName()); - return; - } - - try { - if (!job.isPaused()) { - job.setPaused(true); - } - save(); - jobs.remove(world.getName()); - new BukkitRunnable() { - @Override - public void run() { - while (deepFile.exists()){ - deepFile.delete(); - J.sleep(1000); - } - Iris.info("DeepSearch: " + C.IRIS + world.getName() + C.BLUE + " File deleted and instance closed."); - } - }.runTaskLater(Iris.instance, 20L); - } catch (Exception e) { - Iris.error("Failed to shutdown DeepSearch for " + world.getName()); - e.printStackTrace(); - } finally { - saveNow(); - interrupt(); - } - } - - - public void saveNow() throws IOException { - IO.writeAll(this.destination, new Gson().toJson(job)); - } - - @Data - @Builder - public static class DeepSearchJob { - private World world; - @Builder.Default - private int radiusBlocks = 5000; - @Builder.Default - private int position = 0; - @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..495454f22 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 @@ -82,11 +82,8 @@ 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()); + if (i == null) return 0; + lastUse.put(i, M.ms()); listener.onChunkGenerated(x, z); listener.onChunkCleaned(x, z); return 0; diff --git a/core/src/main/java/com/volmit/iris/core/pregenerator/methods/HeadlessPregenMethod.java b/core/src/main/java/com/volmit/iris/core/pregenerator/methods/HeadlessPregenMethod.java new file mode 100644 index 000000000..7196032fa --- /dev/null +++ b/core/src/main/java/com/volmit/iris/core/pregenerator/methods/HeadlessPregenMethod.java @@ -0,0 +1,87 @@ +package com.volmit.iris.core.pregenerator.methods; + +import com.volmit.iris.Iris; +import com.volmit.iris.core.IrisSettings; +import com.volmit.iris.core.nms.IHeadless; +import com.volmit.iris.core.nms.INMS; +import com.volmit.iris.core.pregenerator.PregenListener; +import com.volmit.iris.core.pregenerator.PregeneratorMethod; +import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.util.mantle.Mantle; +import com.volmit.iris.util.parallel.MultiBurst; + +import java.io.IOException; +import java.util.concurrent.Semaphore; + +public class HeadlessPregenMethod implements PregeneratorMethod { + private final Engine engine; + private final IHeadless headless; + private final Semaphore semaphore; + private final int max; + + public HeadlessPregenMethod(Engine engine) { + this.max = IrisSettings.getThreadCount(IrisSettings.get().getConcurrency().getParallelism()); + this.engine = engine; + this.headless = INMS.get().createHeadless(engine); + this.semaphore = new Semaphore(max); + } + + @Override + public void init() {} + + @Override + public void close() { + try { + semaphore.acquire(max); + } catch (InterruptedException ignored) {} + headless.save(); + try { + headless.close(); + } catch (IOException e) { + Iris.error("Failed to close headless"); + e.printStackTrace(); + } + } + + @Override + public void save() { + headless.save(); + } + + @Override + public boolean supportsRegions(int x, int z, PregenListener listener) { + return false; + } + + @Override + public String getMethod(int x, int z) { + return "Headless"; + } + + @Override + public void generateRegion(int x, int z, PregenListener listener) {} + + @Override + public void generateChunk(int x, int z, PregenListener listener) { + try { + semaphore.acquire(); + } catch (InterruptedException ignored) { + semaphore.release(); + return; + } + MultiBurst.burst.complete(() -> { + try { + listener.onChunkGenerating(x, z); + headless.generateChunk(x, z); + listener.onChunkGenerated(x, z); + } finally { + semaphore.release(); + } + }); + } + + @Override + public Mantle getMantle() { + return engine.getMantle().getMantle(); + } +} diff --git a/core/src/main/java/com/volmit/iris/core/pregenerator/methods/MedievalPregenMethod.java b/core/src/main/java/com/volmit/iris/core/pregenerator/methods/MedievalPregenMethod.java index 2ebfb64b2..cdecf2bcc 100644 --- a/core/src/main/java/com/volmit/iris/core/pregenerator/methods/MedievalPregenMethod.java +++ b/core/src/main/java/com/volmit/iris/core/pregenerator/methods/MedievalPregenMethod.java @@ -119,8 +119,7 @@ public class MedievalPregenMethod implements PregeneratorMethod { listener.onChunkGenerating(x, z); futures.add(J.sfut(() -> { - world.getChunkAt(x, z); - Chunk c = Bukkit.getWorld(world.getUID()).getChunkAt(x, z); + Chunk c = world.getChunkAt(x, z); lastUse.put(c, M.ms()); listener.onChunkGenerated(x, z); listener.onChunkCleaned(x, z); diff --git a/core/src/main/java/com/volmit/iris/core/safeguard/IrisSafeguard.java b/core/src/main/java/com/volmit/iris/core/safeguard/IrisSafeguard.java index 4fb25371a..14a1a66bc 100644 --- a/core/src/main/java/com/volmit/iris/core/safeguard/IrisSafeguard.java +++ b/core/src/main/java/com/volmit/iris/core/safeguard/IrisSafeguard.java @@ -1,15 +1,71 @@ package com.volmit.iris.core.safeguard; import com.volmit.iris.Iris; +import com.volmit.iris.core.IrisSettings; +import com.volmit.iris.core.nms.INMS; +import com.volmit.iris.core.safeguard.handler.onCommandWarning; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.format.C; +import com.volmit.iris.util.format.Form; +import com.volmit.iris.util.misc.getHardware; + +import static org.bukkit.Bukkit.getServer; public class IrisSafeguard { - public static boolean unstablemode = false; - public static boolean warningmode = false; - public static boolean stablemode = false; + public static IrisSafeguard instance; + public boolean acceptUnstable = false; + public boolean unstablemode = false; + public boolean warningmode = false; + public boolean stablemode = false; - public static void IrisSafeguardSystem() { + public static void InitializeSafeguard() { + instance = new IrisSafeguard(); + } + + public void IrisSafeguardSystem() { + acceptUnstable = IrisSettings.get().getSafeguard().ignoreBootMode; + getServer().getPluginManager().registerEvents(new onCommandWarning(), Iris.instance); Iris.info("Enabled Iris SafeGuard"); ServerBootSFG.BootCheck(); } + + public void earlySplash() { + String padd = Form.repeat(" ", 8); + String padd2 = Form.repeat(" ", 4); + String[] info = new String[]{"", "", "", "", "", padd2 + C.RED + " Iris", padd2 + C.GRAY + " by " + C.DARK_RED + "Volmit Software", padd2 + C.GRAY + " v" + C.RED + Iris.instance.getDescription().getVersion()}; + String[] splashunstable = { + padd + C.GRAY + " @@@@@@@@@@@@@@" + C.DARK_GRAY + "@@@", + padd + C.GRAY + " @@&&&&&&&&&" + C.DARK_GRAY + "&&&&&&" + C.RED + " .(((()))). ", + padd + C.GRAY + "@@@&&&&&&&&" + C.DARK_GRAY + "&&&&&" + C.RED + " .((((((())))))). ", + padd + C.GRAY + "@@@&&&&&" + C.DARK_GRAY + "&&&&&&&" + C.RED + " ((((((((())))))))) " + C.GRAY + " @", + padd + C.GRAY + "@@@&&&&" + C.DARK_GRAY + "@@@@@&" + C.RED + " ((((((((-))))))))) " + C.GRAY + " @@", + padd + C.GRAY + "@@@&&" + C.RED + " ((((((({ })))))))) " + C.GRAY + " &&@@@", + padd + C.GRAY + "@@" + C.RED + " ((((((((-))))))))) " + C.DARK_GRAY + "&@@@@@" + C.GRAY + "&&&&@@@", + padd + C.GRAY + "@" + C.RED + " ((((((((())))))))) " + C.DARK_GRAY + "&&&&&" + C.GRAY + "&&&&&&&@@@", + padd + C.GRAY + "" + C.RED + " '((((((()))))))' " + C.DARK_GRAY + "&&&&&" + C.GRAY + "&&&&&&&&@@@", + padd + C.GRAY + "" + C.RED + " '(((())))' " + C.DARK_GRAY + "&&&&&&&&" + C.GRAY + "&&&&&&&@@", + padd + C.GRAY + " " + C.DARK_GRAY + "@@@" + C.GRAY + "@@@@@@@@@@@@@@" + }; + + for (int i = 0; i < info.length; i++) { + splashunstable[i] += info[i]; + } + Iris.info("Java: " + Iris.instance.getJava()); + if (!Iris.instance.getServer().getVersion().contains("Purpur")) { + if (Iris.instance.getServer().getVersion().contains("Spigot") && Iris.instance.getServer().getVersion().contains("Bukkit")) { + Iris.info(C.RED + " Iris requires paper or above to function properly.."); + } else { + Iris.info(C.YELLOW + "Purpur is recommended to use with iris."); + } + } + if (getHardware.getProcessMemory() < 5999) { + Iris.warn("6GB+ Ram is recommended"); + Iris.warn("Process Memory: " + getHardware.getProcessMemory() + " MB"); + } + Iris.info("Custom Biomes: " + INMS.get().countCustomBiomes()); + Iris.info("\n\n " + new KList<>(splashunstable).toString("\n") + "\n"); + UtilsSFG.splash(); + + } } diff --git a/core/src/main/java/com/volmit/iris/core/safeguard/ModesSFG.java b/core/src/main/java/com/volmit/iris/core/safeguard/ModesSFG.java index e9774ceff..127d560f8 100644 --- a/core/src/main/java/com/volmit/iris/core/safeguard/ModesSFG.java +++ b/core/src/main/java/com/volmit/iris/core/safeguard/ModesSFG.java @@ -3,18 +3,20 @@ package com.volmit.iris.core.safeguard; import com.volmit.iris.Iris; import com.volmit.iris.core.IrisSettings; import com.volmit.iris.util.format.C; +import org.bukkit.Bukkit; +import org.bukkit.scheduler.BukkitScheduler; public class ModesSFG { public static void selectMode() { - if (IrisSafeguard.unstablemode) { + if (IrisSafeguard.instance.unstablemode) { Iris.safeguard(C.DARK_RED + "Iris is running in Unstable Mode"); unstable(); } - if (IrisSafeguard.warningmode) { + if (IrisSafeguard.instance.warningmode) { Iris.safeguard(C.GOLD + "Iris is running in Warning Mode"); warning(); } - if (IrisSafeguard.stablemode) { + if (IrisSafeguard.instance.stablemode) { stable(); } } @@ -27,7 +29,7 @@ public class ModesSFG { UtilsSFG.printIncompatibleWarnings(); - if (IrisSafeguard.unstablemode) { + if (IrisSafeguard.instance.unstablemode) { Iris.info(""); Iris.info(C.DARK_GRAY + "--==<" + C.RED + " IMPORTANT " + C.DARK_GRAY + ">==--"); Iris.info(C.RED + "Iris is running in unstable mode which may cause the following issues:"); @@ -44,27 +46,26 @@ public class ModesSFG { Iris.info(C.DARK_RED + "ATTENTION: " + C.RED + "While running Iris in unstable mode, you won't be eligible for support."); Iris.info(C.DARK_RED + "CAUSE: " + C.RED + UtilsSFG.MSGIncompatibleWarnings()); - if (IrisSettings.get().getGeneral().ignoreBootMode) { + if (IrisSettings.get().getSafeguard().ignoreBootMode) { Iris.info(C.DARK_RED + "Boot Unstable is set to true, continuing with the startup process."); } else { Iris.info(C.DARK_RED + "Go to plugins/iris/settings.json and set ignoreBootMode to true if you wish to proceed."); - while (true) { - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - // no - } + Iris.info(C.DARK_RED + "Shutting down server in " + C.UNDERLINE + "" + C.DARK_RED + "50 Seconds"); + try { + Thread.sleep(50000); + Bukkit.shutdown(); + } catch (Exception ignored) { } } - Iris.info(""); } + Iris.info(""); } public static void warning() { UtilsSFG.printIncompatibleWarnings(); - if (IrisSafeguard.warningmode) { + if (IrisSafeguard.instance.warningmode) { Iris.info(""); Iris.info(C.DARK_GRAY + "--==<" + C.GOLD + " IMPORTANT " + C.DARK_GRAY + ">==--"); Iris.info(C.GOLD + "Iris is running in warning mode which may cause the following issues:"); diff --git a/core/src/main/java/com/volmit/iris/core/safeguard/PerformanceSFG.java b/core/src/main/java/com/volmit/iris/core/safeguard/PerformanceSFG.java deleted file mode 100644 index 90aab8cf5..000000000 --- a/core/src/main/java/com/volmit/iris/core/safeguard/PerformanceSFG.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.volmit.iris.core.safeguard; - -public class PerformanceSFG { - public static void calculatePerformance() { - - - } -} 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..45526181a 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 @@ -116,16 +116,16 @@ public class ServerBootSFG { safeguardPassed = (severityHigh == 0 && severityMedium == 0 && severityLow == 0); count = severityHigh + severityMedium + severityLow; if (safeguardPassed) { - stablemode = true; + IrisSafeguard.instance.stablemode = true; Iris.safeguard("Stable mode has been activated."); } if (!safeguardPassed) { if (severityMedium >= 1 && severityHigh == 0) { - warningmode = true; + IrisSafeguard.instance.warningmode = true; Iris.safeguard("Warning mode has been activated."); } if (severityHigh >= 1) { - unstablemode = true; + IrisSafeguard.instance.unstablemode = true; Iris.safeguard("Unstable mode has been activated."); } } 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 659c02bdd..2b2bf5c46 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 @@ -14,10 +14,10 @@ public class UtilsSFG { if (ServerBootSFG.safeguardPassed) { Iris.safeguard(C.BLUE + "0 Conflicts found"); } else { - if (IrisSafeguard.unstablemode) { + if (IrisSafeguard.instance.unstablemode) { Iris.safeguard(C.DARK_RED + "" + ServerBootSFG.count + " Conflicts found"); } - if (IrisSafeguard.warningmode) { + if (IrisSafeguard.instance.warningmode) { Iris.safeguard(C.YELLOW + "" + ServerBootSFG.count + " Conflicts found"); } diff --git a/core/src/main/java/com/volmit/iris/core/safeguard/handler/onCommandWarning.java b/core/src/main/java/com/volmit/iris/core/safeguard/handler/onCommandWarning.java new file mode 100644 index 000000000..95d74328a --- /dev/null +++ b/core/src/main/java/com/volmit/iris/core/safeguard/handler/onCommandWarning.java @@ -0,0 +1,27 @@ +package com.volmit.iris.core.safeguard.handler; + +import com.volmit.iris.core.IrisSettings; +import com.volmit.iris.core.safeguard.IrisSafeguard; +import com.volmit.iris.util.format.C; +import com.volmit.iris.util.plugin.VolmitSender; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerCommandPreprocessEvent; + +public class onCommandWarning implements Listener { + @EventHandler + public void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) { + if (IrisSettings.get().getSafeguard().userUnstableWarning && IrisSafeguard.instance.unstablemode) { + String command = event.getMessage(); + Player player = event.getPlayer(); + if (command.startsWith("/iris")) { + VolmitSender sender = new VolmitSender(player); + boolean perm = sender.hasPermission("iris.all") || sender.isOp(); + if (perm) { + sender.sendMessage(C.DARK_GRAY + "[" + C.RED + "!" + C.DARK_GRAY+ "]" + C.DARK_RED + "Iris is running unstably! Please resolve this."); + } + } + } + } +} 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 720da652f..519a92b5f 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 @@ -22,7 +22,6 @@ import com.google.gson.Gson; import com.google.gson.JsonSyntaxException; import com.volmit.iris.Iris; import com.volmit.iris.core.IrisSettings; -import com.volmit.iris.core.ServerConfigurator; import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.core.pack.IrisPack; import com.volmit.iris.core.project.IrisProject; @@ -296,7 +295,6 @@ public class StudioSVC implements IrisService { } sender.sendMessage("Successfully Aquired " + d.getName()); - ServerConfigurator.installDataPacks(true); } public KMap getListing(boolean cached) { 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 6096582d2..9f5b58e67 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 @@ -21,13 +21,10 @@ package com.volmit.iris.core.tools; import com.google.common.util.concurrent.AtomicDouble; import com.volmit.iris.Iris; 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 +43,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 @@ -145,7 +141,6 @@ public class IrisCreator { .studio(studio) .smartVanillaHeight(smartVanillaHeight) .create(); - ServerConfigurator.installDataPacks(false); access = (PlatformChunkGenerator) wc.generator(); PlatformChunkGenerator finalAccess1 = access; diff --git a/core/src/main/java/com/volmit/iris/core/tools/IrisNoiseBenchmark.java b/core/src/main/java/com/volmit/iris/core/tools/IrisNoiseBenchmark.java new file mode 100644 index 000000000..0d43b26d1 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/core/tools/IrisNoiseBenchmark.java @@ -0,0 +1,254 @@ +package com.volmit.iris.core.tools; + +import com.volmit.iris.Iris; +import com.volmit.iris.core.loader.IrisData; +import com.volmit.iris.core.project.IrisProject; +import com.volmit.iris.engine.object.*; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.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.function.NoiseProvider; +import com.volmit.iris.util.interpolation.InterpolationMethod; +import com.volmit.iris.util.io.IO; +import com.volmit.iris.util.math.RNG; +import com.volmit.iris.util.noise.CNG; +import com.volmit.iris.util.plugin.VolmitSender; +import com.volmit.iris.util.scheduling.PrecisionStopwatch; + +import java.io.File; +import java.io.IOException; + +public class IrisNoiseBenchmark { + private IrisDimension dimension; + private VolmitSender sender; + + public IrisNoiseBenchmark(IrisDimension dimension, VolmitSender sender) { + this.dimension = dimension; + this.sender = sender; + } + + public void runAll() { + // Todo: Make this more accurate + File pack = dimension.getLoadFile().getParentFile().getParentFile(); + File report = Iris.instance.getDataFile(pack.getName() + "-profile.txt"); + IrisProject project = new IrisProject(pack); + IrisData data = IrisData.get(pack); + + KList fileText = new KList<>(); + + KMap styleTimings = new KMap<>(); + KMap interpolatorTimings = new KMap<>(); + KMap generatorTimings = new KMap<>(); + KMap biomeTimings = new KMap<>(); + KMap regionTimings = new KMap<>(); + KMap dimensionTimings = new KMap<>(); + + sender.sendMessage("Calculating Performance Metrics for Noise generators"); + + for (NoiseStyle i : NoiseStyle.values()) { + CNG c = i.create(new RNG(i.hashCode())); + + for (int j = 0; j < 3000; j++) { + c.noise(j, j + 1000, j * j); + c.noise(j, -j); + } + + PrecisionStopwatch px = PrecisionStopwatch.start(); + + for (int j = 0; j < 100000; j++) { + c.noise(j, j + 1000, j * j); + c.noise(j, -j); + } + + styleTimings.put(i, px.getMilliseconds()); + } + + fileText.add("Noise Style Performance Impacts: "); + + for (NoiseStyle i : styleTimings.sortKNumber()) { + fileText.add(i.name() + ": " + styleTimings.get(i)); + } + + fileText.add(""); + + sender.sendMessage("Calculating Interpolator Timings..."); + + for (InterpolationMethod i : InterpolationMethod.values()) { + IrisInterpolator in = new IrisInterpolator(); + in.setFunction(i); + in.setHorizontalScale(8); + + NoiseProvider np = (x, z) -> Math.random(); + + for (int j = 0; j < 3000; j++) { + in.interpolate(j, -j, np); + } + + PrecisionStopwatch px = PrecisionStopwatch.start(); + + for (int j = 0; j < 100000; j++) { + in.interpolate(j + 10000, -j - 100000, np); + } + + interpolatorTimings.put(i, px.getMilliseconds()); + } + + fileText.add("Noise Interpolator Performance Impacts: "); + + for (InterpolationMethod i : interpolatorTimings.sortKNumber()) { + fileText.add(i.name() + ": " + interpolatorTimings.get(i)); + } + + fileText.add(""); + + sender.sendMessage("Processing Generator Scores: "); + + KMap> btx = new KMap<>(); + + for (String i : data.getGeneratorLoader().getPossibleKeys()) { + KList vv = new KList<>(); + IrisGenerator g = data.getGeneratorLoader().load(i); + KList composites = g.getAllComposites(); + double score = 0; + int m = 0; + for (IrisNoiseGenerator j : composites) { + m++; + score += styleTimings.get(j.getStyle().getStyle()); + vv.add("Composite Noise Style " + m + " " + j.getStyle().getStyle().name() + ": " + styleTimings.get(j.getStyle().getStyle())); + } + + score += interpolatorTimings.get(g.getInterpolator().getFunction()); + vv.add("Interpolator " + g.getInterpolator().getFunction().name() + ": " + interpolatorTimings.get(g.getInterpolator().getFunction())); + generatorTimings.put(i, score); + btx.put(i, vv); + } + + fileText.add("Project Generator Performance Impacts: "); + + for (String i : generatorTimings.sortKNumber()) { + fileText.add(i + ": " + generatorTimings.get(i)); + + btx.get(i).forEach((ii) -> fileText.add(" " + ii)); + } + + fileText.add(""); + + KMap> bt = new KMap<>(); + + for (String i : data.getBiomeLoader().getPossibleKeys()) { + KList vv = new KList<>(); + IrisBiome b = data.getBiomeLoader().load(i); + double score = 0; + + int m = 0; + for (IrisBiomePaletteLayer j : b.getLayers()) { + m++; + score += styleTimings.get(j.getStyle().getStyle()); + vv.add("Palette Layer " + m + ": " + styleTimings.get(j.getStyle().getStyle())); + } + + score += styleTimings.get(b.getBiomeStyle().getStyle()); + vv.add("Biome Style: " + styleTimings.get(b.getBiomeStyle().getStyle())); + score += styleTimings.get(b.getChildStyle().getStyle()); + vv.add("Child Style: " + styleTimings.get(b.getChildStyle().getStyle())); + biomeTimings.put(i, score); + bt.put(i, vv); + } + + fileText.add("Project Biome Performance Impacts: "); + + for (String i : biomeTimings.sortKNumber()) { + fileText.add(i + ": " + biomeTimings.get(i)); + + bt.get(i).forEach((ff) -> fileText.add(" " + ff)); + } + + fileText.add(""); + + for (String i : data.getRegionLoader().getPossibleKeys()) { + IrisRegion b = data.getRegionLoader().load(i); + double score = 0; + + score += styleTimings.get(b.getLakeStyle().getStyle()); + score += styleTimings.get(b.getRiverStyle().getStyle()); + regionTimings.put(i, score); + } + + fileText.add("Project Region Performance Impacts: "); + + for (String i : regionTimings.sortKNumber()) { + fileText.add(i + ": " + regionTimings.get(i)); + } + + fileText.add(""); + + double m = 0; + for (double i : biomeTimings.v()) { + m += i; + } + m /= biomeTimings.size(); + double mm = 0; + for (double i : generatorTimings.v()) { + mm += i; + } + mm /= generatorTimings.size(); + m += mm; + double mmm = 0; + for (double i : regionTimings.v()) { + mmm += i; + } + mmm /= regionTimings.size(); + m += mmm; + + KMap> dt = new KMap<>(); + + for (String i : data.getDimensionLoader().getPossibleKeys()) { + IrisDimension d = data.getDimensionLoader().load(i); + KList vv = new KList<>(); + double score = 0; + + score += styleTimings.get(d.getContinentalStyle().getStyle()); + vv.add("Continental Style: " + styleTimings.get(d.getContinentalStyle().getStyle())); + score += styleTimings.get(d.getRegionStyle().getStyle()); + vv.add("Region Style: " + styleTimings.get(d.getRegionStyle().getStyle())); + score += styleTimings.get(d.getBiomeStyle(InferredType.LAND).getStyle()); + vv.add("LandBiome Style: " + styleTimings.get(d.getBiomeStyle(InferredType.LAND).getStyle())); + score += styleTimings.get(d.getBiomeStyle(InferredType.SEA).getStyle()); + vv.add("OceanBiome Style: " + styleTimings.get(d.getBiomeStyle(InferredType.SEA).getStyle())); + score += styleTimings.get(d.getCaveBiomeStyle().getStyle()); + vv.add("CaveBiome Style: " + styleTimings.get(d.getCaveBiomeStyle().getStyle())); + score += styleTimings.get(d.getShoreBiomeStyle().getStyle()); + vv.add("ShoreBiome Style: " + styleTimings.get(d.getShoreBiomeStyle().getStyle())); + dimensionTimings.put(i, score); + dt.put(i, vv); + + } + + fileText.add("Project Dimension Performance Impacts: "); + + for (String i : dimensionTimings.sortKNumber()) { + fileText.add(i + ": " + dimensionTimings.get(i)); + + dt.get(i).forEach((ff) -> fileText.add(" " + ff)); + } + + fileText.add(""); + + fileText.add("Average Score: " + m); + sender.sendMessage("Score: " + Form.duration(m, 0)); + + try { + IO.writeAll(report, fileText.toString("\n")); + } catch (IOException e) { + Iris.reportError(e); + e.printStackTrace(); + } + + sender.sendMessage(C.GREEN + "Done! " + report.getPath()); + + } +} 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..180109b70 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 @@ -2,9 +2,17 @@ package com.volmit.iris.core.tools; import com.volmit.iris.Iris; +import com.volmit.iris.core.IrisSettings; +import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.core.pregenerator.PregenTask; +import com.volmit.iris.core.pregenerator.methods.HeadlessPregenMethod; +import com.volmit.iris.core.pregenerator.methods.HybridPregenMethod; +import com.volmit.iris.core.service.StudioSVC; +import com.volmit.iris.engine.IrisEngine; import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.engine.framework.EngineTarget; import com.volmit.iris.engine.object.IrisDimension; +import com.volmit.iris.engine.object.IrisWorld; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.exceptions.IrisException; @@ -36,28 +44,32 @@ public class IrisPackBenchmarking { public static boolean benchmarkInProgress = false; private IrisDimension IrisDimension; private int radius; + private final boolean headless; + private final boolean gui; private boolean finished = false; + private Engine engine; PrecisionStopwatch stopwatch; - public IrisPackBenchmarking(IrisDimension dimension, int r) { + public IrisPackBenchmarking(IrisDimension dimension, int r, boolean headless, boolean gui) { instance = this; this.IrisDimension = dimension; this.radius = r; - runBenchmark(); + this.headless = headless; + this.gui = gui; } - private void runBenchmark() { + public 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"); + File file = new File(Bukkit.getWorldContainer(), "benchmark"); if (file.exists()) { deleteDirectory(file.toPath()); } - createBenchmark(); - while (!IrisToolbelt.isIrisWorld(Bukkit.getWorld("benchmark"))) { + engine = createBenchmark(); + while (!headless && !IrisToolbelt.isIrisWorld(Bukkit.getWorld("benchmark"))) { J.sleep(1000); Iris.debug("Iris PackBenchmark: Waiting..."); } @@ -75,7 +87,6 @@ public class IrisPackBenchmarking { public void finishedBenchmark(KList cps) { try { String time = Form.duration(stopwatch.getMillis()); - Engine engine = IrisToolbelt.access(Bukkit.getWorld("benchmark")).getEngine(); Iris.info("-----------------"); Iris.info("Results:"); Iris.info("- Total time: " + time); @@ -88,8 +99,8 @@ 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("plugins" + File.separator + "Iris", IrisDimension.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"); @@ -123,15 +134,34 @@ public class IrisPackBenchmarking { e.printStackTrace(); } } - private void createBenchmark(){ + private Engine createBenchmark(){ try { - IrisToolbelt.createWorld() - .dimension(IrisDimension.getName()) + if (headless) { + Iris.info("Using headless benchmark!"); + IrisWorld world = IrisWorld.builder() + .name("benchmark") + .minHeight(IrisDimension.getMinHeight()) + .maxHeight(IrisDimension.getMaxHeight()) + .seed(1337) + .worldFolder(new File(Bukkit.getWorldContainer(), "benchmark")) + .environment(IrisDimension.getEnvironment()) + .build(); + Iris.service(StudioSVC.class).installIntoWorld( + Iris.getSender(), + IrisDimension.getLoadKey(), + world.worldFolder()); + var data = IrisData.get(new File(world.worldFolder(), "iris/pack")); + var dim = data.getDimensionLoader().load(IrisDimension.getLoadKey()); + return new IrisEngine(new EngineTarget(world, dim, data), false); + } + Iris.info("Using Standard benchmark!"); + return IrisToolbelt.access(IrisToolbelt.createWorld() + .dimension(IrisDimension.getLoadKey()) .name("benchmark") .seed(1337) .studio(false) .benchmark(true) - .create(); + .create()).getEngine(); } catch (IrisException e) { throw new RuntimeException(e); } @@ -142,12 +172,12 @@ public class IrisPackBenchmarking { int z = 0; IrisToolbelt.pregenerate(PregenTask .builder() - .gui(false) + .gui(gui) .center(new Position2(x, z)) .width(5) .height(5) - .build(), Bukkit.getWorld("benchmark") - ); + .build(), headless ? new HeadlessPregenMethod(engine) : new HybridPregenMethod(engine.getWorld().realWorld(), + IrisSettings.getThreadCount(IrisSettings.get().getConcurrency().getParallelism())), engine); } private double calculateAverage(KList list) { diff --git a/core/src/main/java/com/volmit/iris/core/tools/IrisWorldCreator.java b/core/src/main/java/com/volmit/iris/core/tools/IrisWorldCreator.java index b815d3809..36aa52789 100644 --- a/core/src/main/java/com/volmit/iris/core/tools/IrisWorldCreator.java +++ b/core/src/main/java/com/volmit/iris/core/tools/IrisWorldCreator.java @@ -19,6 +19,7 @@ package com.volmit.iris.core.tools; import com.volmit.iris.core.loader.IrisData; +import com.volmit.iris.core.nms.INMS; import com.volmit.iris.engine.object.*; import com.volmit.iris.engine.platform.BukkitChunkGenerator; import org.bukkit.Bukkit; @@ -84,6 +85,9 @@ public class IrisWorldCreator { ? dim.getLoader().getDataFolder() : new File(w.worldFolder(), "iris/pack"), dimensionName, smartVanillaHeight); + if (!INMS.get().registerDimension(name, dim)) { + throw new IllegalStateException("Unable to register dimension " + dim.getName()); + } return new WorldCreator(name) .environment(findEnvironment()) diff --git a/core/src/main/java/com/volmit/iris/core/tools/IrisWorldDump.java b/core/src/main/java/com/volmit/iris/core/tools/IrisWorldDump.java new file mode 100644 index 000000000..c6b8da0bf --- /dev/null +++ b/core/src/main/java/com/volmit/iris/core/tools/IrisWorldDump.java @@ -0,0 +1,241 @@ +package com.volmit.iris.core.tools; + +import com.volmit.iris.Iris; +import com.volmit.iris.core.IrisSettings; +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.math.M; +import com.volmit.iris.util.math.RollingSequence; +import com.volmit.iris.util.nbt.mca.Chunk; +import com.volmit.iris.util.nbt.mca.MCAFile; +import com.volmit.iris.util.nbt.mca.MCAUtil; +import com.volmit.iris.util.nbt.tag.CompoundTag; +import com.volmit.iris.util.nbt.tag.StringTag; +import com.volmit.iris.util.plugin.VolmitSender; +import lombok.Getter; +import lombok.Setter; +import org.bukkit.World; + +import java.io.File; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReferenceArray; + +public class IrisWorldDump { + private KMap storage; + private AtomicLong airStorage; + private World world; + private File MCADirectory; + private AtomicInteger threads; + private AtomicInteger regionsProcessed; + private AtomicInteger chunksProcessed; + private AtomicInteger totalToProcess; + private AtomicInteger totalMaxChunks; + private AtomicInteger totalMCAFiles; + private RollingSequence chunksPerSecond; + private Engine engine = null; + private Boolean IrisWorld; + private VolmitSender sender; + private ExecutorService executor; + private ScheduledExecutorService scheduler; + private AtomicLong startTime; + private File dumps; + private File worldDump; + private int mcaCacheSize; + private File temp; + private File blocks; + private File structures; + + public IrisWorldDump(World world, VolmitSender sender) { + sender.sendMessage("Initializing IrisWorldDump..."); + this.world = world; + this.sender = sender; + this.MCADirectory = new File(world.getWorldFolder(), "region"); + this.dumps = new File("plugins" + File.separator + "iris", "dumps"); + this.worldDump = new File(dumps, world.getName()); + this.mcaCacheSize = IrisSettings.get().getWorldDump().mcaCacheSize; + this.regionsProcessed = new AtomicInteger(0); + this.chunksProcessed = new AtomicInteger(0); + this.totalToProcess = new AtomicInteger(0); + this.chunksPerSecond = new RollingSequence(10); + this.temp = new File(worldDump, "temp"); + this.executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() - 1); + this.scheduler = Executors.newSingleThreadScheduledExecutor(); + this.startTime = new AtomicLong(); + this.storage = new KMap<>(); + this.airStorage = new AtomicLong(0); + + this.blocks = new File(worldDump, "blocks"); + this.structures = new File(worldDump, "structures"); + + try { + this.engine = IrisToolbelt.access(world).getEngine(); + this.IrisWorld = true; + } catch (Exception e) { + this.IrisWorld = false; + } + } + + + public void start() { + + if (!dumps.exists()) { + if (!dumps.mkdirs()) { + System.err.println("Failed to create dump directory."); + return; + } + } + + try { + CompletableFuture mcaCount = CompletableFuture.supplyAsync(this::totalMcaFiles); + CompletableFuture chunkCount = CompletableFuture.supplyAsync(this::totalMCAChunks); + this.totalMCAFiles = new AtomicInteger(mcaCount.get()); + this.totalMaxChunks = new AtomicInteger(chunkCount.get()); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + dump(); + updater(); + } + + private void updater() { + startTime.set(System.currentTimeMillis()); + scheduler.scheduleAtFixedRate(() -> { + long eta = computeETA(); + long elapsedSeconds = (System.currentTimeMillis() - startTime.get()) / 3000; + int processed = chunksProcessed.get(); + double cps = elapsedSeconds > 0 ? processed / (double) elapsedSeconds : 0; + chunksPerSecond.put(cps); + double percentage = ((double) chunksProcessed.get() / (double) totalMaxChunks.get()) * 100; + Iris.info("Processed: " + Form.f(processed) + " of " + Form.f(totalMaxChunks.get()) + " (%.0f%%) " + Form.f(chunksPerSecond.getAverage()) + "/s, ETA: " + Form.duration(eta, 2), percentage); + + }, 1, 3, TimeUnit.SECONDS); + + } + + public class blockData { + @Getter + @Setter + private String block; + private int biome; + private int height; + + public blockData(String b, int bm, int h) { + this.block = b; + this.height = h; + this.biome = bm; + } + } + + + private void dump() { + Iris.info("Starting the dump process."); + int threads = Runtime.getRuntime().availableProcessors(); + AtomicInteger f = new AtomicInteger(); + for (File mcaFile : MCADirectory.listFiles()) { + if (mcaFile.getName().endsWith(".mca")) { + executor.submit(() -> { + try { + processMCARegion( MCAUtil.read(mcaFile)); + } catch (Exception e) { + f.getAndIncrement(); + Iris.error("Failed to read mca file"); + e.printStackTrace(); + } + }); + } + } + } + + private void processMCARegion(MCAFile mca) { + AtomicReferenceArray chunks = new AtomicReferenceArray<>(1024); + for (int i = 0; i < chunks.length(); i++) { + chunks.set(i, mca.getChunks().get(i)); + } + for (int i = 0; i < chunks.length(); i++) { + Chunk chunk = chunks.get(i); + if (chunk != null) { + int CHUNK_HEIGHT = (world.getMaxHeight() - world.getMinHeight()); + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + for (int y = 0; y < CHUNK_HEIGHT; y++) { + CompoundTag tag = chunk.getBlockStateAt(x, y, z); + int biome = chunk.getBiomeAt(x, y, z); + if (tag == null) { + String blockName = "minecraft:air"; + //storage.compute(blockName, (key, count) -> (count == null) ? 1 : count + 1); + airStorage.getAndIncrement(); + int ii = 0; + } else { + StringTag nameTag = tag.getStringTag("Name"); + String blockName = nameTag.getValue(); + blockData data = new blockData(blockName, biome, y); + storage.compute(data, (key, count) -> (count == null) ? 1 : count + 1); + int ii = 0; + } + } + } + } + chunksProcessed.getAndIncrement(); + } + } + regionsProcessed.getAndIncrement(); + } + + private int totalMCAChunks() { + AtomicInteger chunks = new AtomicInteger(); + CountDownLatch latch = new CountDownLatch(totalMcaFiles() * 1024); + for (File mcafile : MCADirectory.listFiles()) { + executor.submit(() -> { + try { + if (mcafile.getName().endsWith(".mca")) { + MCAFile mca = MCAUtil.read(mcafile); + for (int width = 0; width < 32; width++) { + for (int depth = 0; depth < 32; depth++) { + Chunk chunk = mca.getChunk(width, depth); + if (chunk != null) { + chunks.getAndIncrement(); + } + latch.countDown(); + } + } + } + } catch (Exception e) { + Iris.error("Failed to read mca file"); + e.printStackTrace(); + } + }); + } + + try { + latch.await(); + } catch (Exception e) { + e.printStackTrace(); + } + + return chunks.get(); + } + + private int totalMcaFiles() { + int size = 0; + for (File mca : MCADirectory.listFiles()) { + if (mca.getName().endsWith(".mca")) { + size++; + } + } + return size; + } + + private long computeETA() { + return (long) (totalMaxChunks.get() > 1024 ? // Generated chunks exceed 1/8th of total? + // If yes, use smooth function (which gets more accurate over time since its less sensitive to outliers) + ((totalMaxChunks.get() - chunksProcessed.get()) * ((double) (M.ms() - startTime.get()) / (double) chunksProcessed.get())) : + // If no, use quick function (which is less accurate over time but responds better to the initial delay) + ((totalMaxChunks.get() - chunksProcessed.get()) / chunksPerSecond.getAverage()) * 1000 + ); + } + +} diff --git a/core/src/main/java/com/volmit/iris/engine/IrisEngine.java b/core/src/main/java/com/volmit/iris/engine/IrisEngine.java index e1ea0a00e..18b67d056 100644 --- a/core/src/main/java/com/volmit/iris/engine/IrisEngine.java +++ b/core/src/main/java/com/volmit/iris/engine/IrisEngine.java @@ -21,8 +21,6 @@ package com.volmit.iris.engine; import com.google.common.util.concurrent.AtomicDouble; import com.google.gson.Gson; import com.volmit.iris.Iris; -import com.volmit.iris.core.IrisSettings; -import com.volmit.iris.core.ServerConfigurator; import com.volmit.iris.core.events.IrisEngineHotloadEvent; import com.volmit.iris.core.gui.PregeneratorJob; import com.volmit.iris.core.nms.container.BlockPos; @@ -53,7 +51,6 @@ import com.volmit.iris.util.scheduling.PrecisionStopwatch; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.ToString; -import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.block.Biome; import org.bukkit.block.data.BlockData; @@ -66,8 +63,6 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; -import java.util.regex.Matcher; -import java.util.regex.Pattern; @Data @EqualsAndHashCode(exclude = "context") @@ -242,11 +237,6 @@ public class IrisEngine implements Engine { getTarget().setDimension(getData().getDimensionLoader().load(getDimension().getLoadKey())); prehotload(); setupEngine(); - J.a(() -> { - synchronized (ServerConfigurator.class) { - ServerConfigurator.installDataPacks(false); - } - }); } @Override @@ -286,6 +276,13 @@ public class IrisEngine implements Engine { return generated.get(); } + @Override + public void addGenerated() { + if (generated.incrementAndGet() == 661) { + J.a(() -> getData().savePrefetch(this)); + } + } + @Override public double getGeneratedPerSecond() { if (perSecondLatch.flip()) { @@ -468,11 +465,7 @@ public class IrisEngine implements Engine { getMantle().getMantle().flag(x >> 4, z >> 4, MantleFlag.REAL, true); getMetrics().getTotal().put(p.getMilliseconds()); - generated.incrementAndGet(); - - if (generated.get() == 661) { - J.a(() -> getData().savePrefetch(this)); - } + addGenerated(); } catch (Throwable e) { Iris.reportError(e); fail("Failed to generate " + x + ", " + z, e); 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 dc3ac48c5..b7aa7298e 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 @@ -41,9 +41,11 @@ public class IrisSeaFloorDecorator extends IrisEngineDecorator { 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())); + 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())); } @@ -73,6 +75,5 @@ public class IrisSeaFloorDecorator extends IrisEngineDecorator { } } } - } } 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 e1f6560dc..0809d0932 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 @@ -577,6 +577,8 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat int getGenerated(); + void addGenerated(); + default IrisPosition lookForStreamResult(T find, ProceduralStream stream, Function2 matcher, long timeout) { AtomicInteger checked = new AtomicInteger(); AtomicLong time = new AtomicLong(M.ms()); diff --git a/core/src/main/java/com/volmit/iris/engine/jvm/VMJavaFX.java b/core/src/main/java/com/volmit/iris/engine/jvm/VMJavaFX.java new file mode 100644 index 000000000..d187c333b --- /dev/null +++ b/core/src/main/java/com/volmit/iris/engine/jvm/VMJavaFX.java @@ -0,0 +1,32 @@ +package com.volmit.iris.engine.jvm; + +import com.volmit.iris.util.plugin.VolmitSender; + +public class VMJavaFX { + private VolmitSender sender; + public VMJavaFX(VolmitSender user) { + this.sender = user; + + } + + public void start() { + try { + // Start JavaFX in a new JVM + ProcessBuilder processBuilder = new ProcessBuilder( + "java", + "--module-path", "path/to/javafx-sdk/lib", // Set path to JavaFX SDK + "--add-modules", "javafx.controls,javafx.fxml", + "-jar", "path/to/javafx-application.jar" + ); + processBuilder.inheritIO(); + processBuilder.start(); + sender.sendMessage("JavaFX application is launched!"); + } catch (Exception e) { + sender.sendMessage("Failed to launch JavaFX application."); + e.printStackTrace(); + } + } + + + +} diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisBiome.java b/core/src/main/java/com/volmit/iris/engine/object/IrisBiome.java index d75dca7ad..669917ab2 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisBiome.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisBiome.java @@ -170,7 +170,6 @@ public class IrisBiome extends IrisRegistrant implements IRare { @Desc("Collection of ores to be generated") @ArrayType(type = IrisOreGenerator.class, min = 1) private KList ores = new KList<>(); - public BlockData generateOres(int x, int y, int z, RNG rng, IrisData data) { if (ores.isEmpty()) { return null; 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..f491dff68 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 @@ -19,7 +19,7 @@ package com.volmit.iris.engine.object; import com.volmit.iris.Iris; -import com.volmit.iris.core.nms.datapack.IDataFixer; +import com.volmit.iris.core.nms.datapack.DataVersion; import com.volmit.iris.engine.object.annotations.*; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; @@ -92,7 +92,7 @@ public class IrisBiomeCustom { @Desc("The color of foliage (hex format). Leave blank / don't define to not change") private String foliageColor = ""; - public String generateJson(IDataFixer fixer) { + public String generateJson() { JSONObject effects = new JSONObject(); effects.put("sky_color", parseColor(getSkyColor())); effects.put("fog_color", parseColor(getFogColor())); @@ -158,7 +158,7 @@ public class IrisBiomeCustom { j.put("spawners", spawners); } - return fixer.fixCustomBiome(this, j).toString(4); + return DataVersion.getDefault().fixCustomBiome(this, j).toString(4); } private int parseColor(String c) { 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 d2aa52db2..98049d32f 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 @@ -1,581 +1,388 @@ -/* - * 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.Iris; -import com.volmit.iris.core.loader.IrisData; -import com.volmit.iris.core.loader.IrisRegistrant; -import com.volmit.iris.core.nms.datapack.IDataFixer; -import com.volmit.iris.engine.data.cache.AtomicCache; -import com.volmit.iris.engine.object.annotations.*; -import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.data.DataProvider; -import com.volmit.iris.util.io.IO; -import com.volmit.iris.util.json.JSONObject; -import com.volmit.iris.util.math.Position2; -import com.volmit.iris.util.math.RNG; -import com.volmit.iris.util.noise.CNG; -import com.volmit.iris.util.plugin.VolmitSender; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; -import lombok.experimental.Accessors; -import org.bukkit.Material; -import org.bukkit.World.Environment; -import org.bukkit.block.data.BlockData; - -import java.io.File; -import java.io.IOException; - -@Accessors(chain = true) -@AllArgsConstructor -@NoArgsConstructor -@Desc("Represents a dimension") -@Data -@EqualsAndHashCode(callSuper = false) -public class IrisDimension extends IrisRegistrant { - public static final BlockData STONE = Material.STONE.createBlockData(); - public static final BlockData WATER = Material.WATER.createBlockData(); - private static final String DP_OVERWORLD_DEFAULT = """ - { - "ambient_light": 0.0, - "bed_works": true, - "coordinate_scale": 1.0, - "effects": "minecraft:overworld", - "has_ceiling": false, - "has_raids": true, - "has_skylight": true, - "infiniburn": "#minecraft:infiniburn_overworld", - "monster_spawn_block_light_limit": 0, - "monster_spawn_light_level": { - "type": "minecraft:uniform", - "value": { - "max_inclusive": 7, - "min_inclusive": 0 - } - }, - "natural": true, - "piglin_safe": false, - "respawn_anchor_works": false, - "ultrawarm": false - }"""; - - private static final String DP_NETHER_DEFAULT = """ - { - "ambient_light": 0.1, - "bed_works": false, - "coordinate_scale": 8.0, - "effects": "minecraft:the_nether", - "fixed_time": 18000, - "has_ceiling": true, - "has_raids": false, - "has_skylight": false, - "infiniburn": "#minecraft:infiniburn_nether", - "monster_spawn_block_light_limit": 15, - "monster_spawn_light_level": 7, - "natural": false, - "piglin_safe": true, - "respawn_anchor_works": true, - "ultrawarm": true - }"""; - - private static final String DP_END_DEFAULT = """ - { - "ambient_light": 0.0, - "bed_works": false, - "coordinate_scale": 1.0, - "effects": "minecraft:the_end", - "fixed_time": 6000, - "has_ceiling": false, - "has_raids": true, - "has_skylight": false, - "infiniburn": "#minecraft:infiniburn_end", - "monster_spawn_block_light_limit": 0, - "monster_spawn_light_level": { - "type": "minecraft:uniform", - "value": { - "max_inclusive": 7, - "min_inclusive": 0 - } - }, - "natural": false, - "piglin_safe": false, - "respawn_anchor_works": false, - "ultrawarm": false - }"""; - private final transient AtomicCache parallaxSize = new AtomicCache<>(); - private final transient AtomicCache rockLayerGenerator = new AtomicCache<>(); - private final transient AtomicCache fluidLayerGenerator = new AtomicCache<>(); - private final transient AtomicCache coordFracture = new AtomicCache<>(); - private final transient AtomicCache sinr = new AtomicCache<>(); - private final transient AtomicCache cosr = new AtomicCache<>(); - private final transient AtomicCache rad = new AtomicCache<>(); - private final transient AtomicCache featuresUsed = new AtomicCache<>(); - private final transient AtomicCache> strongholdsCache = new AtomicCache<>(); - @MinNumber(2) - @Required - @Desc("The human readable name of this dimension") - private String name = "A Dimension"; - @MinNumber(1) - @MaxNumber(2032) - @Desc("Maximum height at which players can be teleported to through gameplay.") - private int logicalHeight = 256; - @Desc("Maximum height at which players can be teleported to through gameplay.") - private int logicalHeightEnd = 256; - @Desc("Maximum height at which players can be teleported to through gameplay.") - private int logicalHeightNether = 256; - @RegistryListResource(IrisJigsawStructure.class) - @Desc("If defined, Iris will place the given jigsaw structure where minecraft should place the overworld stronghold.") - private String stronghold; - @Desc("If set to true, Iris will remove chunks to allow visualizing cross sections of chunks easily") - private boolean debugChunkCrossSections = false; - @Desc("Vertically split up the biome palettes with 3 air blocks in between to visualize them") - private boolean explodeBiomePalettes = false; - @Desc("Studio Mode for testing different parts of the world") - private StudioMode studioMode = StudioMode.NORMAL; - @MinNumber(1) - @MaxNumber(16) - @Desc("Customize the palette height explosion") - private int explodeBiomePaletteSize = 3; - @MinNumber(2) - @MaxNumber(16) - @Desc("Every X/Z % debugCrossSectionsMod == 0 cuts the chunk") - private int debugCrossSectionsMod = 3; - @Desc("The average distance between strongholds") - private int strongholdJumpDistance = 1280; - @Desc("Define the maximum strongholds to place") - private int maxStrongholds = 14; - @Desc("Tree growth override settings") - private IrisTreeSettings treeSettings = new IrisTreeSettings(); - @Desc("Spawn Entities in this dimension over time. Iris will continually replenish these mobs just like vanilla does.") - @ArrayType(min = 1, type = String.class) - @RegistryListResource(IrisSpawner.class) - private KList entitySpawners = new KList<>(); - @Desc("Reference loot tables in this area") - private IrisLootReference loot = new IrisLootReference(); - @MinNumber(0) - @Desc("The version of this dimension. Changing this will stop users from accidentally upgrading (and breaking their worlds).") - private int version = 1; - @ArrayType(min = 1, type = IrisBlockDrops.class) - @Desc("Define custom block drops for this dimension") - private KList blockDrops = new KList<>(); - @Desc("Should bedrock be generated or not.") - private boolean bedrock = true; - @MinNumber(0) - @MaxNumber(1) - @Desc("The land chance. Up to 1.0 for total land or 0.0 for total sea") - private double landChance = 0.625; - @Desc("The placement style of regions") - private IrisGeneratorStyle regionStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style(); - @Desc("The placement style of land/sea") - private IrisGeneratorStyle continentalStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style(); - @Desc("The placement style of biomes") - private IrisGeneratorStyle landBiomeStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style(); - @Desc("The placement style of biomes") - private IrisGeneratorStyle shoreBiomeStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style(); - @Desc("The placement style of biomes") - private IrisGeneratorStyle seaBiomeStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style(); - @Desc("The placement style of biomes") - private IrisGeneratorStyle caveBiomeStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style(); - @Desc("Instead of filling objects with air, fills them with cobweb so you can see them") - private boolean debugSmartBore = false; - @Desc("Generate decorations or not") - private boolean decorate = true; - @Desc("Use post processing or not") - private boolean postProcessing = true; - @Desc("Add slabs in post processing") - private boolean postProcessingSlabs = true; - @Desc("Add painted walls in post processing") - private boolean postProcessingWalls = true; - @Desc("Carving configuration for the dimension") - private IrisCarving carving = new IrisCarving(); - @Desc("Configuration of fluid bodies such as rivers & lakes") - private IrisFluidBodies fluidBodies = new IrisFluidBodies(); - @Desc("forceConvertTo320Height") - private Boolean forceConvertTo320Height = false; - @Desc("The world environment") - private Environment environment = Environment.NORMAL; - @RegistryListResource(IrisRegion.class) - @Required - @ArrayType(min = 1, type = String.class) - @Desc("Define all of the regions to include in this dimension. Dimensions -> Regions -> Biomes -> Objects etc") - private KList regions = new KList<>(); - @ArrayType(min = 1, type = IrisJigsawStructurePlacement.class) - @Desc("Jigsaw structures") - private KList jigsawStructures = new KList<>(); - @Required - @MinNumber(0) - @MaxNumber(1024) - @Desc("The fluid height for this dimension") - private int fluidHeight = 63; - @Desc("Define the min and max Y bounds of this dimension. Please keep in mind that Iris internally generates from 0 to (max - min). \n\nFor example at -64 to 320, Iris is internally generating to 0 to 384, then on outputting chunks, it shifts it down by the min height (64 blocks). The default is -64 to 320. \n\nThe fluid height is placed at (fluid height + min height). So a fluid height of 63 would actually show up in the world at 1.") - private IrisRange dimensionHeight = new IrisRange(-64, 320); - @Desc("Define the min and max Y bounds of this dimension. Please keep in mind that Iris internally generates from 0 to (max - min). \n\nFor example at -64 to 320, Iris is internally generating to 0 to 384, then on outputting chunks, it shifts it down by the min height (64 blocks). The default is -64 to 320. \n\nThe fluid height is placed at (fluid height + min height). So a fluid height of 63 would actually show up in the world at 1.") - private IrisRange dimensionHeightEnd = new IrisRange(-64, 320); - @Desc("Define the min and max Y bounds of this dimension. Please keep in mind that Iris internally generates from 0 to (max - min). \n\nFor example at -64 to 320, Iris is internally generating to 0 to 384, then on outputting chunks, it shifts it down by the min height (64 blocks). The default is -64 to 320. \n\nThe fluid height is placed at (fluid height + min height). So a fluid height of 63 would actually show up in the world at 1.") - private IrisRange dimensionHeightNether = new IrisRange(-64, 320); - @Desc("Enable smart vanilla height") - private boolean smartVanillaHeight = false; - @RegistryListResource(IrisBiome.class) - @Desc("Keep this either undefined or empty. Setting any biome name into this will force iris to only generate the specified biome. Great for testing.") - private String focus = ""; - @RegistryListResource(IrisRegion.class) - @Desc("Keep this either undefined or empty. Setting any region name into this will force iris to only generate the specified region. Great for testing.") - private String focusRegion = ""; - @MinNumber(0.0001) - @MaxNumber(512) - @Desc("Zoom in or out the biome size. Higher = bigger biomes") - private double biomeZoom = 5D; - @MinNumber(0) - @MaxNumber(360) - @Desc("You can rotate the input coordinates by an angle. This can make terrain appear more natural (less sharp corners and lines). This literally rotates the entire dimension by an angle. Hint: Try 12 degrees or something not on a 90 or 45 degree angle.") - private double dimensionAngleDeg = 0; - @Required - @Desc("Define the mode of this dimension (required!)") - private IrisDimensionMode mode = new IrisDimensionMode(); - @MinNumber(0) - @MaxNumber(8192) - @Desc("Coordinate fracturing applies noise to the input coordinates. This creates the 'iris swirls' and wavy features. The distance pushes these waves further into places they shouldnt be. This is a block value multiplier.") - private double coordFractureDistance = 20; - @MinNumber(0.0001) - @MaxNumber(512) - @Desc("Coordinate fracturing zoom. Higher = less frequent warping, Lower = more frequent and rapid warping / swirls.") - private double coordFractureZoom = 8; - @MinNumber(0.0001) - @MaxNumber(512) - @Desc("This zooms in the land space") - private double landZoom = 1; - @MinNumber(0.0001) - @MaxNumber(512) - @Desc("This zooms oceanic biomes") - private double seaZoom = 1; - @MinNumber(0.0001) - @MaxNumber(512) - @Desc("Zoom in continents") - private double continentZoom = 1; - @MinNumber(0.0001) - @MaxNumber(512) - @Desc("Change the size of regions") - private double regionZoom = 1; - @Desc("Disable this to stop placing objects, entities, features & updates") - private boolean useMantle = true; - @Desc("Prevent Leaf decay as if placed in creative mode") - private boolean preventLeafDecay = false; - @ArrayType(min = 1, type = IrisDepositGenerator.class) - @Desc("Define global deposit generators") - private KList deposits = new KList<>(); - @ArrayType(min = 1, type = IrisShapedGeneratorStyle.class) - @Desc("Overlay additional noise on top of the interoplated terrain.") - private KList overlayNoise = new KList<>(); - @Desc("If true, the spawner system has infinite energy. This is NOT recommended because it would allow for mobs to keep spawning over and over without a rate limit") - private boolean infiniteEnergy = false; - @MinNumber(0) - @MaxNumber(10000) - @Desc("This is the maximum energy you can have in a dimension") - private double maximumEnergy = 1000; - @MinNumber(0.0001) - @MaxNumber(512) - @Desc("The rock zoom mostly for zooming in on a wispy palette") - private double rockZoom = 5; - @Desc("The palette of blocks for 'stone'") - 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("Collection of ores to be generated") - @ArrayType(type = IrisOreGenerator.class, min = 1) - private KList ores = new KList<>(); - @MinNumber(0) - @MaxNumber(318) - @Desc("The Subterrain Fluid Layer Height") - private int caveLavaHeight = 8; - - public int getMaxHeight() { - return (int) getDimensionHeight().getMax(); - } - - public int getMinHeight() { - return (int) getDimensionHeight().getMin(); - } - - public BlockData generateOres(int x, int y, int z, RNG rng, IrisData data) { - if (ores.isEmpty()) { - return null; - } - BlockData b = null; - for (IrisOreGenerator i : ores) { - - b = i.generate(x, y, z, rng, data); - if (b != null) { - return b; - } - } - return null; - } - - public KList getStrongholds(long seed) { - return strongholdsCache.aquire(() -> { - KList pos = new KList<>(); - int jump = strongholdJumpDistance; - RNG rng = new RNG((seed * 223) + 12945); - for (int i = 0; i < maxStrongholds + 1; i++) { - int m = i + 1; - pos.add(new Position2( - (int) ((rng.i(jump * i) + (jump * i)) * (rng.b() ? -1D : 1D)), - (int) ((rng.i(jump * i) + (jump * i)) * (rng.b() ? -1D : 1D)) - )); - } - - pos.remove(0); - - return pos; - }); - } - - public int getFluidHeight() { - return fluidHeight - (int) dimensionHeight.getMin(); - } - - public CNG getCoordFracture(RNG rng, int signature) { - return coordFracture.aquire(() -> - { - CNG coordFracture = CNG.signature(rng.nextParallelRNG(signature)); - coordFracture.scale(0.012 / coordFractureZoom); - return coordFracture; - }); - } - - public double getDimensionAngle() { - return rad.aquire(() -> Math.toRadians(dimensionAngleDeg)); - } - - public Environment getEnvironment() { - return environment; - } - - public boolean hasFocusRegion() { - return !focusRegion.equals(""); - } - - public String getFocusRegion() { - return focusRegion; - } - - public double sinRotate() { - return sinr.aquire(() -> Math.sin(getDimensionAngle())); - } - - public double cosRotate() { - return cosr.aquire(() -> Math.cos(getDimensionAngle())); - } - - public KList getAllRegions(DataProvider g) { - KList r = new KList<>(); - - for (String i : getRegions()) { - r.add(g.getData().getRegionLoader().load(i)); - } - - return r; - } - - public KList getAllAnyRegions() { - KList r = new KList<>(); - - for (String i : getRegions()) { - r.add(IrisData.loadAnyRegion(i)); - } - - return r; - } - - public KList getAllBiomes(DataProvider g) { - return g.getData().getBiomeLoader().loadAll(g.getData().getBiomeLoader().getPossibleKeys()); - } - - public KList getAllAnyBiomes() { - KList r = new KList<>(); - - for (IrisRegion i : getAllAnyRegions()) { - if (i == null) { - continue; - } - - r.addAll(i.getAllAnyBiomes()); - } - - return r; - } - - public IrisGeneratorStyle getBiomeStyle(InferredType type) { - switch (type) { - case CAVE: - return caveBiomeStyle; - case LAND: - return landBiomeStyle; - case SEA: - return seaBiomeStyle; - case SHORE: - return shoreBiomeStyle; - default: - break; - } - - return landBiomeStyle; - } - - public boolean installDataPack(IDataFixer fixer, DataProvider data, File datapacks, double ultimateMaxHeight, double ultimateMinHeight) { - boolean write = false; - boolean changed = false; - - IO.delete(new File(datapacks, "iris/data/" + getLoadKey().toLowerCase())); - - for (IrisBiome i : getAllBiomes(data)) { - if (i.isCustom()) { - write = true; - - for (IrisBiomeCustom j : i.getCustomDerivitives()) { - File output = new File(datapacks, "iris/data/" + getLoadKey().toLowerCase() + "/worldgen/biome/" + j.getId() + ".json"); - - if (!output.exists()) { - changed = true; - } - - Iris.verbose(" Installing Data Pack Biome: " + output.getPath()); - output.getParentFile().mkdirs(); - try { - IO.writeAll(output, j.generateJson(fixer)); - } catch (IOException e) { - Iris.reportError(e); - e.printStackTrace(); - } - } - } - } - - if (!dimensionHeight.equals(new IrisRange(-64, 320)) && this.name.equalsIgnoreCase("overworld")) { - Iris.verbose(" Installing Data Pack Dimension Types: \"minecraft:overworld\", \"minecraft:the_nether\", \"minecraft:the_end\""); - dimensionHeight.setMax(ultimateMaxHeight); - dimensionHeight.setMin(ultimateMinHeight); - changed = writeDimensionType(fixer, changed, datapacks); - } - - if (write) { - File mcm = new File(datapacks, "iris/pack.mcmeta"); - try { - IO.writeAll(mcm, """ - { - "pack": { - "description": "Iris Data Pack. This pack contains all installed Iris Packs' resources.", - "pack_format": 10 - } - } - """); - } catch (IOException e) { - Iris.reportError(e); - e.printStackTrace(); - } - Iris.verbose(" Installing Data Pack MCMeta: " + mcm.getPath()); - } - - return changed; - } - - @Override - public String getFolderName() { - return "dimensions"; - } - - @Override - public String getTypeName() { - return "Dimension"; - } - - @Override - public void scanForErrors(JSONObject p, VolmitSender sender) { - - } - - public boolean writeDimensionType(IDataFixer fixer, boolean changed, File datapacks) { - File dimTypeOverworld = new File(datapacks, "iris/data/minecraft/dimension_type/overworld.json"); - if (!dimTypeOverworld.exists()) - changed = true; - dimTypeOverworld.getParentFile().mkdirs(); - try { - IO.writeAll(dimTypeOverworld, generateDatapackJsonOverworld(fixer)); - } catch (IOException e) { - Iris.reportError(e); - e.printStackTrace(); - } - - - File dimTypeNether = new File(datapacks, "iris/data/minecraft/dimension_type/the_nether.json"); - if (!dimTypeNether.exists()) - changed = true; - dimTypeNether.getParentFile().mkdirs(); - try { - IO.writeAll(dimTypeNether, generateDatapackJsonNether(fixer)); - } catch (IOException e) { - Iris.reportError(e); - e.printStackTrace(); - } - - - File dimTypeEnd = new File(datapacks, "iris/data/minecraft/dimension_type/the_end.json"); - if (!dimTypeEnd.exists()) - changed = true; - dimTypeEnd.getParentFile().mkdirs(); - try { - IO.writeAll(dimTypeEnd, generateDatapackJsonEnd(fixer)); - } catch (IOException e) { - Iris.reportError(e); - e.printStackTrace(); - } - - return changed; - } - - private String generateDatapackJsonOverworld(IDataFixer fixer) { - JSONObject obj = new JSONObject(DP_OVERWORLD_DEFAULT); - obj.put("min_y", dimensionHeight.getMin()); - obj.put("height", dimensionHeight.getMax() - dimensionHeight.getMin()); - obj.put("logical_height", logicalHeight); - return fixer.fixDimension(obj).toString(4); - } - - private String generateDatapackJsonNether(IDataFixer fixer) { - JSONObject obj = new JSONObject(DP_NETHER_DEFAULT); - obj.put("min_y", dimensionHeightNether.getMin()); - obj.put("height", dimensionHeightNether.getMax() - dimensionHeightNether.getMin()); - obj.put("logical_height", logicalHeightNether); - return fixer.fixDimension(obj).toString(4); - } - - private String generateDatapackJsonEnd(IDataFixer fixer) { - JSONObject obj = new JSONObject(DP_END_DEFAULT); - obj.put("min_y", dimensionHeightEnd.getMin()); - obj.put("height", dimensionHeightEnd.getMax() - dimensionHeightEnd.getMin()); - obj.put("logical_height", logicalHeightEnd); - return fixer.fixDimension(obj).toString(4); - } -} +/* + * 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.core.loader.IrisData; +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.util.collection.KList; +import com.volmit.iris.util.data.DataProvider; +import com.volmit.iris.util.json.JSONObject; +import com.volmit.iris.util.math.Position2; +import com.volmit.iris.util.math.RNG; +import com.volmit.iris.util.noise.CNG; +import com.volmit.iris.util.plugin.VolmitSender; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import org.bukkit.Material; +import org.bukkit.World.Environment; +import org.bukkit.block.data.BlockData; + +@Accessors(chain = true) +@AllArgsConstructor +@NoArgsConstructor +@Desc("Represents a dimension") +@Data +@EqualsAndHashCode(callSuper = false) +public class IrisDimension extends IrisRegistrant { + public static final BlockData STONE = Material.STONE.createBlockData(); + public static final BlockData WATER = Material.WATER.createBlockData(); + private final transient AtomicCache parallaxSize = new AtomicCache<>(); + private final transient AtomicCache rockLayerGenerator = new AtomicCache<>(); + private final transient AtomicCache fluidLayerGenerator = new AtomicCache<>(); + private final transient AtomicCache coordFracture = new AtomicCache<>(); + private final transient AtomicCache sinr = new AtomicCache<>(); + private final transient AtomicCache cosr = new AtomicCache<>(); + private final transient AtomicCache rad = new AtomicCache<>(); + private final transient AtomicCache featuresUsed = new AtomicCache<>(); + private final transient AtomicCache> strongholdsCache = new AtomicCache<>(); + @MinNumber(2) + @Required + @Desc("The human readable name of this dimension") + private String name = "A Dimension"; + @MinNumber(1) + @MaxNumber(2032) + @Desc("Maximum height at which players can be teleported to through gameplay.") + private int logicalHeight = 256; + @RegistryListResource(IrisJigsawStructure.class) + @Desc("If defined, Iris will place the given jigsaw structure where minecraft should place the overworld stronghold.") + private String stronghold; + @Desc("If set to true, Iris will remove chunks to allow visualizing cross sections of chunks easily") + private boolean debugChunkCrossSections = false; + @Desc("Vertically split up the biome palettes with 3 air blocks in between to visualize them") + private boolean explodeBiomePalettes = false; + @Desc("Studio Mode for testing different parts of the world") + private StudioMode studioMode = StudioMode.NORMAL; + @MinNumber(1) + @MaxNumber(16) + @Desc("Customize the palette height explosion") + private int explodeBiomePaletteSize = 3; + @MinNumber(2) + @MaxNumber(16) + @Desc("Every X/Z % debugCrossSectionsMod == 0 cuts the chunk") + private int debugCrossSectionsMod = 3; + @Desc("The average distance between strongholds") + private int strongholdJumpDistance = 1280; + @Desc("Define the maximum strongholds to place") + private int maxStrongholds = 14; + @Desc("Tree growth override settings") + private IrisTreeSettings treeSettings = new IrisTreeSettings(); + @Desc("Spawn Entities in this dimension over time. Iris will continually replenish these mobs just like vanilla does.") + @ArrayType(min = 1, type = String.class) + @RegistryListResource(IrisSpawner.class) + private KList entitySpawners = new KList<>(); + @Desc("Reference loot tables in this area") + private IrisLootReference loot = new IrisLootReference(); + @MinNumber(0) + @Desc("The version of this dimension. Changing this will stop users from accidentally upgrading (and breaking their worlds).") + private int version = 1; + @ArrayType(min = 1, type = IrisBlockDrops.class) + @Desc("Define custom block drops for this dimension") + private KList blockDrops = new KList<>(); + @Desc("Should bedrock be generated or not.") + private boolean bedrock = true; + @MinNumber(0) + @MaxNumber(1) + @Desc("The land chance. Up to 1.0 for total land or 0.0 for total sea") + private double landChance = 0.625; + @Desc("The placement style of regions") + private IrisGeneratorStyle regionStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style(); + @Desc("The placement style of land/sea") + private IrisGeneratorStyle continentalStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style(); + @Desc("The placement style of biomes") + private IrisGeneratorStyle landBiomeStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style(); + @Desc("The placement style of biomes") + private IrisGeneratorStyle shoreBiomeStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style(); + @Desc("The placement style of biomes") + private IrisGeneratorStyle seaBiomeStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style(); + @Desc("The placement style of biomes") + private IrisGeneratorStyle caveBiomeStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style(); + @Desc("Instead of filling objects with air, fills them with cobweb so you can see them") + private boolean debugSmartBore = false; + @Desc("Generate decorations or not") + private boolean decorate = true; + @Desc("Use post processing or not") + private boolean postProcessing = true; + @Desc("Add slabs in post processing") + private boolean postProcessingSlabs = true; + @Desc("Add painted walls in post processing") + private boolean postProcessingWalls = true; + @Desc("Carving configuration for the dimension") + private IrisCarving carving = new IrisCarving(); + @Desc("Configuration of fluid bodies such as rivers & lakes") + private IrisFluidBodies fluidBodies = new IrisFluidBodies(); + @Desc("forceConvertTo320Height") + private Boolean forceConvertTo320Height = false; + @Desc("The world environment") + private Environment environment = Environment.NORMAL; + @RegistryListResource(IrisRegion.class) + @Required + @ArrayType(min = 1, type = String.class) + @Desc("Define all of the regions to include in this dimension. Dimensions -> Regions -> Biomes -> Objects etc") + private KList regions = new KList<>(); + @ArrayType(min = 1, type = IrisJigsawStructurePlacement.class) + @Desc("Jigsaw structures") + private KList jigsawStructures = new KList<>(); + @Required + @MinNumber(0) + @MaxNumber(1024) + @Desc("The fluid height for this dimension") + private int fluidHeight = 63; + @Desc("Define the min and max Y bounds of this dimension. Please keep in mind that Iris internally generates from 0 to (max - min). \n\nFor example at -64 to 320, Iris is internally generating to 0 to 384, then on outputting chunks, it shifts it down by the min height (64 blocks). The default is -64 to 320. \n\nThe fluid height is placed at (fluid height + min height). So a fluid height of 63 would actually show up in the world at 1.") + private IrisRange dimensionHeight = new IrisRange(-64, 320); + @Desc("Enable smart vanilla height") + private boolean smartVanillaHeight = false; + @RegistryListResource(IrisBiome.class) + @Desc("Keep this either undefined or empty. Setting any biome name into this will force iris to only generate the specified biome. Great for testing.") + private String focus = ""; + @RegistryListResource(IrisRegion.class) + @Desc("Keep this either undefined or empty. Setting any region name into this will force iris to only generate the specified region. Great for testing.") + private String focusRegion = ""; + @MinNumber(0.0001) + @MaxNumber(512) + @Desc("Zoom in or out the biome size. Higher = bigger biomes") + private double biomeZoom = 5D; + @MinNumber(0) + @MaxNumber(360) + @Desc("You can rotate the input coordinates by an angle. This can make terrain appear more natural (less sharp corners and lines). This literally rotates the entire dimension by an angle. Hint: Try 12 degrees or something not on a 90 or 45 degree angle.") + private double dimensionAngleDeg = 0; + @Required + @Desc("Define the mode of this dimension (required!)") + private IrisDimensionMode mode = new IrisDimensionMode(); + @MinNumber(0) + @MaxNumber(8192) + @Desc("Coordinate fracturing applies noise to the input coordinates. This creates the 'iris swirls' and wavy features. The distance pushes these waves further into places they shouldnt be. This is a block value multiplier.") + private double coordFractureDistance = 20; + @MinNumber(0.0001) + @MaxNumber(512) + @Desc("Coordinate fracturing zoom. Higher = less frequent warping, Lower = more frequent and rapid warping / swirls.") + private double coordFractureZoom = 8; + @MinNumber(0.0001) + @MaxNumber(512) + @Desc("This zooms in the land space") + private double landZoom = 1; + @MinNumber(0.0001) + @MaxNumber(512) + @Desc("This zooms oceanic biomes") + private double seaZoom = 1; + @MinNumber(0.0001) + @MaxNumber(512) + @Desc("Zoom in continents") + private double continentZoom = 1; + @MinNumber(0.0001) + @MaxNumber(512) + @Desc("Change the size of regions") + private double regionZoom = 1; + @Desc("Disable this to stop placing objects, entities, features & updates") + private boolean useMantle = true; + @Desc("Prevent Leaf decay as if placed in creative mode") + private boolean preventLeafDecay = false; + @ArrayType(min = 1, type = IrisDepositGenerator.class) + @Desc("Define global deposit generators") + private KList deposits = new KList<>(); + @ArrayType(min = 1, type = IrisShapedGeneratorStyle.class) + @Desc("Overlay additional noise on top of the interoplated terrain.") + private KList overlayNoise = new KList<>(); + @Desc("If true, the spawner system has infinite energy. This is NOT recommended because it would allow for mobs to keep spawning over and over without a rate limit") + private boolean infiniteEnergy = false; + @MinNumber(0) + @MaxNumber(10000) + @Desc("This is the maximum energy you can have in a dimension") + private double maximumEnergy = 1000; + @MinNumber(0.0001) + @MaxNumber(512) + @Desc("The rock zoom mostly for zooming in on a wispy palette") + private double rockZoom = 5; + @Desc("The palette of blocks for 'stone'") + 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("Collection of ores to be generated") + @ArrayType(type = IrisOreGenerator.class, min = 1) + private KList ores = new KList<>(); + @MinNumber(0) + @MaxNumber(318) + @Desc("The Subterrain Fluid Layer Height") + private int caveLavaHeight = 8; + + public int getMaxHeight() { + return (int) getDimensionHeight().getMax(); + } + + public int getMinHeight() { + return (int) getDimensionHeight().getMin(); + } + + public BlockData generateOres(int x, int y, int z, RNG rng, IrisData data) { + if (ores.isEmpty()) { + return null; + } + BlockData b = null; + for (IrisOreGenerator i : ores) { + + b = i.generate(x, y, z, rng, data); + if (b != null) { + return b; + } + } + return null; + } + + public KList getStrongholds(long seed) { + return strongholdsCache.aquire(() -> { + KList pos = new KList<>(); + int jump = strongholdJumpDistance; + RNG rng = new RNG((seed * 223) + 12945); + for (int i = 0; i < maxStrongholds + 1; i++) { + int m = i + 1; + pos.add(new Position2( + (int) ((rng.i(jump * i) + (jump * i)) * (rng.b() ? -1D : 1D)), + (int) ((rng.i(jump * i) + (jump * i)) * (rng.b() ? -1D : 1D)) + )); + } + + pos.remove(0); + + return pos; + }); + } + + public int getFluidHeight() { + return fluidHeight - (int) dimensionHeight.getMin(); + } + + public CNG getCoordFracture(RNG rng, int signature) { + return coordFracture.aquire(() -> + { + CNG coordFracture = CNG.signature(rng.nextParallelRNG(signature)); + coordFracture.scale(0.012 / coordFractureZoom); + return coordFracture; + }); + } + + public double getDimensionAngle() { + return rad.aquire(() -> Math.toRadians(dimensionAngleDeg)); + } + + public Environment getEnvironment() { + return environment; + } + + public IrisRange getDimensionHeight() { + return smartVanillaHeight ? new IrisRange(-64, 320) : dimensionHeight; + } + + public int getLogicalHeight() { + return smartVanillaHeight ? 256 : logicalHeight; + } + + public boolean hasFocusRegion() { + return !focusRegion.equals(""); + } + + public String getFocusRegion() { + return focusRegion; + } + + public double sinRotate() { + return sinr.aquire(() -> Math.sin(getDimensionAngle())); + } + + public double cosRotate() { + return cosr.aquire(() -> Math.cos(getDimensionAngle())); + } + + public KList getAllRegions(DataProvider g) { + KList r = new KList<>(); + + for (String i : getRegions()) { + r.add(g.getData().getRegionLoader().load(i)); + } + + return r; + } + + public KList getAllAnyRegions() { + KList r = new KList<>(); + + for (String i : getRegions()) { + r.add(IrisData.loadAnyRegion(i)); + } + + return r; + } + + public KList getAllBiomes(DataProvider g) { + return g.getData().getBiomeLoader().loadAll(g.getData().getBiomeLoader().getPossibleKeys()); + } + + public KList getAllAnyBiomes() { + KList r = new KList<>(); + + for (IrisRegion i : getAllAnyRegions()) { + if (i == null) { + continue; + } + + r.addAll(i.getAllAnyBiomes()); + } + + return r; + } + + public IrisGeneratorStyle getBiomeStyle(InferredType type) { + switch (type) { + case CAVE: + return caveBiomeStyle; + case LAND: + return landBiomeStyle; + case SEA: + return seaBiomeStyle; + case SHORE: + return shoreBiomeStyle; + default: + break; + } + + return landBiomeStyle; + } + + @Override + public String getFolderName() { + return "dimensions"; + } + + @Override + public String getTypeName() { + return "Dimension"; + } + + @Override + public void scanForErrors(JSONObject p, VolmitSender sender) { + + } +} 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..66f6ec3ac 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 @@ -454,16 +454,21 @@ public class IrisEntity extends IrisRegistrant { return ae.get(); } - if (isSpecialType()) { + if (isSpecialType() && Iris.linkMythicMobs.isEnabled()) { if (specialType.toLowerCase().startsWith("mythicmobs:")) { return Iris.linkMythicMobs.spawnMob(specialType.substring(11), at); } else { Iris.warn("Invalid mob type to spawn: '" + specialType + "'!"); return null; } + } else { + if (isSpecialType()) { + Iris.warn("MythicMobs is not enabled, falling back to: " + type + "'!"); + } } + return INMS.get().spawnEntity(at, getType(), getReason()); } 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 ce0bebd74..ad0f5c86a 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 @@ -21,6 +21,7 @@ package com.volmit.iris.engine.platform; import com.volmit.iris.Iris; import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.core.nms.INMS; +import com.volmit.iris.core.safeguard.IrisSafeguard; import com.volmit.iris.core.service.StudioSVC; import com.volmit.iris.engine.IrisEngine; import com.volmit.iris.engine.data.chunk.TerrainChunk; @@ -32,6 +33,7 @@ import com.volmit.iris.engine.object.StudioMode; import com.volmit.iris.engine.platform.studio.StudioGenerator; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.data.IrisBiomeStorage; +import com.volmit.iris.util.format.C; import com.volmit.iris.util.hunk.Hunk; import com.volmit.iris.util.hunk.view.BiomeGridHunkHolder; import com.volmit.iris.util.hunk.view.ChunkDataHunkHolder; @@ -263,6 +265,15 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun } private Engine getEngine(WorldInfo world) { +// if (!IrisSafeguard.instance.acceptUnstable && IrisSafeguard.instance.unstablemode) { +// Iris.info(C.RED + "------------------------------------------------------------"); +// Iris.info(C.DARK_RED + "Cancelled World Loading of " + world.getName() + "!"); +// Iris.info(C.RED + "World loading has been disabled until the incompatibility is resolved."); +// Iris.info(C.DARK_RED + "Alternatively, go to plugins/iris/settings.json and set ignoreBootMode to true."); +// Iris.info(C.RED + "------------------------------------------------------------"); +// return null; +// } + if (setup.get()) { return getEngine(); } diff --git a/core/src/main/java/com/volmit/iris/util/decree/specialhandlers/NullableBiomeHandler.java b/core/src/main/java/com/volmit/iris/util/decree/specialhandlers/NullableBiomeHandler.java new file mode 100644 index 000000000..8af2fcfc7 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/decree/specialhandlers/NullableBiomeHandler.java @@ -0,0 +1,15 @@ +package com.volmit.iris.util.decree.specialhandlers; + +import com.volmit.iris.engine.object.IrisBiome; +import com.volmit.iris.util.decree.exceptions.DecreeParsingException; +import com.volmit.iris.util.decree.handlers.BiomeHandler; +import com.volmit.iris.util.decree.handlers.PlayerHandler; +import org.bukkit.entity.Player; + +public class NullableBiomeHandler extends BiomeHandler { + + @Override + public IrisBiome parse(String in, boolean force) throws DecreeParsingException { + return getPossibilities(in).stream().filter((i) -> toString(i).equalsIgnoreCase(in)).findFirst().orElse(null); + } +} diff --git a/core/src/main/java/com/volmit/iris/util/decree/specialhandlers/NullableRegionHandler.java b/core/src/main/java/com/volmit/iris/util/decree/specialhandlers/NullableRegionHandler.java new file mode 100644 index 000000000..f2c9c1665 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/decree/specialhandlers/NullableRegionHandler.java @@ -0,0 +1,17 @@ +package com.volmit.iris.util.decree.specialhandlers; + +import com.volmit.iris.engine.object.IrisBiome; +import com.volmit.iris.engine.object.IrisRegion; +import com.volmit.iris.util.decree.exceptions.DecreeParsingException; +import com.volmit.iris.util.decree.handlers.BiomeHandler; +import com.volmit.iris.util.decree.handlers.RegionHandler; + +import javax.swing.plaf.synth.Region; + +public class NullableRegionHandler extends RegionHandler { + + @Override + public IrisRegion parse(String in, boolean force) throws DecreeParsingException { + return getPossibilities(in).stream().filter((i) -> toString(i).equalsIgnoreCase(in)).findFirst().orElse(null); + } +} diff --git a/core/src/main/java/com/volmit/iris/util/nbt/mca/Chunk.java b/core/src/main/java/com/volmit/iris/util/nbt/mca/Chunk.java index f74cdcf7f..7fbe2e106 100644 --- a/core/src/main/java/com/volmit/iris/util/nbt/mca/Chunk.java +++ b/core/src/main/java/com/volmit/iris/util/nbt/mca/Chunk.java @@ -20,7 +20,9 @@ package com.volmit.iris.util.nbt.mca; import com.volmit.iris.Iris; import com.volmit.iris.core.nms.INMS; +import com.volmit.iris.core.tools.IrisWorldDump; import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.format.Form; import com.volmit.iris.util.nbt.io.NBTDeserializer; import com.volmit.iris.util.nbt.io.NBTSerializer; import com.volmit.iris.util.nbt.io.NamedTag; @@ -57,6 +59,8 @@ public class Chunk { private ListTag> postProcessing; private String status; private CompoundTag structures; + private int d; + private int f; Chunk(int lastMCAUpdate) { this.lastMCAUpdate = lastMCAUpdate; @@ -69,6 +73,8 @@ public class Chunk { */ public Chunk(CompoundTag data) { this.data = data; + d = 0; + f = 0; initReferences(ALL_DATA); setStatus("full"); } @@ -150,8 +156,8 @@ public class Chunk { if ((loadFlags & STRUCTURES) != 0) { structures = level.getCompoundTag("Structures"); } - if ((loadFlags & (BLOCK_LIGHTS | BLOCK_STATES | SKY_LIGHT)) != 0 && level.containsKey("Sections")) { - for (CompoundTag section : level.getListTag("Sections").asCompoundTagList()) { + if ((loadFlags & (BLOCK_LIGHTS | BLOCK_STATES | SKY_LIGHT)) != 0 && level.containsKey("sections")) { + for (CompoundTag section : level.getListTag("sections").asCompoundTagList()) { int sectionIndex = section.getByte("Y"); if (sectionIndex > 15 || sectionIndex < 0) { continue; diff --git a/core/src/main/java/com/volmit/iris/util/nbt/mca/Section.java b/core/src/main/java/com/volmit/iris/util/nbt/mca/Section.java index f58f74faf..713d7ee1f 100644 --- a/core/src/main/java/com/volmit/iris/util/nbt/mca/Section.java +++ b/core/src/main/java/com/volmit/iris/util/nbt/mca/Section.java @@ -37,12 +37,17 @@ public class Section { public Section(CompoundTag sectionRoot, int dataVersion, long loadFlags) { data = sectionRoot; - ListTag rawPalette = sectionRoot.getListTag("Palette"); + //ListTag rawPalette = sectionRoot.getListTag("palette"); + ListTag rawPalette = sectionRoot.getCompoundTag("biomes").getListTag("palette"); if (rawPalette == null) { return; } palette = INMS.get().createPalette(); - palette.readFromSection(sectionRoot); + try { + palette.readFromSection(sectionRoot); + } catch (Exception e) { + e.printStackTrace(); + } ByteArrayTag blockLight = sectionRoot.getByteArrayTag("BlockLight"); ByteArrayTag skyLight = sectionRoot.getByteArrayTag("SkyLight"); this.blockLight = blockLight != null ? blockLight.getValue() : null; diff --git a/core/src/main/java/com/volmit/iris/util/nbt/mca/palette/MCABitStorageByteArray.java b/core/src/main/java/com/volmit/iris/util/nbt/mca/palette/MCABitStorageByteArray.java new file mode 100644 index 000000000..22a073314 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/nbt/mca/palette/MCABitStorageByteArray.java @@ -0,0 +1,112 @@ +/* + * 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 + * abyte with this program. If not, see . + */ + +package com.volmit.iris.util.nbt.mca.palette; + +import org.apache.commons.lang3.Validate; +// todo Cool idea but im way to dumb for this for now +public class MCABitStorageByteArray { + private final byte[] data; + private final int bits; + private final int mask; + private final int size; + private final int valuesPerByte; + + private final int divideMul; + private final int divideAdd; + private final int divideShift; + + public MCABitStorageByteArray(int bits, int length) { + this(bits, length, null); + } + + public MCABitStorageByteArray(int bits, int length, byte[] data) { + Validate.inclusiveBetween(1L, 8L, bits); // Ensure bits are between 1 and 8 + this.size = length; + this.bits = bits; + this.mask = (1 << bits) - 1; + this.valuesPerByte = 8 / bits; + int[] divisionParams = computeDivisionParameters(this.valuesPerByte); + this.divideMul = divisionParams[0]; + this.divideAdd = divisionParams[1]; + this.divideShift = divisionParams[2]; + int numBytes = (length + this.valuesPerByte - 1) / this.valuesPerByte; + if (data != null) { + if (data.length != numBytes) + throw new IllegalArgumentException("Data array length does not match the required size."); + this.data = data; + } else { + this.data = new byte[numBytes]; + } + } + + private int[] computeDivisionParameters(int denom) { + long two32 = 1L << 32; + long magic = two32 / denom; + int shift = 0; + while ((1L << (shift + 32)) < magic * denom) { + shift++; + } + return new int[]{(int) magic, 0, shift}; + } + + private int cellIndex(int index) { + long indexLong = Integer.toUnsignedLong(this.divideMul); + long addLong = Integer.toUnsignedLong(this.divideAdd); + return (int) ((index * indexLong + addLong) >>> 32 >>> this.divideShift); + } + + public int getAndSet(int index, int newValue) { + Validate.inclusiveBetween(0L, (this.size - 1), index); + Validate.inclusiveBetween(0L, this.mask, newValue); + int byteIndex = cellIndex(index); + int bitOffset = (index - byteIndex * this.valuesPerByte) * this.bits; + int currentValue = (this.data[byteIndex] >> bitOffset) & this.mask; + this.data[byteIndex] = (byte) ((this.data[byteIndex] & ~(this.mask << bitOffset)) | (newValue & this.mask) << bitOffset); + return currentValue; + } + + public void set(int index, int value) { + Validate.inclusiveBetween(0L, (this.size - 1), index); + Validate.inclusiveBetween(0L, this.mask, value); + int byteIndex = cellIndex(index); + int bitOffset = (index - byteIndex * this.valuesPerByte) * this.bits; + this.data[byteIndex] = (byte) ((this.data[byteIndex] & ~(this.mask << bitOffset)) | (value & this.mask) << bitOffset); + } + + public int get(int index) { + Validate.inclusiveBetween(0L, (this.size - 1), index); + int byteIndex = cellIndex(index); + int bitOffset = (index - byteIndex * this.valuesPerByte) * this.bits; + return (this.data[byteIndex] >> bitOffset) & this.mask; + } + + public byte[] getRaw() { + return this.data; + } + + public int getSize() { + return this.size; + } + + public int getBits() { + return this.bits; + } +} + + diff --git a/core/src/main/java/com/volmit/iris/util/nbt/mca/palette/MCABitStorage.java b/core/src/main/java/com/volmit/iris/util/nbt/mca/palette/MCABitStorageLongArray.java similarity index 96% rename from core/src/main/java/com/volmit/iris/util/nbt/mca/palette/MCABitStorage.java rename to core/src/main/java/com/volmit/iris/util/nbt/mca/palette/MCABitStorageLongArray.java index b3413bf5b..e2d9e157f 100644 --- a/core/src/main/java/com/volmit/iris/util/nbt/mca/palette/MCABitStorage.java +++ b/core/src/main/java/com/volmit/iris/util/nbt/mca/palette/MCABitStorageLongArray.java @@ -20,9 +20,10 @@ package com.volmit.iris.util.nbt.mca.palette; import org.apache.commons.lang3.Validate; +import java.util.BitSet; import java.util.function.IntConsumer; -public class MCABitStorage { +public class MCABitStorageLongArray { private static final int[] MAGIC = new int[]{ -1, -1, 0, Integer.MIN_VALUE, 0, 0, 1431655765, 1431655765, 0, Integer.MIN_VALUE, 0, 1, 858993459, 858993459, 0, 715827882, 715827882, 0, 613566756, 613566756, @@ -61,11 +62,11 @@ public class MCABitStorage { private final int divideShift; - public MCABitStorage(int bits, int length) { + public MCABitStorageLongArray(int bits, int length) { this(bits, length, null); } - public MCABitStorage(int bits, int length, long[] data) { + public MCABitStorageLongArray(int bits, int length, long[] data) { Validate.inclusiveBetween(1L, 32L, bits); this.size = length; this.bits = bits; diff --git a/core/src/main/java/com/volmit/iris/util/nbt/mca/palette/MCAPalettedContainer.java b/core/src/main/java/com/volmit/iris/util/nbt/mca/palette/MCAPalettedContainer.java index 941c37447..4af6e234e 100644 --- a/core/src/main/java/com/volmit/iris/util/nbt/mca/palette/MCAPalettedContainer.java +++ b/core/src/main/java/com/volmit/iris/util/nbt/mca/palette/MCAPalettedContainer.java @@ -41,7 +41,8 @@ public class MCAPalettedContainer implements MCAPaletteResize { private final T defaultValue; - protected MCABitStorage storage; + // Todo multiple storage systems cause long isnt the only one? + protected MCABitStorageLongArray storage; private MCAPalette palette; @@ -74,11 +75,11 @@ public class MCAPalettedContainer implements MCAPaletteResize { this.bits = MCAMth.ceillog2(this.registry.size()); } this.palette.idFor(this.defaultValue); - this.storage = new MCABitStorage(this.bits, 4096); + this.storage = new MCABitStorageLongArray(this.bits, 4096); } public int onResize(int var0, T var1) { - MCABitStorage var2 = this.storage; + MCABitStorageLongArray var2 = this.storage; MCAPalette var3 = this.palette; setBits(var0); for (int var4 = 0; var4 < var2.getSize(); var4++) { @@ -121,6 +122,12 @@ public class MCAPalettedContainer implements MCAPaletteResize { T var1 = this.palette.valueFor(this.storage.get(var0)); return (var1 == null) ? this.defaultValue : var1; } + /** + /** + * Reads and processes block data from encoded byte arrays. + * @param var0 BlockID Strings - List of block types identified by strings. + * @param var1 Encoded Locations - Long array containing compactly encoded block IDs, representing sequential block positions within a chunk. + */ public void read(ListTag var0, long[] var1) { int var2 = Math.max(4, MCAMth.ceillog2(var0.size())); @@ -131,18 +138,69 @@ public class MCAPalettedContainer implements MCAPaletteResize { if (this.palette == this.globalPalette) { MCAPalette var4 = new MCAHashMapPalette<>(this.registry, var2, this.dummyPaletteResize, this.reader, this.writer); var4.read(var0); - MCABitStorage var5 = new MCABitStorage(var2, 4096, var1); + MCABitStorageLongArray var5 = new MCABitStorageLongArray(var2, 4096, var1); for (int var6 = 0; var6 < 4096; var6++) this.storage.set(var6, this.globalPalette.idFor(var4.valueFor(var5.get(var6)))); } else if (var3 == this.bits) { System.arraycopy(var1, 0, this.storage.getRaw(), 0, var1.length); } else { - MCABitStorage var4 = new MCABitStorage(var3, 4096, var1); + MCABitStorageLongArray var4 = new MCABitStorageLongArray(var3, 4096, var1); for (int var5 = 0; var5 < 4096; var5++) this.storage.set(var5, var4.get(var5)); } } + /** + * Reads and processes block data from encoded byte arrays. + * @param var0 BlockID Strings - List of block types identified by strings. + * @param var1 Encoded Locations - Byte array containing compactly encoded block IDs, representing sequential block positions within a chunk. + * Currently, Minecraft doesn't use ByteArray storage. + */ + + public void read(ListTag var0, byte[] var1) { + int requiredBits = Math.max(4, MCAMth.ceillog2(var0.size())); + if (requiredBits != this.bits) { + setBits(requiredBits); + } + this.palette.read(var0); + + int bitsPerByte = 8 * var1.length / 4096; + if (this.palette == this.globalPalette) { + MCAPalette var4 = new MCAHashMapPalette<>(this.registry, requiredBits, this.dummyPaletteResize, this.reader, this.writer); + var4.read(var0); + MCABitStorageByteArray var5 = new MCABitStorageByteArray(requiredBits, 4096, var1); + for (int var6 = 0; var6 < 4096; var6++) { + this.storage.set(var6, this.globalPalette.idFor(var4.valueFor(var5.get(var6)))); + } + } else if (bitsPerByte == this.bits) { + System.arraycopy(var1, 0, this.storage.getRaw(), 0, var1.length); + } else { + MCABitStorageByteArray var4 = new MCABitStorageByteArray(bitsPerByte, 4096, var1); + for (int var5 = 0; var5 < 4096; var5++) { + this.storage.set(var5, var4.get(var5)); + } + } + } + + /** + * Reads and processes block data from encoded byte arrays. + * @param var0 BlockID Strings - List of block types identified by strings. + * This method is primarily used to read air sections. + */ + + public void read(ListTag var0) { + int requiredBits = Math.max(4, MCAMth.ceillog2(var0.size())); + if (requiredBits != this.bits) { + setBits(requiredBits); + } + this.palette.read(var0); + int defaultValue = 0; + for (int i = 0; i < 4096; i++) { + this.storage.set(i, defaultValue); + } + } + + public void write(CompoundTag var0, String var1, String var2) { MCAHashMapPalette var3 = new MCAHashMapPalette<>(this.registry, this.bits, this.dummyPaletteResize, this.reader, this.writer); T var4 = this.defaultValue; @@ -160,7 +218,7 @@ public class MCAPalettedContainer implements MCAPaletteResize { var3.write(paletteList); var0.put(var1, paletteList); int var8 = Math.max(4, MCAMth.ceillog2(paletteList.size())); - MCABitStorage var9 = new MCABitStorage(var8, 4096); + MCABitStorageLongArray var9 = new MCABitStorageLongArray(var8, 4096); for (int var10 = 0; var10 < var6.length; var10++) { var9.set(var10, var6[var10]); } diff --git a/core/src/main/java/com/volmit/iris/util/nbt/mca/palette/MCAWrappedPalettedContainer.java b/core/src/main/java/com/volmit/iris/util/nbt/mca/palette/MCAWrappedPalettedContainer.java index fcd7edd5c..06ba7c439 100644 --- a/core/src/main/java/com/volmit/iris/util/nbt/mca/palette/MCAWrappedPalettedContainer.java +++ b/core/src/main/java/com/volmit/iris/util/nbt/mca/palette/MCAWrappedPalettedContainer.java @@ -18,7 +18,10 @@ package com.volmit.iris.util.nbt.mca.palette; +import com.volmit.iris.Iris; +import com.volmit.iris.util.nbt.tag.ByteArrayTag; import com.volmit.iris.util.nbt.tag.CompoundTag; +import com.volmit.iris.util.nbt.tag.LongArrayTag; import lombok.RequiredArgsConstructor; import java.util.function.Function; @@ -42,6 +45,22 @@ public class MCAWrappedPalettedContainer implements MCAPaletteAccess { } public void readFromSection(CompoundTag tag) { - container.read(tag.getListTag("Palette"), tag.getLongArrayTag("BlockStates").getValue()); + // container.read(tag.getCompoundTag("block_states").getListTag("palette"), tag.getCompoundTag("block_states").getLongArrayTag("data").getValue()); + CompoundTag blockStates = tag.getCompoundTag("block_states"); + if (blockStates == null) { + throw new IllegalArgumentException("block_states tag is missing"); + } + LongArrayTag longData = blockStates.getLongArrayTag("data"); + if (longData != null && longData.getValue() != null) { + container.read(tag.getCompoundTag("block_states").getListTag("palette"), tag.getCompoundTag("block_states").getLongArrayTag("data").getValue()); + } else { + ByteArrayTag byteData = blockStates.getByteArrayTag("data"); + if (byteData == null) { + container.read(tag.getCompoundTag("block_states").getListTag("palette")); + } else { + throw new IllegalArgumentException("No palette data tag found or data value is null"); + } + } } + } diff --git a/core/src/main/resources/plugin.yml b/core/src/main/resources/plugin.yml index a54d30829..05cd9bf4b 100644 --- a/core/src/main/resources/plugin.yml +++ b/core/src/main/resources/plugin.yml @@ -6,6 +6,10 @@ authors: [ cyberpwn, NextdoorPsycho, Vatuu ] website: volmit.com description: More than a Dimension! libraries: + - org.bytedeco:cuda-platform:12.3-8.9-1.5.10 + - org.bytedeco:javacpp:1.5.10 + - net.bytebuddy:byte-buddy:1.14.14 + - net.bytebuddy:byte-buddy-agent:1.12.8 - com.googlecode.concurrentlinkedhashmap:concurrentlinkedhashmap-lru:1.4.2 - com.github.ben-manes.caffeine:caffeine:3.0.5 - org.apache.commons:commons-lang3:3.12.0 diff --git a/nms/v1_19_R1/src/main/java/com/volmit/iris/core/nms/v1_19_R1/CustomBiomeSource.java b/nms/v1_19_R1/src/main/java/com/volmit/iris/core/nms/v1_19_R1/CustomBiomeSource.java index a24e62d85..5a1f89970 100644 --- a/nms/v1_19_R1/src/main/java/com/volmit/iris/core/nms/v1_19_R1/CustomBiomeSource.java +++ b/nms/v1_19_R1/src/main/java/com/volmit/iris/core/nms/v1_19_R1/CustomBiomeSource.java @@ -2,6 +2,7 @@ package com.volmit.iris.core.nms.v1_19_R1; import com.mojang.serialization.Codec; import com.volmit.iris.Iris; +import com.volmit.iris.core.nms.INMS; import com.volmit.iris.engine.data.cache.AtomicCache; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.object.IrisBiome; @@ -11,6 +12,7 @@ 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.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.BiomeSource; @@ -25,6 +27,7 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; +import java.util.Optional; public class CustomBiomeSource extends BiomeSource { private final long seed; @@ -118,8 +121,28 @@ public class CustomBiomeSource extends BiomeSource { for (IrisBiome i : engine.getAllBiomes()) { if (i.isCustom()) { for (IrisBiomeCustom j : i.getCustomDerivitives()) { - m.put(j.getId(), customRegistry.getHolder(customRegistry.getResourceKey(customRegistry - .get(new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId()))).get()).get()); + ResourceLocation location = new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId()); + Biome biome = customRegistry.get(location); + if (biome == null) { + INMS.get().registerBiome(location.getNamespace(), j, false); + biome = customRegistry.get(location); + if (biome == null) { + Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName()); + continue; + } + } + 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.getHolder(biomeKey); + if (optionalReferenceHolder.isEmpty()) { + Iris.error("Cannot find reference to biome " + biomeKey + " for engine " + engine.getName()); + continue; + } + m.put(j.getId(), optionalReferenceHolder.get()); } } } 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 49c921288..264d0abce 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 @@ -4,14 +4,44 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; +import java.io.File; 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.Map; +import java.util.Optional; import java.util.Vector; +import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Logger; +import com.google.common.base.Preconditions; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.Codec; +import com.mojang.serialization.JsonOps; +import com.mojang.serialization.Lifecycle; +import com.volmit.iris.engine.object.IrisBiomeCustom; +import com.volmit.iris.engine.object.IrisDimension; +import com.volmit.iris.util.format.C; +import it.unimi.dsi.fastutil.objects.Reference2IntMap; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.dynamic.loading.ClassReloadingStrategy; +import net.bytebuddy.matcher.ElementMatchers; +import net.minecraft.core.MappedRegistry; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.progress.ChunkProgressListener; +import net.minecraft.util.GsonHelper; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.storage.LevelStorageSource; +import net.minecraft.world.level.storage.PrimaryLevelData; import org.bukkit.*; import org.bukkit.block.Biome; import org.bukkit.block.data.BlockData; @@ -25,6 +55,7 @@ import org.bukkit.craftbukkit.v1_19_R1.inventory.CraftItemStack; import org.bukkit.entity.Dolphin; import org.bukkit.entity.Entity; import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.ChunkGenerator; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; @@ -170,6 +201,17 @@ public class NMSBinding implements INMSBinding { return null; } + private RegistryAccess getRegistryAccess(World world) { + try { + var field = getField(Level.class, RegistryAccess.class); + field.setAccessible(true); + return (RegistryAccess) field.get(((CraftWorld) world).getHandle()); + } catch (Throwable e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + @Override public void deserializeTile(CompoundTag c, Location pos) { ((CraftWorld) pos.getWorld()).getHandle().getChunkAt(new BlockPos(pos.getBlockX(), 0, pos.getBlockZ())).setBlockEntityNbt(convert(c)); @@ -253,8 +295,7 @@ public class NMSBinding implements INMSBinding { @Override public Object getBiomeBase(World world, Biome biome) { - return CraftBlock.biomeToBiomeBase(((CraftWorld) world).getHandle() - .registryAccess().registry(Registry.BIOME_REGISTRY).orElse(null), biome); + return CraftBlock.biomeToBiomeBase(getRegistryAccess(world).registry(Registry.BIOME_REGISTRY).orElse(null), biome); } @Override @@ -291,8 +332,11 @@ public class NMSBinding implements INMSBinding { public int getBiomeId(Biome biome) { for (World i : Bukkit.getWorlds()) { if (i.getEnvironment().equals(World.Environment.NORMAL)) { - Registry registry = ((CraftWorld) i).getHandle().registryAccess().registry(Registry.BIOME_REGISTRY).orElse(null); - return registry.getId((net.minecraft.world.level.biome.Biome) getBiomeBase(registry, biome)); + var registry = getRegistryAccess(i).registry(Registry.BIOME_REGISTRY).orElse(null); + if (registry != null) { + var holder = (Holder) getBiomeBase(registry, biome); + return registry.getId(holder.value()); + } } } @@ -517,6 +561,139 @@ public class NMSBinding implements INMSBinding { return ((CraftWorld) location.getWorld()).spawn(location, type.getEntityClass(), null, reason); } + @Override + public boolean registerDimension(String name, IrisDimension dimension) { + var registry = registry(Registry.DIMENSION_TYPE_REGISTRY); + var baseLocation = switch (dimension.getEnvironment()) { + case NORMAL -> new ResourceLocation("minecraft", "overworld"); + case NETHER -> new ResourceLocation("minecraft", "the_nether"); + case THE_END -> new ResourceLocation("minecraft", "the_end"); + case CUSTOM -> throw new IllegalArgumentException("Cannot register custom dimension"); + }; + var base = registry.getHolder(ResourceKey.create(Registry.DIMENSION_TYPE_REGISTRY, baseLocation)).orElse(null); + if (base == null) return false; + var json = encode(DimensionType.CODEC, base).orElse(null); + if (json == null) return false; + var object = json.getAsJsonObject(); + var height = dimension.getDimensionHeight(); + object.addProperty("min_y", height.getMin()); + object.addProperty("height", height.getMax() - height.getMin()); + object.addProperty("logical_height", dimension.getLogicalHeight()); + var value = decode(DimensionType.CODEC, object.toString()).map(Holder::value).orElse(null); + if (value == null) return false; + return register(Registry.DIMENSION_TYPE_REGISTRY, new ResourceLocation("iris", name), value, true); + } + + @Override + public boolean registerBiome(String dimensionId, IrisBiomeCustom biome, boolean replace) { + var biomeBase = decode(net.minecraft.world.level.biome.Biome.CODEC, biome.generateJson()).map(Holder::value).orElse(null); + if (biomeBase == null) return false; + return register(Registry.BIOME_REGISTRY, new ResourceLocation(dimensionId, biome.getId()), biomeBase, replace); + } + + private Optional decode(Codec codec, String json) { + return codec.decode(JsonOps.INSTANCE, GsonHelper.parse(json)).get().left().map(Pair::getFirst); + } + + private Optional encode(Codec codec, T value) { + return codec.encode(value, JsonOps.INSTANCE, new JsonObject()).result(); + } + + private boolean register(ResourceKey> registryKey, ResourceLocation location, T value, boolean replace) { + Preconditions.checkArgument(registryKey != null, "The registry cannot be null!"); + Preconditions.checkArgument(location != null, "The location cannot be null!"); + Preconditions.checkArgument(value != null, "The value cannot be null!"); + var registry = registry(registryKey); + var key = ResourceKey.create(registryKey, location); + try { + if (registry.containsKey(key)) { + if (!replace) return false; + return replace(registryKey, location, value); + } + Field field = getField(MappedRegistry.class, boolean.class); + field.setAccessible(true); + boolean frozen = field.getBoolean(registry); + field.setBoolean(registry, false); + Field valueField = getField(Holder.Reference.class, "T"); + valueField.setAccessible(true); + + try { + var holder = registry.register(key, value, Lifecycle.stable()); + if (frozen) valueField.set(holder, value); + return true; + } finally { + field.setBoolean(registry, frozen); + } + } catch (Throwable e) { + throw new IllegalStateException(e); + } + } + + @SuppressWarnings("unchecked") + private boolean replace(ResourceKey> registryKey, ResourceLocation location, T value) { + Preconditions.checkArgument(registryKey != null, "The registryKey cannot be null!"); + Preconditions.checkArgument(location != null, "The location cannot be null!"); + Preconditions.checkArgument(value != null, "The value cannot be null!"); + var registry = registry(registryKey); + var key = ResourceKey.create(registryKey, location); + try { + var holder = registry.getHolder(key).orElse(null); + if (holder == null) return false; + var oldValue = holder.value(); + Field valueField = getField(Holder.Reference.class, "T"); + valueField.setAccessible(true); + Field toIdField = getField(MappedRegistry.class, buildType(Reference2IntMap.class, "T")); + toIdField.setAccessible(true); + Field byValueField = getField(MappedRegistry.class, buildType(Map.class, "T", buildType(Holder.Reference.class, "T"))); + byValueField.setAccessible(true); + Field lifecyclesField = getField(MappedRegistry.class, buildType(Map.class, "T", Lifecycle.class.getName())); + lifecyclesField.setAccessible(true); + var toId = (Reference2IntMap) toIdField.get(registry); + var byValue = (Map>) byValueField.get(registry); + var lifecycles = (Map) lifecyclesField.get(registry); + + valueField.set(holder, value); + toId.put(value, toId.removeInt(oldValue)); + byValue.put(value, byValue.remove(oldValue)); + lifecycles.put(value, lifecycles.remove(oldValue)); + return true; + } catch (Throwable e) { + throw new IllegalStateException(e); + } + } + + private MappedRegistry registry(ResourceKey> registryKey) { + var rawRegistry = registry().registry(registryKey).orElse(null); + if (!(rawRegistry instanceof MappedRegistry registry)) + throw new IllegalStateException("The Registry is not a mapped Registry!"); + return registry; + } + + private static String buildType(Class clazz, String... parameterTypes) { + if (parameterTypes.length == 0) return clazz.getName(); + var builder = new StringBuilder(clazz.getName()) + .append("<"); + for (int i = 0; i < parameterTypes.length; i++) { + builder.append(parameterTypes[i]).append(parameterTypes.length - 1 == i ? ">" : ", "); + } + return builder.toString(); + } + + private static Field getField(Class clazz, String type) throws NoSuchFieldException { + try { + for (Field f : clazz.getDeclaredFields()) { + if (f.getGenericType().getTypeName().equals(type)) + return f; + } + throw new NoSuchFieldException(type); + } catch (NoSuchFieldException e) { + Class superClass = clazz.getSuperclass(); + if (superClass == null) throw e; + return getField(superClass, type); + } + } + + private static Field getField(Class clazz, Class fieldType) throws NoSuchFieldException { try { for (Field f : clazz.getDeclaredFields()) { @@ -533,4 +710,76 @@ public class NMSBinding implements INMSBinding { } } } -} + + public void injectBukkit() { + try { + Iris.info("Injecting Bukkit"); + new ByteBuddy() + .redefine(CraftServer.class) + .visit(Advice.to(CraftServerAdvice.class).on(ElementMatchers.isMethod().and(ElementMatchers.takesArguments(WorldCreator.class)))) + .make() + .load(CraftServer.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); + new ByteBuddy() + .redefine(ServerLevel.class) + .visit(Advice.to(ServerLevelAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(MinecraftServer.class, Executor.class, LevelStorageSource.LevelStorageAccess.class, + PrimaryLevelData.class, ResourceKey.class, LevelStem.class, ChunkProgressListener.class, boolean.class, long.class, + List.class, boolean.class, World.Environment.class, ChunkGenerator.class, BiomeProvider.class)))) + .make() + .load(ServerLevel.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); + Iris.info("Injected Bukkit Successfully!"); + } catch (Exception e) { + Iris.info(C.RED + "Failed to Inject Bukkit!"); + e.printStackTrace(); + Iris.reportError(e); + } + + } + + private static class ServerLevelAdvice { + @Advice.OnMethodEnter + static void enter(@Advice.Argument(0) MinecraftServer server, @Advice.Argument(2) LevelStorageSource.LevelStorageAccess access, @Advice.Argument(4) ResourceKey key, @Advice.Argument(value = 5, readOnly = false) LevelStem levelStem) { + File iris = new File(access.levelDirectory.path().toFile(), "iris"); + if (!iris.exists() && !key.location().getPath().startsWith("iris/")) return; + var logger = MinecraftServer.LOGGER; + ResourceKey typeKey = ResourceKey.create(Registry.DIMENSION_TYPE_REGISTRY, new ResourceLocation("iris", key.location().getPath())); + RegistryAccess registryAccess = server.registryAccess(); + Registry registry = registryAccess.registry(Registry.DIMENSION_TYPE_REGISTRY).orElse(null); + if (registry == null) throw new IllegalStateException("Unable to find registry for dimension type " + typeKey); + Holder holder = registry.getHolder(typeKey).orElse(null); + if (holder == null) throw new IllegalStateException("Unable to find dimension type " + typeKey); + levelStem = new LevelStem(holder, levelStem.generator()); + } + } + + private static class CraftServerAdvice { + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + static boolean enter(@Advice.This CraftServer self, @Advice.Argument(0) WorldCreator creator) { + File isIrisWorld = new File(self.getWorldContainer(), creator.name() + "/iris"); + boolean isFromIris = false; + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + for (StackTraceElement stack : stackTrace) { + if (stack.getClassName().contains("Iris")) { + isFromIris = true; + break; + } + } + if (isIrisWorld.exists() && !isFromIris) { + var logger = Logger.getLogger("Iris"); + logger.warning("detected another Plugin trying to load " + creator.name() + ". This is not supported and will be ignored."); + + if (System.getProperty("iris.debug", "false").equals("true")) { + new RuntimeException().printStackTrace(); + } + return true; + } + return false; + } + + @Advice.OnMethodExit + static void exit(@Advice.Enter boolean bool, @Advice.Return(readOnly = false) World returned) { + if (bool) { + returned = null; + } + } + } +} \ No newline at end of file diff --git a/nms/v1_19_R2/src/main/java/com/volmit/iris/core/nms/v1_19_R2/CustomBiomeSource.java b/nms/v1_19_R2/src/main/java/com/volmit/iris/core/nms/v1_19_R2/CustomBiomeSource.java index 7a82acf42..340c053cd 100644 --- a/nms/v1_19_R2/src/main/java/com/volmit/iris/core/nms/v1_19_R2/CustomBiomeSource.java +++ b/nms/v1_19_R2/src/main/java/com/volmit/iris/core/nms/v1_19_R2/CustomBiomeSource.java @@ -2,6 +2,7 @@ package com.volmit.iris.core.nms.v1_19_R2; import com.mojang.serialization.Codec; import com.volmit.iris.Iris; +import com.volmit.iris.core.nms.INMS; import com.volmit.iris.engine.data.cache.AtomicCache; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.object.IrisBiome; @@ -12,6 +13,7 @@ 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; @@ -26,6 +28,7 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; +import java.util.Optional; public class CustomBiomeSource extends BiomeSource { @@ -120,8 +123,28 @@ public class CustomBiomeSource extends BiomeSource { for (IrisBiome i : engine.getAllBiomes()) { if (i.isCustom()) { for (IrisBiomeCustom j : i.getCustomDerivitives()) { - m.put(j.getId(), customRegistry.getHolder(customRegistry.getResourceKey(customRegistry - .get(new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId()))).get()).get()); + ResourceLocation location = new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId()); + Biome biome = customRegistry.get(location); + if (biome == null) { + INMS.get().registerBiome(location.getNamespace(), j, false); + biome = customRegistry.get(location); + if (biome == null) { + Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName()); + continue; + } + } + 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.getHolder(biomeKey); + if (optionalReferenceHolder.isEmpty()) { + Iris.error("Cannot find reference to biome " + biomeKey + " for engine " + engine.getName()); + continue; + } + m.put(j.getId(), optionalReferenceHolder.get()); } } } 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 8aa76bc52..6af6c20bb 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 @@ -4,14 +4,44 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; +import java.io.File; 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.Map; +import java.util.Optional; import java.util.Vector; +import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Logger; +import com.google.common.base.Preconditions; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.Codec; +import com.mojang.serialization.JsonOps; +import com.mojang.serialization.Lifecycle; +import com.volmit.iris.engine.object.IrisBiomeCustom; +import com.volmit.iris.engine.object.IrisDimension; +import com.volmit.iris.util.format.C; +import it.unimi.dsi.fastutil.objects.Reference2IntMap; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.dynamic.loading.ClassReloadingStrategy; +import net.bytebuddy.matcher.ElementMatchers; +import net.minecraft.core.MappedRegistry; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.progress.ChunkProgressListener; +import net.minecraft.util.GsonHelper; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.storage.LevelStorageSource; +import net.minecraft.world.level.storage.PrimaryLevelData; import org.bukkit.*; import org.bukkit.block.Biome; import org.bukkit.block.data.BlockData; @@ -25,6 +55,7 @@ import org.bukkit.craftbukkit.v1_19_R2.inventory.CraftItemStack; import org.bukkit.entity.Dolphin; import org.bukkit.entity.Entity; import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.ChunkGenerator; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; @@ -171,6 +202,17 @@ public class NMSBinding implements INMSBinding { return null; } + private RegistryAccess getRegistryAccess(World world) { + try { + var field = getField(Level.class, RegistryAccess.class); + field.setAccessible(true); + return (RegistryAccess) field.get(((CraftWorld) world).getHandle()); + } catch (Throwable e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + @Override public void deserializeTile(CompoundTag c, Location pos) { ((CraftWorld) pos.getWorld()).getHandle().getChunkAt(new BlockPos(pos.getBlockX(), 0, pos.getBlockZ())).setBlockEntityNbt(convert(c)); @@ -254,8 +296,7 @@ public class NMSBinding implements INMSBinding { @Override public Object getBiomeBase(World world, Biome biome) { - return CraftBlock.biomeToBiomeBase(((CraftWorld) world).getHandle() - .registryAccess().registry(Registries.BIOME).orElse(null), biome); + return CraftBlock.biomeToBiomeBase(getRegistryAccess(world).registry(Registries.BIOME).orElse(null), biome); } @Override @@ -292,8 +333,11 @@ public class NMSBinding implements INMSBinding { public int getBiomeId(Biome biome) { for (World i : Bukkit.getWorlds()) { if (i.getEnvironment().equals(World.Environment.NORMAL)) { - Registry registry = ((CraftWorld) i).getHandle().registryAccess().registry(Registries.BIOME).orElse(null); - return registry.getId((net.minecraft.world.level.biome.Biome) getBiomeBase(registry, biome)); + var registry = getRegistryAccess(i).registry(Registries.BIOME).orElse(null); + if (registry != null) { + var holder = (Holder) getBiomeBase(registry, biome); + return registry.getId(holder.value()); + } } } @@ -519,6 +563,138 @@ public class NMSBinding implements INMSBinding { return ((CraftWorld) location.getWorld()).spawn(location, type.getEntityClass(), null, reason); } + @Override + public boolean registerDimension(String name, IrisDimension dimension) { + var registry = registry(Registries.DIMENSION_TYPE); + var baseLocation = switch (dimension.getEnvironment()) { + case NORMAL -> new ResourceLocation("minecraft", "overworld"); + case NETHER -> new ResourceLocation("minecraft", "the_nether"); + case THE_END -> new ResourceLocation("minecraft", "the_end"); + case CUSTOM -> throw new IllegalArgumentException("Cannot register custom dimension"); + }; + var base = registry.getHolder(ResourceKey.create(Registries.DIMENSION_TYPE, baseLocation)).orElse(null); + if (base == null) return false; + var json = encode(DimensionType.CODEC, base).orElse(null); + if (json == null) return false; + var object = json.getAsJsonObject(); + var height = dimension.getDimensionHeight(); + object.addProperty("min_y", height.getMin()); + object.addProperty("height", height.getMax() - height.getMin()); + object.addProperty("logical_height", dimension.getLogicalHeight()); + var value = decode(DimensionType.CODEC, object.toString()).map(Holder::value).orElse(null); + if (value == null) return false; + return register(Registries.DIMENSION_TYPE, new ResourceLocation("iris", name), value, true); + } + + @Override + public boolean registerBiome(String dimensionId, IrisBiomeCustom biome, boolean replace) { + var biomeBase = decode(net.minecraft.world.level.biome.Biome.CODEC, biome.generateJson()).map(Holder::value).orElse(null); + if (biomeBase == null) return false; + return register(Registries.BIOME, new ResourceLocation(dimensionId, biome.getId()), biomeBase, replace); + } + + private Optional decode(Codec codec, String json) { + return codec.decode(JsonOps.INSTANCE, GsonHelper.parse(json)).get().left().map(Pair::getFirst); + } + + private Optional encode(Codec codec, T value) { + return codec.encode(value, JsonOps.INSTANCE, new JsonObject()).result(); + } + + private boolean register(ResourceKey> registryKey, ResourceLocation location, T value, boolean replace) { + Preconditions.checkArgument(registryKey != null, "The registry cannot be null!"); + Preconditions.checkArgument(location != null, "The location cannot be null!"); + Preconditions.checkArgument(value != null, "The value cannot be null!"); + var registry = registry(registryKey); + var key = ResourceKey.create(registryKey, location); + try { + if (registry.containsKey(key)) { + if (!replace) return false; + return replace(registryKey, location, value); + } + Field field = getField(MappedRegistry.class, boolean.class); + field.setAccessible(true); + boolean frozen = field.getBoolean(registry); + field.setBoolean(registry, false); + Field valueField = getField(Holder.Reference.class, "T"); + valueField.setAccessible(true); + + try { + var holder = registry.register(key, value, Lifecycle.stable()); + if (frozen) valueField.set(holder, value); + return true; + } finally { + field.setBoolean(registry, frozen); + } + } catch (Throwable e) { + throw new IllegalStateException(e); + } + } + + @SuppressWarnings("unchecked") + private boolean replace(ResourceKey> registryKey, ResourceLocation location, T value) { + Preconditions.checkArgument(registryKey != null, "The registryKey cannot be null!"); + Preconditions.checkArgument(location != null, "The location cannot be null!"); + Preconditions.checkArgument(value != null, "The value cannot be null!"); + var registry = registry(registryKey); + var key = ResourceKey.create(registryKey, location); + try { + var holder = registry.getHolder(key).orElse(null); + if (holder == null) return false; + var oldValue = holder.value(); + Field valueField = getField(Holder.Reference.class, "T"); + valueField.setAccessible(true); + Field toIdField = getField(MappedRegistry.class, buildType(Reference2IntMap.class, "T")); + toIdField.setAccessible(true); + Field byValueField = getField(MappedRegistry.class, buildType(Map.class, "T", buildType(Holder.Reference.class, "T"))); + byValueField.setAccessible(true); + Field lifecyclesField = getField(MappedRegistry.class, buildType(Map.class, "T", Lifecycle.class.getName())); + lifecyclesField.setAccessible(true); + var toId = (Reference2IntMap) toIdField.get(registry); + var byValue = (Map>) byValueField.get(registry); + var lifecycles = (Map) lifecyclesField.get(registry); + + valueField.set(holder, value); + toId.put(value, toId.removeInt(oldValue)); + byValue.put(value, byValue.remove(oldValue)); + lifecycles.put(value, lifecycles.remove(oldValue)); + return true; + } catch (Throwable e) { + throw new IllegalStateException(e); + } + } + + private MappedRegistry registry(ResourceKey> registryKey) { + var rawRegistry = registry().registry(registryKey).orElse(null); + if (!(rawRegistry instanceof MappedRegistry registry)) + throw new IllegalStateException("The Registry is not a mapped Registry!"); + return registry; + } + + private static String buildType(Class clazz, String... parameterTypes) { + if (parameterTypes.length == 0) return clazz.getName(); + var builder = new StringBuilder(clazz.getName()) + .append("<"); + for (int i = 0; i < parameterTypes.length; i++) { + builder.append(parameterTypes[i]).append(parameterTypes.length - 1 == i ? ">" : ", "); + } + return builder.toString(); + } + + private static Field getField(Class clazz, String type) throws NoSuchFieldException { + try { + for (Field f : clazz.getDeclaredFields()) { + if (f.getGenericType().getTypeName().equals(type)) + return f; + } + throw new NoSuchFieldException(type); + } catch (NoSuchFieldException e) { + Class superClass = clazz.getSuperclass(); + if (superClass == null) throw e; + return getField(superClass, type); + } + } + private static Field getField(Class clazz, Class fieldType) throws NoSuchFieldException { try { for (Field f : clazz.getDeclaredFields()) { @@ -535,4 +711,75 @@ public class NMSBinding implements INMSBinding { } } } -} + + public void injectBukkit() { + try { + Iris.info("Injecting Bukkit"); + new ByteBuddy() + .redefine(CraftServer.class) + .visit(Advice.to(CraftServerAdvice.class).on(ElementMatchers.isMethod().and(ElementMatchers.takesArguments(WorldCreator.class)))) + .make() + .load(CraftServer.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); + new ByteBuddy() + .redefine(ServerLevel.class) + .visit(Advice.to(ServerLevelAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(MinecraftServer.class, Executor.class, LevelStorageSource.LevelStorageAccess.class, + PrimaryLevelData.class, ResourceKey.class, LevelStem.class, ChunkProgressListener.class, boolean.class, long.class, + List.class, boolean.class, World.Environment.class, ChunkGenerator.class, BiomeProvider.class)))) + .make() + .load(ServerLevel.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); + Iris.info("Injected Bukkit Successfully!"); + } catch (Exception e) { + Iris.info(C.RED + "Failed to Inject Bukkit!"); + e.printStackTrace(); + Iris.reportError(e); + } + + } + + private static class ServerLevelAdvice { + @Advice.OnMethodEnter + static void enter(@Advice.Argument(0) MinecraftServer server, @Advice.Argument(2) LevelStorageSource.LevelStorageAccess access, @Advice.Argument(4) ResourceKey key, @Advice.Argument(value = 5, readOnly = false) LevelStem levelStem) { + File iris = new File(access.levelDirectory.path().toFile(), "iris"); + if (!iris.exists() && !key.location().getPath().startsWith("iris/")) return; + ResourceKey typeKey = ResourceKey.create(Registries.DIMENSION_TYPE, new ResourceLocation("iris", key.location().getPath())); + RegistryAccess registryAccess = server.registryAccess(); + Registry registry = registryAccess.registry(Registries.DIMENSION_TYPE).orElse(null); + if (registry == null) throw new IllegalStateException("Unable to find registry for dimension type " + typeKey); + Holder holder = registry.getHolder(typeKey).orElse(null); + if (holder == null) throw new IllegalStateException("Unable to find dimension type " + typeKey); + levelStem = new LevelStem(holder, levelStem.generator()); + } + } + + private static class CraftServerAdvice { + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + static boolean enter(@Advice.This CraftServer self, @Advice.Argument(0) WorldCreator creator) { + File isIrisWorld = new File(self.getWorldContainer(), creator.name() + "/iris"); + boolean isFromIris = false; + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + for (StackTraceElement stack : stackTrace) { + if (stack.getClassName().contains("Iris")) { + isFromIris = true; + break; + } + } + if (isIrisWorld.exists() && !isFromIris) { + var logger = Logger.getLogger("Iris"); + logger.warning("detected another Plugin trying to load " + creator.name() + ". This is not supported and will be ignored."); + + if (System.getProperty("iris.debug", "false").equals("true")) { + new RuntimeException().printStackTrace(); + } + return true; + } + return false; + } + + @Advice.OnMethodExit + static void exit(@Advice.Enter boolean bool, @Advice.Return(readOnly = false) World returned) { + if (bool) { + returned = null; + } + } + } +} \ No newline at end of file diff --git a/nms/v1_19_R3/src/main/java/com/volmit/iris/core/nms/v1_19_R3/CustomBiomeSource.java b/nms/v1_19_R3/src/main/java/com/volmit/iris/core/nms/v1_19_R3/CustomBiomeSource.java index f3debbaad..3e26f6f79 100644 --- a/nms/v1_19_R3/src/main/java/com/volmit/iris/core/nms/v1_19_R3/CustomBiomeSource.java +++ b/nms/v1_19_R3/src/main/java/com/volmit/iris/core/nms/v1_19_R3/CustomBiomeSource.java @@ -2,6 +2,7 @@ package com.volmit.iris.core.nms.v1_19_R3; import com.mojang.serialization.Codec; import com.volmit.iris.Iris; +import com.volmit.iris.core.nms.INMS; import com.volmit.iris.engine.data.cache.AtomicCache; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.object.IrisBiome; @@ -125,8 +126,16 @@ public class CustomBiomeSource extends BiomeSource { for (IrisBiome i : engine.getAllBiomes()) { if (i.isCustom()) { for (IrisBiomeCustom j : i.getCustomDerivitives()) { - ResourceLocation resourceLocation = new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId()); - Biome biome = customRegistry.get(resourceLocation); + ResourceLocation location = new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId()); + Biome biome = customRegistry.get(location); + if (biome == null) { + INMS.get().registerBiome(location.getNamespace(), j, false); + biome = customRegistry.get(location); + if (biome == null) { + Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName()); + continue; + } + } Optional> optionalBiomeKey = customRegistry.getResourceKey(biome); if (optionalBiomeKey.isEmpty()) { Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName()); 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 c32354ae9..4a33fed2d 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 @@ -4,14 +4,44 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; +import java.io.File; 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.Map; +import java.util.Optional; import java.util.Vector; +import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Logger; +import com.google.common.base.Preconditions; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.Codec; +import com.mojang.serialization.JsonOps; +import com.mojang.serialization.Lifecycle; +import com.volmit.iris.engine.object.IrisBiomeCustom; +import com.volmit.iris.engine.object.IrisDimension; +import com.volmit.iris.util.format.C; +import it.unimi.dsi.fastutil.objects.Reference2IntMap; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.dynamic.loading.ClassReloadingStrategy; +import net.bytebuddy.matcher.ElementMatchers; +import net.minecraft.core.MappedRegistry; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.progress.ChunkProgressListener; +import net.minecraft.util.GsonHelper; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.storage.LevelStorageSource; +import net.minecraft.world.level.storage.PrimaryLevelData; import org.bukkit.*; import org.bukkit.block.Biome; import org.bukkit.block.data.BlockData; @@ -25,6 +55,7 @@ import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftItemStack; import org.bukkit.entity.Dolphin; import org.bukkit.entity.Entity; import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.ChunkGenerator; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; @@ -172,6 +203,17 @@ public class NMSBinding implements INMSBinding { return null; } + private RegistryAccess getRegistryAccess(World world) { + try { + var field = getField(Level.class, RegistryAccess.class); + field.setAccessible(true); + return (RegistryAccess) field.get(((CraftWorld) world).getHandle()); + } catch (Throwable e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + @Override public void deserializeTile(CompoundTag c, Location pos) { ((CraftWorld) pos.getWorld()).getHandle().getChunkAt(new BlockPos(pos.getBlockX(), 0, pos.getBlockZ())).setBlockEntityNbt(convert(c)); @@ -255,8 +297,7 @@ public class NMSBinding implements INMSBinding { @Override public Object getBiomeBase(World world, Biome biome) { - return CraftBlock.biomeToBiomeBase(((CraftWorld) world).getHandle() - .registryAccess().registry(Registries.BIOME).orElse(null), biome); + return CraftBlock.biomeToBiomeBase(getRegistryAccess(world).registry(Registries.BIOME).orElse(null), biome); } @Override @@ -294,8 +335,11 @@ public class NMSBinding implements INMSBinding { public int getBiomeId(Biome biome) { for (World i : Bukkit.getWorlds()) { if (i.getEnvironment().equals(World.Environment.NORMAL)) { - Registry registry = ((CraftWorld) i).getHandle().registryAccess().registry(Registries.BIOME).orElse(null); - return registry.getId((net.minecraft.world.level.biome.Biome) getBiomeBase(registry, biome)); + var registry = getRegistryAccess(i).registry(Registries.BIOME).orElse(null); + if (registry != null) { + var holder = (Holder) getBiomeBase(registry, biome); + return registry.getId(holder.value()); + } } } @@ -523,6 +567,138 @@ public class NMSBinding implements INMSBinding { return ((CraftWorld) location.getWorld()).spawn(location, type.getEntityClass(), null, reason); } + @Override + public boolean registerDimension(String name, IrisDimension dimension) { + var registry = registry(Registries.DIMENSION_TYPE); + var baseLocation = switch (dimension.getEnvironment()) { + case NORMAL -> new ResourceLocation("minecraft", "overworld"); + case NETHER -> new ResourceLocation("minecraft", "the_nether"); + case THE_END -> new ResourceLocation("minecraft", "the_end"); + case CUSTOM -> throw new IllegalArgumentException("Cannot register custom dimension"); + }; + var base = registry.getHolder(ResourceKey.create(Registries.DIMENSION_TYPE, baseLocation)).orElse(null); + if (base == null) return false; + var json = encode(DimensionType.CODEC, base).orElse(null); + if (json == null) return false; + var object = json.getAsJsonObject(); + var height = dimension.getDimensionHeight(); + object.addProperty("min_y", height.getMin()); + object.addProperty("height", height.getMax() - height.getMin()); + object.addProperty("logical_height", dimension.getLogicalHeight()); + var value = decode(DimensionType.CODEC, object.toString()).map(Holder::value).orElse(null); + if (value == null) return false; + return register(Registries.DIMENSION_TYPE, new ResourceLocation("iris", name), value, true); + } + + @Override + public boolean registerBiome(String dimensionId, IrisBiomeCustom biome, boolean replace) { + var biomeBase = decode(net.minecraft.world.level.biome.Biome.CODEC, biome.generateJson()).map(Holder::value).orElse(null); + if (biomeBase == null) return false; + return register(Registries.BIOME, new ResourceLocation(dimensionId, biome.getId()), biomeBase, replace); + } + + private Optional decode(Codec codec, String json) { + return codec.decode(JsonOps.INSTANCE, GsonHelper.parse(json)).get().left().map(Pair::getFirst); + } + + private Optional encode(Codec codec, T value) { + return codec.encode(value, JsonOps.INSTANCE, new JsonObject()).result(); + } + + private boolean register(ResourceKey> registryKey, ResourceLocation location, T value, boolean replace) { + Preconditions.checkArgument(registryKey != null, "The registry cannot be null!"); + Preconditions.checkArgument(location != null, "The location cannot be null!"); + Preconditions.checkArgument(value != null, "The value cannot be null!"); + var registry = registry(registryKey); + var key = ResourceKey.create(registryKey, location); + try { + if (registry.containsKey(key)) { + if (!replace) return false; + return replace(registryKey, location, value); + } + Field field = getField(MappedRegistry.class, boolean.class); + field.setAccessible(true); + boolean frozen = field.getBoolean(registry); + field.setBoolean(registry, false); + Field valueField = getField(Holder.Reference.class, "T"); + valueField.setAccessible(true); + + try { + var holder = registry.register(key, value, Lifecycle.stable()); + if (frozen) valueField.set(holder, value); + return true; + } finally { + field.setBoolean(registry, frozen); + } + } catch (Throwable e) { + throw new IllegalStateException(e); + } + } + + @SuppressWarnings("unchecked") + private boolean replace(ResourceKey> registryKey, ResourceLocation location, T value) { + Preconditions.checkArgument(registryKey != null, "The registryKey cannot be null!"); + Preconditions.checkArgument(location != null, "The location cannot be null!"); + Preconditions.checkArgument(value != null, "The value cannot be null!"); + var registry = registry(registryKey); + var key = ResourceKey.create(registryKey, location); + try { + var holder = registry.getHolder(key).orElse(null); + if (holder == null) return false; + var oldValue = holder.value(); + Field valueField = getField(Holder.Reference.class, "T"); + valueField.setAccessible(true); + Field toIdField = getField(MappedRegistry.class, buildType(Reference2IntMap.class, "T")); + toIdField.setAccessible(true); + Field byValueField = getField(MappedRegistry.class, buildType(Map.class, "T", buildType(Holder.Reference.class, "T"))); + byValueField.setAccessible(true); + Field lifecyclesField = getField(MappedRegistry.class, buildType(Map.class, "T", Lifecycle.class.getName())); + lifecyclesField.setAccessible(true); + var toId = (Reference2IntMap) toIdField.get(registry); + var byValue = (Map>) byValueField.get(registry); + var lifecycles = (Map) lifecyclesField.get(registry); + + valueField.set(holder, value); + toId.put(value, toId.removeInt(oldValue)); + byValue.put(value, byValue.remove(oldValue)); + lifecycles.put(value, lifecycles.remove(oldValue)); + return true; + } catch (Throwable e) { + throw new IllegalStateException(e); + } + } + + private MappedRegistry registry(ResourceKey> registryKey) { + var rawRegistry = registry().registry(registryKey).orElse(null); + if (!(rawRegistry instanceof MappedRegistry registry)) + throw new IllegalStateException("The Registry is not a mapped Registry!"); + return registry; + } + + private static String buildType(Class clazz, String... parameterTypes) { + if (parameterTypes.length == 0) return clazz.getName(); + var builder = new StringBuilder(clazz.getName()) + .append("<"); + for (int i = 0; i < parameterTypes.length; i++) { + builder.append(parameterTypes[i]).append(parameterTypes.length - 1 == i ? ">" : ", "); + } + return builder.toString(); + } + + private static Field getField(Class clazz, String type) throws NoSuchFieldException { + try { + for (Field f : clazz.getDeclaredFields()) { + if (f.getGenericType().getTypeName().equals(type)) + return f; + } + throw new NoSuchFieldException(type); + } catch (NoSuchFieldException e) { + Class superClass = clazz.getSuperclass(); + if (superClass == null) throw e; + return getField(superClass, type); + } + } + private static Field getField(Class clazz, Class fieldType) throws NoSuchFieldException { try { for (Field f : clazz.getDeclaredFields()) { @@ -539,4 +715,75 @@ public class NMSBinding implements INMSBinding { } } } + + public void injectBukkit() { + try { + Iris.info("Injecting Bukkit"); + new ByteBuddy() + .redefine(CraftServer.class) + .visit(Advice.to(CraftServerAdvice.class).on(ElementMatchers.isMethod().and(ElementMatchers.takesArguments(WorldCreator.class)))) + .make() + .load(CraftServer.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); + new ByteBuddy() + .redefine(ServerLevel.class) + .visit(Advice.to(ServerLevelAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(MinecraftServer.class, Executor.class, LevelStorageSource.LevelStorageAccess.class, + PrimaryLevelData.class, ResourceKey.class, LevelStem.class, ChunkProgressListener.class, boolean.class, long.class, + List.class, boolean.class, World.Environment.class, ChunkGenerator.class, BiomeProvider.class)))) + .make() + .load(ServerLevel.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); + Iris.info("Injected Bukkit Successfully!"); + } catch (Exception e) { + Iris.info(C.RED + "Failed to Inject Bukkit!"); + e.printStackTrace(); + Iris.reportError(e); + } + + } + + private static class ServerLevelAdvice { + @Advice.OnMethodEnter + static void enter(@Advice.Argument(0) MinecraftServer server, @Advice.Argument(2) LevelStorageSource.LevelStorageAccess access, @Advice.Argument(4) ResourceKey key, @Advice.Argument(value = 5, readOnly = false) LevelStem levelStem) { + File iris = new File(access.levelDirectory.path().toFile(), "iris"); + if (!iris.exists() && !key.location().getPath().startsWith("iris/")) return; + ResourceKey typeKey = ResourceKey.create(Registries.DIMENSION_TYPE, new ResourceLocation("iris", key.location().getPath())); + RegistryAccess registryAccess = server.registryAccess(); + Registry registry = registryAccess.registry(Registries.DIMENSION_TYPE).orElse(null); + if (registry == null) throw new IllegalStateException("Unable to find registry for dimension type " + typeKey); + Holder holder = registry.getHolder(typeKey).orElse(null); + if (holder == null) throw new IllegalStateException("Unable to find dimension type " + typeKey); + levelStem = new LevelStem(holder, levelStem.generator()); + } + } + + private static class CraftServerAdvice { + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + static boolean enter(@Advice.This CraftServer self, @Advice.Argument(0) WorldCreator creator) { + File isIrisWorld = new File(self.getWorldContainer(), creator.name() + "/iris"); + boolean isFromIris = false; + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + for (StackTraceElement stack : stackTrace) { + if (stack.getClassName().contains("Iris")) { + isFromIris = true; + break; + } + } + if (isIrisWorld.exists() && !isFromIris) { + var logger = Logger.getLogger("Iris"); + logger.warning("detected another Plugin trying to load " + creator.name() + ". This is not supported and will be ignored."); + + if (System.getProperty("iris.debug", "false").equals("true")) { + new RuntimeException().printStackTrace(); + } + return true; + } + return false; + } + + @Advice.OnMethodExit + static void exit(@Advice.Enter boolean bool, @Advice.Return(readOnly = false) World returned) { + if (bool) { + returned = null; + } + } + } } diff --git a/nms/v1_20_R1/src/main/java/com/volmit/iris/core/nms/v1_20_R1/CustomBiomeSource.java b/nms/v1_20_R1/src/main/java/com/volmit/iris/core/nms/v1_20_R1/CustomBiomeSource.java index 41f4170c8..6ebea1093 100644 --- a/nms/v1_20_R1/src/main/java/com/volmit/iris/core/nms/v1_20_R1/CustomBiomeSource.java +++ b/nms/v1_20_R1/src/main/java/com/volmit/iris/core/nms/v1_20_R1/CustomBiomeSource.java @@ -2,6 +2,7 @@ package com.volmit.iris.core.nms.v1_20_R1; import com.mojang.serialization.Codec; import com.volmit.iris.Iris; +import com.volmit.iris.core.nms.INMS; import com.volmit.iris.engine.data.cache.AtomicCache; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.object.IrisBiome; @@ -125,8 +126,16 @@ public class CustomBiomeSource extends BiomeSource { for (IrisBiome i : engine.getAllBiomes()) { if (i.isCustom()) { for (IrisBiomeCustom j : i.getCustomDerivitives()) { - ResourceLocation resourceLocation = new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId()); - Biome biome = customRegistry.get(resourceLocation); + ResourceLocation location = new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId()); + Biome biome = customRegistry.get(location); + if (biome == null) { + INMS.get().registerBiome(location.getNamespace(), j, false); + biome = customRegistry.get(location); + if (biome == null) { + Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName()); + continue; + } + } Optional> optionalBiomeKey = customRegistry.getResourceKey(biome); if (optionalBiomeKey.isEmpty()) { Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName()); 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 165f97808..1451edd92 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,12 +1,22 @@ package com.volmit.iris.core.nms.v1_20_R1; +import com.google.common.base.Preconditions; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.Codec; +import com.mojang.serialization.JsonOps; +import com.mojang.serialization.Lifecycle; 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.engine.object.IrisBiomeCustom; +import com.volmit.iris.engine.object.IrisDimension; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.format.C; import com.volmit.iris.util.hunk.Hunk; import com.volmit.iris.util.json.JSONObject; import com.volmit.iris.util.mantle.Mantle; @@ -17,16 +27,27 @@ 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 it.unimi.dsi.fastutil.objects.Reference2IntMap; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.dynamic.loading.ClassReloadingStrategy; +import net.bytebuddy.matcher.ElementMatchers; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; +import net.minecraft.core.MappedRegistry; 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.MinecraftServer; import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.entity.EntityDimensions; +import net.minecraft.server.level.progress.ChunkProgressListener; +import net.minecraft.util.GsonHelper; +import net.minecraft.world.RandomSequences; +import net.minecraft.world.level.Level; import net.minecraft.world.level.biome.BiomeSource; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; @@ -34,6 +55,10 @@ import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.storage.LevelStorageSource; +import net.minecraft.world.level.storage.PrimaryLevelData; import org.bukkit.*; import org.bukkit.block.Biome; import org.bukkit.block.data.BlockData; @@ -47,9 +72,8 @@ import org.bukkit.craftbukkit.v1_20_R1.entity.CraftDolphin; import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftItemStack; import org.bukkit.entity.Dolphin; import org.bukkit.entity.Entity; -import org.bukkit.entity.EntityType; import org.bukkit.event.entity.CreatureSpawnEvent; -import org.bukkit.entity.EntityType; +import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.ChunkGenerator; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; @@ -59,13 +83,18 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; +import java.io.File; 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.Map; +import java.util.Optional; import java.util.Vector; +import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Logger; public class NMSBinding implements INMSBinding { @@ -175,6 +204,17 @@ public class NMSBinding implements INMSBinding { return null; } + private RegistryAccess getRegistryAccess(World world) { + try { + var field = getField(Level.class, RegistryAccess.class); + field.setAccessible(true); + return (RegistryAccess) field.get(((CraftWorld) world).getHandle()); + } catch (Throwable e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + @Override public void deserializeTile(CompoundTag c, Location pos) { ((CraftWorld) pos.getWorld()).getHandle().getChunkAt(new BlockPos(pos.getBlockX(), 0, pos.getBlockZ())).setBlockEntityNbt(convert(c)); @@ -258,8 +298,7 @@ public class NMSBinding implements INMSBinding { @Override public Object getBiomeBase(World world, Biome biome) { - return CraftBlock.biomeToBiomeBase(((CraftWorld) world).getHandle() - .registryAccess().registry(Registries.BIOME).orElse(null), biome); + return CraftBlock.biomeToBiomeBase(getRegistryAccess(world).registry(Registries.BIOME).orElse(null), biome); } @Override @@ -296,8 +335,11 @@ public class NMSBinding implements INMSBinding { public int getBiomeId(Biome biome) { for (World i : Bukkit.getWorlds()) { if (i.getEnvironment().equals(World.Environment.NORMAL)) { - Registry registry = ((CraftWorld) i).getHandle().registryAccess().registry(Registries.BIOME).orElse(null); - return registry.getId((net.minecraft.world.level.biome.Biome) getBiomeBase(registry, biome)); + var registry = getRegistryAccess(i).registry(Registries.BIOME).orElse(null); + if (registry != null) { + var holder = (Holder) getBiomeBase(registry, biome); + return registry.getId(holder.value()); + } } } @@ -509,6 +551,138 @@ public class NMSBinding implements INMSBinding { return ((CraftWorld) location.getWorld()).spawn(location, type.getEntityClass(), null, reason); } + @Override + public boolean registerDimension(String name, IrisDimension dimension) { + var registry = registry(Registries.DIMENSION_TYPE); + var baseLocation = switch (dimension.getEnvironment()) { + case NORMAL -> new ResourceLocation("minecraft", "overworld"); + case NETHER -> new ResourceLocation("minecraft", "the_nether"); + case THE_END -> new ResourceLocation("minecraft", "the_end"); + case CUSTOM -> throw new IllegalArgumentException("Cannot register custom dimension"); + }; + var base = registry.getHolder(ResourceKey.create(Registries.DIMENSION_TYPE, baseLocation)).orElse(null); + if (base == null) return false; + var json = encode(DimensionType.CODEC, base).orElse(null); + if (json == null) return false; + var object = json.getAsJsonObject(); + var height = dimension.getDimensionHeight(); + object.addProperty("min_y", height.getMin()); + object.addProperty("height", height.getMax() - height.getMin()); + object.addProperty("logical_height", dimension.getLogicalHeight()); + var value = decode(DimensionType.CODEC, object.toString()).map(Holder::value).orElse(null); + if (value == null) return false; + return register(Registries.DIMENSION_TYPE, new ResourceLocation("iris", name), value, true); + } + + @Override + public boolean registerBiome(String dimensionId, IrisBiomeCustom biome, boolean replace) { + var biomeBase = decode(net.minecraft.world.level.biome.Biome.CODEC, biome.generateJson()).map(Holder::value).orElse(null); + if (biomeBase == null) return false; + return register(Registries.BIOME, new ResourceLocation(dimensionId, biome.getId()), biomeBase, replace); + } + + private Optional decode(Codec codec, String json) { + return codec.decode(JsonOps.INSTANCE, GsonHelper.parse(json)).get().left().map(Pair::getFirst); + } + + private Optional encode(Codec codec, T value) { + return codec.encode(value, JsonOps.INSTANCE, new JsonObject()).result(); + } + + private boolean register(ResourceKey> registryKey, ResourceLocation location, T value, boolean replace) { + Preconditions.checkArgument(registryKey != null, "The registry cannot be null!"); + Preconditions.checkArgument(location != null, "The location cannot be null!"); + Preconditions.checkArgument(value != null, "The value cannot be null!"); + var registry = registry(registryKey); + var key = ResourceKey.create(registryKey, location); + try { + if (registry.containsKey(key)) { + if (!replace) return false; + return replace(registryKey, location, value); + } + Field field = getField(MappedRegistry.class, boolean.class); + field.setAccessible(true); + boolean frozen = field.getBoolean(registry); + field.setBoolean(registry, false); + Field valueField = getField(Holder.Reference.class, "T"); + valueField.setAccessible(true); + + try { + var holder = registry.register(key, value, Lifecycle.stable()); + if (frozen) valueField.set(holder, value); + return true; + } finally { + field.setBoolean(registry, frozen); + } + } catch (Throwable e) { + throw new IllegalStateException(e); + } + } + + @SuppressWarnings("unchecked") + private boolean replace(ResourceKey> registryKey, ResourceLocation location, T value) { + Preconditions.checkArgument(registryKey != null, "The registryKey cannot be null!"); + Preconditions.checkArgument(location != null, "The location cannot be null!"); + Preconditions.checkArgument(value != null, "The value cannot be null!"); + var registry = registry(registryKey); + var key = ResourceKey.create(registryKey, location); + try { + var holder = registry.getHolder(key).orElse(null); + if (holder == null) return false; + var oldValue = holder.value(); + Field valueField = getField(Holder.Reference.class, "T"); + valueField.setAccessible(true); + Field toIdField = getField(MappedRegistry.class, buildType(Reference2IntMap.class, "T")); + toIdField.setAccessible(true); + Field byValueField = getField(MappedRegistry.class, buildType(Map.class, "T", buildType(Holder.Reference.class, "T"))); + byValueField.setAccessible(true); + Field lifecyclesField = getField(MappedRegistry.class, buildType(Map.class, "T", Lifecycle.class.getName())); + lifecyclesField.setAccessible(true); + var toId = (Reference2IntMap) toIdField.get(registry); + var byValue = (Map>) byValueField.get(registry); + var lifecycles = (Map) lifecyclesField.get(registry); + + valueField.set(holder, value); + toId.put(value, toId.removeInt(oldValue)); + byValue.put(value, byValue.remove(oldValue)); + lifecycles.put(value, lifecycles.remove(oldValue)); + return true; + } catch (Throwable e) { + throw new IllegalStateException(e); + } + } + + private MappedRegistry registry(ResourceKey> registryKey) { + var rawRegistry = registry().registry(registryKey).orElse(null); + if (!(rawRegistry instanceof MappedRegistry registry)) + throw new IllegalStateException("The Registry is not a mapped Registry!"); + return registry; + } + + private static String buildType(Class clazz, String... parameterTypes) { + if (parameterTypes.length == 0) return clazz.getName(); + var builder = new StringBuilder(clazz.getName()) + .append("<"); + for (int i = 0; i < parameterTypes.length; i++) { + builder.append(parameterTypes[i]).append(parameterTypes.length - 1 == i ? ">" : ", "); + } + return builder.toString(); + } + + private static Field getField(Class clazz, String type) throws NoSuchFieldException { + try { + for (Field f : clazz.getDeclaredFields()) { + if (f.getGenericType().getTypeName().equals(type)) + return f; + } + throw new NoSuchFieldException(type); + } catch (NoSuchFieldException e) { + Class superClass = clazz.getSuperclass(); + if (superClass == null) throw e; + return getField(superClass, type); + } + } + public void inject(long seed, Engine engine, World world) throws NoSuchFieldException, IllegalAccessException { ServerLevel serverLevel = ((CraftWorld)world).getHandle(); Class clazz = serverLevel.getChunkSource().chunkMap.generator.getClass(); @@ -538,4 +712,75 @@ public class NMSBinding implements INMSBinding { } } } + + public void injectBukkit() { + try { + Iris.info("Injecting Bukkit"); + new ByteBuddy() + .redefine(CraftServer.class) + .visit(Advice.to(CraftServerAdvice.class).on(ElementMatchers.isMethod().and(ElementMatchers.takesArguments(WorldCreator.class)))) + .make() + .load(CraftServer.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); + new ByteBuddy() + .redefine(ServerLevel.class) + .visit(Advice.to(ServerLevelAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(MinecraftServer.class, Executor.class, LevelStorageSource.LevelStorageAccess.class, + PrimaryLevelData.class, ResourceKey.class, LevelStem.class, ChunkProgressListener.class, boolean.class, long.class, + List.class, boolean.class, RandomSequences.class, World.Environment.class, ChunkGenerator.class, BiomeProvider.class)))) + .make() + .load(ServerLevel.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); + Iris.info("Injected Bukkit Successfully!"); + } catch (Exception e) { + Iris.info(C.RED + "Failed to Inject Bukkit!"); + e.printStackTrace(); + Iris.reportError(e); + } + + } + + private static class ServerLevelAdvice { + @Advice.OnMethodEnter + static void enter(@Advice.Argument(0) MinecraftServer server, @Advice.Argument(2) LevelStorageSource.LevelStorageAccess access, @Advice.Argument(4) ResourceKey key, @Advice.Argument(value = 5, readOnly = false) LevelStem levelStem) { + File iris = new File(access.levelDirectory.path().toFile(), "iris"); + if (!iris.exists() && !key.location().getPath().startsWith("iris/")) return; + ResourceKey typeKey = ResourceKey.create(Registries.DIMENSION_TYPE, new ResourceLocation("iris", key.location().getPath())); + RegistryAccess registryAccess = server.registryAccess(); + Registry registry = registryAccess.registry(Registries.DIMENSION_TYPE).orElse(null); + if (registry == null) throw new IllegalStateException("Unable to find registry for dimension type " + typeKey); + Holder holder = registry.getHolder(typeKey).orElse(null); + if (holder == null) throw new IllegalStateException("Unable to find dimension type " + typeKey); + levelStem = new LevelStem(holder, levelStem.generator()); + } + } + + private static class CraftServerAdvice { + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + static boolean enter(@Advice.This CraftServer self, @Advice.Argument(0) WorldCreator creator) { + File isIrisWorld = new File(self.getWorldContainer(), creator.name() + "/iris"); + boolean isFromIris = false; + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + for (StackTraceElement stack : stackTrace) { + if (stack.getClassName().contains("Iris")) { + isFromIris = true; + break; + } + } + if (isIrisWorld.exists() && !isFromIris) { + var logger = Logger.getLogger("Iris"); + logger.warning("detected another Plugin trying to load " + creator.name() + ". This is not supported and will be ignored."); + + if (System.getProperty("iris.debug", "false").equals("true")) { + new RuntimeException().printStackTrace(); + } + return true; + } + return false; + } + + @Advice.OnMethodExit + static void exit(@Advice.Enter boolean bool, @Advice.Return(readOnly = false) World returned) { + if (bool) { + returned = null; + } + } + } } diff --git a/nms/v1_20_R2/src/main/java/com/volmit/iris/core/nms/v1_20_R2/CustomBiomeSource.java b/nms/v1_20_R2/src/main/java/com/volmit/iris/core/nms/v1_20_R2/CustomBiomeSource.java index fcf462f95..24ce23216 100644 --- a/nms/v1_20_R2/src/main/java/com/volmit/iris/core/nms/v1_20_R2/CustomBiomeSource.java +++ b/nms/v1_20_R2/src/main/java/com/volmit/iris/core/nms/v1_20_R2/CustomBiomeSource.java @@ -2,6 +2,7 @@ package com.volmit.iris.core.nms.v1_20_R2; import com.mojang.serialization.Codec; import com.volmit.iris.Iris; +import com.volmit.iris.core.nms.INMS; import com.volmit.iris.engine.data.cache.AtomicCache; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.object.IrisBiome; @@ -124,8 +125,16 @@ public class CustomBiomeSource extends BiomeSource { for (IrisBiome i : engine.getAllBiomes()) { if (i.isCustom()) { for (IrisBiomeCustom j : i.getCustomDerivitives()) { - ResourceLocation resourceLocation = new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId()); - Biome biome = customRegistry.get(resourceLocation); + ResourceLocation location = new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId()); + Biome biome = customRegistry.get(location); + if (biome == null) { + INMS.get().registerBiome(location.getNamespace(), j, false); + biome = customRegistry.get(location); + if (biome == null) { + Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName()); + continue; + } + } Optional> optionalBiomeKey = customRegistry.getResourceKey(biome); if (optionalBiomeKey.isEmpty()) { Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName()); 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 73a15d89c..d74e7c125 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 @@ -4,14 +4,44 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; +import java.io.File; 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.Map; +import java.util.Optional; import java.util.Vector; +import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Logger; +import com.google.common.base.Preconditions; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.Codec; +import com.mojang.serialization.JsonOps; +import com.mojang.serialization.Lifecycle; +import com.volmit.iris.engine.object.IrisBiomeCustom; +import com.volmit.iris.engine.object.IrisDimension; +import com.volmit.iris.util.format.C; +import it.unimi.dsi.fastutil.objects.Reference2IntMap; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.dynamic.loading.ClassReloadingStrategy; +import net.bytebuddy.matcher.ElementMatchers; +import net.minecraft.core.MappedRegistry; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.progress.ChunkProgressListener; +import net.minecraft.util.GsonHelper; +import net.minecraft.world.RandomSequences; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.storage.LevelStorageSource; +import net.minecraft.world.level.storage.PrimaryLevelData; import org.bukkit.*; import org.bukkit.block.Biome; import org.bukkit.block.data.BlockData; @@ -25,6 +55,7 @@ import org.bukkit.craftbukkit.v1_20_R2.util.CraftNamespacedKey; import org.bukkit.entity.Dolphin; import org.bukkit.entity.Entity; import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.ChunkGenerator; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; @@ -173,6 +204,17 @@ public class NMSBinding implements INMSBinding { return null; } + private RegistryAccess getRegistryAccess(World world) { + try { + var field = getField(Level.class, RegistryAccess.class); + field.setAccessible(true); + return (RegistryAccess) field.get(((CraftWorld) world).getHandle()); + } catch (Throwable e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + @Override public void deserializeTile(CompoundTag c, Location pos) { ((CraftWorld) pos.getWorld()).getHandle().getChunkAt(new BlockPos(pos.getBlockX(), 0, pos.getBlockZ())).setBlockEntityNbt(convert(c)); @@ -256,8 +298,7 @@ public class NMSBinding implements INMSBinding { @Override public Object getBiomeBase(World world, Biome biome) { - return biomeToBiomeBase(((CraftWorld) world).getHandle() - .registryAccess().registry(Registries.BIOME).orElse(null), biome); + return biomeToBiomeBase(getRegistryAccess(world).registry(Registries.BIOME).orElse(null), biome); } @Override @@ -294,8 +335,11 @@ public class NMSBinding implements INMSBinding { public int getBiomeId(Biome biome) { for (World i : Bukkit.getWorlds()) { if (i.getEnvironment().equals(World.Environment.NORMAL)) { - Registry registry = ((CraftWorld) i).getHandle().registryAccess().registry(Registries.BIOME).orElse(null); - return registry.getId((net.minecraft.world.level.biome.Biome) getBiomeBase(registry, biome)); + var registry = getRegistryAccess(i).registry(Registries.BIOME).orElse(null); + if (registry != null) { + var holder = (Holder) getBiomeBase(registry, biome); + return registry.getId(holder.value()); + } } } @@ -520,6 +564,139 @@ public class NMSBinding implements INMSBinding { return ((CraftWorld) location.getWorld()).spawn(location, type.getEntityClass(), null, reason); } + @Override + public boolean registerDimension(String name, IrisDimension dimension) { + var registry = registry(Registries.DIMENSION_TYPE); + var baseLocation = switch (dimension.getEnvironment()) { + case NORMAL -> new ResourceLocation("minecraft", "overworld"); + case NETHER -> new ResourceLocation("minecraft", "the_nether"); + case THE_END -> new ResourceLocation("minecraft", "the_end"); + case CUSTOM -> throw new IllegalArgumentException("Cannot register custom dimension"); + }; + var base = registry.getHolder(ResourceKey.create(Registries.DIMENSION_TYPE, baseLocation)).orElse(null); + if (base == null) return false; + var json = encode(DimensionType.CODEC, base).orElse(null); + if (json == null) return false; + var object = json.getAsJsonObject(); + var height = dimension.getDimensionHeight(); + object.addProperty("min_y", height.getMin()); + object.addProperty("height", height.getMax() - height.getMin()); + object.addProperty("logical_height", dimension.getLogicalHeight()); + var value = decode(DimensionType.CODEC, object.toString()).map(Holder::value).orElse(null); + if (value == null) return false; + return register(Registries.DIMENSION_TYPE, new ResourceLocation("iris", name), value, true); + } + + @Override + public boolean registerBiome(String dimensionId, IrisBiomeCustom biome, boolean replace) { + var biomeBase = decode(net.minecraft.world.level.biome.Biome.CODEC, biome.generateJson()).map(Holder::value).orElse(null); + if (biomeBase == null) return false; + return register(Registries.BIOME, new ResourceLocation(dimensionId, biome.getId()), biomeBase, replace); + } + + private Optional decode(Codec codec, String json) { + return codec.decode(JsonOps.INSTANCE, GsonHelper.parse(json)).get().left().map(Pair::getFirst); + } + + private Optional encode(Codec codec, T value) { + return codec.encode(value, JsonOps.INSTANCE, new JsonObject()).result(); + } + + private boolean register(ResourceKey> registryKey, ResourceLocation location, T value, boolean replace) { + Preconditions.checkArgument(registryKey != null, "The registry cannot be null!"); + Preconditions.checkArgument(location != null, "The location cannot be null!"); + Preconditions.checkArgument(value != null, "The value cannot be null!"); + var registry = registry(registryKey); + var key = ResourceKey.create(registryKey, location); + try { + if (registry.containsKey(key)) { + if (!replace) return false; + return replace(registryKey, location, value); + } + Field field = getField(MappedRegistry.class, boolean.class); + field.setAccessible(true); + boolean frozen = field.getBoolean(registry); + field.setBoolean(registry, false); + Field valueField = getField(Holder.Reference.class, "T"); + valueField.setAccessible(true); + + try { + var holder = registry.register(key, value, Lifecycle.stable()); + if (frozen) valueField.set(holder, value); + return true; + } finally { + field.setBoolean(registry, frozen); + } + } catch (Throwable e) { + throw new IllegalStateException(e); + } + } + + @SuppressWarnings("unchecked") + private boolean replace(ResourceKey> registryKey, ResourceLocation location, T value) { + Preconditions.checkArgument(registryKey != null, "The registryKey cannot be null!"); + Preconditions.checkArgument(location != null, "The location cannot be null!"); + Preconditions.checkArgument(value != null, "The value cannot be null!"); + var registry = registry(registryKey); + var key = ResourceKey.create(registryKey, location); + try { + var holder = registry.getHolder(key).orElse(null); + if (holder == null) return false; + var oldValue = holder.value(); + Field valueField = getField(Holder.Reference.class, "T"); + valueField.setAccessible(true); + Field toIdField = getField(MappedRegistry.class, buildType(Reference2IntMap.class, "T")); + toIdField.setAccessible(true); + Field byValueField = getField(MappedRegistry.class, buildType(Map.class, "T", buildType(Holder.Reference.class, "T"))); + byValueField.setAccessible(true); + Field lifecyclesField = getField(MappedRegistry.class, buildType(Map.class, "T", Lifecycle.class.getName())); + lifecyclesField.setAccessible(true); + var toId = (Reference2IntMap) toIdField.get(registry); + var byValue = (Map>) byValueField.get(registry); + var lifecycles = (Map) lifecyclesField.get(registry); + + valueField.set(holder, value); + toId.put(value, toId.removeInt(oldValue)); + byValue.put(value, byValue.remove(oldValue)); + lifecycles.put(value, lifecycles.remove(oldValue)); + return true; + } catch (Throwable e) { + throw new IllegalStateException(e); + } + } + + private MappedRegistry registry(ResourceKey> registryKey) { + var rawRegistry = registry().registry(registryKey).orElse(null); + if (!(rawRegistry instanceof MappedRegistry registry)) + throw new IllegalStateException("The Registry is not a mapped Registry!"); + return registry; + } + + private static String buildType(Class clazz, String... parameterTypes) { + if (parameterTypes.length == 0) return clazz.getName(); + var builder = new StringBuilder(clazz.getName()) + .append("<"); + for (int i = 0; i < parameterTypes.length; i++) { + builder.append(parameterTypes[i]).append(parameterTypes.length - 1 == i ? ">" : ", "); + } + return builder.toString(); + } + + private static Field getField(Class clazz, String type) throws NoSuchFieldException { + try { + for (Field f : clazz.getDeclaredFields()) { + if (f.getGenericType().getTypeName().equals(type)) + return f; + } + throw new NoSuchFieldException(type); + } catch (NoSuchFieldException e) { + Class superClass = clazz.getSuperclass(); + if (superClass == null) throw e; + return getField(superClass, type); + } + } + + private static Field getField(Class clazz, Class fieldType) throws NoSuchFieldException { try { for (Field f : clazz.getDeclaredFields()) { @@ -540,4 +717,75 @@ public class NMSBinding implements INMSBinding { public static Holder biomeToBiomeBase(Registry registry, Biome biome) { return registry.getHolderOrThrow(ResourceKey.create(Registries.BIOME, CraftNamespacedKey.toMinecraft(biome.getKey()))); } + + public void injectBukkit() { + try { + Iris.info("Injecting Bukkit"); + new ByteBuddy() + .redefine(CraftServer.class) + .visit(Advice.to(CraftServerAdvice.class).on(ElementMatchers.isMethod().and(ElementMatchers.takesArguments(WorldCreator.class)))) + .make() + .load(CraftServer.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); + new ByteBuddy() + .redefine(ServerLevel.class) + .visit(Advice.to(ServerLevelAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(MinecraftServer.class, Executor.class, LevelStorageSource.LevelStorageAccess.class, + PrimaryLevelData.class, ResourceKey.class, LevelStem.class, ChunkProgressListener.class, boolean.class, long.class, + List.class, boolean.class, RandomSequences.class, World.Environment.class, ChunkGenerator.class, BiomeProvider.class)))) + .make() + .load(ServerLevel.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); + Iris.info("Injected Bukkit Successfully!"); + } catch (Exception e) { + Iris.info(C.RED + "Failed to Inject Bukkit!"); + e.printStackTrace(); + Iris.reportError(e); + } + + } + + private static class ServerLevelAdvice { + @Advice.OnMethodEnter + static void enter(@Advice.Argument(0) MinecraftServer server, @Advice.Argument(2) LevelStorageSource.LevelStorageAccess access, @Advice.Argument(4) ResourceKey key, @Advice.Argument(value = 5, readOnly = false) LevelStem levelStem) { + File iris = new File(access.levelDirectory.path().toFile(), "iris"); + if (!iris.exists() && !key.location().getPath().startsWith("iris/")) return; + ResourceKey typeKey = ResourceKey.create(Registries.DIMENSION_TYPE, new ResourceLocation("iris", key.location().getPath())); + RegistryAccess registryAccess = server.registryAccess(); + Registry registry = registryAccess.registry(Registries.DIMENSION_TYPE).orElse(null); + if (registry == null) throw new IllegalStateException("Unable to find registry for dimension type " + typeKey); + Holder holder = registry.getHolder(typeKey).orElse(null); + if (holder == null) throw new IllegalStateException("Unable to find dimension type " + typeKey); + levelStem = new LevelStem(holder, levelStem.generator()); + } + } + + private static class CraftServerAdvice { + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + static boolean enter(@Advice.This CraftServer self, @Advice.Argument(0) WorldCreator creator) { + File isIrisWorld = new File(self.getWorldContainer(), creator.name() + "/iris"); + boolean isFromIris = false; + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + for (StackTraceElement stack : stackTrace) { + if (stack.getClassName().contains("Iris")) { + isFromIris = true; + break; + } + } + if (isIrisWorld.exists() && !isFromIris) { + var logger = Logger.getLogger("Iris"); + logger.warning("detected another Plugin trying to load " + creator.name() + ". This is not supported and will be ignored."); + + if (System.getProperty("iris.debug", "false").equals("true")) { + new RuntimeException().printStackTrace(); + } + return true; + } + return false; + } + + @Advice.OnMethodExit + static void exit(@Advice.Enter boolean bool, @Advice.Return(readOnly = false) World returned) { + if (bool) { + returned = null; + } + } + } } diff --git a/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/CustomBiomeSource.java b/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/CustomBiomeSource.java index 3d3582239..323fa255b 100644 --- a/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/CustomBiomeSource.java +++ b/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/CustomBiomeSource.java @@ -2,6 +2,7 @@ package com.volmit.iris.core.nms.v1_20_R3; import com.mojang.serialization.Codec; import com.volmit.iris.Iris; +import com.volmit.iris.core.nms.INMS; import com.volmit.iris.engine.data.cache.AtomicCache; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.object.IrisBiome; @@ -124,8 +125,16 @@ public class CustomBiomeSource extends BiomeSource { for (IrisBiome i : engine.getAllBiomes()) { if (i.isCustom()) { for (IrisBiomeCustom j : i.getCustomDerivitives()) { - ResourceLocation resourceLocation = new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId()); - Biome biome = customRegistry.get(resourceLocation); + ResourceLocation location = new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId()); + Biome biome = customRegistry.get(location); + if (biome == null) { + INMS.get().registerBiome(location.getNamespace(), j, false); + biome = customRegistry.get(location); + if (biome == null) { + Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName()); + continue; + } + } Optional> optionalBiomeKey = customRegistry.getResourceKey(biome); if (optionalBiomeKey.isEmpty()) { Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName()); diff --git a/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/Headless.java b/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/Headless.java new file mode 100644 index 000000000..36b552545 --- /dev/null +++ b/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/Headless.java @@ -0,0 +1,256 @@ +package com.volmit.iris.core.nms.v1_20_R3; + +import com.volmit.iris.Iris; +import com.volmit.iris.core.nms.BiomeBaseInjector; +import com.volmit.iris.core.nms.IHeadless; +import com.volmit.iris.core.nms.v1_20_R3.mca.MCATerrainChunk; +import com.volmit.iris.core.nms.v1_20_R3.mca.RegionFileStorage; +import com.volmit.iris.core.pregenerator.PregenListener; +import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.engine.framework.EngineStage; +import com.volmit.iris.engine.framework.WrongEngineBroException; +import com.volmit.iris.engine.object.IrisBiome; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.context.ChunkContext; +import com.volmit.iris.util.context.IrisContext; +import com.volmit.iris.util.documentation.BlockCoordinates; +import com.volmit.iris.util.documentation.RegionCoordinates; +import com.volmit.iris.util.hunk.Hunk; +import com.volmit.iris.util.hunk.view.BiomeGridHunkHolder; +import com.volmit.iris.util.hunk.view.ChunkDataHunkHolder; +import com.volmit.iris.util.mantle.MantleFlag; +import com.volmit.iris.util.math.RNG; +import com.volmit.iris.util.parallel.MultiBurst; +import com.volmit.iris.util.scheduling.Looper; +import com.volmit.iris.util.scheduling.PrecisionStopwatch; +import net.minecraft.core.Holder; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.LevelHeightAccessor; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.chunk.ProtoChunk; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.block.data.BlockData; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayDeque; +import java.util.Queue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Consumer; + +public class Headless implements IHeadless, LevelHeightAccessor { + private final NMSBinding binding; + private final Engine engine; + private final RegionFileStorage storage; + private final Queue chunkQueue = new ArrayDeque<>(); + private final ReentrantLock saveLock = new ReentrantLock(); + private final KMap> customBiomes = new KMap<>(); + private final KMap> minecraftBiomes = new KMap<>(); + private boolean closed = false; + + public Headless(NMSBinding binding, Engine engine) { + this.binding = binding; + this.engine = engine; + this.storage = new RegionFileStorage(new File(engine.getWorld().worldFolder(), "region").toPath(), false); + var queueLooper = new Looper() { + @Override + protected long loop() { + save(); + return closed ? -1 : 100; + } + }; + queueLooper.setName("Region Save Looper"); + queueLooper.start(); + + var dimKey = engine.getDimension().getLoadKey(); + for (var biome : engine.getAllBiomes()) { + if (!biome.isCustom()) continue; + for (var custom : biome.getCustomDerivitives()) { + binding.registerBiome(dimKey, custom, false); + } + } + } + + @Override + public boolean exists(int x, int z) { + if (closed) return false; + try { + CompoundTag tag = storage.read(new ChunkPos(x, z)); + return tag != null && !"empty".equals(tag.getString("Status")); + } catch (IOException e) { + return false; + } + } + + @Override + public void save() { + if (closed) return; + saveLock.lock(); + try { + while (!chunkQueue.isEmpty()) { + ChunkAccess chunk = chunkQueue.poll(); + if (chunk == null) break; + try { + storage.write(chunk.getPos(), binding.serializeChunk(chunk, this)); + } catch (Throwable e) { + Iris.error("Failed to save chunk " + chunk.getPos().x + ", " + chunk.getPos().z); + e.printStackTrace(); + } + } + } finally { + saveLock.unlock(); + } + } + + @Override + public void generateRegion(MultiBurst burst, int x, int z, PregenListener listener) { + if (closed) return; + boolean listening = listener != null; + if (listening) listener.onRegionGenerating(x, z); + CountDownLatch latch = new CountDownLatch(1024); + iterateRegion(x, z, pos -> burst.complete(() -> { + if (listening) listener.onChunkGenerating(pos.x, pos.z); + generateChunk(pos.x, pos.z); + if (listening) listener.onChunkGenerated(pos.x, pos.z); + latch.countDown(); + })); + try { + latch.await(); + } catch (InterruptedException ignored) {} + if (listening) listener.onRegionGenerated(x, z); + } + + @RegionCoordinates + private static void iterateRegion(int x, int z, Consumer chunkPos) { + int cX = x << 5; + int cZ = z << 5; + for (int xx = 0; xx < 32; xx++) { + for (int zz = 0; zz < 32; zz++) { + chunkPos.accept(new ChunkPos(cX + xx, cZ + zz)); + } + } + } + + @Override + public void generateChunk(int x, int z) { + if (closed || exists(x, z)) return; + try { + var pos = new ChunkPos(x, z); + ProtoChunk chunk = binding.createProtoChunk(pos, this); + var tc = new MCATerrainChunk(chunk); + + ChunkDataHunkHolder blocks = new ChunkDataHunkHolder(tc); + BiomeGridHunkHolder biomes = new BiomeGridHunkHolder(tc, tc.getMinHeight(), tc.getMaxHeight()); + ChunkContext ctx = generate(engine, pos.x << 4, pos.z << 4, blocks, biomes); + blocks.apply(); + biomes.apply(); + + inject(engine, tc.getBiomeBaseInjector(), chunk, ctx); //TODO improve + chunk.setStatus(ChunkStatus.FULL); + chunkQueue.add(chunk); + } catch (Throwable e) { + Iris.error("Failed to generate " + x + ", " + z); + e.printStackTrace(); + } + } + + @BlockCoordinates + private ChunkContext generate(Engine engine, int x, int z, Hunk vblocks, Hunk vbiomes) throws WrongEngineBroException { + if (engine.isClosed()) { + throw new WrongEngineBroException(); + } + + engine.getContext().touch(); + engine.getEngineData().getStatistics().generatedChunk(); + ChunkContext ctx = null; + try { + PrecisionStopwatch p = PrecisionStopwatch.start(); + Hunk blocks = vblocks.listen((xx, y, zz, t) -> engine.catchBlockUpdates(x + xx, y + engine.getMinHeight(), z + zz, t)); + + var dimension = engine.getDimension(); + if (dimension.isDebugChunkCrossSections() && ((x >> 4) % dimension.getDebugCrossSectionsMod() == 0 || (z >> 4) % dimension.getDebugCrossSectionsMod() == 0)) { + for (int i = 0; i < 16; i++) { + for (int j = 0; j < 16; j++) { + blocks.set(i, 0, j, Material.CRYING_OBSIDIAN.createBlockData()); + } + } + } else { + ctx = new ChunkContext(x, z, engine.getComplex()); + IrisContext.getOr(engine).setChunkContext(ctx); + + for (EngineStage i : engine.getMode().getStages()) { + i.generate(x, z, blocks, vbiomes, false, ctx); + } + } + + engine.getMantle().getMantle().flag(x >> 4, z >> 4, MantleFlag.REAL, true); + engine.getMetrics().getTotal().put(p.getMilliseconds()); + engine.addGenerated(); + + } catch (Throwable e) { + Iris.reportError(e); + engine.fail("Failed to generate " + x + ", " + z, e); + } + return ctx; + } + + private void inject(Engine engine, BiomeBaseInjector injector, ChunkAccess chunk, ChunkContext ctx) { + var pos = chunk.getPos(); + for (int y = engine.getMinHeight(); y < engine.getMaxHeight(); y++) { + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + int wX = pos.getBlockX(x); + int wZ = pos.getBlockZ(z); + try { + injector.setBiome(x, y, z, getNoiseBiome(engine, ctx, x, z, wX, y, wZ)); + } catch (Throwable e) { + Iris.error("Failed to inject biome for " + wX + ", " + y + ", " + wZ); + e.printStackTrace(); + } + } + } + } + } + + private Holder getNoiseBiome(Engine engine, ChunkContext ctx, int rX, int rZ, int x, int y, int z) { + RNG rng = new RNG(engine.getSeedManager().getBiome()); + int m = (y - engine.getMinHeight()) << 2; + IrisBiome ib = ctx == null ? + engine.getComplex().getTrueBiomeStream().get(x << 2, z << 2) : + ctx.getBiome().get(rX, rZ); + if (ib.isCustom()) { + return customBiomes.computeIfAbsent(ib.getCustomBiome(rng, x << 2, m, z << 2).getId(), + id -> binding.getBiomeHolder(engine.getDimension().getLoadKey(), id)); + } else { + return minecraftBiomes.computeIfAbsent(ib.getSkyBiome(rng, x << 2, m, z << 2).getKey(), + id -> binding.getBiomeHolder(id.getNamespace(), id.getKey())); + } + } + + @Override + public void close() throws IOException { + if (closed) return; + try { + storage.close(); + } finally { + closed = true; + customBiomes.clear(); + minecraftBiomes.clear(); + } + } + + @Override + public int getHeight() { + return engine.getHeight(); + } + + @Override + public int getMinBuildHeight() { + return engine.getMinHeight(); + } +} 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 01fa75d94..79ec5e942 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 @@ -4,14 +4,52 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; +import java.io.File; 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.Vector; +import java.util.*; +import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Logger; +import com.google.common.base.Preconditions; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.Codec; +import com.mojang.serialization.JsonOps; +import com.mojang.serialization.Lifecycle; +import com.volmit.iris.core.nms.IHeadless; +import com.volmit.iris.core.nms.v1_20_R3.mca.ChunkSerializer; +import com.volmit.iris.engine.object.IrisBiomeCustom; +import com.volmit.iris.engine.object.IrisDimension; +import com.volmit.iris.util.format.C; +import it.unimi.dsi.fastutil.objects.Reference2IntMap; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.dynamic.loading.ClassReloadingStrategy; +import net.bytebuddy.implementation.bytecode.StackManipulation; +import net.bytebuddy.implementation.bytecode.assign.Assigner; +import net.bytebuddy.implementation.bytecode.member.MethodInvocation; +import net.bytebuddy.matcher.ElementMatchers; +import net.minecraft.core.IdMapper; +import net.minecraft.core.MappedRegistry; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.progress.ChunkProgressListener; +import net.minecraft.util.GsonHelper; +import net.minecraft.world.RandomSequences; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelHeightAccessor; +import net.minecraft.world.level.chunk.ProtoChunk; +import net.minecraft.world.level.chunk.UpgradeData; +import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.storage.LevelStorageSource; +import net.minecraft.world.level.storage.PrimaryLevelData; import org.bukkit.*; import org.bukkit.block.Biome; import org.bukkit.block.data.BlockData; @@ -25,6 +63,7 @@ import org.bukkit.craftbukkit.v1_20_R3.util.CraftNamespacedKey; import org.bukkit.entity.Dolphin; import org.bukkit.entity.Entity; import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.ChunkGenerator; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; @@ -67,6 +106,8 @@ import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.LevelChunk; import sun.misc.Unsafe; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + public class NMSBinding implements INMSBinding { private final KMap baseBiomeCache = new KMap<>(); private final BlockData AIR = Material.AIR.createBlockData(); @@ -173,6 +214,17 @@ public class NMSBinding implements INMSBinding { return null; } + private RegistryAccess getRegistryAccess(World world) { + try { + var field = getField(Level.class, RegistryAccess.class); + field.setAccessible(true); + return (RegistryAccess) field.get(((CraftWorld) world).getHandle()); + } catch (Throwable e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + @Override public void deserializeTile(CompoundTag c, Location pos) { ((CraftWorld) pos.getWorld()).getHandle().getChunkAt(new BlockPos(pos.getBlockX(), 0, pos.getBlockZ())).setBlockEntityNbt(convert(c)); @@ -256,8 +308,7 @@ public class NMSBinding implements INMSBinding { @Override public Object getBiomeBase(World world, Biome biome) { - return biomeToBiomeBase(((CraftWorld) world).getHandle() - .registryAccess().registry(Registries.BIOME).orElse(null), biome); + return biomeToBiomeBase(getRegistryAccess(world).registry(Registries.BIOME).orElse(null), biome); } @Override @@ -294,8 +345,11 @@ public class NMSBinding implements INMSBinding { public int getBiomeId(Biome biome) { for (World i : Bukkit.getWorlds()) { if (i.getEnvironment().equals(World.Environment.NORMAL)) { - Registry registry = ((CraftWorld) i).getHandle().registryAccess().registry(Registries.BIOME).orElse(null); - return registry.getId((net.minecraft.world.level.biome.Biome) getBiomeBase(registry, biome)); + var registry = getRegistryAccess(i).registry(Registries.BIOME).orElse(null); + if (registry != null) { + var holder = (Holder) getBiomeBase(registry, biome); + return registry.getId(holder.value()); + } } } @@ -415,13 +469,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); @@ -515,12 +569,143 @@ public class NMSBinding implements INMSBinding { 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 boolean registerDimension(String name, IrisDimension dimension) { + var registry = registry(Registries.DIMENSION_TYPE); + var baseLocation = switch (dimension.getEnvironment()) { + case NORMAL -> new ResourceLocation("minecraft", "overworld"); + case NETHER -> new ResourceLocation("minecraft", "the_nether"); + case THE_END -> new ResourceLocation("minecraft", "the_end"); + case CUSTOM -> throw new IllegalArgumentException("Cannot register custom dimension"); + }; + var base = registry.getHolder(ResourceKey.create(Registries.DIMENSION_TYPE, baseLocation)).orElse(null); + if (base == null) return false; + var json = encode(DimensionType.CODEC, base).orElse(null); + if (json == null) return false; + var object = json.getAsJsonObject(); + var height = dimension.getDimensionHeight(); + object.addProperty("min_y", height.getMin()); + object.addProperty("height", height.getMax() - height.getMin()); + object.addProperty("logical_height", dimension.getLogicalHeight()); + var value = decode(DimensionType.CODEC, object.toString()).map(Holder::value).orElse(null); + if (value == null) return false; + return register(Registries.DIMENSION_TYPE, new ResourceLocation("iris", name), value, true); + } + + @Override + public boolean registerBiome(String dimensionId, IrisBiomeCustom biome, boolean replace) { + var biomeBase = decode(net.minecraft.world.level.biome.Biome.CODEC, biome.generateJson()).map(Holder::value).orElse(null); + if (biomeBase == null) return false; + return register(Registries.BIOME, new ResourceLocation(dimensionId, biome.getId()), biomeBase, replace); + } + + private Optional decode(Codec codec, String json) { + return codec.decode(JsonOps.INSTANCE, GsonHelper.parse(json)).get().left().map(Pair::getFirst); + } + + private Optional encode(Codec codec, T value) { + return codec.encode(value, JsonOps.INSTANCE, new JsonObject()).result(); + } + + private boolean register(ResourceKey> registryKey, ResourceLocation location, T value, boolean replace) { + Preconditions.checkArgument(registryKey != null, "The registry cannot be null!"); + Preconditions.checkArgument(location != null, "The location cannot be null!"); + Preconditions.checkArgument(value != null, "The value cannot be null!"); + var registry = registry(registryKey); + var key = ResourceKey.create(registryKey, location); + try { + if (registry.containsKey(key)) { + if (!replace) return false; + return replace(registryKey, location, value); + } + Field field = getField(MappedRegistry.class, boolean.class); + field.setAccessible(true); + boolean frozen = field.getBoolean(registry); + field.setBoolean(registry, false); + Field valueField = getField(Holder.Reference.class, "T"); + valueField.setAccessible(true); + + try { + var holder = registry.register(key, value, Lifecycle.stable()); + if (frozen) valueField.set(holder, value); + return true; + } finally { + field.setBoolean(registry, frozen); + } + } catch (Throwable e) { + throw new IllegalStateException(e); + } + } + + @SuppressWarnings("unchecked") + private boolean replace(ResourceKey> registryKey, ResourceLocation location, T value) { + Preconditions.checkArgument(registryKey != null, "The registryKey cannot be null!"); + Preconditions.checkArgument(location != null, "The location cannot be null!"); + Preconditions.checkArgument(value != null, "The value cannot be null!"); + var registry = registry(registryKey); + var key = ResourceKey.create(registryKey, location); + try { + var holder = registry.getHolder(key).orElse(null); + if (holder == null) return false; + var oldValue = holder.value(); + Field valueField = getField(Holder.Reference.class, "T"); + valueField.setAccessible(true); + Field toIdField = getField(MappedRegistry.class, buildType(Reference2IntMap.class, "T")); + toIdField.setAccessible(true); + Field byValueField = getField(MappedRegistry.class, buildType(Map.class, "T", buildType(Holder.Reference.class, "T"))); + byValueField.setAccessible(true); + Field lifecyclesField = getField(MappedRegistry.class, buildType(Map.class, "T", Lifecycle.class.getName())); + lifecyclesField.setAccessible(true); + var toId = (Reference2IntMap) toIdField.get(registry); + var byValue = (Map>) byValueField.get(registry); + var lifecycles = (Map) lifecyclesField.get(registry); + + valueField.set(holder, value); + toId.put(value, toId.removeInt(oldValue)); + byValue.put(value, byValue.remove(oldValue)); + lifecycles.put(value, lifecycles.remove(oldValue)); + return true; + } catch (Throwable e) { + throw new IllegalStateException(e); + } + } + + private MappedRegistry registry(ResourceKey> registryKey) { + var rawRegistry = registry().registry(registryKey).orElse(null); + if (!(rawRegistry instanceof MappedRegistry registry)) + throw new IllegalStateException("The Registry is not a mapped Registry!"); + return registry; + } + + private static String buildType(Class clazz, String... parameterTypes) { + if (parameterTypes.length == 0) return clazz.getName(); + var builder = new StringBuilder(clazz.getName()) + .append("<"); + for (int i = 0; i < parameterTypes.length; i++) { + builder.append(parameterTypes[i]).append(parameterTypes.length - 1 == i ? ">" : ", "); + } + return builder.toString(); + } + + private static Field getField(Class clazz, String type) throws NoSuchFieldException { + try { + for (Field f : clazz.getDeclaredFields()) { + if (f.getGenericType().getTypeName().equals(type)) + return f; + } + throw new NoSuchFieldException(type); + } catch (NoSuchFieldException e) { + Class superClass = clazz.getSuperclass(); + if (superClass == null) throw e; + return getField(superClass, type); + } + } + private static Field getField(Class clazz, Class fieldType) throws NoSuchFieldException { try { for (Field f : clazz.getDeclaredFields()) { @@ -541,4 +726,92 @@ public class NMSBinding implements INMSBinding { public static Holder biomeToBiomeBase(Registry registry, Biome biome) { return registry.getHolderOrThrow(ResourceKey.create(Registries.BIOME, CraftNamespacedKey.toMinecraft(biome.getKey()))); } + + public void injectBukkit() { + try { + Iris.info("Injecting Bukkit"); + new ByteBuddy() + .redefine(CraftServer.class) + .visit(Advice.to(CraftServerAdvice.class).on(ElementMatchers.isMethod().and(ElementMatchers.takesArguments(WorldCreator.class)))) + .make() + .load(CraftServer.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); + new ByteBuddy() + .redefine(ServerLevel.class) + .visit(Advice.to(ServerLevelAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(MinecraftServer.class, Executor.class, LevelStorageSource.LevelStorageAccess.class, + PrimaryLevelData.class, ResourceKey.class, LevelStem.class, ChunkProgressListener.class, boolean.class, long.class, + List.class, boolean.class, RandomSequences.class, World.Environment.class, ChunkGenerator.class, BiomeProvider.class)))) + .make() + .load(ServerLevel.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); + Iris.info("Injected Bukkit Successfully!"); + } catch (Exception e) { + Iris.info(C.RED + "Failed to Inject Bukkit!"); + e.printStackTrace(); + Iris.reportError(e); + } + + } + + @Override + public IHeadless createHeadless(Engine engine) { + return new Headless(this, engine); + } + + private static class ServerLevelAdvice { + @Advice.OnMethodEnter + static void enter(@Advice.Argument(0) MinecraftServer server, @Advice.Argument(2) LevelStorageSource.LevelStorageAccess access, @Advice.Argument(4) ResourceKey key, @Advice.Argument(value = 5, readOnly = false) LevelStem levelStem) { + File iris = new File(access.levelDirectory.path().toFile(), "iris"); + if (!iris.exists() && !key.location().getPath().startsWith("iris/")) return; + ResourceKey typeKey = ResourceKey.create(Registries.DIMENSION_TYPE, new ResourceLocation("iris", key.location().getPath())); + RegistryAccess registryAccess = server.registryAccess(); + Registry registry = registryAccess.registry(Registries.DIMENSION_TYPE).orElse(null); + if (registry == null) throw new IllegalStateException("Unable to find registry for dimension type " + typeKey); + Holder holder = registry.getHolder(typeKey).orElse(null); + if (holder == null) throw new IllegalStateException("Unable to find dimension type " + typeKey); + levelStem = new LevelStem(holder, levelStem.generator()); + } + } + + private static class CraftServerAdvice { + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + static boolean enter(@Advice.This CraftServer self, @Advice.Argument(0) WorldCreator creator) { + File isIrisWorld = new File(self.getWorldContainer(), creator.name() + "/iris"); + boolean isFromIris = false; + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + for (StackTraceElement stack : stackTrace) { + if (stack.getClassName().contains("Iris")) { + isFromIris = true; + break; + } + } + if (isIrisWorld.exists() && !isFromIris) { + var logger = Logger.getLogger("Iris"); + logger.warning("detected another Plugin trying to load " + creator.name() + ". This is not supported and will be ignored."); + + if (System.getProperty("iris.debug", "false").equals("true")) { + new RuntimeException().printStackTrace(); + } + return true; + } + return false; + } + + @Advice.OnMethodExit + static void exit(@Advice.Enter boolean bool, @Advice.Return(readOnly = false) World returned) { + if (bool) { + returned = null; + } + } + } + + Holder.Reference getBiomeHolder(String namespace, String id) { + return getCustomBiomeRegistry().getHolder(ResourceKey.create(Registries.BIOME, new ResourceLocation(namespace, id))).orElse(null); + } + + ProtoChunk createProtoChunk(ChunkPos pos, LevelHeightAccessor heightAccessor) { + return new ProtoChunk(pos, UpgradeData.EMPTY, heightAccessor, getCustomBiomeRegistry(), null); + } + + net.minecraft.nbt.CompoundTag serializeChunk(ChunkAccess chunkAccess, LevelHeightAccessor heightAccessor) { + return ChunkSerializer.write(chunkAccess, heightAccessor, getCustomBiomeRegistry()); + } } diff --git a/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/mca/ChunkSerializer.java b/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/mca/ChunkSerializer.java new file mode 100644 index 000000000..a90699c53 --- /dev/null +++ b/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/mca/ChunkSerializer.java @@ -0,0 +1,181 @@ +package com.volmit.iris.core.nms.v1_20_R3.mca; + +import com.mojang.logging.LogUtils; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import it.unimi.dsi.fastutil.shorts.ShortList; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Map.Entry; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.LongArrayTag; +import net.minecraft.nbt.NbtOps; +import net.minecraft.nbt.NbtUtils; +import net.minecraft.nbt.ShortTag; +import net.minecraft.nbt.Tag; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.LevelHeightAccessor; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.chunk.CarvingMask; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.PalettedContainerRO; +import net.minecraft.world.level.chunk.ProtoChunk; +import net.minecraft.world.level.chunk.UpgradeData; +import net.minecraft.world.level.chunk.ChunkAccess.TicksToSave; +import net.minecraft.world.level.levelgen.BelowZeroRetrogen; +import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.world.level.levelgen.GenerationStep.Carving; +import net.minecraft.world.level.levelgen.Heightmap.Types; +import net.minecraft.world.level.levelgen.blending.BlendingData; +import org.slf4j.Logger; + +import static net.minecraft.world.level.chunk.storage.ChunkSerializer.BLOCK_STATE_CODEC; + +public class ChunkSerializer { + private static final Logger LOGGER = LogUtils.getLogger(); + private static Method CODEC = null; + + static { + for (Method method : net.minecraft.world.level.chunk.storage.ChunkSerializer.class.getDeclaredMethods()) { + if (method.getReturnType().equals(Codec.class) && method.getParameterCount() == 1) { + CODEC = method; + CODEC.setAccessible(true); + break; + } + } + } + + public static CompoundTag write(ChunkAccess chunk, LevelHeightAccessor heightAccessor, Registry biomeRegistry) { + ChunkPos pos = chunk.getPos(); + CompoundTag data = NbtUtils.addCurrentDataVersion(new CompoundTag()); + data.putInt("xPos", pos.x); + data.putInt("yPos", ((LevelHeightAccessor) chunk).getMinSection()); + data.putInt("zPos", pos.z); + data.putLong("LastUpdate", 0L); + data.putLong("InhabitedTime", chunk.getInhabitedTime()); + data.putString("Status", BuiltInRegistries.CHUNK_STATUS.getKey(chunk.getStatus()).toString()); + BlendingData blendingdata = chunk.getBlendingData(); + if (blendingdata != null) { + DataResult dataResult = BlendingData.CODEC.encodeStart(NbtOps.INSTANCE, blendingdata); + dataResult.resultOrPartial(LOGGER::error).ifPresent(base -> data.put("blending_data", base)); + } + + BelowZeroRetrogen belowzeroretrogen = chunk.getBelowZeroRetrogen(); + if (belowzeroretrogen != null) { + DataResult dataResult = BelowZeroRetrogen.CODEC.encodeStart(NbtOps.INSTANCE, belowzeroretrogen); + dataResult.resultOrPartial(LOGGER::error).ifPresent(base -> data.put("below_zero_retrogen", base)); + } + + UpgradeData upgradeData = chunk.getUpgradeData(); + if (!upgradeData.isEmpty()) { + data.put("UpgradeData", upgradeData.write()); + } + + LevelChunkSection[] chunkSections = chunk.getSections(); + ListTag sections = new ListTag(); + Codec>> codec; + try { + codec = (Codec>>) CODEC.invoke(null, biomeRegistry); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + + int minLightSection = heightAccessor.getMinSection() - 1; + int maxLightSection = minLightSection + heightAccessor.getSectionsCount() + 2; + for (int y = minLightSection; y < maxLightSection; y++) { + int i = ((LevelHeightAccessor) chunk).getSectionIndexFromSectionY(y); + if (i >= 0 && i < chunkSections.length) { + CompoundTag section = new CompoundTag(); + LevelChunkSection chunkSection = chunkSections[i]; + DataResult dataResult = BLOCK_STATE_CODEC.encodeStart(NbtOps.INSTANCE, chunkSection.getStates()); + section.put("block_states", dataResult.getOrThrow(false, LOGGER::error)); + dataResult = codec.encodeStart(NbtOps.INSTANCE, chunkSection.getBiomes()); + section.put("biomes", dataResult.getOrThrow(false, LOGGER::error)); + + if (!section.isEmpty()) { + section.putByte("Y", (byte)y); + sections.add(section); + } + } + } + + data.put("sections", sections); + if (chunk.isLightCorrect()) { + data.putBoolean("isLightOn", true); + } + + ListTag blockEntities = new ListTag(); + for (BlockPos blockPos : chunk.getBlockEntitiesPos()) { + CompoundTag blockEntityNbt = chunk.getBlockEntityNbtForSaving(blockPos); + if (blockEntityNbt != null) { + blockEntities.add(blockEntityNbt); + } + } + data.put("block_entities", blockEntities); + + if (chunk instanceof ProtoChunk protoChunk) { + ListTag entities = new ListTag(); + entities.addAll(protoChunk.getEntities()); + data.put("entities", entities); + + CompoundTag carvingMasks = new CompoundTag(); + for (Carving carving : Carving.values()) { + CarvingMask carvingMask = protoChunk.getCarvingMask(carving); + if (carvingMask != null) { + carvingMasks.putLongArray(carving.toString(), carvingMask.toArray()); + } + } + data.put("CarvingMasks", carvingMasks); + } + + saveTicks(data, chunk.getTicksForSerialization()); + data.put("PostProcessing", packOffsets(chunk.getPostProcessing())); + CompoundTag heightmaps = new CompoundTag(); + + for (Entry entry : chunk.getHeightmaps()) { + if (chunk.getStatus().heightmapsAfter().contains(entry.getKey())) { + heightmaps.put(entry.getKey().getSerializationKey(), new LongArrayTag(entry.getValue().getRawData())); + } + } + + data.put("Heightmaps", heightmaps); + + CompoundTag structures = new CompoundTag(); + structures.put("starts", new CompoundTag()); + structures.put("References", new CompoundTag()); + data.put("structures", structures); + if (!chunk.persistentDataContainer.isEmpty()) { + data.put("ChunkBukkitValues", chunk.persistentDataContainer.toTagCompound()); + } + + return data; + } + + private static void saveTicks(CompoundTag compoundTag, TicksToSave ticksToSave) { + compoundTag.put("block_ticks", ticksToSave.blocks().save(0, block -> BuiltInRegistries.BLOCK.getKey(block).toString())); + compoundTag.put("fluid_ticks", ticksToSave.fluids().save(0, fluid -> BuiltInRegistries.FLUID.getKey(fluid).toString())); + } + + public static ListTag packOffsets(ShortList[] offsets) { + ListTag tags = new ListTag(); + for (ShortList shorts : offsets) { + ListTag listTag = new ListTag(); + if (shorts != null) { + for (Short s : shorts) { + listTag.add(ShortTag.valueOf(s)); + } + } + tags.add(listTag); + } + + return tags; + } +} diff --git a/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/mca/MCATerrainChunk.java b/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/mca/MCATerrainChunk.java new file mode 100644 index 000000000..e574c892a --- /dev/null +++ b/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/mca/MCATerrainChunk.java @@ -0,0 +1,159 @@ +package com.volmit.iris.core.nms.v1_20_R3.mca; + +import com.volmit.iris.Iris; +import com.volmit.iris.core.nms.BiomeBaseInjector; +import com.volmit.iris.engine.data.chunk.TerrainChunk; +import com.volmit.iris.util.data.IrisBlockData; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.world.level.LevelHeightAccessor; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; +import org.bukkit.Material; +import org.bukkit.block.Biome; +import org.bukkit.block.data.BlockData; +import org.bukkit.craftbukkit.v1_20_R3.block.CraftBiome; +import org.bukkit.craftbukkit.v1_20_R3.block.data.CraftBlockData; +import org.bukkit.generator.ChunkGenerator; +import org.bukkit.material.MaterialData; +import org.jetbrains.annotations.NotNull; + +public record MCATerrainChunk(ChunkAccess chunk) implements TerrainChunk { + + @Override + public BiomeBaseInjector getBiomeBaseInjector() { + return (x, y, z, biomeBase) -> chunk.setBiome(x, y, z, (Holder) biomeBase); + } + + @Override + public Biome getBiome(int x, int z) { + return Biome.THE_VOID; + } + + @Override + public Biome getBiome(int x, int y, int z) { + return Biome.THE_VOID; + } + + @Override + public void setBiome(int x, int z, Biome bio) { + setBiome(x, 0, z, bio); + } + + @Override + public void setBiome(int x, int y, int z, Biome bio) { + if (y < 0) return; + y += getMinHeight(); + if (y > getMaxHeight()) return; + chunk.setBiome(x & 15, y, z & 15, CraftBiome.bukkitToMinecraftHolder(bio)); + } + + private LevelHeightAccessor height() { + return chunk; + } + + @Override + public int getMinHeight() { + return height().getMinBuildHeight(); + } + + @Override + public int getMaxHeight() { + return height().getMaxBuildHeight(); + } + + @Override + public void setBlock(int x, int y, int z, BlockData blockData) { + if (y < 0) return; + y += getMinHeight(); + if (y > getMaxHeight()) return; + + if (blockData == null) { + Iris.error("NULL BD"); + } + if (blockData instanceof IrisBlockData data) + blockData = data.getBase(); + if (!(blockData instanceof CraftBlockData craftBlockData)) + throw new IllegalArgumentException("Expected CraftBlockData, got " + blockData.getClass().getSimpleName() + " instead"); + chunk.setBlockState(new BlockPos(x & 15, y, z & 15), craftBlockData.getState(), false); + } + + private BlockState getBlockState(int x, int y, int z) { + if (y < 0) { + y = 0; + } + y += getMinHeight(); + if (y > getMaxHeight()) { + y = getMaxHeight(); + } + + return chunk.getBlockState(new BlockPos(x & 15, y, z & 15)); + } + + @NotNull + @Override + public org.bukkit.block.data.BlockData getBlockData(int x, int y, int z) { + return CraftBlockData.fromData(getBlockState(x, y, z)); + } + + @Override + public ChunkGenerator.ChunkData getRaw() { + return null; + } + + @Override + public void setRaw(ChunkGenerator.ChunkData data) { + + } + + @Override + @Deprecated + public void inject(ChunkGenerator.BiomeGrid biome) { + + } + + @Override + public void setBlock(int x, int y, int z, @NotNull Material material) { + + } + + @Override + @Deprecated + public void setBlock(int x, int y, int z, @NotNull MaterialData material) { + + } + + @Override + public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, @NotNull Material material) { + + } + + @Override + @Deprecated + public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, @NotNull MaterialData material) { + + } + + @Override + public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, @NotNull BlockData blockData) { + + } + + + @NotNull + @Override + public Material getType(int x, int y, int z) { + return getBlockData(x, y, z).getMaterial(); + } + + @NotNull + @Override + public MaterialData getTypeAndData(int x, int y, int z) { + return getBlockData(x, y, z).createBlockState().getData(); + } + + @Override + public byte getData(int x, int y, int z) { + return getTypeAndData(x, y, z).getData(); + } +} diff --git a/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/mca/RegionFileStorage.java b/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/mca/RegionFileStorage.java new file mode 100644 index 000000000..d7a9d1cee --- /dev/null +++ b/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/mca/RegionFileStorage.java @@ -0,0 +1,109 @@ +package com.volmit.iris.core.nms.v1_20_R3.mca; + +import com.google.common.base.Preconditions; +import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import javax.annotation.Nullable; +import net.minecraft.FileUtil; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtAccounter; +import net.minecraft.nbt.NbtIo; +import net.minecraft.nbt.StreamTagVisitor; +import net.minecraft.util.ExceptionCollector; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.storage.RegionFile; + +public final class RegionFileStorage implements AutoCloseable { + public final Long2ObjectLinkedOpenHashMap regionCache = new Long2ObjectLinkedOpenHashMap<>(); + private final Path folder; + private final boolean sync; + + public RegionFileStorage(Path folder, boolean sync) { + this.folder = folder; + this.sync = sync; + } + + public RegionFile getRegionFile(ChunkPos chunkPos, boolean existingOnly) throws IOException { + long id = ChunkPos.asLong(chunkPos.getRegionX(), chunkPos.getRegionZ()); + RegionFile regionFile = this.regionCache.getAndMoveToFirst(id); + if (regionFile != null) { + return regionFile; + } else { + if (this.regionCache.size() >= 256) { + this.regionCache.removeLast().close(); + } + + FileUtil.createDirectoriesSafe(this.folder); + Path path = folder.resolve("r." + chunkPos.getRegionX() + "." + chunkPos.getRegionZ() + ".mca"); + if (existingOnly && !Files.exists(path)) { + return null; + } else { + regionFile = new RegionFile(path, this.folder, this.sync); + this.regionCache.putAndMoveToFirst(id, regionFile); + return regionFile; + } + } + } + + @Nullable + public CompoundTag read(ChunkPos chunkPos) throws IOException { + RegionFile regionFile = this.getRegionFile(chunkPos, true); + if (regionFile != null) { + try (DataInputStream datainputstream = regionFile.getChunkDataInputStream(chunkPos)) { + if (datainputstream != null) { + return NbtIo.read(datainputstream); + } + } + + } + return null; + } + + public void scanChunk(ChunkPos chunkPos, StreamTagVisitor visitor) throws IOException { + RegionFile regionFile = this.getRegionFile(chunkPos, true); + if (regionFile != null) { + try (DataInputStream din = regionFile.getChunkDataInputStream(chunkPos)) { + if (din != null) { + NbtIo.parse(din, visitor, NbtAccounter.unlimitedHeap()); + } + } + } + } + + public void write(ChunkPos chunkPos, @Nullable CompoundTag compound) throws IOException { + RegionFile regionFile = this.getRegionFile(chunkPos, false); + Preconditions.checkArgument(regionFile != null, "Failed to find region file for chunk %s", chunkPos); + if (compound == null) { + regionFile.clear(chunkPos); + } else { + try (DataOutputStream dos = regionFile.getChunkDataOutputStream(chunkPos)) { + NbtIo.write(compound, dos); + } + } + } + + @Override + public void close() throws IOException { + ExceptionCollector collector = new ExceptionCollector<>(); + + for (RegionFile regionFile : this.regionCache.values()) { + try { + regionFile.close(); + } catch (IOException e) { + collector.add(e); + } + } + + collector.throwIfPresent(); + } + + public void flush() throws IOException { + for (RegionFile regionfile : this.regionCache.values()) { + regionfile.flush(); + } + } +} \ No newline at end of file diff --git a/nms/v1_20_R4/src/main/java/com/volmit/iris/core/nms/v1_20_R4/CustomBiomeSource.java b/nms/v1_20_R4/src/main/java/com/volmit/iris/core/nms/v1_20_R4/CustomBiomeSource.java index d37284c25..a27c9a306 100644 --- a/nms/v1_20_R4/src/main/java/com/volmit/iris/core/nms/v1_20_R4/CustomBiomeSource.java +++ b/nms/v1_20_R4/src/main/java/com/volmit/iris/core/nms/v1_20_R4/CustomBiomeSource.java @@ -2,6 +2,7 @@ package com.volmit.iris.core.nms.v1_20_R4; import com.mojang.serialization.MapCodec; import com.volmit.iris.Iris; +import com.volmit.iris.core.nms.INMS; import com.volmit.iris.engine.data.cache.AtomicCache; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.object.IrisBiome; @@ -124,8 +125,16 @@ public class CustomBiomeSource extends BiomeSource { for (IrisBiome i : engine.getAllBiomes()) { if (i.isCustom()) { for (IrisBiomeCustom j : i.getCustomDerivitives()) { - ResourceLocation resourceLocation = new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId()); - Biome biome = customRegistry.get(resourceLocation); + ResourceLocation location = new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId()); + Biome biome = customRegistry.get(location); + if (biome == null) { + INMS.get().registerBiome(location.getNamespace(), j, false); + biome = customRegistry.get(location); + if (biome == null) { + Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName()); + continue; + } + } Optional> optionalBiomeKey = customRegistry.getResourceKey(biome); if (optionalBiomeKey.isEmpty()) { Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName()); 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 59f90620b..1ecfd0f7e 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 @@ -4,18 +4,48 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; +import java.io.File; 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.Map; +import java.util.Optional; import java.util.Vector; +import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Logger; +import com.google.common.base.Preconditions; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.Codec; +import com.mojang.serialization.JsonOps; import com.volmit.iris.core.nms.datapack.DataVersion; +import com.volmit.iris.engine.object.IrisBiomeCustom; +import com.volmit.iris.engine.object.IrisDimension; +import com.volmit.iris.util.format.C; +import it.unimi.dsi.fastutil.objects.Reference2IntMap; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.dynamic.loading.ClassReloadingStrategy; +import net.bytebuddy.matcher.ElementMatchers; +import net.minecraft.core.MappedRegistry; +import net.minecraft.core.RegistrationInfo; import net.minecraft.core.component.DataComponents; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.progress.ChunkProgressListener; +import net.minecraft.util.GsonHelper; +import net.minecraft.world.RandomSequences; import net.minecraft.world.item.component.CustomData; +import net.minecraft.world.level.Level; import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.storage.LevelStorageSource; +import net.minecraft.world.level.storage.PrimaryLevelData; import org.bukkit.*; import org.bukkit.block.Biome; import org.bukkit.block.data.BlockData; @@ -29,6 +59,7 @@ import org.bukkit.craftbukkit.v1_20_R4.util.CraftNamespacedKey; import org.bukkit.entity.Dolphin; import org.bukkit.entity.Entity; import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.ChunkGenerator; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; @@ -549,4 +580,203 @@ public class NMSBinding implements INMSBinding { public DataVersion getDataVersion() { return DataVersion.V1205; } + + @Override + public boolean registerDimension(String name, IrisDimension dimension) { + var registry = registry(Registries.DIMENSION_TYPE); + var baseLocation = switch (dimension.getEnvironment()) { + case NORMAL -> new ResourceLocation("minecraft", "overworld"); + case NETHER -> new ResourceLocation("minecraft", "the_nether"); + case THE_END -> new ResourceLocation("minecraft", "the_end"); + case CUSTOM -> throw new IllegalArgumentException("Cannot register custom dimension"); + }; + var base = registry.getHolder(ResourceKey.create(Registries.DIMENSION_TYPE, baseLocation)).orElse(null); + if (base == null) return false; + var json = encode(DimensionType.CODEC, base).orElse(null); + if (json == null) return false; + var object = json.getAsJsonObject(); + var height = dimension.getDimensionHeight(); + object.addProperty("min_y", height.getMin()); + object.addProperty("height", height.getMax() - height.getMin()); + object.addProperty("logical_height", dimension.getLogicalHeight()); + var value = decode(DimensionType.CODEC, object.toString()).map(Holder::value).orElse(null); + if (value == null) return false; + return register(Registries.DIMENSION_TYPE, new ResourceLocation("iris", name), value, true); + } + + @Override + public boolean registerBiome(String dimensionId, IrisBiomeCustom biome, boolean replace) { + var biomeBase = decode(net.minecraft.world.level.biome.Biome.CODEC, biome.generateJson()).map(Holder::value).orElse(null); + if (biomeBase == null) return false; + return register(Registries.BIOME, new ResourceLocation(dimensionId, biome.getId()), biomeBase, replace); + } + + private Optional decode(Codec codec, String json) { + return codec.decode(JsonOps.INSTANCE, GsonHelper.parse(json)).result().map(Pair::getFirst); + } + + private Optional encode(Codec codec, T value) { + return codec.encode(value, JsonOps.INSTANCE, new JsonObject()).result(); + } + + private boolean register(ResourceKey> registryKey, ResourceLocation location, T value, boolean replace) { + Preconditions.checkArgument(registryKey != null, "The registry cannot be null!"); + Preconditions.checkArgument(location != null, "The location cannot be null!"); + Preconditions.checkArgument(value != null, "The value cannot be null!"); + var registry = registry(registryKey); + var key = ResourceKey.create(registryKey, location); + try { + if (registry.containsKey(key)) { + if (!replace) return false; + return replace(registryKey, location, value); + } + Field field = getField(MappedRegistry.class, boolean.class); + field.setAccessible(true); + boolean frozen = field.getBoolean(registry); + field.setBoolean(registry, false); + Field valueField = getField(Holder.Reference.class, "T"); + valueField.setAccessible(true); + + try { + var holder = registry.register(key, value, RegistrationInfo.BUILT_IN); + if (frozen) valueField.set(holder, value); + return true; + } finally { + field.setBoolean(registry, frozen); + } + } catch (Throwable e) { + throw new IllegalStateException(e); + } + } + + @SuppressWarnings("unchecked") + private boolean replace(ResourceKey> registryKey, ResourceLocation location, T value) { + Preconditions.checkArgument(registryKey != null, "The registryKey cannot be null!"); + Preconditions.checkArgument(location != null, "The location cannot be null!"); + Preconditions.checkArgument(value != null, "The value cannot be null!"); + var registry = registry(registryKey); + var key = ResourceKey.create(registryKey, location); + try { + var holder = registry.getHolder(key).orElse(null); + if (holder == null) return false; + var oldValue = holder.value(); + Field valueField = getField(Holder.Reference.class, "T"); + valueField.setAccessible(true); + Field toIdField = getField(MappedRegistry.class, buildType(Reference2IntMap.class, "T")); + toIdField.setAccessible(true); + Field byValueField = getField(MappedRegistry.class, buildType(Map.class, "T", buildType(Holder.Reference.class, "T"))); + byValueField.setAccessible(true); + var toId = (Reference2IntMap) toIdField.get(registry); + var byValue = (Map>) byValueField.get(registry); + + valueField.set(holder, value); + toId.put(value, toId.removeInt(oldValue)); + byValue.put(value, byValue.remove(oldValue)); + return true; + } catch (Throwable e) { + throw new IllegalStateException(e); + } + } + + private MappedRegistry registry(ResourceKey> registryKey) { + var rawRegistry = registry().registry(registryKey).orElse(null); + if (!(rawRegistry instanceof MappedRegistry registry)) + throw new IllegalStateException("The Registry is not a mapped Registry!"); + return registry; + } + + private static String buildType(Class clazz, String... parameterTypes) { + if (parameterTypes.length == 0) return clazz.getName(); + var builder = new StringBuilder(clazz.getName()) + .append("<"); + for (int i = 0; i < parameterTypes.length; i++) { + builder.append(parameterTypes[i]).append(parameterTypes.length - 1 == i ? ">" : ", "); + } + return builder.toString(); + } + + private static Field getField(Class clazz, String type) throws NoSuchFieldException { + try { + for (Field f : clazz.getDeclaredFields()) { + if (f.getGenericType().getTypeName().equals(type)) + return f; + } + throw new NoSuchFieldException(type); + } catch (NoSuchFieldException e) { + Class superClass = clazz.getSuperclass(); + if (superClass == null) throw e; + return getField(superClass, type); + } + } + + public void injectBukkit() { + try { + Iris.info("Injecting Bukkit"); + new ByteBuddy() + .redefine(CraftServer.class) + .visit(Advice.to(CraftServerAdvice.class).on(ElementMatchers.isMethod().and(ElementMatchers.takesArguments(WorldCreator.class)))) + .make() + .load(CraftServer.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); + new ByteBuddy() + .redefine(ServerLevel.class) + .visit(Advice.to(ServerLevelAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(MinecraftServer.class, Executor.class, LevelStorageSource.LevelStorageAccess.class, + PrimaryLevelData.class, ResourceKey.class, LevelStem.class, ChunkProgressListener.class, boolean.class, long.class, + List.class, boolean.class, RandomSequences.class, World.Environment.class, ChunkGenerator.class, BiomeProvider.class)))) + .make() + .load(ServerLevel.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); + Iris.info("Injected Bukkit Successfully!"); + } catch (Exception e) { + Iris.info(C.RED + "Failed to Inject Bukkit!"); + e.printStackTrace(); + Iris.reportError(e); + } + + } + + private static class ServerLevelAdvice { + @Advice.OnMethodEnter + static void enter(@Advice.Argument(0) MinecraftServer server, @Advice.Argument(2) LevelStorageSource.LevelStorageAccess access, @Advice.Argument(4) ResourceKey key, @Advice.Argument(value = 5, readOnly = false) LevelStem levelStem) { + File iris = new File(access.levelDirectory.path().toFile(), "iris"); + if (!iris.exists() && !key.location().getPath().startsWith("iris/")) return; + ResourceKey typeKey = ResourceKey.create(Registries.DIMENSION_TYPE, new ResourceLocation("iris", key.location().getPath())); + RegistryAccess registryAccess = server.registryAccess(); + Registry registry = registryAccess.registry(Registries.DIMENSION_TYPE).orElse(null); + if (registry == null) throw new IllegalStateException("Unable to find registry for dimension type " + typeKey); + Holder holder = registry.getHolder(typeKey).orElse(null); + if (holder == null) throw new IllegalStateException("Unable to find dimension type " + typeKey); + levelStem = new LevelStem(holder, levelStem.generator()); + } + } + + private static class CraftServerAdvice { + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + static boolean enter(@Advice.This CraftServer self, @Advice.Argument(0) WorldCreator creator) { + File isIrisWorld = new File(self.getWorldContainer(), creator.name() + "/iris"); + boolean isFromIris = false; + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + for (StackTraceElement stack : stackTrace) { + if (stack.getClassName().contains("Iris")) { + isFromIris = true; + break; + } + } + if (isIrisWorld.exists() && !isFromIris) { + var logger = Logger.getLogger("Iris"); + logger.warning("detected another Plugin trying to load " + creator.name() + ". This is not supported and will be ignored."); + + if (System.getProperty("iris.debug", "false").equals("true")) { + new RuntimeException().printStackTrace(); + } + return true; + } + return false; + } + + @Advice.OnMethodExit + static void exit(@Advice.Enter boolean bool, @Advice.Return(readOnly = false) World returned) { + if (bool) { + returned = null; + } + } + } } diff --git a/settings.gradle b/settings.gradle index c4b3fa275..2052a7afe 100644 --- a/settings.gradle +++ b/settings.gradle @@ -23,7 +23,7 @@ pluginManagement { } } rootProject.name = 'Iris' - +//include 'app', 'com.volmit.gui' include(':core') include( ':nms:v1_20_R4',