From 05f4955989a1ea1eb8a16d7489002ae2c83acc27 Mon Sep 17 00:00:00 2001 From: CrazyDev22 Date: Fri, 3 May 2024 19:01:33 +0200 Subject: [PATCH] implement proper datapack reload+per world height --- core/src/main/java/com/volmit/iris/Iris.java | 3 + .../volmit/iris/core/ServerConfigurator.java | 2 +- .../iris/core/commands/CommandStudio.java | 16 +- .../com/volmit/iris/core/nms/INMSBinding.java | 7 +- .../iris/core/nms/v1X/NMSBinding1X.java | 2 +- .../iris/core/tools/IrisWorldCreator.java | 4 + .../iris/core/nms/v1_19_R1/NMSBinding.java | 225 ++++++++++------ .../iris/core/nms/v1_19_R2/NMSBinding.java | 225 ++++++++++------ .../iris/core/nms/v1_19_R3/NMSBinding.java | 223 ++++++++++------ .../iris/core/nms/v1_20_R1/NMSBinding.java | 224 ++++++++++------ .../iris/core/nms/v1_20_R2/NMSBinding.java | 224 ++++++++++------ .../iris/core/nms/v1_20_R3/NMSBinding.java | 247 ++++++++++++------ 12 files changed, 935 insertions(+), 467 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/Iris.java b/core/src/main/java/com/volmit/iris/Iris.java index dbe796d4f..213d96709 100644 --- a/core/src/main/java/com/volmit/iris/Iris.java +++ b/core/src/main/java/com/volmit/iris/Iris.java @@ -747,6 +747,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); } 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 b4744cc20..c4d20f73e 100644 --- a/core/src/main/java/com/volmit/iris/core/ServerConfigurator.java +++ b/core/src/main/java/com/volmit/iris/core/ServerConfigurator.java @@ -228,7 +228,7 @@ public class ServerConfigurator { Iris.info( "Hotloading all Datapacks!"); if (INMS.get().supportsDataPacks()) { for (File folder : getDatapacksFolder()) { - INMS.get().loadDatapack(folder); + INMS.get().loadDatapack(folder, false); } Iris.info("Datapacks Hotloaded!"); Iris.info(C.YELLOW + "============================================================================"); 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 71e5b89dd..55ef323ab 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 @@ -20,9 +20,11 @@ package com.volmit.iris.core.commands; import com.volmit.iris.Iris; import com.volmit.iris.core.IrisSettings; +import com.volmit.iris.core.ServerConfigurator; 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.nms.INMS; import com.volmit.iris.core.project.IrisProject; import com.volmit.iris.core.service.ConversionSVC; import com.volmit.iris.core.service.StudioSVC; @@ -279,13 +281,23 @@ public class CommandStudio implements DecreeExecutor { } @Decree(description = "Hotload a studio", aliases = {"reload", "h"}) - public void hotload() { + public void hotload(@Param(defaultValue = "false") boolean reloadDataPack) { if (!Iris.service(StudioSVC.class).isProjectOpen()) { 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"); + if (reloadDataPack) { + var world = provider.getTarget().getWorld().realWorld(); + if (world == null) { + sender().sendMessage(C.RED + "Failed to reload datapacks."); + return; + } + boolean success = INMS.get().loadDatapack(new File(world.getWorldFolder(), "datapacks"), true); + sender().sendMessage(success ? C.GREEN + "Reloaded datapacks." : C.RED + "Failed to reload datapacks."); + } } @Decree(description = "Show loot if a chest were right here", origin = DecreeOrigin.PLAYER, sync = true) 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 53c71a833..0ed630cf4 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 @@ -19,6 +19,7 @@ package com.volmit.iris.core.nms; import com.volmit.iris.engine.framework.Engine; +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; @@ -113,7 +114,11 @@ public interface INMSBinding { Entity spawnEntity(Location location, EntityType type, CreatureSpawnEvent.SpawnReason reason); - boolean loadDatapack(File datapackFolder); + boolean loadDatapack(File datapackFolder, boolean replace); + + default boolean registerDimension(String name, IrisDimension dimension) { + return false; + } void injectBukkit(); } 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 77e8e18de..c928db7aa 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 @@ -107,7 +107,7 @@ public class NMSBinding1X implements INMSBinding { } @Override - public boolean loadDatapack(File datapackFolder) { + public boolean loadDatapack(File datapackFolder, boolean replace) { return false; } 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/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 ca74aad02..3561eb997 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 @@ -13,13 +13,19 @@ import java.util.IdentityHashMap; 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 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.IrisDimension; import com.volmit.iris.util.format.C; import com.volmit.iris.util.io.IO; import it.unimi.dsi.fastutil.objects.Reference2IntMap; @@ -29,9 +35,14 @@ 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; @@ -45,6 +56,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; @@ -551,25 +563,34 @@ public class NMSBinding implements INMSBinding { } @Override - public boolean loadDatapack(File folder) { + 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 loadDatapack(File folder, boolean replace) { var data = new File(folder, "iris/data"); if (!data.exists() || !data.isDirectory()) return false; FilenameFilter jsonFilter = (dir, name) -> new File(dir, name).isFile() && name.toLowerCase().endsWith(".json"); - var dimensionFolder = new File(data, "minecraft/dimension_type"); - if (dimensionFolder.exists()) { - var files = dimensionFolder.listFiles(jsonFilter); - if (files != null) { - for (File file : files) { - try { - modifyDimension(file); - } catch (Throwable e) { - Iris.error("Unable to modify dimension!"); - e.printStackTrace(); - } - } - } - } var files = data.listFiles((dir, name) -> new File(dir, name).isDirectory()); if (files == null) return false; for (File file : files) { @@ -579,7 +600,8 @@ public class NMSBinding implements INMSBinding { if (biomeFiles == null) continue; for (File biomeFile : biomeFiles) { try { - registerBiome(file.getName(), biomeFile); + var value = decode(net.minecraft.world.level.biome.Biome.CODEC, IO.readAll(file)).map(Holder::value).orElse(null); + register(Registry.BIOME_REGISTRY, from(file.getName(), biomeFile), value, replace); } catch (Throwable e) { Iris.error("Failed to register biome " + file.getName() + ":" + biomeFile.getName()); e.printStackTrace(); @@ -594,75 +616,95 @@ public class NMSBinding implements INMSBinding { return new ResourceLocation(namespace, name.substring(0, name.lastIndexOf('.'))); } - private void registerBiome(String namespace, File file) throws Throwable { - var rawRegistry = registry().registry(Registry.BIOME_REGISTRY).orElse(null); - var key = ResourceKey.create(Registry.BIOME_REGISTRY, from(namespace, file)); - if (!(rawRegistry instanceof MappedRegistry registry)) - throw new IllegalStateException("The Biome Registry is not a mapped Registry!"); - if (registry.containsKey(key)) return; - Field field = getField(MappedRegistry.class, boolean.class); - field.setAccessible(true); - boolean frozen = field.getBoolean(registry); - field.setBoolean(registry, false); - Field holdersField = null; - boolean holders = false; - for (Field f : MappedRegistry.class.getDeclaredFields()) { - if (!f.getGenericType().getTypeName().startsWith("java.util.Map()); - } + 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 { - var biome = net.minecraft.world.level.biome.Biome.CODEC.decode(JsonOps.INSTANCE, GsonHelper.parse(IO.readAll(file))) - .get().left().map(Pair::getFirst).map(Holder::value).orElse(null); - if (biome == null) - throw new IllegalStateException("Failed to decode biome " + file.getName()); - - registry.createIntrusiveHolder(biome); - registry.register(key, biome, Lifecycle.stable()); - } finally { - field.setBoolean(registry, frozen); - if (holders) { - holdersField.set(registry, null); + 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 holdersField = null; + boolean holders = false; + for (Field f : MappedRegistry.class.getDeclaredFields()) { + if (!f.getGenericType().getTypeName().startsWith("java.util.Map()); + } + + try { + registry.createIntrusiveHolder(value); + registry.register(key, value, Lifecycle.stable()); + return true; + } finally { + field.setBoolean(registry, frozen); + if (holders) { + holdersField.set(registry, null); + } + } + } catch (Throwable e) { + throw new IllegalStateException(e); } } @SuppressWarnings("unchecked") - private void modifyDimension(File file) throws Throwable { - var key = ResourceKey.create(Registry.DIMENSION_TYPE_REGISTRY, from("minecraft", file)); - var rawRegistry = registry().registry(Registry.DIMENSION_TYPE_REGISTRY).orElse(null); - if (!(rawRegistry instanceof MappedRegistry registry)) - throw new IllegalStateException("The Dimension Registry is not a mapped Registry!"); + 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); - var holder = registry.getHolder(key).orElseThrow(() -> new IllegalStateException("Unknown dimension type: " + key)); - 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); + } + } - var newValue = DimensionType.CODEC.decode(JsonOps.INSTANCE, GsonHelper.parse(IO.readAll(file))) - .get().left().map(Pair::getFirst).map(Holder::value).orElse(null); - if (newValue == null) - throw new IllegalArgumentException("Failed to parse dimension type " + key.location() + " from " + file); - - valueField.set(holder, newValue); - toId.put(newValue, toId.removeInt(oldValue)); - byValue.put(newValue, byValue.remove(oldValue)); - lifecycles.put(newValue, lifecycles.remove(oldValue)); + 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) { @@ -715,6 +757,13 @@ public class NMSBinding implements INMSBinding { .visit(Advice.to(WorldCreatorAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(String.class)))) .make() .load(WorldCreator.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!"); @@ -724,6 +773,28 @@ public class NMSBinding implements INMSBinding { } + 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()) 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) { + logger.warn("Unable to find registry for dimension type {}", typeKey); + return; + } + Holder holder = registry.getHolder(typeKey).orElse(null); + if (holder == null) { + logger.warn("Unable to find dimension type {}", typeKey); + return; + } + levelStem = new LevelStem(holder, levelStem.generator()); + } + } + private static class WorldCreatorAdvice { @Advice.OnMethodEnter static void enter(@Advice.Argument(0) String name) { @@ -741,4 +812,4 @@ public class NMSBinding implements INMSBinding { } } } -} +} \ No newline at end of file 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 c58eb3143..03105825c 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 @@ -13,13 +13,19 @@ import java.util.IdentityHashMap; 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 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.IrisDimension; import com.volmit.iris.util.format.C; import com.volmit.iris.util.io.IO; import it.unimi.dsi.fastutil.objects.Reference2IntMap; @@ -29,9 +35,14 @@ 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; @@ -45,6 +56,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; @@ -553,25 +565,34 @@ public class NMSBinding implements INMSBinding { } @Override - public boolean loadDatapack(File folder) { + 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 loadDatapack(File folder, boolean replace) { var data = new File(folder, "iris/data"); if (!data.exists() || !data.isDirectory()) return false; FilenameFilter jsonFilter = (dir, name) -> new File(dir, name).isFile() && name.toLowerCase().endsWith(".json"); - var dimensionFolder = new File(data, "minecraft/dimension_type"); - if (dimensionFolder.exists()) { - var files = dimensionFolder.listFiles(jsonFilter); - if (files != null) { - for (File file : files) { - try { - modifyDimension(file); - } catch (Throwable e) { - Iris.error("Unable to modify dimension!"); - e.printStackTrace(); - } - } - } - } var files = data.listFiles((dir, name) -> new File(dir, name).isDirectory()); if (files == null) return false; for (File file : files) { @@ -581,7 +602,8 @@ public class NMSBinding implements INMSBinding { if (biomeFiles == null) continue; for (File biomeFile : biomeFiles) { try { - registerBiome(file.getName(), biomeFile); + var value = decode(net.minecraft.world.level.biome.Biome.CODEC, IO.readAll(file)).map(Holder::value).orElse(null); + register(Registries.BIOME, from(file.getName(), biomeFile), value, replace); } catch (Throwable e) { Iris.error("Failed to register biome " + file.getName() + ":" + biomeFile.getName()); e.printStackTrace(); @@ -596,75 +618,95 @@ public class NMSBinding implements INMSBinding { return new ResourceLocation(namespace, name.substring(0, name.lastIndexOf('.'))); } - private void registerBiome(String namespace, File file) throws Throwable { - var rawRegistry = registry().registry(Registries.BIOME).orElse(null); - var key = ResourceKey.create(Registries.BIOME, from(namespace, file)); - if (!(rawRegistry instanceof MappedRegistry registry)) - throw new IllegalStateException("The Biome Registry is not a mapped Registry!"); - if (registry.containsKey(key)) return; - Field field = getField(MappedRegistry.class, boolean.class); - field.setAccessible(true); - boolean frozen = field.getBoolean(registry); - field.setBoolean(registry, false); - Field holdersField = null; - boolean holders = false; - for (Field f : MappedRegistry.class.getDeclaredFields()) { - if (!f.getGenericType().getTypeName().startsWith("java.util.Map()); - } + 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 { - var biome = net.minecraft.world.level.biome.Biome.CODEC.decode(JsonOps.INSTANCE, GsonHelper.parse(IO.readAll(file))) - .get().left().map(Pair::getFirst).map(Holder::value).orElse(null); - if (biome == null) - throw new IllegalStateException("Failed to decode biome " + file.getName()); - - registry.createIntrusiveHolder(biome); - registry.register(key, biome, Lifecycle.stable()); - } finally { - field.setBoolean(registry, frozen); - if (holders) { - holdersField.set(registry, null); + 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 holdersField = null; + boolean holders = false; + for (Field f : MappedRegistry.class.getDeclaredFields()) { + if (!f.getGenericType().getTypeName().startsWith("java.util.Map()); + } + + try { + registry.createIntrusiveHolder(value); + registry.register(key, value, Lifecycle.stable()); + return true; + } finally { + field.setBoolean(registry, frozen); + if (holders) { + holdersField.set(registry, null); + } + } + } catch (Throwable e) { + throw new IllegalStateException(e); } } @SuppressWarnings("unchecked") - private void modifyDimension(File file) throws Throwable { - var key = ResourceKey.create(Registries.DIMENSION_TYPE, from("minecraft", file)); - var rawRegistry = registry().registry(Registries.DIMENSION_TYPE).orElse(null); - if (!(rawRegistry instanceof MappedRegistry registry)) - throw new IllegalStateException("The Dimension Registry is not a mapped Registry!"); + 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); - var holder = registry.getHolder(key).orElseThrow(() -> new IllegalStateException("Unknown dimension type: " + key)); - 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); + } + } - var newValue = DimensionType.CODEC.decode(JsonOps.INSTANCE, GsonHelper.parse(IO.readAll(file))) - .get().left().map(Pair::getFirst).map(Holder::value).orElse(null); - if (newValue == null) - throw new IllegalArgumentException("Failed to parse dimension type " + key.location() + " from " + file); - - valueField.set(holder, newValue); - toId.put(newValue, toId.removeInt(oldValue)); - byValue.put(newValue, byValue.remove(oldValue)); - lifecycles.put(newValue, lifecycles.remove(oldValue)); + 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) { @@ -716,6 +758,13 @@ public class NMSBinding implements INMSBinding { .visit(Advice.to(WorldCreatorAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(String.class)))) .make() .load(WorldCreator.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!"); @@ -725,6 +774,28 @@ public class NMSBinding implements INMSBinding { } + 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()) return; + var logger = MinecraftServer.LOGGER; + 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) { + logger.warn("Unable to find registry for dimension type {}", typeKey); + return; + } + Holder holder = registry.getHolder(typeKey).orElse(null); + if (holder == null) { + logger.warn("Unable to find dimension type {}", typeKey); + return; + } + levelStem = new LevelStem(holder, levelStem.generator()); + } + } + private static class WorldCreatorAdvice { @Advice.OnMethodEnter static void enter(@Advice.Argument(0) String name) { @@ -742,4 +813,4 @@ public class NMSBinding implements INMSBinding { } } } -} +} \ No newline at end of file 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 bf1f82c6e..8eef4e427 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 @@ -13,13 +13,19 @@ import java.util.IdentityHashMap; 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 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.IrisDimension; import com.volmit.iris.util.format.C; import com.volmit.iris.util.io.IO; import it.unimi.dsi.fastutil.objects.Reference2IntMap; @@ -29,9 +35,14 @@ 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; @@ -45,6 +56,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; @@ -557,25 +569,34 @@ public class NMSBinding implements INMSBinding { } @Override - public boolean loadDatapack(File folder) { + 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 loadDatapack(File folder, boolean replace) { var data = new File(folder, "iris/data"); if (!data.exists() || !data.isDirectory()) return false; FilenameFilter jsonFilter = (dir, name) -> new File(dir, name).isFile() && name.toLowerCase().endsWith(".json"); - var dimensionFolder = new File(data, "minecraft/dimension_type"); - if (dimensionFolder.exists()) { - var files = dimensionFolder.listFiles(jsonFilter); - if (files != null) { - for (File file : files) { - try { - modifyDimension(file); - } catch (Throwable e) { - Iris.error("Unable to modify dimension!"); - e.printStackTrace(); - } - } - } - } var files = data.listFiles((dir, name) -> new File(dir, name).isDirectory()); if (files == null) return false; for (File file : files) { @@ -585,7 +606,8 @@ public class NMSBinding implements INMSBinding { if (biomeFiles == null) continue; for (File biomeFile : biomeFiles) { try { - registerBiome(file.getName(), biomeFile); + var value = decode(net.minecraft.world.level.biome.Biome.CODEC, IO.readAll(file)).map(Holder::value).orElse(null); + register(Registries.BIOME, from(file.getName(), biomeFile), value, replace); } catch (Throwable e) { Iris.error("Failed to register biome " + file.getName() + ":" + biomeFile.getName()); e.printStackTrace(); @@ -600,75 +622,95 @@ public class NMSBinding implements INMSBinding { return new ResourceLocation(namespace, name.substring(0, name.lastIndexOf('.'))); } - private void registerBiome(String namespace, File file) throws Throwable { - var rawRegistry = registry().registry(Registries.BIOME).orElse(null); - var key = ResourceKey.create(Registries.BIOME, from(namespace, file)); - if (!(rawRegistry instanceof MappedRegistry registry)) - throw new IllegalStateException("The Biome Registry is not a mapped Registry!"); - if (registry.containsKey(key)) return; - Field field = getField(MappedRegistry.class, boolean.class); - field.setAccessible(true); - boolean frozen = field.getBoolean(registry); - field.setBoolean(registry, false); - Field holdersField = null; - boolean holders = false; - for (Field f : MappedRegistry.class.getDeclaredFields()) { - if (!f.getGenericType().getTypeName().startsWith("java.util.Map()); - } + 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 { - var biome = net.minecraft.world.level.biome.Biome.CODEC.decode(JsonOps.INSTANCE, GsonHelper.parse(IO.readAll(file))) - .get().left().map(Pair::getFirst).map(Holder::value).orElse(null); - if (biome == null) - throw new IllegalStateException("Failed to decode biome " + file.getName()); - - registry.createIntrusiveHolder(biome); - registry.register(key, biome, Lifecycle.stable()); - } finally { - field.setBoolean(registry, frozen); - if (holders) { - holdersField.set(registry, null); + 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 holdersField = null; + boolean holders = false; + for (Field f : MappedRegistry.class.getDeclaredFields()) { + if (!f.getGenericType().getTypeName().startsWith("java.util.Map()); + } + + try { + registry.createIntrusiveHolder(value); + registry.register(key, value, Lifecycle.stable()); + return true; + } finally { + field.setBoolean(registry, frozen); + if (holders) { + holdersField.set(registry, null); + } + } + } catch (Throwable e) { + throw new IllegalStateException(e); } } @SuppressWarnings("unchecked") - private void modifyDimension(File file) throws Throwable { - var key = ResourceKey.create(Registries.DIMENSION_TYPE, from("minecraft", file)); - var rawRegistry = registry().registry(Registries.DIMENSION_TYPE).orElse(null); - if (!(rawRegistry instanceof MappedRegistry registry)) - throw new IllegalStateException("The Dimension Registry is not a mapped Registry!"); + 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); - var holder = registry.getHolder(key).orElseThrow(() -> new IllegalStateException("Unknown dimension type: " + key)); - 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); + } + } - var newValue = DimensionType.CODEC.decode(JsonOps.INSTANCE, GsonHelper.parse(IO.readAll(file))) - .get().left().map(Pair::getFirst).map(Holder::value).orElse(null); - if (newValue == null) - throw new IllegalArgumentException("Failed to parse dimension type " + key.location() + " from " + file); - - valueField.set(holder, newValue); - toId.put(newValue, toId.removeInt(oldValue)); - byValue.put(newValue, byValue.remove(oldValue)); - lifecycles.put(newValue, lifecycles.remove(oldValue)); + 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) { @@ -720,6 +762,13 @@ public class NMSBinding implements INMSBinding { .visit(Advice.to(WorldCreatorAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(String.class)))) .make() .load(WorldCreator.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!"); @@ -729,6 +778,28 @@ public class NMSBinding implements INMSBinding { } + 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()) return; + var logger = MinecraftServer.LOGGER; + 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) { + logger.warn("Unable to find registry for dimension type {}", typeKey); + return; + } + Holder holder = registry.getHolder(typeKey).orElse(null); + if (holder == null) { + logger.warn("Unable to find dimension type {}", typeKey); + return; + } + levelStem = new LevelStem(holder, levelStem.generator()); + } + } + private static class WorldCreatorAdvice { @Advice.OnMethodEnter static void enter(@Advice.Argument(0) String name) { 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 eb4fb4162..7e6e8c963 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,14 +1,18 @@ 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.IrisDimension; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.format.C; @@ -38,8 +42,11 @@ 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.server.level.progress.ChunkProgressListener; import net.minecraft.util.GsonHelper; +import net.minecraft.world.RandomSequences; import net.minecraft.world.entity.EntityDimensions; import net.minecraft.world.level.Level; import net.minecraft.world.level.biome.BiomeSource; @@ -50,6 +57,9 @@ 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; @@ -66,6 +76,7 @@ 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; @@ -84,7 +95,9 @@ import java.util.IdentityHashMap; 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; public class NMSBinding implements INMSBinding { @@ -543,25 +556,34 @@ public class NMSBinding implements INMSBinding { } @Override - public boolean loadDatapack(File folder) { + 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 loadDatapack(File folder, boolean replace) { var data = new File(folder, "iris/data"); if (!data.exists() || !data.isDirectory()) return false; FilenameFilter jsonFilter = (dir, name) -> new File(dir, name).isFile() && name.toLowerCase().endsWith(".json"); - var dimensionFolder = new File(data, "minecraft/dimension_type"); - if (dimensionFolder.exists()) { - var files = dimensionFolder.listFiles(jsonFilter); - if (files != null) { - for (File file : files) { - try { - modifyDimension(file); - } catch (Throwable e) { - Iris.error("Unable to modify dimension!"); - e.printStackTrace(); - } - } - } - } var files = data.listFiles((dir, name) -> new File(dir, name).isDirectory()); if (files == null) return false; for (File file : files) { @@ -571,7 +593,8 @@ public class NMSBinding implements INMSBinding { if (biomeFiles == null) continue; for (File biomeFile : biomeFiles) { try { - registerBiome(file.getName(), biomeFile); + var value = decode(net.minecraft.world.level.biome.Biome.CODEC, IO.readAll(file)).map(Holder::value).orElse(null); + register(Registries.BIOME, from(file.getName(), biomeFile), value, replace); } catch (Throwable e) { Iris.error("Failed to register biome " + file.getName() + ":" + biomeFile.getName()); e.printStackTrace(); @@ -586,75 +609,95 @@ public class NMSBinding implements INMSBinding { return new ResourceLocation(namespace, name.substring(0, name.lastIndexOf('.'))); } - private void registerBiome(String namespace, File file) throws Throwable { - var rawRegistry = registry().registry(Registries.BIOME).orElse(null); - var key = ResourceKey.create(Registries.BIOME, from(namespace, file)); - if (!(rawRegistry instanceof MappedRegistry registry)) - throw new IllegalStateException("The Biome Registry is not a mapped Registry!"); - if (registry.containsKey(key)) return; - Field field = getField(MappedRegistry.class, boolean.class); - field.setAccessible(true); - boolean frozen = field.getBoolean(registry); - field.setBoolean(registry, false); - Field holdersField = null; - boolean holders = false; - for (Field f : MappedRegistry.class.getDeclaredFields()) { - if (!f.getGenericType().getTypeName().startsWith("java.util.Map()); - } + 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 { - var biome = net.minecraft.world.level.biome.Biome.CODEC.decode(JsonOps.INSTANCE, GsonHelper.parse(IO.readAll(file))) - .get().left().map(Pair::getFirst).map(Holder::value).orElse(null); - if (biome == null) - throw new IllegalStateException("Failed to decode biome " + file.getName()); - - registry.createIntrusiveHolder(biome); - registry.register(key, biome, Lifecycle.stable()); - } finally { - field.setBoolean(registry, frozen); - if (holders) { - holdersField.set(registry, null); + 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 holdersField = null; + boolean holders = false; + for (Field f : MappedRegistry.class.getDeclaredFields()) { + if (!f.getGenericType().getTypeName().startsWith("java.util.Map()); + } + + try { + registry.createIntrusiveHolder(value); + registry.register(key, value, Lifecycle.stable()); + return true; + } finally { + field.setBoolean(registry, frozen); + if (holders) { + holdersField.set(registry, null); + } + } + } catch (Throwable e) { + throw new IllegalStateException(e); } } @SuppressWarnings("unchecked") - private void modifyDimension(File file) throws Throwable { - var key = ResourceKey.create(Registries.DIMENSION_TYPE, from("minecraft", file)); - var rawRegistry = registry().registry(Registries.DIMENSION_TYPE).orElse(null); - if (!(rawRegistry instanceof MappedRegistry registry)) - throw new IllegalStateException("The Dimension Registry is not a mapped Registry!"); + 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); - var holder = registry.getHolder(key).orElseThrow(() -> new IllegalStateException("Unknown dimension type: " + key)); - 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); + } + } - var newValue = DimensionType.CODEC.decode(JsonOps.INSTANCE, GsonHelper.parse(IO.readAll(file))) - .get().left().map(Pair::getFirst).map(Holder::value).orElse(null); - if (newValue == null) - throw new IllegalArgumentException("Failed to parse dimension type " + key.location() + " from " + file); - - valueField.set(holder, newValue); - toId.put(newValue, toId.removeInt(oldValue)); - byValue.put(newValue, byValue.remove(oldValue)); - lifecycles.put(newValue, lifecycles.remove(oldValue)); + 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) { @@ -719,6 +762,13 @@ public class NMSBinding implements INMSBinding { .visit(Advice.to(WorldCreatorAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(String.class)))) .make() .load(WorldCreator.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!"); @@ -728,6 +778,28 @@ public class NMSBinding implements INMSBinding { } + 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()) return; + var logger = MinecraftServer.LOGGER; + 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) { + logger.warn("Unable to find registry for dimension type {}", typeKey); + return; + } + Holder holder = registry.getHolder(typeKey).orElse(null); + if (holder == null) { + logger.warn("Unable to find dimension type {}", typeKey); + return; + } + levelStem = new LevelStem(holder, levelStem.generator()); + } + } + private static class WorldCreatorAdvice { @Advice.OnMethodEnter static void enter(@Advice.Argument(0) String name) { 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 2e9720472..724b52dc1 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 @@ -13,13 +13,19 @@ import java.util.IdentityHashMap; 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 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.IrisDimension; import com.volmit.iris.util.format.C; import com.volmit.iris.util.io.IO; import it.unimi.dsi.fastutil.objects.Reference2IntMap; @@ -28,9 +34,15 @@ 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; @@ -44,6 +56,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; @@ -553,25 +566,34 @@ public class NMSBinding implements INMSBinding { } @Override - public boolean loadDatapack(File folder) { + 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 loadDatapack(File folder, boolean replace) { var data = new File(folder, "iris/data"); if (!data.exists() || !data.isDirectory()) return false; FilenameFilter jsonFilter = (dir, name) -> new File(dir, name).isFile() && name.toLowerCase().endsWith(".json"); - var dimensionFolder = new File(data, "minecraft/dimension_type"); - if (dimensionFolder.exists()) { - var files = dimensionFolder.listFiles(jsonFilter); - if (files != null) { - for (File file : files) { - try { - modifyDimension(file); - } catch (Throwable e) { - Iris.error("Unable to modify dimension!"); - e.printStackTrace(); - } - } - } - } var files = data.listFiles((dir, name) -> new File(dir, name).isDirectory()); if (files == null) return false; for (File file : files) { @@ -581,7 +603,8 @@ public class NMSBinding implements INMSBinding { if (biomeFiles == null) continue; for (File biomeFile : biomeFiles) { try { - registerBiome(file.getName(), biomeFile); + var value = decode(net.minecraft.world.level.biome.Biome.CODEC, IO.readAll(file)).map(Holder::value).orElse(null); + register(Registries.BIOME, from(file.getName(), biomeFile), value, replace); } catch (Throwable e) { Iris.error("Failed to register biome " + file.getName() + ":" + biomeFile.getName()); e.printStackTrace(); @@ -596,75 +619,95 @@ public class NMSBinding implements INMSBinding { return new ResourceLocation(namespace, name.substring(0, name.lastIndexOf('.'))); } - private void registerBiome(String namespace, File file) throws Throwable { - var rawRegistry = registry().registry(Registries.BIOME).orElse(null); - var key = ResourceKey.create(Registries.BIOME, from(namespace, file)); - if (!(rawRegistry instanceof MappedRegistry registry)) - throw new IllegalStateException("The Biome Registry is not a mapped Registry!"); - if (registry.containsKey(key)) return; - Field field = getField(MappedRegistry.class, boolean.class); - field.setAccessible(true); - boolean frozen = field.getBoolean(registry); - field.setBoolean(registry, false); - Field holdersField = null; - boolean holders = false; - for (Field f : MappedRegistry.class.getDeclaredFields()) { - if (!f.getGenericType().getTypeName().startsWith("java.util.Map()); - } + 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 { - var biome = net.minecraft.world.level.biome.Biome.CODEC.decode(JsonOps.INSTANCE, GsonHelper.parse(IO.readAll(file))) - .get().left().map(Pair::getFirst).map(Holder::value).orElse(null); - if (biome == null) - throw new IllegalStateException("Failed to decode biome " + file.getName()); - - registry.createIntrusiveHolder(biome); - registry.register(key, biome, Lifecycle.stable()); - } finally { - field.setBoolean(registry, frozen); - if (holders) { - holdersField.set(registry, null); + 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 holdersField = null; + boolean holders = false; + for (Field f : MappedRegistry.class.getDeclaredFields()) { + if (!f.getGenericType().getTypeName().startsWith("java.util.Map()); + } + + try { + registry.createIntrusiveHolder(value); + registry.register(key, value, Lifecycle.stable()); + return true; + } finally { + field.setBoolean(registry, frozen); + if (holders) { + holdersField.set(registry, null); + } + } + } catch (Throwable e) { + throw new IllegalStateException(e); } } @SuppressWarnings("unchecked") - private void modifyDimension(File file) throws Throwable { - var key = ResourceKey.create(Registries.DIMENSION_TYPE, from("minecraft", file)); - var rawRegistry = registry().registry(Registries.DIMENSION_TYPE).orElse(null); - if (!(rawRegistry instanceof MappedRegistry registry)) - throw new IllegalStateException("The Dimension Registry is not a mapped Registry!"); + 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); - var holder = registry.getHolder(key).orElseThrow(() -> new IllegalStateException("Unknown dimension type: " + key)); - 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); + } + } - var newValue = DimensionType.CODEC.decode(JsonOps.INSTANCE, GsonHelper.parse(IO.readAll(file))) - .get().left().map(Pair::getFirst).map(Holder::value).orElse(null); - if (newValue == null) - throw new IllegalArgumentException("Failed to parse dimension type " + key.location() + " from " + file); - - valueField.set(holder, newValue); - toId.put(newValue, toId.removeInt(oldValue)); - byValue.put(newValue, byValue.remove(oldValue)); - lifecycles.put(newValue, lifecycles.remove(oldValue)); + 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) { @@ -721,6 +764,13 @@ public class NMSBinding implements INMSBinding { .visit(Advice.to(WorldCreatorAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(String.class)))) .make() .load(WorldCreator.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!"); @@ -730,6 +780,28 @@ public class NMSBinding implements INMSBinding { } + 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()) return; + var logger = MinecraftServer.LOGGER; + 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) { + logger.warn("Unable to find registry for dimension type {}", typeKey); + return; + } + Holder holder = registry.getHolder(typeKey).orElse(null); + if (holder == null) { + logger.warn("Unable to find dimension type {}", typeKey); + return; + } + levelStem = new LevelStem(holder, levelStem.generator()); + } + } + private static class WorldCreatorAdvice { @Advice.OnMethodEnter static void enter(@Advice.Argument(0) String name) { 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 14403b714..cb67298e0 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 @@ -6,27 +6,54 @@ import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FilenameFilter; +import java.io.IOException; +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.Instrumentation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.nio.file.Files; 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.common.base.Supplier; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +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.IrisDimension; import com.volmit.iris.util.format.C; +import com.volmit.iris.util.function.NastySupplier; import com.volmit.iris.util.io.IO; import it.unimi.dsi.fastutil.objects.Reference2IntMap; import net.bytebuddy.ByteBuddy; +import net.bytebuddy.agent.builder.AgentBuilder; import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.dynamic.loading.ClassReloadingStrategy; import net.bytebuddy.matcher.ElementMatchers; +import net.bytebuddy.utility.JavaModule; +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.util.worldupdate.WorldUpgrader; +import net.minecraft.world.RandomSequences; import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.LevelHeightAccessor; 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; @@ -40,6 +67,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; @@ -445,13 +473,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); @@ -551,25 +579,34 @@ public class NMSBinding implements INMSBinding { } @Override - public boolean loadDatapack(File folder) { + 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 loadDatapack(File folder, boolean replace) { var data = new File(folder, "iris/data"); if (!data.exists() || !data.isDirectory()) return false; FilenameFilter jsonFilter = (dir, name) -> new File(dir, name).isFile() && name.toLowerCase().endsWith(".json"); - var dimensionFolder = new File(data, "minecraft/dimension_type"); - if (dimensionFolder.exists()) { - var files = dimensionFolder.listFiles(jsonFilter); - if (files != null) { - for (File file : files) { - try { - modifyDimension(file); - } catch (Throwable e) { - Iris.error("Unable to modify dimension!"); - e.printStackTrace(); - } - } - } - } var files = data.listFiles((dir, name) -> new File(dir, name).isDirectory()); if (files == null) return false; for (File file : files) { @@ -579,7 +616,8 @@ public class NMSBinding implements INMSBinding { if (biomeFiles == null) continue; for (File biomeFile : biomeFiles) { try { - registerBiome(file.getName(), biomeFile); + var value = decode(net.minecraft.world.level.biome.Biome.CODEC, IO.readAll(file)).map(Holder::value).orElse(null); + register(Registries.BIOME, from(file.getName(), biomeFile), value, replace); } catch (Throwable e) { Iris.error("Failed to register biome " + file.getName() + ":" + biomeFile.getName()); e.printStackTrace(); @@ -594,75 +632,95 @@ public class NMSBinding implements INMSBinding { return new ResourceLocation(namespace, name.substring(0, name.lastIndexOf('.'))); } - private void registerBiome(String namespace, File file) throws Throwable { - var rawRegistry = registry().registry(Registries.BIOME).orElse(null); - var key = ResourceKey.create(Registries.BIOME, from(namespace, file)); - if (!(rawRegistry instanceof MappedRegistry registry)) - throw new IllegalStateException("The Biome Registry is not a mapped Registry!"); - if (registry.containsKey(key)) return; - Field field = getField(MappedRegistry.class, boolean.class); - field.setAccessible(true); - boolean frozen = field.getBoolean(registry); - field.setBoolean(registry, false); - Field holdersField = null; - boolean holders = false; - for (Field f : MappedRegistry.class.getDeclaredFields()) { - if (!f.getGenericType().getTypeName().startsWith("java.util.Map()); - } + 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 { - var biome = net.minecraft.world.level.biome.Biome.CODEC.decode(JsonOps.INSTANCE, GsonHelper.parse(IO.readAll(file))) - .get().left().map(Pair::getFirst).map(Holder::value).orElse(null); - if (biome == null) - throw new IllegalStateException("Failed to decode biome " + file.getName()); - - registry.createIntrusiveHolder(biome); - registry.register(key, biome, Lifecycle.stable()); - } finally { - field.setBoolean(registry, frozen); - if (holders) { - holdersField.set(registry, null); + 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 holdersField = null; + boolean holders = false; + for (Field f : MappedRegistry.class.getDeclaredFields()) { + if (!f.getGenericType().getTypeName().startsWith("java.util.Map()); + } + + try { + registry.createIntrusiveHolder(value); + registry.register(key, value, Lifecycle.stable()); + return true; + } finally { + field.setBoolean(registry, frozen); + if (holders) { + holdersField.set(registry, null); + } + } + } catch (Throwable e) { + throw new IllegalStateException(e); } } @SuppressWarnings("unchecked") - private void modifyDimension(File file) throws Throwable { - var key = ResourceKey.create(Registries.DIMENSION_TYPE, from("minecraft", file)); - var rawRegistry = registry().registry(Registries.DIMENSION_TYPE).orElse(null); - if (!(rawRegistry instanceof MappedRegistry registry)) - throw new IllegalStateException("The Dimension Registry is not a mapped Registry!"); + 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); - var holder = registry.getHolder(key).orElseThrow(() -> new IllegalStateException("Unknown dimension type: " + key)); - 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); + } + } - var newValue = DimensionType.CODEC.decode(JsonOps.INSTANCE, GsonHelper.parse(IO.readAll(file))) - .get().left().map(Pair::getFirst).map(Holder::value).orElse(null); - if (newValue == null) - throw new IllegalArgumentException("Failed to parse dimension type " + key.location() + " from " + file); - - valueField.set(holder, newValue); - toId.put(newValue, toId.removeInt(oldValue)); - byValue.put(newValue, byValue.remove(oldValue)); - lifecycles.put(newValue, lifecycles.remove(oldValue)); + 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) { @@ -718,6 +776,13 @@ public class NMSBinding implements INMSBinding { .visit(Advice.to(WorldCreatorAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(String.class)))) .make() .load(WorldCreator.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!"); @@ -727,6 +792,28 @@ public class NMSBinding implements INMSBinding { } + 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()) return; + var logger = MinecraftServer.LOGGER; + 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) { + logger.warn("Unable to find registry for dimension type {}", typeKey); + return; + } + Holder holder = registry.getHolder(typeKey).orElse(null); + if (holder == null) { + logger.warn("Unable to find dimension type {}", typeKey); + return; + } + levelStem = new LevelStem(holder, levelStem.generator()); + } + } + private static class WorldCreatorAdvice { @Advice.OnMethodEnter static void enter(@Advice.Argument(0) String name) {