diff --git a/build.gradle b/build.gradle index e13410ee2..ed5e204c3 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 fafeb768d..c0b7dbcd3 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 @@ -18,15 +18,12 @@ 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; @@ -41,9 +38,6 @@ 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 @@ -53,73 +47,6 @@ import java.io.IOException; 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<>(); @@ -137,10 +64,6 @@ public class IrisDimension extends IrisRegistrant { @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; @@ -229,10 +152,6 @@ public class IrisDimension extends IrisRegistrant { 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) @@ -375,6 +294,14 @@ public class IrisDimension extends IrisRegistrant { 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(""); } @@ -446,63 +373,6 @@ public class IrisDimension extends IrisRegistrant { 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"; @@ -517,67 +387,4 @@ public class IrisDimension extends IrisRegistrant { 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); - } } 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 f1464496d..9227e91e6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -27,7 +27,7 @@ plugins { } rootProject.name = 'Iris' - +//include 'app', 'com.volmit.gui' include(':core') include( ':nms:v1_20_R4',