implement proper datapack reload+per world height

This commit is contained in:
CrazyDev22
2024-05-03 19:01:33 +02:00
parent bb78f412e0
commit 05f4955989
12 changed files with 935 additions and 467 deletions
@@ -747,6 +747,9 @@ public class Iris extends VolmitPlugin implements Listener {
ff.mkdirs(); ff.mkdirs();
service(StudioSVC.class).installIntoWorld(getSender(), dim.getLoadKey(), w.worldFolder()); 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); return new BukkitChunkGenerator(w, false, ff, dim.getLoadKey(), false);
} }
@@ -228,7 +228,7 @@ public class ServerConfigurator {
Iris.info( "Hotloading all Datapacks!"); Iris.info( "Hotloading all Datapacks!");
if (INMS.get().supportsDataPacks()) { if (INMS.get().supportsDataPacks()) {
for (File folder : getDatapacksFolder()) { for (File folder : getDatapacksFolder()) {
INMS.get().loadDatapack(folder); INMS.get().loadDatapack(folder, false);
} }
Iris.info("Datapacks Hotloaded!"); Iris.info("Datapacks Hotloaded!");
Iris.info(C.YELLOW + "============================================================================"); Iris.info(C.YELLOW + "============================================================================");
@@ -20,9 +20,11 @@ package com.volmit.iris.core.commands;
import com.volmit.iris.Iris; import com.volmit.iris.Iris;
import com.volmit.iris.core.IrisSettings; 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.NoiseExplorerGUI;
import com.volmit.iris.core.gui.VisionGUI; import com.volmit.iris.core.gui.VisionGUI;
import com.volmit.iris.core.loader.IrisData; 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.project.IrisProject;
import com.volmit.iris.core.service.ConversionSVC; import com.volmit.iris.core.service.ConversionSVC;
import com.volmit.iris.core.service.StudioSVC; import com.volmit.iris.core.service.StudioSVC;
@@ -279,13 +281,23 @@ public class CommandStudio implements DecreeExecutor {
} }
@Decree(description = "Hotload a studio", aliases = {"reload", "h"}) @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()) { if (!Iris.service(StudioSVC.class).isProjectOpen()) {
sender().sendMessage(C.RED + "No studio world open!"); sender().sendMessage(C.RED + "No studio world open!");
return; 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"); 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) @Decree(description = "Show loot if a chest were right here", origin = DecreeOrigin.PLAYER, sync = true)
@@ -19,6 +19,7 @@
package com.volmit.iris.core.nms; package com.volmit.iris.core.nms;
import com.volmit.iris.engine.framework.Engine; 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.KList;
import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.collection.KMap;
import com.volmit.iris.util.mantle.Mantle; import com.volmit.iris.util.mantle.Mantle;
@@ -113,7 +114,11 @@ public interface INMSBinding {
Entity spawnEntity(Location location, EntityType type, CreatureSpawnEvent.SpawnReason reason); 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(); void injectBukkit();
} }
@@ -107,7 +107,7 @@ public class NMSBinding1X implements INMSBinding {
} }
@Override @Override
public boolean loadDatapack(File datapackFolder) { public boolean loadDatapack(File datapackFolder, boolean replace) {
return false; return false;
} }
@@ -19,6 +19,7 @@
package com.volmit.iris.core.tools; package com.volmit.iris.core.tools;
import com.volmit.iris.core.loader.IrisData; 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.object.*;
import com.volmit.iris.engine.platform.BukkitChunkGenerator; import com.volmit.iris.engine.platform.BukkitChunkGenerator;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@@ -84,6 +85,9 @@ public class IrisWorldCreator {
? dim.getLoader().getDataFolder() : ? dim.getLoader().getDataFolder() :
new File(w.worldFolder(), "iris/pack"), dimensionName, smartVanillaHeight); 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) return new WorldCreator(name)
.environment(findEnvironment()) .environment(findEnvironment())
@@ -13,13 +13,19 @@ import java.util.IdentityHashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Vector; import java.util.Vector;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import com.google.common.base.Preconditions; 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.datafixers.util.Pair;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps; import com.mojang.serialization.JsonOps;
import com.mojang.serialization.Lifecycle; import com.mojang.serialization.Lifecycle;
import com.volmit.iris.engine.object.IrisDimension;
import com.volmit.iris.util.format.C; import com.volmit.iris.util.format.C;
import com.volmit.iris.util.io.IO; import com.volmit.iris.util.io.IO;
import it.unimi.dsi.fastutil.objects.Reference2IntMap; import it.unimi.dsi.fastutil.objects.Reference2IntMap;
@@ -29,9 +35,14 @@ import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
import net.bytebuddy.matcher.ElementMatchers; import net.bytebuddy.matcher.ElementMatchers;
import net.minecraft.core.MappedRegistry; import net.minecraft.core.MappedRegistry;
import net.minecraft.resources.ResourceKey; 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.util.GsonHelper;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.dimension.DimensionType; 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.*;
import org.bukkit.block.Biome; import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData; 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.Dolphin;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.generator.BiomeProvider;
import org.bukkit.generator.ChunkGenerator; import org.bukkit.generator.ChunkGenerator;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -551,25 +563,34 @@ public class NMSBinding implements INMSBinding {
} }
@Override @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"); var data = new File(folder, "iris/data");
if (!data.exists() || !data.isDirectory()) return false; if (!data.exists() || !data.isDirectory()) return false;
FilenameFilter jsonFilter = (dir, name) -> new File(dir, name).isFile() && name.toLowerCase().endsWith(".json"); 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()); var files = data.listFiles((dir, name) -> new File(dir, name).isDirectory());
if (files == null) return false; if (files == null) return false;
for (File file : files) { for (File file : files) {
@@ -579,7 +600,8 @@ public class NMSBinding implements INMSBinding {
if (biomeFiles == null) continue; if (biomeFiles == null) continue;
for (File biomeFile : biomeFiles) { for (File biomeFile : biomeFiles) {
try { 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) { } catch (Throwable e) {
Iris.error("Failed to register biome " + file.getName() + ":" + biomeFile.getName()); Iris.error("Failed to register biome " + file.getName() + ":" + biomeFile.getName());
e.printStackTrace(); e.printStackTrace();
@@ -594,12 +616,25 @@ public class NMSBinding implements INMSBinding {
return new ResourceLocation(namespace, name.substring(0, name.lastIndexOf('.'))); return new ResourceLocation(namespace, name.substring(0, name.lastIndexOf('.')));
} }
private void registerBiome(String namespace, File file) throws Throwable { private <T> Optional<T> decode(Codec<T> codec, String json) {
var rawRegistry = registry().registry(Registry.BIOME_REGISTRY).orElse(null); return codec.decode(JsonOps.INSTANCE, GsonHelper.parse(json)).get().left().map(Pair::getFirst);
var key = ResourceKey.create(Registry.BIOME_REGISTRY, from(namespace, file)); }
if (!(rawRegistry instanceof MappedRegistry<net.minecraft.world.level.biome.Biome> registry))
throw new IllegalStateException("The Biome Registry is not a mapped Registry!"); private <T> Optional<JsonElement> encode(Codec<T> codec, T value) {
if (registry.containsKey(key)) return; return codec.encode(value, JsonOps.INSTANCE, new JsonObject()).result();
}
private <T> boolean register(ResourceKey<Registry<T>> 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 field = getField(MappedRegistry.class, boolean.class);
field.setAccessible(true); field.setAccessible(true);
boolean frozen = field.getBoolean(registry); boolean frozen = field.getBoolean(registry);
@@ -618,29 +653,30 @@ public class NMSBinding implements INMSBinding {
} }
try { try {
var biome = net.minecraft.world.level.biome.Biome.CODEC.decode(JsonOps.INSTANCE, GsonHelper.parse(IO.readAll(file))) registry.createIntrusiveHolder(value);
.get().left().map(Pair::getFirst).map(Holder::value).orElse(null); registry.register(key, value, Lifecycle.stable());
if (biome == null) return true;
throw new IllegalStateException("Failed to decode biome " + file.getName());
registry.createIntrusiveHolder(biome);
registry.register(key, biome, Lifecycle.stable());
} finally { } finally {
field.setBoolean(registry, frozen); field.setBoolean(registry, frozen);
if (holders) { if (holders) {
holdersField.set(registry, null); holdersField.set(registry, null);
} }
} }
} catch (Throwable e) {
throw new IllegalStateException(e);
}
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private void modifyDimension(File file) throws Throwable { private <T> boolean replace(ResourceKey<Registry<T>> registryKey, ResourceLocation location, T value) {
var key = ResourceKey.create(Registry.DIMENSION_TYPE_REGISTRY, from("minecraft", file)); Preconditions.checkArgument(registryKey != null, "The registryKey cannot be null!");
var rawRegistry = registry().registry(Registry.DIMENSION_TYPE_REGISTRY).orElse(null); Preconditions.checkArgument(location != null, "The location cannot be null!");
if (!(rawRegistry instanceof MappedRegistry<DimensionType> registry)) Preconditions.checkArgument(value != null, "The value cannot be null!");
throw new IllegalStateException("The Dimension Registry is not a mapped Registry!"); var registry = registry(registryKey);
var key = ResourceKey.create(registryKey, location);
var holder = registry.getHolder(key).orElseThrow(() -> new IllegalStateException("Unknown dimension type: " + key)); try {
var holder = registry.getHolder(key).orElse(null);
if (holder == null) return false;
var oldValue = holder.value(); var oldValue = holder.value();
Field valueField = getField(Holder.Reference.class, "T"); Field valueField = getField(Holder.Reference.class, "T");
valueField.setAccessible(true); valueField.setAccessible(true);
@@ -650,19 +686,25 @@ public class NMSBinding implements INMSBinding {
byValueField.setAccessible(true); byValueField.setAccessible(true);
Field lifecyclesField = getField(MappedRegistry.class, buildType(Map.class, "T", Lifecycle.class.getName())); Field lifecyclesField = getField(MappedRegistry.class, buildType(Map.class, "T", Lifecycle.class.getName()));
lifecyclesField.setAccessible(true); lifecyclesField.setAccessible(true);
var toId = (Reference2IntMap<DimensionType>) toIdField.get(registry); var toId = (Reference2IntMap<T>) toIdField.get(registry);
var byValue = (Map<DimensionType, Holder.Reference<DimensionType>>) byValueField.get(registry); var byValue = (Map<T, Holder.Reference<T>>) byValueField.get(registry);
var lifecycles = (Map<DimensionType, Lifecycle>) lifecyclesField.get(registry); var lifecycles = (Map<T, Lifecycle>) lifecyclesField.get(registry);
var newValue = DimensionType.CODEC.decode(JsonOps.INSTANCE, GsonHelper.parse(IO.readAll(file))) valueField.set(holder, value);
.get().left().map(Pair::getFirst).map(Holder::value).orElse(null); toId.put(value, toId.removeInt(oldValue));
if (newValue == null) byValue.put(value, byValue.remove(oldValue));
throw new IllegalArgumentException("Failed to parse dimension type " + key.location() + " from " + file); lifecycles.put(value, lifecycles.remove(oldValue));
return true;
} catch (Throwable e) {
throw new IllegalStateException(e);
}
}
valueField.set(holder, newValue); private <T> MappedRegistry<T> registry(ResourceKey<Registry<T>> registryKey) {
toId.put(newValue, toId.removeInt(oldValue)); var rawRegistry = registry().registry(registryKey).orElse(null);
byValue.put(newValue, byValue.remove(oldValue)); if (!(rawRegistry instanceof MappedRegistry<T> registry))
lifecycles.put(newValue, lifecycles.remove(oldValue)); throw new IllegalStateException("The Registry is not a mapped Registry!");
return registry;
} }
private static String buildType(Class<?> clazz, String... parameterTypes) { 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)))) .visit(Advice.to(WorldCreatorAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(String.class))))
.make() .make()
.load(WorldCreator.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); .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!"); Iris.info("Injected Bukkit Successfully!");
} catch (Exception e) { } catch (Exception e) {
Iris.info(C.RED + "Failed to Inject Bukkit!"); 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<Level> 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<DimensionType> typeKey = ResourceKey.create(Registry.DIMENSION_TYPE_REGISTRY, new ResourceLocation("iris", key.location().getPath()));
RegistryAccess registryAccess = server.registryAccess();
Registry<DimensionType> registry = registryAccess.registry(Registry.DIMENSION_TYPE_REGISTRY).orElse(null);
if (registry == null) {
logger.warn("Unable to find registry for dimension type {}", typeKey);
return;
}
Holder<DimensionType> 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 { private static class WorldCreatorAdvice {
@Advice.OnMethodEnter @Advice.OnMethodEnter
static void enter(@Advice.Argument(0) String name) { static void enter(@Advice.Argument(0) String name) {
@@ -13,13 +13,19 @@ import java.util.IdentityHashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Vector; import java.util.Vector;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import com.google.common.base.Preconditions; 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.datafixers.util.Pair;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps; import com.mojang.serialization.JsonOps;
import com.mojang.serialization.Lifecycle; import com.mojang.serialization.Lifecycle;
import com.volmit.iris.engine.object.IrisDimension;
import com.volmit.iris.util.format.C; import com.volmit.iris.util.format.C;
import com.volmit.iris.util.io.IO; import com.volmit.iris.util.io.IO;
import it.unimi.dsi.fastutil.objects.Reference2IntMap; import it.unimi.dsi.fastutil.objects.Reference2IntMap;
@@ -29,9 +35,14 @@ import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
import net.bytebuddy.matcher.ElementMatchers; import net.bytebuddy.matcher.ElementMatchers;
import net.minecraft.core.MappedRegistry; import net.minecraft.core.MappedRegistry;
import net.minecraft.resources.ResourceKey; 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.util.GsonHelper;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.dimension.DimensionType; 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.*;
import org.bukkit.block.Biome; import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData; 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.Dolphin;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.generator.BiomeProvider;
import org.bukkit.generator.ChunkGenerator; import org.bukkit.generator.ChunkGenerator;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -553,25 +565,34 @@ public class NMSBinding implements INMSBinding {
} }
@Override @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"); var data = new File(folder, "iris/data");
if (!data.exists() || !data.isDirectory()) return false; if (!data.exists() || !data.isDirectory()) return false;
FilenameFilter jsonFilter = (dir, name) -> new File(dir, name).isFile() && name.toLowerCase().endsWith(".json"); 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()); var files = data.listFiles((dir, name) -> new File(dir, name).isDirectory());
if (files == null) return false; if (files == null) return false;
for (File file : files) { for (File file : files) {
@@ -581,7 +602,8 @@ public class NMSBinding implements INMSBinding {
if (biomeFiles == null) continue; if (biomeFiles == null) continue;
for (File biomeFile : biomeFiles) { for (File biomeFile : biomeFiles) {
try { 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) { } catch (Throwable e) {
Iris.error("Failed to register biome " + file.getName() + ":" + biomeFile.getName()); Iris.error("Failed to register biome " + file.getName() + ":" + biomeFile.getName());
e.printStackTrace(); e.printStackTrace();
@@ -596,12 +618,25 @@ public class NMSBinding implements INMSBinding {
return new ResourceLocation(namespace, name.substring(0, name.lastIndexOf('.'))); return new ResourceLocation(namespace, name.substring(0, name.lastIndexOf('.')));
} }
private void registerBiome(String namespace, File file) throws Throwable { private <T> Optional<T> decode(Codec<T> codec, String json) {
var rawRegistry = registry().registry(Registries.BIOME).orElse(null); return codec.decode(JsonOps.INSTANCE, GsonHelper.parse(json)).get().left().map(Pair::getFirst);
var key = ResourceKey.create(Registries.BIOME, from(namespace, file)); }
if (!(rawRegistry instanceof MappedRegistry<net.minecraft.world.level.biome.Biome> registry))
throw new IllegalStateException("The Biome Registry is not a mapped Registry!"); private <T> Optional<JsonElement> encode(Codec<T> codec, T value) {
if (registry.containsKey(key)) return; return codec.encode(value, JsonOps.INSTANCE, new JsonObject()).result();
}
private <T> boolean register(ResourceKey<Registry<T>> 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 field = getField(MappedRegistry.class, boolean.class);
field.setAccessible(true); field.setAccessible(true);
boolean frozen = field.getBoolean(registry); boolean frozen = field.getBoolean(registry);
@@ -620,29 +655,30 @@ public class NMSBinding implements INMSBinding {
} }
try { try {
var biome = net.minecraft.world.level.biome.Biome.CODEC.decode(JsonOps.INSTANCE, GsonHelper.parse(IO.readAll(file))) registry.createIntrusiveHolder(value);
.get().left().map(Pair::getFirst).map(Holder::value).orElse(null); registry.register(key, value, Lifecycle.stable());
if (biome == null) return true;
throw new IllegalStateException("Failed to decode biome " + file.getName());
registry.createIntrusiveHolder(biome);
registry.register(key, biome, Lifecycle.stable());
} finally { } finally {
field.setBoolean(registry, frozen); field.setBoolean(registry, frozen);
if (holders) { if (holders) {
holdersField.set(registry, null); holdersField.set(registry, null);
} }
} }
} catch (Throwable e) {
throw new IllegalStateException(e);
}
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private void modifyDimension(File file) throws Throwable { private <T> boolean replace(ResourceKey<Registry<T>> registryKey, ResourceLocation location, T value) {
var key = ResourceKey.create(Registries.DIMENSION_TYPE, from("minecraft", file)); Preconditions.checkArgument(registryKey != null, "The registryKey cannot be null!");
var rawRegistry = registry().registry(Registries.DIMENSION_TYPE).orElse(null); Preconditions.checkArgument(location != null, "The location cannot be null!");
if (!(rawRegistry instanceof MappedRegistry<DimensionType> registry)) Preconditions.checkArgument(value != null, "The value cannot be null!");
throw new IllegalStateException("The Dimension Registry is not a mapped Registry!"); var registry = registry(registryKey);
var key = ResourceKey.create(registryKey, location);
var holder = registry.getHolder(key).orElseThrow(() -> new IllegalStateException("Unknown dimension type: " + key)); try {
var holder = registry.getHolder(key).orElse(null);
if (holder == null) return false;
var oldValue = holder.value(); var oldValue = holder.value();
Field valueField = getField(Holder.Reference.class, "T"); Field valueField = getField(Holder.Reference.class, "T");
valueField.setAccessible(true); valueField.setAccessible(true);
@@ -652,19 +688,25 @@ public class NMSBinding implements INMSBinding {
byValueField.setAccessible(true); byValueField.setAccessible(true);
Field lifecyclesField = getField(MappedRegistry.class, buildType(Map.class, "T", Lifecycle.class.getName())); Field lifecyclesField = getField(MappedRegistry.class, buildType(Map.class, "T", Lifecycle.class.getName()));
lifecyclesField.setAccessible(true); lifecyclesField.setAccessible(true);
var toId = (Reference2IntMap<DimensionType>) toIdField.get(registry); var toId = (Reference2IntMap<T>) toIdField.get(registry);
var byValue = (Map<DimensionType, Holder.Reference<DimensionType>>) byValueField.get(registry); var byValue = (Map<T, Holder.Reference<T>>) byValueField.get(registry);
var lifecycles = (Map<DimensionType, Lifecycle>) lifecyclesField.get(registry); var lifecycles = (Map<T, Lifecycle>) lifecyclesField.get(registry);
var newValue = DimensionType.CODEC.decode(JsonOps.INSTANCE, GsonHelper.parse(IO.readAll(file))) valueField.set(holder, value);
.get().left().map(Pair::getFirst).map(Holder::value).orElse(null); toId.put(value, toId.removeInt(oldValue));
if (newValue == null) byValue.put(value, byValue.remove(oldValue));
throw new IllegalArgumentException("Failed to parse dimension type " + key.location() + " from " + file); lifecycles.put(value, lifecycles.remove(oldValue));
return true;
} catch (Throwable e) {
throw new IllegalStateException(e);
}
}
valueField.set(holder, newValue); private <T> MappedRegistry<T> registry(ResourceKey<Registry<T>> registryKey) {
toId.put(newValue, toId.removeInt(oldValue)); var rawRegistry = registry().registry(registryKey).orElse(null);
byValue.put(newValue, byValue.remove(oldValue)); if (!(rawRegistry instanceof MappedRegistry<T> registry))
lifecycles.put(newValue, lifecycles.remove(oldValue)); throw new IllegalStateException("The Registry is not a mapped Registry!");
return registry;
} }
private static String buildType(Class<?> clazz, String... parameterTypes) { 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)))) .visit(Advice.to(WorldCreatorAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(String.class))))
.make() .make()
.load(WorldCreator.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); .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!"); Iris.info("Injected Bukkit Successfully!");
} catch (Exception e) { } catch (Exception e) {
Iris.info(C.RED + "Failed to Inject Bukkit!"); 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<Level> 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<DimensionType> typeKey = ResourceKey.create(Registries.DIMENSION_TYPE, new ResourceLocation("iris", key.location().getPath()));
RegistryAccess registryAccess = server.registryAccess();
Registry<DimensionType> registry = registryAccess.registry(Registries.DIMENSION_TYPE).orElse(null);
if (registry == null) {
logger.warn("Unable to find registry for dimension type {}", typeKey);
return;
}
Holder<DimensionType> 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 { private static class WorldCreatorAdvice {
@Advice.OnMethodEnter @Advice.OnMethodEnter
static void enter(@Advice.Argument(0) String name) { static void enter(@Advice.Argument(0) String name) {
@@ -13,13 +13,19 @@ import java.util.IdentityHashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Vector; import java.util.Vector;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import com.google.common.base.Preconditions; 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.datafixers.util.Pair;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps; import com.mojang.serialization.JsonOps;
import com.mojang.serialization.Lifecycle; import com.mojang.serialization.Lifecycle;
import com.volmit.iris.engine.object.IrisDimension;
import com.volmit.iris.util.format.C; import com.volmit.iris.util.format.C;
import com.volmit.iris.util.io.IO; import com.volmit.iris.util.io.IO;
import it.unimi.dsi.fastutil.objects.Reference2IntMap; import it.unimi.dsi.fastutil.objects.Reference2IntMap;
@@ -29,9 +35,14 @@ import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
import net.bytebuddy.matcher.ElementMatchers; import net.bytebuddy.matcher.ElementMatchers;
import net.minecraft.core.MappedRegistry; import net.minecraft.core.MappedRegistry;
import net.minecraft.resources.ResourceKey; 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.util.GsonHelper;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.dimension.DimensionType; 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.*;
import org.bukkit.block.Biome; import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData; 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.Dolphin;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.generator.BiomeProvider;
import org.bukkit.generator.ChunkGenerator; import org.bukkit.generator.ChunkGenerator;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -557,25 +569,34 @@ public class NMSBinding implements INMSBinding {
} }
@Override @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"); var data = new File(folder, "iris/data");
if (!data.exists() || !data.isDirectory()) return false; if (!data.exists() || !data.isDirectory()) return false;
FilenameFilter jsonFilter = (dir, name) -> new File(dir, name).isFile() && name.toLowerCase().endsWith(".json"); 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()); var files = data.listFiles((dir, name) -> new File(dir, name).isDirectory());
if (files == null) return false; if (files == null) return false;
for (File file : files) { for (File file : files) {
@@ -585,7 +606,8 @@ public class NMSBinding implements INMSBinding {
if (biomeFiles == null) continue; if (biomeFiles == null) continue;
for (File biomeFile : biomeFiles) { for (File biomeFile : biomeFiles) {
try { 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) { } catch (Throwable e) {
Iris.error("Failed to register biome " + file.getName() + ":" + biomeFile.getName()); Iris.error("Failed to register biome " + file.getName() + ":" + biomeFile.getName());
e.printStackTrace(); e.printStackTrace();
@@ -600,12 +622,25 @@ public class NMSBinding implements INMSBinding {
return new ResourceLocation(namespace, name.substring(0, name.lastIndexOf('.'))); return new ResourceLocation(namespace, name.substring(0, name.lastIndexOf('.')));
} }
private void registerBiome(String namespace, File file) throws Throwable { private <T> Optional<T> decode(Codec<T> codec, String json) {
var rawRegistry = registry().registry(Registries.BIOME).orElse(null); return codec.decode(JsonOps.INSTANCE, GsonHelper.parse(json)).get().left().map(Pair::getFirst);
var key = ResourceKey.create(Registries.BIOME, from(namespace, file)); }
if (!(rawRegistry instanceof MappedRegistry<net.minecraft.world.level.biome.Biome> registry))
throw new IllegalStateException("The Biome Registry is not a mapped Registry!"); private <T> Optional<JsonElement> encode(Codec<T> codec, T value) {
if (registry.containsKey(key)) return; return codec.encode(value, JsonOps.INSTANCE, new JsonObject()).result();
}
private <T> boolean register(ResourceKey<Registry<T>> 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 field = getField(MappedRegistry.class, boolean.class);
field.setAccessible(true); field.setAccessible(true);
boolean frozen = field.getBoolean(registry); boolean frozen = field.getBoolean(registry);
@@ -624,29 +659,30 @@ public class NMSBinding implements INMSBinding {
} }
try { try {
var biome = net.minecraft.world.level.biome.Biome.CODEC.decode(JsonOps.INSTANCE, GsonHelper.parse(IO.readAll(file))) registry.createIntrusiveHolder(value);
.get().left().map(Pair::getFirst).map(Holder::value).orElse(null); registry.register(key, value, Lifecycle.stable());
if (biome == null) return true;
throw new IllegalStateException("Failed to decode biome " + file.getName());
registry.createIntrusiveHolder(biome);
registry.register(key, biome, Lifecycle.stable());
} finally { } finally {
field.setBoolean(registry, frozen); field.setBoolean(registry, frozen);
if (holders) { if (holders) {
holdersField.set(registry, null); holdersField.set(registry, null);
} }
} }
} catch (Throwable e) {
throw new IllegalStateException(e);
}
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private void modifyDimension(File file) throws Throwable { private <T> boolean replace(ResourceKey<Registry<T>> registryKey, ResourceLocation location, T value) {
var key = ResourceKey.create(Registries.DIMENSION_TYPE, from("minecraft", file)); Preconditions.checkArgument(registryKey != null, "The registryKey cannot be null!");
var rawRegistry = registry().registry(Registries.DIMENSION_TYPE).orElse(null); Preconditions.checkArgument(location != null, "The location cannot be null!");
if (!(rawRegistry instanceof MappedRegistry<DimensionType> registry)) Preconditions.checkArgument(value != null, "The value cannot be null!");
throw new IllegalStateException("The Dimension Registry is not a mapped Registry!"); var registry = registry(registryKey);
var key = ResourceKey.create(registryKey, location);
var holder = registry.getHolder(key).orElseThrow(() -> new IllegalStateException("Unknown dimension type: " + key)); try {
var holder = registry.getHolder(key).orElse(null);
if (holder == null) return false;
var oldValue = holder.value(); var oldValue = holder.value();
Field valueField = getField(Holder.Reference.class, "T"); Field valueField = getField(Holder.Reference.class, "T");
valueField.setAccessible(true); valueField.setAccessible(true);
@@ -656,19 +692,25 @@ public class NMSBinding implements INMSBinding {
byValueField.setAccessible(true); byValueField.setAccessible(true);
Field lifecyclesField = getField(MappedRegistry.class, buildType(Map.class, "T", Lifecycle.class.getName())); Field lifecyclesField = getField(MappedRegistry.class, buildType(Map.class, "T", Lifecycle.class.getName()));
lifecyclesField.setAccessible(true); lifecyclesField.setAccessible(true);
var toId = (Reference2IntMap<DimensionType>) toIdField.get(registry); var toId = (Reference2IntMap<T>) toIdField.get(registry);
var byValue = (Map<DimensionType, Holder.Reference<DimensionType>>) byValueField.get(registry); var byValue = (Map<T, Holder.Reference<T>>) byValueField.get(registry);
var lifecycles = (Map<DimensionType, Lifecycle>) lifecyclesField.get(registry); var lifecycles = (Map<T, Lifecycle>) lifecyclesField.get(registry);
var newValue = DimensionType.CODEC.decode(JsonOps.INSTANCE, GsonHelper.parse(IO.readAll(file))) valueField.set(holder, value);
.get().left().map(Pair::getFirst).map(Holder::value).orElse(null); toId.put(value, toId.removeInt(oldValue));
if (newValue == null) byValue.put(value, byValue.remove(oldValue));
throw new IllegalArgumentException("Failed to parse dimension type " + key.location() + " from " + file); lifecycles.put(value, lifecycles.remove(oldValue));
return true;
} catch (Throwable e) {
throw new IllegalStateException(e);
}
}
valueField.set(holder, newValue); private <T> MappedRegistry<T> registry(ResourceKey<Registry<T>> registryKey) {
toId.put(newValue, toId.removeInt(oldValue)); var rawRegistry = registry().registry(registryKey).orElse(null);
byValue.put(newValue, byValue.remove(oldValue)); if (!(rawRegistry instanceof MappedRegistry<T> registry))
lifecycles.put(newValue, lifecycles.remove(oldValue)); throw new IllegalStateException("The Registry is not a mapped Registry!");
return registry;
} }
private static String buildType(Class<?> clazz, String... parameterTypes) { 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)))) .visit(Advice.to(WorldCreatorAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(String.class))))
.make() .make()
.load(WorldCreator.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); .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!"); Iris.info("Injected Bukkit Successfully!");
} catch (Exception e) { } catch (Exception e) {
Iris.info(C.RED + "Failed to Inject Bukkit!"); 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<Level> 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<DimensionType> typeKey = ResourceKey.create(Registries.DIMENSION_TYPE, new ResourceLocation("iris", key.location().getPath()));
RegistryAccess registryAccess = server.registryAccess();
Registry<DimensionType> registry = registryAccess.registry(Registries.DIMENSION_TYPE).orElse(null);
if (registry == null) {
logger.warn("Unable to find registry for dimension type {}", typeKey);
return;
}
Holder<DimensionType> 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 { private static class WorldCreatorAdvice {
@Advice.OnMethodEnter @Advice.OnMethodEnter
static void enter(@Advice.Argument(0) String name) { static void enter(@Advice.Argument(0) String name) {
@@ -1,14 +1,18 @@
package com.volmit.iris.core.nms.v1_20_R1; package com.volmit.iris.core.nms.v1_20_R1;
import com.google.common.base.Preconditions; 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.brigadier.exceptions.CommandSyntaxException;
import com.mojang.datafixers.util.Pair; import com.mojang.datafixers.util.Pair;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps; import com.mojang.serialization.JsonOps;
import com.mojang.serialization.Lifecycle; import com.mojang.serialization.Lifecycle;
import com.volmit.iris.Iris; import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.INMSBinding; import com.volmit.iris.core.nms.INMSBinding;
import com.volmit.iris.engine.data.cache.AtomicCache; import com.volmit.iris.engine.data.cache.AtomicCache;
import com.volmit.iris.engine.framework.Engine; 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.KList;
import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.collection.KMap;
import com.volmit.iris.util.format.C; import com.volmit.iris.util.format.C;
@@ -38,8 +42,11 @@ import net.minecraft.nbt.NbtIo;
import net.minecraft.nbt.TagParser; import net.minecraft.nbt.TagParser;
import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.progress.ChunkProgressListener;
import net.minecraft.util.GsonHelper; import net.minecraft.util.GsonHelper;
import net.minecraft.world.RandomSequences;
import net.minecraft.world.entity.EntityDimensions; import net.minecraft.world.entity.EntityDimensions;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.biome.BiomeSource; 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.ChunkStatus;
import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.dimension.DimensionType; 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.*;
import org.bukkit.block.Biome; import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData; import org.bukkit.block.data.BlockData;
@@ -66,6 +76,7 @@ import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType; import org.bukkit.entity.EntityType;
import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.entity.EntityType; import org.bukkit.entity.EntityType;
import org.bukkit.generator.BiomeProvider;
import org.bukkit.generator.ChunkGenerator; import org.bukkit.generator.ChunkGenerator;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -84,7 +95,9 @@ import java.util.IdentityHashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Vector; import java.util.Vector;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
public class NMSBinding implements INMSBinding { public class NMSBinding implements INMSBinding {
@@ -543,25 +556,34 @@ public class NMSBinding implements INMSBinding {
} }
@Override @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"); var data = new File(folder, "iris/data");
if (!data.exists() || !data.isDirectory()) return false; if (!data.exists() || !data.isDirectory()) return false;
FilenameFilter jsonFilter = (dir, name) -> new File(dir, name).isFile() && name.toLowerCase().endsWith(".json"); 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()); var files = data.listFiles((dir, name) -> new File(dir, name).isDirectory());
if (files == null) return false; if (files == null) return false;
for (File file : files) { for (File file : files) {
@@ -571,7 +593,8 @@ public class NMSBinding implements INMSBinding {
if (biomeFiles == null) continue; if (biomeFiles == null) continue;
for (File biomeFile : biomeFiles) { for (File biomeFile : biomeFiles) {
try { 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) { } catch (Throwable e) {
Iris.error("Failed to register biome " + file.getName() + ":" + biomeFile.getName()); Iris.error("Failed to register biome " + file.getName() + ":" + biomeFile.getName());
e.printStackTrace(); e.printStackTrace();
@@ -586,12 +609,25 @@ public class NMSBinding implements INMSBinding {
return new ResourceLocation(namespace, name.substring(0, name.lastIndexOf('.'))); return new ResourceLocation(namespace, name.substring(0, name.lastIndexOf('.')));
} }
private void registerBiome(String namespace, File file) throws Throwable { private <T> Optional<T> decode(Codec<T> codec, String json) {
var rawRegistry = registry().registry(Registries.BIOME).orElse(null); return codec.decode(JsonOps.INSTANCE, GsonHelper.parse(json)).get().left().map(Pair::getFirst);
var key = ResourceKey.create(Registries.BIOME, from(namespace, file)); }
if (!(rawRegistry instanceof MappedRegistry<net.minecraft.world.level.biome.Biome> registry))
throw new IllegalStateException("The Biome Registry is not a mapped Registry!"); private <T> Optional<JsonElement> encode(Codec<T> codec, T value) {
if (registry.containsKey(key)) return; return codec.encode(value, JsonOps.INSTANCE, new JsonObject()).result();
}
private <T> boolean register(ResourceKey<Registry<T>> 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 field = getField(MappedRegistry.class, boolean.class);
field.setAccessible(true); field.setAccessible(true);
boolean frozen = field.getBoolean(registry); boolean frozen = field.getBoolean(registry);
@@ -610,29 +646,30 @@ public class NMSBinding implements INMSBinding {
} }
try { try {
var biome = net.minecraft.world.level.biome.Biome.CODEC.decode(JsonOps.INSTANCE, GsonHelper.parse(IO.readAll(file))) registry.createIntrusiveHolder(value);
.get().left().map(Pair::getFirst).map(Holder::value).orElse(null); registry.register(key, value, Lifecycle.stable());
if (biome == null) return true;
throw new IllegalStateException("Failed to decode biome " + file.getName());
registry.createIntrusiveHolder(biome);
registry.register(key, biome, Lifecycle.stable());
} finally { } finally {
field.setBoolean(registry, frozen); field.setBoolean(registry, frozen);
if (holders) { if (holders) {
holdersField.set(registry, null); holdersField.set(registry, null);
} }
} }
} catch (Throwable e) {
throw new IllegalStateException(e);
}
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private void modifyDimension(File file) throws Throwable { private <T> boolean replace(ResourceKey<Registry<T>> registryKey, ResourceLocation location, T value) {
var key = ResourceKey.create(Registries.DIMENSION_TYPE, from("minecraft", file)); Preconditions.checkArgument(registryKey != null, "The registryKey cannot be null!");
var rawRegistry = registry().registry(Registries.DIMENSION_TYPE).orElse(null); Preconditions.checkArgument(location != null, "The location cannot be null!");
if (!(rawRegistry instanceof MappedRegistry<DimensionType> registry)) Preconditions.checkArgument(value != null, "The value cannot be null!");
throw new IllegalStateException("The Dimension Registry is not a mapped Registry!"); var registry = registry(registryKey);
var key = ResourceKey.create(registryKey, location);
var holder = registry.getHolder(key).orElseThrow(() -> new IllegalStateException("Unknown dimension type: " + key)); try {
var holder = registry.getHolder(key).orElse(null);
if (holder == null) return false;
var oldValue = holder.value(); var oldValue = holder.value();
Field valueField = getField(Holder.Reference.class, "T"); Field valueField = getField(Holder.Reference.class, "T");
valueField.setAccessible(true); valueField.setAccessible(true);
@@ -642,19 +679,25 @@ public class NMSBinding implements INMSBinding {
byValueField.setAccessible(true); byValueField.setAccessible(true);
Field lifecyclesField = getField(MappedRegistry.class, buildType(Map.class, "T", Lifecycle.class.getName())); Field lifecyclesField = getField(MappedRegistry.class, buildType(Map.class, "T", Lifecycle.class.getName()));
lifecyclesField.setAccessible(true); lifecyclesField.setAccessible(true);
var toId = (Reference2IntMap<DimensionType>) toIdField.get(registry); var toId = (Reference2IntMap<T>) toIdField.get(registry);
var byValue = (Map<DimensionType, Holder.Reference<DimensionType>>) byValueField.get(registry); var byValue = (Map<T, Holder.Reference<T>>) byValueField.get(registry);
var lifecycles = (Map<DimensionType, Lifecycle>) lifecyclesField.get(registry); var lifecycles = (Map<T, Lifecycle>) lifecyclesField.get(registry);
var newValue = DimensionType.CODEC.decode(JsonOps.INSTANCE, GsonHelper.parse(IO.readAll(file))) valueField.set(holder, value);
.get().left().map(Pair::getFirst).map(Holder::value).orElse(null); toId.put(value, toId.removeInt(oldValue));
if (newValue == null) byValue.put(value, byValue.remove(oldValue));
throw new IllegalArgumentException("Failed to parse dimension type " + key.location() + " from " + file); lifecycles.put(value, lifecycles.remove(oldValue));
return true;
} catch (Throwable e) {
throw new IllegalStateException(e);
}
}
valueField.set(holder, newValue); private <T> MappedRegistry<T> registry(ResourceKey<Registry<T>> registryKey) {
toId.put(newValue, toId.removeInt(oldValue)); var rawRegistry = registry().registry(registryKey).orElse(null);
byValue.put(newValue, byValue.remove(oldValue)); if (!(rawRegistry instanceof MappedRegistry<T> registry))
lifecycles.put(newValue, lifecycles.remove(oldValue)); throw new IllegalStateException("The Registry is not a mapped Registry!");
return registry;
} }
private static String buildType(Class<?> clazz, String... parameterTypes) { 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)))) .visit(Advice.to(WorldCreatorAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(String.class))))
.make() .make()
.load(WorldCreator.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); .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!"); Iris.info("Injected Bukkit Successfully!");
} catch (Exception e) { } catch (Exception e) {
Iris.info(C.RED + "Failed to Inject Bukkit!"); 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<Level> 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<DimensionType> typeKey = ResourceKey.create(Registries.DIMENSION_TYPE, new ResourceLocation("iris", key.location().getPath()));
RegistryAccess registryAccess = server.registryAccess();
Registry<DimensionType> registry = registryAccess.registry(Registries.DIMENSION_TYPE).orElse(null);
if (registry == null) {
logger.warn("Unable to find registry for dimension type {}", typeKey);
return;
}
Holder<DimensionType> 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 { private static class WorldCreatorAdvice {
@Advice.OnMethodEnter @Advice.OnMethodEnter
static void enter(@Advice.Argument(0) String name) { static void enter(@Advice.Argument(0) String name) {
@@ -13,13 +13,19 @@ import java.util.IdentityHashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Vector; import java.util.Vector;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import com.google.common.base.Preconditions; 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.datafixers.util.Pair;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps; import com.mojang.serialization.JsonOps;
import com.mojang.serialization.Lifecycle; import com.mojang.serialization.Lifecycle;
import com.volmit.iris.engine.object.IrisDimension;
import com.volmit.iris.util.format.C; import com.volmit.iris.util.format.C;
import com.volmit.iris.util.io.IO; import com.volmit.iris.util.io.IO;
import it.unimi.dsi.fastutil.objects.Reference2IntMap; 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.dynamic.loading.ClassReloadingStrategy;
import net.bytebuddy.matcher.ElementMatchers; import net.bytebuddy.matcher.ElementMatchers;
import net.minecraft.core.MappedRegistry; 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.GsonHelper;
import net.minecraft.world.RandomSequences;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.dimension.DimensionType; 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.*;
import org.bukkit.block.Biome; import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData; 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.Dolphin;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.generator.BiomeProvider;
import org.bukkit.generator.ChunkGenerator; import org.bukkit.generator.ChunkGenerator;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -553,25 +566,34 @@ public class NMSBinding implements INMSBinding {
} }
@Override @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"); var data = new File(folder, "iris/data");
if (!data.exists() || !data.isDirectory()) return false; if (!data.exists() || !data.isDirectory()) return false;
FilenameFilter jsonFilter = (dir, name) -> new File(dir, name).isFile() && name.toLowerCase().endsWith(".json"); 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()); var files = data.listFiles((dir, name) -> new File(dir, name).isDirectory());
if (files == null) return false; if (files == null) return false;
for (File file : files) { for (File file : files) {
@@ -581,7 +603,8 @@ public class NMSBinding implements INMSBinding {
if (biomeFiles == null) continue; if (biomeFiles == null) continue;
for (File biomeFile : biomeFiles) { for (File biomeFile : biomeFiles) {
try { 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) { } catch (Throwable e) {
Iris.error("Failed to register biome " + file.getName() + ":" + biomeFile.getName()); Iris.error("Failed to register biome " + file.getName() + ":" + biomeFile.getName());
e.printStackTrace(); e.printStackTrace();
@@ -596,12 +619,25 @@ public class NMSBinding implements INMSBinding {
return new ResourceLocation(namespace, name.substring(0, name.lastIndexOf('.'))); return new ResourceLocation(namespace, name.substring(0, name.lastIndexOf('.')));
} }
private void registerBiome(String namespace, File file) throws Throwable { private <T> Optional<T> decode(Codec<T> codec, String json) {
var rawRegistry = registry().registry(Registries.BIOME).orElse(null); return codec.decode(JsonOps.INSTANCE, GsonHelper.parse(json)).get().left().map(Pair::getFirst);
var key = ResourceKey.create(Registries.BIOME, from(namespace, file)); }
if (!(rawRegistry instanceof MappedRegistry<net.minecraft.world.level.biome.Biome> registry))
throw new IllegalStateException("The Biome Registry is not a mapped Registry!"); private <T> Optional<JsonElement> encode(Codec<T> codec, T value) {
if (registry.containsKey(key)) return; return codec.encode(value, JsonOps.INSTANCE, new JsonObject()).result();
}
private <T> boolean register(ResourceKey<Registry<T>> 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 field = getField(MappedRegistry.class, boolean.class);
field.setAccessible(true); field.setAccessible(true);
boolean frozen = field.getBoolean(registry); boolean frozen = field.getBoolean(registry);
@@ -620,29 +656,30 @@ public class NMSBinding implements INMSBinding {
} }
try { try {
var biome = net.minecraft.world.level.biome.Biome.CODEC.decode(JsonOps.INSTANCE, GsonHelper.parse(IO.readAll(file))) registry.createIntrusiveHolder(value);
.get().left().map(Pair::getFirst).map(Holder::value).orElse(null); registry.register(key, value, Lifecycle.stable());
if (biome == null) return true;
throw new IllegalStateException("Failed to decode biome " + file.getName());
registry.createIntrusiveHolder(biome);
registry.register(key, biome, Lifecycle.stable());
} finally { } finally {
field.setBoolean(registry, frozen); field.setBoolean(registry, frozen);
if (holders) { if (holders) {
holdersField.set(registry, null); holdersField.set(registry, null);
} }
} }
} catch (Throwable e) {
throw new IllegalStateException(e);
}
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private void modifyDimension(File file) throws Throwable { private <T> boolean replace(ResourceKey<Registry<T>> registryKey, ResourceLocation location, T value) {
var key = ResourceKey.create(Registries.DIMENSION_TYPE, from("minecraft", file)); Preconditions.checkArgument(registryKey != null, "The registryKey cannot be null!");
var rawRegistry = registry().registry(Registries.DIMENSION_TYPE).orElse(null); Preconditions.checkArgument(location != null, "The location cannot be null!");
if (!(rawRegistry instanceof MappedRegistry<DimensionType> registry)) Preconditions.checkArgument(value != null, "The value cannot be null!");
throw new IllegalStateException("The Dimension Registry is not a mapped Registry!"); var registry = registry(registryKey);
var key = ResourceKey.create(registryKey, location);
var holder = registry.getHolder(key).orElseThrow(() -> new IllegalStateException("Unknown dimension type: " + key)); try {
var holder = registry.getHolder(key).orElse(null);
if (holder == null) return false;
var oldValue = holder.value(); var oldValue = holder.value();
Field valueField = getField(Holder.Reference.class, "T"); Field valueField = getField(Holder.Reference.class, "T");
valueField.setAccessible(true); valueField.setAccessible(true);
@@ -652,19 +689,25 @@ public class NMSBinding implements INMSBinding {
byValueField.setAccessible(true); byValueField.setAccessible(true);
Field lifecyclesField = getField(MappedRegistry.class, buildType(Map.class, "T", Lifecycle.class.getName())); Field lifecyclesField = getField(MappedRegistry.class, buildType(Map.class, "T", Lifecycle.class.getName()));
lifecyclesField.setAccessible(true); lifecyclesField.setAccessible(true);
var toId = (Reference2IntMap<DimensionType>) toIdField.get(registry); var toId = (Reference2IntMap<T>) toIdField.get(registry);
var byValue = (Map<DimensionType, Holder.Reference<DimensionType>>) byValueField.get(registry); var byValue = (Map<T, Holder.Reference<T>>) byValueField.get(registry);
var lifecycles = (Map<DimensionType, Lifecycle>) lifecyclesField.get(registry); var lifecycles = (Map<T, Lifecycle>) lifecyclesField.get(registry);
var newValue = DimensionType.CODEC.decode(JsonOps.INSTANCE, GsonHelper.parse(IO.readAll(file))) valueField.set(holder, value);
.get().left().map(Pair::getFirst).map(Holder::value).orElse(null); toId.put(value, toId.removeInt(oldValue));
if (newValue == null) byValue.put(value, byValue.remove(oldValue));
throw new IllegalArgumentException("Failed to parse dimension type " + key.location() + " from " + file); lifecycles.put(value, lifecycles.remove(oldValue));
return true;
} catch (Throwable e) {
throw new IllegalStateException(e);
}
}
valueField.set(holder, newValue); private <T> MappedRegistry<T> registry(ResourceKey<Registry<T>> registryKey) {
toId.put(newValue, toId.removeInt(oldValue)); var rawRegistry = registry().registry(registryKey).orElse(null);
byValue.put(newValue, byValue.remove(oldValue)); if (!(rawRegistry instanceof MappedRegistry<T> registry))
lifecycles.put(newValue, lifecycles.remove(oldValue)); throw new IllegalStateException("The Registry is not a mapped Registry!");
return registry;
} }
private static String buildType(Class<?> clazz, String... parameterTypes) { 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)))) .visit(Advice.to(WorldCreatorAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(String.class))))
.make() .make()
.load(WorldCreator.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); .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!"); Iris.info("Injected Bukkit Successfully!");
} catch (Exception e) { } catch (Exception e) {
Iris.info(C.RED + "Failed to Inject Bukkit!"); 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<Level> 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<DimensionType> typeKey = ResourceKey.create(Registries.DIMENSION_TYPE, new ResourceLocation("iris", key.location().getPath()));
RegistryAccess registryAccess = server.registryAccess();
Registry<DimensionType> registry = registryAccess.registry(Registries.DIMENSION_TYPE).orElse(null);
if (registry == null) {
logger.warn("Unable to find registry for dimension type {}", typeKey);
return;
}
Holder<DimensionType> 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 { private static class WorldCreatorAdvice {
@Advice.OnMethodEnter @Advice.OnMethodEnter
static void enter(@Advice.Argument(0) String name) { static void enter(@Advice.Argument(0) String name) {
@@ -6,27 +6,54 @@ import java.io.DataInputStream;
import java.io.DataOutputStream; import java.io.DataOutputStream;
import java.io.File; import java.io.File;
import java.io.FilenameFilter; 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.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.util.*; import java.util.*;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;
import com.google.common.base.Preconditions; 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.datafixers.util.Pair;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps; import com.mojang.serialization.JsonOps;
import com.mojang.serialization.Lifecycle; import com.mojang.serialization.Lifecycle;
import com.volmit.iris.engine.object.IrisDimension;
import com.volmit.iris.util.format.C; import com.volmit.iris.util.format.C;
import com.volmit.iris.util.function.NastySupplier;
import com.volmit.iris.util.io.IO; import com.volmit.iris.util.io.IO;
import it.unimi.dsi.fastutil.objects.Reference2IntMap; import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import net.bytebuddy.ByteBuddy; import net.bytebuddy.ByteBuddy;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice; 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.dynamic.loading.ClassReloadingStrategy;
import net.bytebuddy.matcher.ElementMatchers; import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.utility.JavaModule;
import net.minecraft.core.IdMapper;
import net.minecraft.core.MappedRegistry; 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.GsonHelper;
import net.minecraft.util.worldupdate.WorldUpgrader;
import net.minecraft.world.RandomSequences;
import net.minecraft.world.level.Level; 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.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.*;
import org.bukkit.block.Biome; import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData; 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.Dolphin;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.generator.BiomeProvider;
import org.bukkit.generator.ChunkGenerator; import org.bukkit.generator.ChunkGenerator;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -445,13 +473,13 @@ public class NMSBinding implements INMSBinding {
@Override @Override
public MCAPaletteAccess createPalette() { public MCAPaletteAccess createPalette() {
MCAIdMapper<BlockState> registry = registryCache.aquireNasty(() -> { MCAIdMapper<BlockState> registry = registryCache.aquireNasty(() -> {
Field cf = net.minecraft.core.IdMapper.class.getDeclaredField("tToId"); Field cf = IdMapper.class.getDeclaredField("tToId");
Field df = net.minecraft.core.IdMapper.class.getDeclaredField("idToT"); Field df = IdMapper.class.getDeclaredField("idToT");
Field bf = net.minecraft.core.IdMapper.class.getDeclaredField("nextId"); Field bf = IdMapper.class.getDeclaredField("nextId");
cf.setAccessible(true); cf.setAccessible(true);
df.setAccessible(true); df.setAccessible(true);
bf.setAccessible(true); bf.setAccessible(true);
net.minecraft.core.IdMapper<BlockState> blockData = Block.BLOCK_STATE_REGISTRY; IdMapper<BlockState> blockData = Block.BLOCK_STATE_REGISTRY;
int b = bf.getInt(blockData); int b = bf.getInt(blockData);
Object2IntMap<BlockState> c = (Object2IntMap<BlockState>) cf.get(blockData); Object2IntMap<BlockState> c = (Object2IntMap<BlockState>) cf.get(blockData);
List<BlockState> d = (List<BlockState>) df.get(blockData); List<BlockState> d = (List<BlockState>) df.get(blockData);
@@ -551,25 +579,34 @@ public class NMSBinding implements INMSBinding {
} }
@Override @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"); var data = new File(folder, "iris/data");
if (!data.exists() || !data.isDirectory()) return false; if (!data.exists() || !data.isDirectory()) return false;
FilenameFilter jsonFilter = (dir, name) -> new File(dir, name).isFile() && name.toLowerCase().endsWith(".json"); 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()); var files = data.listFiles((dir, name) -> new File(dir, name).isDirectory());
if (files == null) return false; if (files == null) return false;
for (File file : files) { for (File file : files) {
@@ -579,7 +616,8 @@ public class NMSBinding implements INMSBinding {
if (biomeFiles == null) continue; if (biomeFiles == null) continue;
for (File biomeFile : biomeFiles) { for (File biomeFile : biomeFiles) {
try { 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) { } catch (Throwable e) {
Iris.error("Failed to register biome " + file.getName() + ":" + biomeFile.getName()); Iris.error("Failed to register biome " + file.getName() + ":" + biomeFile.getName());
e.printStackTrace(); e.printStackTrace();
@@ -594,12 +632,25 @@ public class NMSBinding implements INMSBinding {
return new ResourceLocation(namespace, name.substring(0, name.lastIndexOf('.'))); return new ResourceLocation(namespace, name.substring(0, name.lastIndexOf('.')));
} }
private void registerBiome(String namespace, File file) throws Throwable { private <T> Optional<T> decode(Codec<T> codec, String json) {
var rawRegistry = registry().registry(Registries.BIOME).orElse(null); return codec.decode(JsonOps.INSTANCE, GsonHelper.parse(json)).get().left().map(Pair::getFirst);
var key = ResourceKey.create(Registries.BIOME, from(namespace, file)); }
if (!(rawRegistry instanceof MappedRegistry<net.minecraft.world.level.biome.Biome> registry))
throw new IllegalStateException("The Biome Registry is not a mapped Registry!"); private <T> Optional<JsonElement> encode(Codec<T> codec, T value) {
if (registry.containsKey(key)) return; return codec.encode(value, JsonOps.INSTANCE, new JsonObject()).result();
}
private <T> boolean register(ResourceKey<Registry<T>> 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 field = getField(MappedRegistry.class, boolean.class);
field.setAccessible(true); field.setAccessible(true);
boolean frozen = field.getBoolean(registry); boolean frozen = field.getBoolean(registry);
@@ -618,29 +669,30 @@ public class NMSBinding implements INMSBinding {
} }
try { try {
var biome = net.minecraft.world.level.biome.Biome.CODEC.decode(JsonOps.INSTANCE, GsonHelper.parse(IO.readAll(file))) registry.createIntrusiveHolder(value);
.get().left().map(Pair::getFirst).map(Holder::value).orElse(null); registry.register(key, value, Lifecycle.stable());
if (biome == null) return true;
throw new IllegalStateException("Failed to decode biome " + file.getName());
registry.createIntrusiveHolder(biome);
registry.register(key, biome, Lifecycle.stable());
} finally { } finally {
field.setBoolean(registry, frozen); field.setBoolean(registry, frozen);
if (holders) { if (holders) {
holdersField.set(registry, null); holdersField.set(registry, null);
} }
} }
} catch (Throwable e) {
throw new IllegalStateException(e);
}
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private void modifyDimension(File file) throws Throwable { private <T> boolean replace(ResourceKey<Registry<T>> registryKey, ResourceLocation location, T value) {
var key = ResourceKey.create(Registries.DIMENSION_TYPE, from("minecraft", file)); Preconditions.checkArgument(registryKey != null, "The registryKey cannot be null!");
var rawRegistry = registry().registry(Registries.DIMENSION_TYPE).orElse(null); Preconditions.checkArgument(location != null, "The location cannot be null!");
if (!(rawRegistry instanceof MappedRegistry<DimensionType> registry)) Preconditions.checkArgument(value != null, "The value cannot be null!");
throw new IllegalStateException("The Dimension Registry is not a mapped Registry!"); var registry = registry(registryKey);
var key = ResourceKey.create(registryKey, location);
var holder = registry.getHolder(key).orElseThrow(() -> new IllegalStateException("Unknown dimension type: " + key)); try {
var holder = registry.getHolder(key).orElse(null);
if (holder == null) return false;
var oldValue = holder.value(); var oldValue = holder.value();
Field valueField = getField(Holder.Reference.class, "T"); Field valueField = getField(Holder.Reference.class, "T");
valueField.setAccessible(true); valueField.setAccessible(true);
@@ -650,19 +702,25 @@ public class NMSBinding implements INMSBinding {
byValueField.setAccessible(true); byValueField.setAccessible(true);
Field lifecyclesField = getField(MappedRegistry.class, buildType(Map.class, "T", Lifecycle.class.getName())); Field lifecyclesField = getField(MappedRegistry.class, buildType(Map.class, "T", Lifecycle.class.getName()));
lifecyclesField.setAccessible(true); lifecyclesField.setAccessible(true);
var toId = (Reference2IntMap<DimensionType>) toIdField.get(registry); var toId = (Reference2IntMap<T>) toIdField.get(registry);
var byValue = (Map<DimensionType, Holder.Reference<DimensionType>>) byValueField.get(registry); var byValue = (Map<T, Holder.Reference<T>>) byValueField.get(registry);
var lifecycles = (Map<DimensionType, Lifecycle>) lifecyclesField.get(registry); var lifecycles = (Map<T, Lifecycle>) lifecyclesField.get(registry);
var newValue = DimensionType.CODEC.decode(JsonOps.INSTANCE, GsonHelper.parse(IO.readAll(file))) valueField.set(holder, value);
.get().left().map(Pair::getFirst).map(Holder::value).orElse(null); toId.put(value, toId.removeInt(oldValue));
if (newValue == null) byValue.put(value, byValue.remove(oldValue));
throw new IllegalArgumentException("Failed to parse dimension type " + key.location() + " from " + file); lifecycles.put(value, lifecycles.remove(oldValue));
return true;
} catch (Throwable e) {
throw new IllegalStateException(e);
}
}
valueField.set(holder, newValue); private <T> MappedRegistry<T> registry(ResourceKey<Registry<T>> registryKey) {
toId.put(newValue, toId.removeInt(oldValue)); var rawRegistry = registry().registry(registryKey).orElse(null);
byValue.put(newValue, byValue.remove(oldValue)); if (!(rawRegistry instanceof MappedRegistry<T> registry))
lifecycles.put(newValue, lifecycles.remove(oldValue)); throw new IllegalStateException("The Registry is not a mapped Registry!");
return registry;
} }
private static String buildType(Class<?> clazz, String... parameterTypes) { 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)))) .visit(Advice.to(WorldCreatorAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(String.class))))
.make() .make()
.load(WorldCreator.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); .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!"); Iris.info("Injected Bukkit Successfully!");
} catch (Exception e) { } catch (Exception e) {
Iris.info(C.RED + "Failed to Inject Bukkit!"); 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<Level> 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<DimensionType> typeKey = ResourceKey.create(Registries.DIMENSION_TYPE, new ResourceLocation("iris", key.location().getPath()));
RegistryAccess registryAccess = server.registryAccess();
Registry<DimensionType> registry = registryAccess.registry(Registries.DIMENSION_TYPE).orElse(null);
if (registry == null) {
logger.warn("Unable to find registry for dimension type {}", typeKey);
return;
}
Holder<DimensionType> 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 { private static class WorldCreatorAdvice {
@Advice.OnMethodEnter @Advice.OnMethodEnter
static void enter(@Advice.Argument(0) String name) { static void enter(@Advice.Argument(0) String name) {