Revert "Revert "Merge branch 'mca' into jigsaw_dist""

This reverts commit 55017b9a
This commit is contained in:
Julian Krings
2024-07-09 20:24:21 +02:00
parent 21d7d96dbc
commit 5ad848fc54
68 changed files with 3934 additions and 1241 deletions
@@ -2,6 +2,7 @@ package com.volmit.iris.core.nms.v1_19_R1;
import com.mojang.serialization.Codec;
import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.INMS;
import com.volmit.iris.engine.data.cache.AtomicCache;
import com.volmit.iris.engine.framework.Engine;
import com.volmit.iris.engine.object.IrisBiome;
@@ -11,6 +12,7 @@ import com.volmit.iris.util.math.RNG;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeSource;
@@ -25,6 +27,7 @@ import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class CustomBiomeSource extends BiomeSource {
private final long seed;
@@ -118,8 +121,28 @@ public class CustomBiomeSource extends BiomeSource {
for (IrisBiome i : engine.getAllBiomes()) {
if (i.isCustom()) {
for (IrisBiomeCustom j : i.getCustomDerivitives()) {
m.put(j.getId(), customRegistry.getHolder(customRegistry.getResourceKey(customRegistry
.get(new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId()))).get()).get());
ResourceLocation location = new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId());
Biome biome = customRegistry.get(location);
if (biome == null) {
INMS.get().registerBiome(location.getNamespace(), j, false);
biome = customRegistry.get(location);
if (biome == null) {
Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName());
continue;
}
}
Optional<ResourceKey<Biome>> optionalBiomeKey = customRegistry.getResourceKey(biome);
if (optionalBiomeKey.isEmpty()) {
Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName());
continue;
}
ResourceKey<Biome> biomeKey = optionalBiomeKey.get();
Optional<Holder<Biome>> optionalReferenceHolder = customRegistry.getHolder(biomeKey);
if (optionalReferenceHolder.isEmpty()) {
Iris.error("Cannot find reference to biome " + biomeKey + " for engine " + engine.getName());
continue;
}
m.put(j.getId(), optionalReferenceHolder.get());
}
}
}
@@ -4,14 +4,44 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Vector;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;
import com.google.common.base.Preconditions;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.mojang.datafixers.util.Pair;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps;
import com.mojang.serialization.Lifecycle;
import com.volmit.iris.engine.object.IrisBiomeCustom;
import com.volmit.iris.engine.object.IrisDimension;
import com.volmit.iris.util.format.C;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
import net.bytebuddy.matcher.ElementMatchers;
import net.minecraft.core.MappedRegistry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.progress.ChunkProgressListener;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.storage.LevelStorageSource;
import net.minecraft.world.level.storage.PrimaryLevelData;
import org.bukkit.*;
import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData;
@@ -25,6 +55,7 @@ import org.bukkit.craftbukkit.v1_19_R1.inventory.CraftItemStack;
import org.bukkit.entity.Dolphin;
import org.bukkit.entity.Entity;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.generator.BiomeProvider;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
@@ -170,6 +201,17 @@ public class NMSBinding implements INMSBinding {
return null;
}
private RegistryAccess getRegistryAccess(World world) {
try {
var field = getField(Level.class, RegistryAccess.class);
field.setAccessible(true);
return (RegistryAccess) field.get(((CraftWorld) world).getHandle());
} catch (Throwable e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
@Override
public void deserializeTile(CompoundTag c, Location pos) {
((CraftWorld) pos.getWorld()).getHandle().getChunkAt(new BlockPos(pos.getBlockX(), 0, pos.getBlockZ())).setBlockEntityNbt(convert(c));
@@ -253,8 +295,7 @@ public class NMSBinding implements INMSBinding {
@Override
public Object getBiomeBase(World world, Biome biome) {
return CraftBlock.biomeToBiomeBase(((CraftWorld) world).getHandle()
.registryAccess().registry(Registry.BIOME_REGISTRY).orElse(null), biome);
return CraftBlock.biomeToBiomeBase(getRegistryAccess(world).registry(Registry.BIOME_REGISTRY).orElse(null), biome);
}
@Override
@@ -291,8 +332,11 @@ public class NMSBinding implements INMSBinding {
public int getBiomeId(Biome biome) {
for (World i : Bukkit.getWorlds()) {
if (i.getEnvironment().equals(World.Environment.NORMAL)) {
Registry<net.minecraft.world.level.biome.Biome> registry = ((CraftWorld) i).getHandle().registryAccess().registry(Registry.BIOME_REGISTRY).orElse(null);
return registry.getId((net.minecraft.world.level.biome.Biome) getBiomeBase(registry, biome));
var registry = getRegistryAccess(i).registry(Registry.BIOME_REGISTRY).orElse(null);
if (registry != null) {
var holder = (Holder<net.minecraft.world.level.biome.Biome>) getBiomeBase(registry, biome);
return registry.getId(holder.value());
}
}
}
@@ -517,6 +561,139 @@ public class NMSBinding implements INMSBinding {
return ((CraftWorld) location.getWorld()).spawn(location, type.getEntityClass(), null, reason);
}
@Override
public boolean registerDimension(String name, IrisDimension dimension) {
var registry = registry(Registry.DIMENSION_TYPE_REGISTRY);
var baseLocation = switch (dimension.getEnvironment()) {
case NORMAL -> new ResourceLocation("minecraft", "overworld");
case NETHER -> new ResourceLocation("minecraft", "the_nether");
case THE_END -> new ResourceLocation("minecraft", "the_end");
case CUSTOM -> throw new IllegalArgumentException("Cannot register custom dimension");
};
var base = registry.getHolder(ResourceKey.create(Registry.DIMENSION_TYPE_REGISTRY, baseLocation)).orElse(null);
if (base == null) return false;
var json = encode(DimensionType.CODEC, base).orElse(null);
if (json == null) return false;
var object = json.getAsJsonObject();
var height = dimension.getDimensionHeight();
object.addProperty("min_y", height.getMin());
object.addProperty("height", height.getMax() - height.getMin());
object.addProperty("logical_height", dimension.getLogicalHeight());
var value = decode(DimensionType.CODEC, object.toString()).map(Holder::value).orElse(null);
if (value == null) return false;
return register(Registry.DIMENSION_TYPE_REGISTRY, new ResourceLocation("iris", name), value, true);
}
@Override
public boolean registerBiome(String dimensionId, IrisBiomeCustom biome, boolean replace) {
var biomeBase = decode(net.minecraft.world.level.biome.Biome.CODEC, biome.generateJson()).map(Holder::value).orElse(null);
if (biomeBase == null) return false;
return register(Registry.BIOME_REGISTRY, new ResourceLocation(dimensionId, biome.getId()), biomeBase, replace);
}
private <T> Optional<T> decode(Codec<T> codec, String json) {
return codec.decode(JsonOps.INSTANCE, GsonHelper.parse(json)).get().left().map(Pair::getFirst);
}
private <T> Optional<JsonElement> encode(Codec<T> codec, T value) {
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.setAccessible(true);
boolean frozen = field.getBoolean(registry);
field.setBoolean(registry, false);
Field valueField = getField(Holder.Reference.class, "T");
valueField.setAccessible(true);
try {
var holder = registry.register(key, value, Lifecycle.stable());
if (frozen) valueField.set(holder, value);
return true;
} finally {
field.setBoolean(registry, frozen);
}
} catch (Throwable e) {
throw new IllegalStateException(e);
}
}
@SuppressWarnings("unchecked")
private <T> boolean replace(ResourceKey<Registry<T>> registryKey, ResourceLocation location, T value) {
Preconditions.checkArgument(registryKey != null, "The registryKey cannot be null!");
Preconditions.checkArgument(location != null, "The location cannot be null!");
Preconditions.checkArgument(value != null, "The value cannot be null!");
var registry = registry(registryKey);
var key = ResourceKey.create(registryKey, location);
try {
var holder = registry.getHolder(key).orElse(null);
if (holder == null) return false;
var oldValue = holder.value();
Field valueField = getField(Holder.Reference.class, "T");
valueField.setAccessible(true);
Field toIdField = getField(MappedRegistry.class, buildType(Reference2IntMap.class, "T"));
toIdField.setAccessible(true);
Field byValueField = getField(MappedRegistry.class, buildType(Map.class, "T", buildType(Holder.Reference.class, "T")));
byValueField.setAccessible(true);
Field lifecyclesField = getField(MappedRegistry.class, buildType(Map.class, "T", Lifecycle.class.getName()));
lifecyclesField.setAccessible(true);
var toId = (Reference2IntMap<T>) toIdField.get(registry);
var byValue = (Map<T, Holder.Reference<T>>) byValueField.get(registry);
var lifecycles = (Map<T, Lifecycle>) lifecyclesField.get(registry);
valueField.set(holder, value);
toId.put(value, toId.removeInt(oldValue));
byValue.put(value, byValue.remove(oldValue));
lifecycles.put(value, lifecycles.remove(oldValue));
return true;
} catch (Throwable e) {
throw new IllegalStateException(e);
}
}
private <T> MappedRegistry<T> registry(ResourceKey<Registry<T>> registryKey) {
var rawRegistry = registry().registry(registryKey).orElse(null);
if (!(rawRegistry instanceof MappedRegistry<T> registry))
throw new IllegalStateException("The Registry is not a mapped Registry!");
return registry;
}
private static String buildType(Class<?> clazz, String... parameterTypes) {
if (parameterTypes.length == 0) return clazz.getName();
var builder = new StringBuilder(clazz.getName())
.append("<");
for (int i = 0; i < parameterTypes.length; i++) {
builder.append(parameterTypes[i]).append(parameterTypes.length - 1 == i ? ">" : ", ");
}
return builder.toString();
}
private static Field getField(Class<?> clazz, String type) throws NoSuchFieldException {
try {
for (Field f : clazz.getDeclaredFields()) {
if (f.getGenericType().getTypeName().equals(type))
return f;
}
throw new NoSuchFieldException(type);
} catch (NoSuchFieldException e) {
Class<?> superClass = clazz.getSuperclass();
if (superClass == null) throw e;
return getField(superClass, type);
}
}
private static Field getField(Class<?> clazz, Class<?> fieldType) throws NoSuchFieldException {
try {
for (Field f : clazz.getDeclaredFields()) {
@@ -533,4 +710,76 @@ public class NMSBinding implements INMSBinding {
}
}
}
}
public void injectBukkit() {
try {
Iris.info("Injecting Bukkit");
new ByteBuddy()
.redefine(CraftServer.class)
.visit(Advice.to(CraftServerAdvice.class).on(ElementMatchers.isMethod().and(ElementMatchers.takesArguments(WorldCreator.class))))
.make()
.load(CraftServer.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
new ByteBuddy()
.redefine(ServerLevel.class)
.visit(Advice.to(ServerLevelAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(MinecraftServer.class, Executor.class, LevelStorageSource.LevelStorageAccess.class,
PrimaryLevelData.class, ResourceKey.class, LevelStem.class, ChunkProgressListener.class, boolean.class, long.class,
List.class, boolean.class, World.Environment.class, ChunkGenerator.class, BiomeProvider.class))))
.make()
.load(ServerLevel.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
Iris.info("Injected Bukkit Successfully!");
} catch (Exception e) {
Iris.info(C.RED + "Failed to Inject Bukkit!");
e.printStackTrace();
Iris.reportError(e);
}
}
private static class ServerLevelAdvice {
@Advice.OnMethodEnter
static void enter(@Advice.Argument(0) MinecraftServer server, @Advice.Argument(2) LevelStorageSource.LevelStorageAccess access, @Advice.Argument(4) ResourceKey<Level> key, @Advice.Argument(value = 5, readOnly = false) LevelStem levelStem) {
File iris = new File(access.levelDirectory.path().toFile(), "iris");
if (!iris.exists() && !key.location().getPath().startsWith("iris/")) return;
var logger = MinecraftServer.LOGGER;
ResourceKey<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) throw new IllegalStateException("Unable to find registry for dimension type " + typeKey);
Holder<DimensionType> holder = registry.getHolder(typeKey).orElse(null);
if (holder == null) throw new IllegalStateException("Unable to find dimension type " + typeKey);
levelStem = new LevelStem(holder, levelStem.generator());
}
}
private static class CraftServerAdvice {
@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
static boolean enter(@Advice.This CraftServer self, @Advice.Argument(0) WorldCreator creator) {
File isIrisWorld = new File(self.getWorldContainer(), creator.name() + "/iris");
boolean isFromIris = false;
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
for (StackTraceElement stack : stackTrace) {
if (stack.getClassName().contains("Iris")) {
isFromIris = true;
break;
}
}
if (isIrisWorld.exists() && !isFromIris) {
var logger = Logger.getLogger("Iris");
logger.warning("detected another Plugin trying to load " + creator.name() + ". This is not supported and will be ignored.");
if (System.getProperty("iris.debug", "false").equals("true")) {
new RuntimeException().printStackTrace();
}
return true;
}
return false;
}
@Advice.OnMethodExit
static void exit(@Advice.Enter boolean bool, @Advice.Return(readOnly = false) World returned) {
if (bool) {
returned = null;
}
}
}
}
@@ -2,6 +2,7 @@ package com.volmit.iris.core.nms.v1_19_R2;
import com.mojang.serialization.Codec;
import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.INMS;
import com.volmit.iris.engine.data.cache.AtomicCache;
import com.volmit.iris.engine.framework.Engine;
import com.volmit.iris.engine.object.IrisBiome;
@@ -12,6 +13,7 @@ import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeSource;
@@ -26,6 +28,7 @@ import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class CustomBiomeSource extends BiomeSource {
@@ -120,8 +123,28 @@ public class CustomBiomeSource extends BiomeSource {
for (IrisBiome i : engine.getAllBiomes()) {
if (i.isCustom()) {
for (IrisBiomeCustom j : i.getCustomDerivitives()) {
m.put(j.getId(), customRegistry.getHolder(customRegistry.getResourceKey(customRegistry
.get(new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId()))).get()).get());
ResourceLocation location = new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId());
Biome biome = customRegistry.get(location);
if (biome == null) {
INMS.get().registerBiome(location.getNamespace(), j, false);
biome = customRegistry.get(location);
if (biome == null) {
Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName());
continue;
}
}
Optional<ResourceKey<Biome>> optionalBiomeKey = customRegistry.getResourceKey(biome);
if (optionalBiomeKey.isEmpty()) {
Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName());
continue;
}
ResourceKey<Biome> biomeKey = optionalBiomeKey.get();
Optional<Holder.Reference<Biome>> optionalReferenceHolder = customRegistry.getHolder(biomeKey);
if (optionalReferenceHolder.isEmpty()) {
Iris.error("Cannot find reference to biome " + biomeKey + " for engine " + engine.getName());
continue;
}
m.put(j.getId(), optionalReferenceHolder.get());
}
}
}
@@ -4,14 +4,44 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Vector;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;
import com.google.common.base.Preconditions;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.mojang.datafixers.util.Pair;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps;
import com.mojang.serialization.Lifecycle;
import com.volmit.iris.engine.object.IrisBiomeCustom;
import com.volmit.iris.engine.object.IrisDimension;
import com.volmit.iris.util.format.C;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
import net.bytebuddy.matcher.ElementMatchers;
import net.minecraft.core.MappedRegistry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.progress.ChunkProgressListener;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.storage.LevelStorageSource;
import net.minecraft.world.level.storage.PrimaryLevelData;
import org.bukkit.*;
import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData;
@@ -25,6 +55,7 @@ import org.bukkit.craftbukkit.v1_19_R2.inventory.CraftItemStack;
import org.bukkit.entity.Dolphin;
import org.bukkit.entity.Entity;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.generator.BiomeProvider;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
@@ -171,6 +202,17 @@ public class NMSBinding implements INMSBinding {
return null;
}
private RegistryAccess getRegistryAccess(World world) {
try {
var field = getField(Level.class, RegistryAccess.class);
field.setAccessible(true);
return (RegistryAccess) field.get(((CraftWorld) world).getHandle());
} catch (Throwable e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
@Override
public void deserializeTile(CompoundTag c, Location pos) {
((CraftWorld) pos.getWorld()).getHandle().getChunkAt(new BlockPos(pos.getBlockX(), 0, pos.getBlockZ())).setBlockEntityNbt(convert(c));
@@ -254,8 +296,7 @@ public class NMSBinding implements INMSBinding {
@Override
public Object getBiomeBase(World world, Biome biome) {
return CraftBlock.biomeToBiomeBase(((CraftWorld) world).getHandle()
.registryAccess().registry(Registries.BIOME).orElse(null), biome);
return CraftBlock.biomeToBiomeBase(getRegistryAccess(world).registry(Registries.BIOME).orElse(null), biome);
}
@Override
@@ -292,8 +333,11 @@ public class NMSBinding implements INMSBinding {
public int getBiomeId(Biome biome) {
for (World i : Bukkit.getWorlds()) {
if (i.getEnvironment().equals(World.Environment.NORMAL)) {
Registry<net.minecraft.world.level.biome.Biome> registry = ((CraftWorld) i).getHandle().registryAccess().registry(Registries.BIOME).orElse(null);
return registry.getId((net.minecraft.world.level.biome.Biome) getBiomeBase(registry, biome));
var registry = getRegistryAccess(i).registry(Registries.BIOME).orElse(null);
if (registry != null) {
var holder = (Holder<net.minecraft.world.level.biome.Biome>) getBiomeBase(registry, biome);
return registry.getId(holder.value());
}
}
}
@@ -519,6 +563,138 @@ public class NMSBinding implements INMSBinding {
return ((CraftWorld) location.getWorld()).spawn(location, type.getEntityClass(), null, reason);
}
@Override
public boolean registerDimension(String name, IrisDimension dimension) {
var registry = registry(Registries.DIMENSION_TYPE);
var baseLocation = switch (dimension.getEnvironment()) {
case NORMAL -> new ResourceLocation("minecraft", "overworld");
case NETHER -> new ResourceLocation("minecraft", "the_nether");
case THE_END -> new ResourceLocation("minecraft", "the_end");
case CUSTOM -> throw new IllegalArgumentException("Cannot register custom dimension");
};
var base = registry.getHolder(ResourceKey.create(Registries.DIMENSION_TYPE, baseLocation)).orElse(null);
if (base == null) return false;
var json = encode(DimensionType.CODEC, base).orElse(null);
if (json == null) return false;
var object = json.getAsJsonObject();
var height = dimension.getDimensionHeight();
object.addProperty("min_y", height.getMin());
object.addProperty("height", height.getMax() - height.getMin());
object.addProperty("logical_height", dimension.getLogicalHeight());
var value = decode(DimensionType.CODEC, object.toString()).map(Holder::value).orElse(null);
if (value == null) return false;
return register(Registries.DIMENSION_TYPE, new ResourceLocation("iris", name), value, true);
}
@Override
public boolean registerBiome(String dimensionId, IrisBiomeCustom biome, boolean replace) {
var biomeBase = decode(net.minecraft.world.level.biome.Biome.CODEC, biome.generateJson()).map(Holder::value).orElse(null);
if (biomeBase == null) return false;
return register(Registries.BIOME, new ResourceLocation(dimensionId, biome.getId()), biomeBase, replace);
}
private <T> Optional<T> decode(Codec<T> codec, String json) {
return codec.decode(JsonOps.INSTANCE, GsonHelper.parse(json)).get().left().map(Pair::getFirst);
}
private <T> Optional<JsonElement> encode(Codec<T> codec, T value) {
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.setAccessible(true);
boolean frozen = field.getBoolean(registry);
field.setBoolean(registry, false);
Field valueField = getField(Holder.Reference.class, "T");
valueField.setAccessible(true);
try {
var holder = registry.register(key, value, Lifecycle.stable());
if (frozen) valueField.set(holder, value);
return true;
} finally {
field.setBoolean(registry, frozen);
}
} catch (Throwable e) {
throw new IllegalStateException(e);
}
}
@SuppressWarnings("unchecked")
private <T> boolean replace(ResourceKey<Registry<T>> registryKey, ResourceLocation location, T value) {
Preconditions.checkArgument(registryKey != null, "The registryKey cannot be null!");
Preconditions.checkArgument(location != null, "The location cannot be null!");
Preconditions.checkArgument(value != null, "The value cannot be null!");
var registry = registry(registryKey);
var key = ResourceKey.create(registryKey, location);
try {
var holder = registry.getHolder(key).orElse(null);
if (holder == null) return false;
var oldValue = holder.value();
Field valueField = getField(Holder.Reference.class, "T");
valueField.setAccessible(true);
Field toIdField = getField(MappedRegistry.class, buildType(Reference2IntMap.class, "T"));
toIdField.setAccessible(true);
Field byValueField = getField(MappedRegistry.class, buildType(Map.class, "T", buildType(Holder.Reference.class, "T")));
byValueField.setAccessible(true);
Field lifecyclesField = getField(MappedRegistry.class, buildType(Map.class, "T", Lifecycle.class.getName()));
lifecyclesField.setAccessible(true);
var toId = (Reference2IntMap<T>) toIdField.get(registry);
var byValue = (Map<T, Holder.Reference<T>>) byValueField.get(registry);
var lifecycles = (Map<T, Lifecycle>) lifecyclesField.get(registry);
valueField.set(holder, value);
toId.put(value, toId.removeInt(oldValue));
byValue.put(value, byValue.remove(oldValue));
lifecycles.put(value, lifecycles.remove(oldValue));
return true;
} catch (Throwable e) {
throw new IllegalStateException(e);
}
}
private <T> MappedRegistry<T> registry(ResourceKey<Registry<T>> registryKey) {
var rawRegistry = registry().registry(registryKey).orElse(null);
if (!(rawRegistry instanceof MappedRegistry<T> registry))
throw new IllegalStateException("The Registry is not a mapped Registry!");
return registry;
}
private static String buildType(Class<?> clazz, String... parameterTypes) {
if (parameterTypes.length == 0) return clazz.getName();
var builder = new StringBuilder(clazz.getName())
.append("<");
for (int i = 0; i < parameterTypes.length; i++) {
builder.append(parameterTypes[i]).append(parameterTypes.length - 1 == i ? ">" : ", ");
}
return builder.toString();
}
private static Field getField(Class<?> clazz, String type) throws NoSuchFieldException {
try {
for (Field f : clazz.getDeclaredFields()) {
if (f.getGenericType().getTypeName().equals(type))
return f;
}
throw new NoSuchFieldException(type);
} catch (NoSuchFieldException e) {
Class<?> superClass = clazz.getSuperclass();
if (superClass == null) throw e;
return getField(superClass, type);
}
}
private static Field getField(Class<?> clazz, Class<?> fieldType) throws NoSuchFieldException {
try {
for (Field f : clazz.getDeclaredFields()) {
@@ -535,4 +711,75 @@ public class NMSBinding implements INMSBinding {
}
}
}
}
public void injectBukkit() {
try {
Iris.info("Injecting Bukkit");
new ByteBuddy()
.redefine(CraftServer.class)
.visit(Advice.to(CraftServerAdvice.class).on(ElementMatchers.isMethod().and(ElementMatchers.takesArguments(WorldCreator.class))))
.make()
.load(CraftServer.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
new ByteBuddy()
.redefine(ServerLevel.class)
.visit(Advice.to(ServerLevelAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(MinecraftServer.class, Executor.class, LevelStorageSource.LevelStorageAccess.class,
PrimaryLevelData.class, ResourceKey.class, LevelStem.class, ChunkProgressListener.class, boolean.class, long.class,
List.class, boolean.class, World.Environment.class, ChunkGenerator.class, BiomeProvider.class))))
.make()
.load(ServerLevel.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
Iris.info("Injected Bukkit Successfully!");
} catch (Exception e) {
Iris.info(C.RED + "Failed to Inject Bukkit!");
e.printStackTrace();
Iris.reportError(e);
}
}
private static class ServerLevelAdvice {
@Advice.OnMethodEnter
static void enter(@Advice.Argument(0) MinecraftServer server, @Advice.Argument(2) LevelStorageSource.LevelStorageAccess access, @Advice.Argument(4) ResourceKey<Level> key, @Advice.Argument(value = 5, readOnly = false) LevelStem levelStem) {
File iris = new File(access.levelDirectory.path().toFile(), "iris");
if (!iris.exists() && !key.location().getPath().startsWith("iris/")) return;
ResourceKey<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) throw new IllegalStateException("Unable to find registry for dimension type " + typeKey);
Holder<DimensionType> holder = registry.getHolder(typeKey).orElse(null);
if (holder == null) throw new IllegalStateException("Unable to find dimension type " + typeKey);
levelStem = new LevelStem(holder, levelStem.generator());
}
}
private static class CraftServerAdvice {
@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
static boolean enter(@Advice.This CraftServer self, @Advice.Argument(0) WorldCreator creator) {
File isIrisWorld = new File(self.getWorldContainer(), creator.name() + "/iris");
boolean isFromIris = false;
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
for (StackTraceElement stack : stackTrace) {
if (stack.getClassName().contains("Iris")) {
isFromIris = true;
break;
}
}
if (isIrisWorld.exists() && !isFromIris) {
var logger = Logger.getLogger("Iris");
logger.warning("detected another Plugin trying to load " + creator.name() + ". This is not supported and will be ignored.");
if (System.getProperty("iris.debug", "false").equals("true")) {
new RuntimeException().printStackTrace();
}
return true;
}
return false;
}
@Advice.OnMethodExit
static void exit(@Advice.Enter boolean bool, @Advice.Return(readOnly = false) World returned) {
if (bool) {
returned = null;
}
}
}
}
@@ -2,6 +2,7 @@ package com.volmit.iris.core.nms.v1_19_R3;
import com.mojang.serialization.Codec;
import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.INMS;
import com.volmit.iris.engine.data.cache.AtomicCache;
import com.volmit.iris.engine.framework.Engine;
import com.volmit.iris.engine.object.IrisBiome;
@@ -125,8 +126,16 @@ public class CustomBiomeSource extends BiomeSource {
for (IrisBiome i : engine.getAllBiomes()) {
if (i.isCustom()) {
for (IrisBiomeCustom j : i.getCustomDerivitives()) {
ResourceLocation resourceLocation = new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId());
Biome biome = customRegistry.get(resourceLocation);
ResourceLocation location = new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId());
Biome biome = customRegistry.get(location);
if (biome == null) {
INMS.get().registerBiome(location.getNamespace(), j, false);
biome = customRegistry.get(location);
if (biome == null) {
Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName());
continue;
}
}
Optional<ResourceKey<Biome>> optionalBiomeKey = customRegistry.getResourceKey(biome);
if (optionalBiomeKey.isEmpty()) {
Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName());
@@ -4,14 +4,44 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Vector;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;
import com.google.common.base.Preconditions;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.mojang.datafixers.util.Pair;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps;
import com.mojang.serialization.Lifecycle;
import com.volmit.iris.engine.object.IrisBiomeCustom;
import com.volmit.iris.engine.object.IrisDimension;
import com.volmit.iris.util.format.C;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
import net.bytebuddy.matcher.ElementMatchers;
import net.minecraft.core.MappedRegistry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.progress.ChunkProgressListener;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.storage.LevelStorageSource;
import net.minecraft.world.level.storage.PrimaryLevelData;
import org.bukkit.*;
import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData;
@@ -25,6 +55,7 @@ import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftItemStack;
import org.bukkit.entity.Dolphin;
import org.bukkit.entity.Entity;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.generator.BiomeProvider;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
@@ -172,6 +203,17 @@ public class NMSBinding implements INMSBinding {
return null;
}
private RegistryAccess getRegistryAccess(World world) {
try {
var field = getField(Level.class, RegistryAccess.class);
field.setAccessible(true);
return (RegistryAccess) field.get(((CraftWorld) world).getHandle());
} catch (Throwable e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
@Override
public void deserializeTile(CompoundTag c, Location pos) {
((CraftWorld) pos.getWorld()).getHandle().getChunkAt(new BlockPos(pos.getBlockX(), 0, pos.getBlockZ())).setBlockEntityNbt(convert(c));
@@ -255,8 +297,7 @@ public class NMSBinding implements INMSBinding {
@Override
public Object getBiomeBase(World world, Biome biome) {
return CraftBlock.biomeToBiomeBase(((CraftWorld) world).getHandle()
.registryAccess().registry(Registries.BIOME).orElse(null), biome);
return CraftBlock.biomeToBiomeBase(getRegistryAccess(world).registry(Registries.BIOME).orElse(null), biome);
}
@Override
@@ -294,8 +335,11 @@ public class NMSBinding implements INMSBinding {
public int getBiomeId(Biome biome) {
for (World i : Bukkit.getWorlds()) {
if (i.getEnvironment().equals(World.Environment.NORMAL)) {
Registry<net.minecraft.world.level.biome.Biome> registry = ((CraftWorld) i).getHandle().registryAccess().registry(Registries.BIOME).orElse(null);
return registry.getId((net.minecraft.world.level.biome.Biome) getBiomeBase(registry, biome));
var registry = getRegistryAccess(i).registry(Registries.BIOME).orElse(null);
if (registry != null) {
var holder = (Holder<net.minecraft.world.level.biome.Biome>) getBiomeBase(registry, biome);
return registry.getId(holder.value());
}
}
}
@@ -523,6 +567,138 @@ public class NMSBinding implements INMSBinding {
return ((CraftWorld) location.getWorld()).spawn(location, type.getEntityClass(), null, reason);
}
@Override
public boolean registerDimension(String name, IrisDimension dimension) {
var registry = registry(Registries.DIMENSION_TYPE);
var baseLocation = switch (dimension.getEnvironment()) {
case NORMAL -> new ResourceLocation("minecraft", "overworld");
case NETHER -> new ResourceLocation("minecraft", "the_nether");
case THE_END -> new ResourceLocation("minecraft", "the_end");
case CUSTOM -> throw new IllegalArgumentException("Cannot register custom dimension");
};
var base = registry.getHolder(ResourceKey.create(Registries.DIMENSION_TYPE, baseLocation)).orElse(null);
if (base == null) return false;
var json = encode(DimensionType.CODEC, base).orElse(null);
if (json == null) return false;
var object = json.getAsJsonObject();
var height = dimension.getDimensionHeight();
object.addProperty("min_y", height.getMin());
object.addProperty("height", height.getMax() - height.getMin());
object.addProperty("logical_height", dimension.getLogicalHeight());
var value = decode(DimensionType.CODEC, object.toString()).map(Holder::value).orElse(null);
if (value == null) return false;
return register(Registries.DIMENSION_TYPE, new ResourceLocation("iris", name), value, true);
}
@Override
public boolean registerBiome(String dimensionId, IrisBiomeCustom biome, boolean replace) {
var biomeBase = decode(net.minecraft.world.level.biome.Biome.CODEC, biome.generateJson()).map(Holder::value).orElse(null);
if (biomeBase == null) return false;
return register(Registries.BIOME, new ResourceLocation(dimensionId, biome.getId()), biomeBase, replace);
}
private <T> Optional<T> decode(Codec<T> codec, String json) {
return codec.decode(JsonOps.INSTANCE, GsonHelper.parse(json)).get().left().map(Pair::getFirst);
}
private <T> Optional<JsonElement> encode(Codec<T> codec, T value) {
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.setAccessible(true);
boolean frozen = field.getBoolean(registry);
field.setBoolean(registry, false);
Field valueField = getField(Holder.Reference.class, "T");
valueField.setAccessible(true);
try {
var holder = registry.register(key, value, Lifecycle.stable());
if (frozen) valueField.set(holder, value);
return true;
} finally {
field.setBoolean(registry, frozen);
}
} catch (Throwable e) {
throw new IllegalStateException(e);
}
}
@SuppressWarnings("unchecked")
private <T> boolean replace(ResourceKey<Registry<T>> registryKey, ResourceLocation location, T value) {
Preconditions.checkArgument(registryKey != null, "The registryKey cannot be null!");
Preconditions.checkArgument(location != null, "The location cannot be null!");
Preconditions.checkArgument(value != null, "The value cannot be null!");
var registry = registry(registryKey);
var key = ResourceKey.create(registryKey, location);
try {
var holder = registry.getHolder(key).orElse(null);
if (holder == null) return false;
var oldValue = holder.value();
Field valueField = getField(Holder.Reference.class, "T");
valueField.setAccessible(true);
Field toIdField = getField(MappedRegistry.class, buildType(Reference2IntMap.class, "T"));
toIdField.setAccessible(true);
Field byValueField = getField(MappedRegistry.class, buildType(Map.class, "T", buildType(Holder.Reference.class, "T")));
byValueField.setAccessible(true);
Field lifecyclesField = getField(MappedRegistry.class, buildType(Map.class, "T", Lifecycle.class.getName()));
lifecyclesField.setAccessible(true);
var toId = (Reference2IntMap<T>) toIdField.get(registry);
var byValue = (Map<T, Holder.Reference<T>>) byValueField.get(registry);
var lifecycles = (Map<T, Lifecycle>) lifecyclesField.get(registry);
valueField.set(holder, value);
toId.put(value, toId.removeInt(oldValue));
byValue.put(value, byValue.remove(oldValue));
lifecycles.put(value, lifecycles.remove(oldValue));
return true;
} catch (Throwable e) {
throw new IllegalStateException(e);
}
}
private <T> MappedRegistry<T> registry(ResourceKey<Registry<T>> registryKey) {
var rawRegistry = registry().registry(registryKey).orElse(null);
if (!(rawRegistry instanceof MappedRegistry<T> registry))
throw new IllegalStateException("The Registry is not a mapped Registry!");
return registry;
}
private static String buildType(Class<?> clazz, String... parameterTypes) {
if (parameterTypes.length == 0) return clazz.getName();
var builder = new StringBuilder(clazz.getName())
.append("<");
for (int i = 0; i < parameterTypes.length; i++) {
builder.append(parameterTypes[i]).append(parameterTypes.length - 1 == i ? ">" : ", ");
}
return builder.toString();
}
private static Field getField(Class<?> clazz, String type) throws NoSuchFieldException {
try {
for (Field f : clazz.getDeclaredFields()) {
if (f.getGenericType().getTypeName().equals(type))
return f;
}
throw new NoSuchFieldException(type);
} catch (NoSuchFieldException e) {
Class<?> superClass = clazz.getSuperclass();
if (superClass == null) throw e;
return getField(superClass, type);
}
}
private static Field getField(Class<?> clazz, Class<?> fieldType) throws NoSuchFieldException {
try {
for (Field f : clazz.getDeclaredFields()) {
@@ -539,4 +715,75 @@ public class NMSBinding implements INMSBinding {
}
}
}
public void injectBukkit() {
try {
Iris.info("Injecting Bukkit");
new ByteBuddy()
.redefine(CraftServer.class)
.visit(Advice.to(CraftServerAdvice.class).on(ElementMatchers.isMethod().and(ElementMatchers.takesArguments(WorldCreator.class))))
.make()
.load(CraftServer.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
new ByteBuddy()
.redefine(ServerLevel.class)
.visit(Advice.to(ServerLevelAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(MinecraftServer.class, Executor.class, LevelStorageSource.LevelStorageAccess.class,
PrimaryLevelData.class, ResourceKey.class, LevelStem.class, ChunkProgressListener.class, boolean.class, long.class,
List.class, boolean.class, World.Environment.class, ChunkGenerator.class, BiomeProvider.class))))
.make()
.load(ServerLevel.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
Iris.info("Injected Bukkit Successfully!");
} catch (Exception e) {
Iris.info(C.RED + "Failed to Inject Bukkit!");
e.printStackTrace();
Iris.reportError(e);
}
}
private static class ServerLevelAdvice {
@Advice.OnMethodEnter
static void enter(@Advice.Argument(0) MinecraftServer server, @Advice.Argument(2) LevelStorageSource.LevelStorageAccess access, @Advice.Argument(4) ResourceKey<Level> key, @Advice.Argument(value = 5, readOnly = false) LevelStem levelStem) {
File iris = new File(access.levelDirectory.path().toFile(), "iris");
if (!iris.exists() && !key.location().getPath().startsWith("iris/")) return;
ResourceKey<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) throw new IllegalStateException("Unable to find registry for dimension type " + typeKey);
Holder<DimensionType> holder = registry.getHolder(typeKey).orElse(null);
if (holder == null) throw new IllegalStateException("Unable to find dimension type " + typeKey);
levelStem = new LevelStem(holder, levelStem.generator());
}
}
private static class CraftServerAdvice {
@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
static boolean enter(@Advice.This CraftServer self, @Advice.Argument(0) WorldCreator creator) {
File isIrisWorld = new File(self.getWorldContainer(), creator.name() + "/iris");
boolean isFromIris = false;
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
for (StackTraceElement stack : stackTrace) {
if (stack.getClassName().contains("Iris")) {
isFromIris = true;
break;
}
}
if (isIrisWorld.exists() && !isFromIris) {
var logger = Logger.getLogger("Iris");
logger.warning("detected another Plugin trying to load " + creator.name() + ". This is not supported and will be ignored.");
if (System.getProperty("iris.debug", "false").equals("true")) {
new RuntimeException().printStackTrace();
}
return true;
}
return false;
}
@Advice.OnMethodExit
static void exit(@Advice.Enter boolean bool, @Advice.Return(readOnly = false) World returned) {
if (bool) {
returned = null;
}
}
}
}
@@ -2,6 +2,7 @@ package com.volmit.iris.core.nms.v1_20_R1;
import com.mojang.serialization.Codec;
import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.INMS;
import com.volmit.iris.engine.data.cache.AtomicCache;
import com.volmit.iris.engine.framework.Engine;
import com.volmit.iris.engine.object.IrisBiome;
@@ -125,8 +126,16 @@ public class CustomBiomeSource extends BiomeSource {
for (IrisBiome i : engine.getAllBiomes()) {
if (i.isCustom()) {
for (IrisBiomeCustom j : i.getCustomDerivitives()) {
ResourceLocation resourceLocation = new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId());
Biome biome = customRegistry.get(resourceLocation);
ResourceLocation location = new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId());
Biome biome = customRegistry.get(location);
if (biome == null) {
INMS.get().registerBiome(location.getNamespace(), j, false);
biome = customRegistry.get(location);
if (biome == null) {
Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName());
continue;
}
}
Optional<ResourceKey<Biome>> optionalBiomeKey = customRegistry.getResourceKey(biome);
if (optionalBiomeKey.isEmpty()) {
Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName());
@@ -1,12 +1,22 @@
package com.volmit.iris.core.nms.v1_20_R1;
import com.google.common.base.Preconditions;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.datafixers.util.Pair;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps;
import com.mojang.serialization.Lifecycle;
import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.INMSBinding;
import com.volmit.iris.engine.data.cache.AtomicCache;
import com.volmit.iris.engine.framework.Engine;
import com.volmit.iris.engine.object.IrisBiomeCustom;
import com.volmit.iris.engine.object.IrisDimension;
import com.volmit.iris.util.collection.KList;
import com.volmit.iris.util.collection.KMap;
import com.volmit.iris.util.format.C;
import com.volmit.iris.util.hunk.Hunk;
import com.volmit.iris.util.json.JSONObject;
import com.volmit.iris.util.mantle.Mantle;
@@ -17,16 +27,27 @@ import com.volmit.iris.util.nbt.mca.NBTWorld;
import com.volmit.iris.util.nbt.mca.palette.*;
import com.volmit.iris.util.nbt.tag.CompoundTag;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
import net.bytebuddy.matcher.ElementMatchers;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.MappedRegistry;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.NbtIo;
import net.minecraft.nbt.TagParser;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.EntityDimensions;
import net.minecraft.server.level.progress.ChunkProgressListener;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.RandomSequences;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.biome.BiomeSource;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
@@ -34,6 +55,10 @@ import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.storage.LevelStorageSource;
import net.minecraft.world.level.storage.PrimaryLevelData;
import org.bukkit.*;
import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData;
@@ -47,9 +72,8 @@ import org.bukkit.craftbukkit.v1_20_R1.entity.CraftDolphin;
import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftItemStack;
import org.bukkit.entity.Dolphin;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.entity.EntityType;
import org.bukkit.generator.BiomeProvider;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
@@ -59,13 +83,18 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Vector;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;
public class NMSBinding implements INMSBinding {
@@ -175,6 +204,17 @@ public class NMSBinding implements INMSBinding {
return null;
}
private RegistryAccess getRegistryAccess(World world) {
try {
var field = getField(Level.class, RegistryAccess.class);
field.setAccessible(true);
return (RegistryAccess) field.get(((CraftWorld) world).getHandle());
} catch (Throwable e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
@Override
public void deserializeTile(CompoundTag c, Location pos) {
((CraftWorld) pos.getWorld()).getHandle().getChunkAt(new BlockPos(pos.getBlockX(), 0, pos.getBlockZ())).setBlockEntityNbt(convert(c));
@@ -258,8 +298,7 @@ public class NMSBinding implements INMSBinding {
@Override
public Object getBiomeBase(World world, Biome biome) {
return CraftBlock.biomeToBiomeBase(((CraftWorld) world).getHandle()
.registryAccess().registry(Registries.BIOME).orElse(null), biome);
return CraftBlock.biomeToBiomeBase(getRegistryAccess(world).registry(Registries.BIOME).orElse(null), biome);
}
@Override
@@ -296,8 +335,11 @@ public class NMSBinding implements INMSBinding {
public int getBiomeId(Biome biome) {
for (World i : Bukkit.getWorlds()) {
if (i.getEnvironment().equals(World.Environment.NORMAL)) {
Registry<net.minecraft.world.level.biome.Biome> registry = ((CraftWorld) i).getHandle().registryAccess().registry(Registries.BIOME).orElse(null);
return registry.getId((net.minecraft.world.level.biome.Biome) getBiomeBase(registry, biome));
var registry = getRegistryAccess(i).registry(Registries.BIOME).orElse(null);
if (registry != null) {
var holder = (Holder<net.minecraft.world.level.biome.Biome>) getBiomeBase(registry, biome);
return registry.getId(holder.value());
}
}
}
@@ -509,6 +551,138 @@ public class NMSBinding implements INMSBinding {
return ((CraftWorld) location.getWorld()).spawn(location, type.getEntityClass(), null, reason);
}
@Override
public boolean registerDimension(String name, IrisDimension dimension) {
var registry = registry(Registries.DIMENSION_TYPE);
var baseLocation = switch (dimension.getEnvironment()) {
case NORMAL -> new ResourceLocation("minecraft", "overworld");
case NETHER -> new ResourceLocation("minecraft", "the_nether");
case THE_END -> new ResourceLocation("minecraft", "the_end");
case CUSTOM -> throw new IllegalArgumentException("Cannot register custom dimension");
};
var base = registry.getHolder(ResourceKey.create(Registries.DIMENSION_TYPE, baseLocation)).orElse(null);
if (base == null) return false;
var json = encode(DimensionType.CODEC, base).orElse(null);
if (json == null) return false;
var object = json.getAsJsonObject();
var height = dimension.getDimensionHeight();
object.addProperty("min_y", height.getMin());
object.addProperty("height", height.getMax() - height.getMin());
object.addProperty("logical_height", dimension.getLogicalHeight());
var value = decode(DimensionType.CODEC, object.toString()).map(Holder::value).orElse(null);
if (value == null) return false;
return register(Registries.DIMENSION_TYPE, new ResourceLocation("iris", name), value, true);
}
@Override
public boolean registerBiome(String dimensionId, IrisBiomeCustom biome, boolean replace) {
var biomeBase = decode(net.minecraft.world.level.biome.Biome.CODEC, biome.generateJson()).map(Holder::value).orElse(null);
if (biomeBase == null) return false;
return register(Registries.BIOME, new ResourceLocation(dimensionId, biome.getId()), biomeBase, replace);
}
private <T> Optional<T> decode(Codec<T> codec, String json) {
return codec.decode(JsonOps.INSTANCE, GsonHelper.parse(json)).get().left().map(Pair::getFirst);
}
private <T> Optional<JsonElement> encode(Codec<T> codec, T value) {
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.setAccessible(true);
boolean frozen = field.getBoolean(registry);
field.setBoolean(registry, false);
Field valueField = getField(Holder.Reference.class, "T");
valueField.setAccessible(true);
try {
var holder = registry.register(key, value, Lifecycle.stable());
if (frozen) valueField.set(holder, value);
return true;
} finally {
field.setBoolean(registry, frozen);
}
} catch (Throwable e) {
throw new IllegalStateException(e);
}
}
@SuppressWarnings("unchecked")
private <T> boolean replace(ResourceKey<Registry<T>> registryKey, ResourceLocation location, T value) {
Preconditions.checkArgument(registryKey != null, "The registryKey cannot be null!");
Preconditions.checkArgument(location != null, "The location cannot be null!");
Preconditions.checkArgument(value != null, "The value cannot be null!");
var registry = registry(registryKey);
var key = ResourceKey.create(registryKey, location);
try {
var holder = registry.getHolder(key).orElse(null);
if (holder == null) return false;
var oldValue = holder.value();
Field valueField = getField(Holder.Reference.class, "T");
valueField.setAccessible(true);
Field toIdField = getField(MappedRegistry.class, buildType(Reference2IntMap.class, "T"));
toIdField.setAccessible(true);
Field byValueField = getField(MappedRegistry.class, buildType(Map.class, "T", buildType(Holder.Reference.class, "T")));
byValueField.setAccessible(true);
Field lifecyclesField = getField(MappedRegistry.class, buildType(Map.class, "T", Lifecycle.class.getName()));
lifecyclesField.setAccessible(true);
var toId = (Reference2IntMap<T>) toIdField.get(registry);
var byValue = (Map<T, Holder.Reference<T>>) byValueField.get(registry);
var lifecycles = (Map<T, Lifecycle>) lifecyclesField.get(registry);
valueField.set(holder, value);
toId.put(value, toId.removeInt(oldValue));
byValue.put(value, byValue.remove(oldValue));
lifecycles.put(value, lifecycles.remove(oldValue));
return true;
} catch (Throwable e) {
throw new IllegalStateException(e);
}
}
private <T> MappedRegistry<T> registry(ResourceKey<Registry<T>> registryKey) {
var rawRegistry = registry().registry(registryKey).orElse(null);
if (!(rawRegistry instanceof MappedRegistry<T> registry))
throw new IllegalStateException("The Registry is not a mapped Registry!");
return registry;
}
private static String buildType(Class<?> clazz, String... parameterTypes) {
if (parameterTypes.length == 0) return clazz.getName();
var builder = new StringBuilder(clazz.getName())
.append("<");
for (int i = 0; i < parameterTypes.length; i++) {
builder.append(parameterTypes[i]).append(parameterTypes.length - 1 == i ? ">" : ", ");
}
return builder.toString();
}
private static Field getField(Class<?> clazz, String type) throws NoSuchFieldException {
try {
for (Field f : clazz.getDeclaredFields()) {
if (f.getGenericType().getTypeName().equals(type))
return f;
}
throw new NoSuchFieldException(type);
} catch (NoSuchFieldException e) {
Class<?> superClass = clazz.getSuperclass();
if (superClass == null) throw e;
return getField(superClass, type);
}
}
public void inject(long seed, Engine engine, World world) throws NoSuchFieldException, IllegalAccessException {
ServerLevel serverLevel = ((CraftWorld)world).getHandle();
Class<?> clazz = serverLevel.getChunkSource().chunkMap.generator.getClass();
@@ -538,4 +712,75 @@ public class NMSBinding implements INMSBinding {
}
}
}
public void injectBukkit() {
try {
Iris.info("Injecting Bukkit");
new ByteBuddy()
.redefine(CraftServer.class)
.visit(Advice.to(CraftServerAdvice.class).on(ElementMatchers.isMethod().and(ElementMatchers.takesArguments(WorldCreator.class))))
.make()
.load(CraftServer.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
new ByteBuddy()
.redefine(ServerLevel.class)
.visit(Advice.to(ServerLevelAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(MinecraftServer.class, Executor.class, LevelStorageSource.LevelStorageAccess.class,
PrimaryLevelData.class, ResourceKey.class, LevelStem.class, ChunkProgressListener.class, boolean.class, long.class,
List.class, boolean.class, RandomSequences.class, World.Environment.class, ChunkGenerator.class, BiomeProvider.class))))
.make()
.load(ServerLevel.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
Iris.info("Injected Bukkit Successfully!");
} catch (Exception e) {
Iris.info(C.RED + "Failed to Inject Bukkit!");
e.printStackTrace();
Iris.reportError(e);
}
}
private static class ServerLevelAdvice {
@Advice.OnMethodEnter
static void enter(@Advice.Argument(0) MinecraftServer server, @Advice.Argument(2) LevelStorageSource.LevelStorageAccess access, @Advice.Argument(4) ResourceKey<Level> key, @Advice.Argument(value = 5, readOnly = false) LevelStem levelStem) {
File iris = new File(access.levelDirectory.path().toFile(), "iris");
if (!iris.exists() && !key.location().getPath().startsWith("iris/")) return;
ResourceKey<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) throw new IllegalStateException("Unable to find registry for dimension type " + typeKey);
Holder<DimensionType> holder = registry.getHolder(typeKey).orElse(null);
if (holder == null) throw new IllegalStateException("Unable to find dimension type " + typeKey);
levelStem = new LevelStem(holder, levelStem.generator());
}
}
private static class CraftServerAdvice {
@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
static boolean enter(@Advice.This CraftServer self, @Advice.Argument(0) WorldCreator creator) {
File isIrisWorld = new File(self.getWorldContainer(), creator.name() + "/iris");
boolean isFromIris = false;
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
for (StackTraceElement stack : stackTrace) {
if (stack.getClassName().contains("Iris")) {
isFromIris = true;
break;
}
}
if (isIrisWorld.exists() && !isFromIris) {
var logger = Logger.getLogger("Iris");
logger.warning("detected another Plugin trying to load " + creator.name() + ". This is not supported and will be ignored.");
if (System.getProperty("iris.debug", "false").equals("true")) {
new RuntimeException().printStackTrace();
}
return true;
}
return false;
}
@Advice.OnMethodExit
static void exit(@Advice.Enter boolean bool, @Advice.Return(readOnly = false) World returned) {
if (bool) {
returned = null;
}
}
}
}
@@ -2,6 +2,7 @@ package com.volmit.iris.core.nms.v1_20_R2;
import com.mojang.serialization.Codec;
import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.INMS;
import com.volmit.iris.engine.data.cache.AtomicCache;
import com.volmit.iris.engine.framework.Engine;
import com.volmit.iris.engine.object.IrisBiome;
@@ -124,8 +125,16 @@ public class CustomBiomeSource extends BiomeSource {
for (IrisBiome i : engine.getAllBiomes()) {
if (i.isCustom()) {
for (IrisBiomeCustom j : i.getCustomDerivitives()) {
ResourceLocation resourceLocation = new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId());
Biome biome = customRegistry.get(resourceLocation);
ResourceLocation location = new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId());
Biome biome = customRegistry.get(location);
if (biome == null) {
INMS.get().registerBiome(location.getNamespace(), j, false);
biome = customRegistry.get(location);
if (biome == null) {
Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName());
continue;
}
}
Optional<ResourceKey<Biome>> optionalBiomeKey = customRegistry.getResourceKey(biome);
if (optionalBiomeKey.isEmpty()) {
Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName());
@@ -4,14 +4,44 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Vector;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;
import com.google.common.base.Preconditions;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.mojang.datafixers.util.Pair;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps;
import com.mojang.serialization.Lifecycle;
import com.volmit.iris.engine.object.IrisBiomeCustom;
import com.volmit.iris.engine.object.IrisDimension;
import com.volmit.iris.util.format.C;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
import net.bytebuddy.matcher.ElementMatchers;
import net.minecraft.core.MappedRegistry;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.progress.ChunkProgressListener;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.RandomSequences;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.storage.LevelStorageSource;
import net.minecraft.world.level.storage.PrimaryLevelData;
import org.bukkit.*;
import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData;
@@ -25,6 +55,7 @@ import org.bukkit.craftbukkit.v1_20_R2.util.CraftNamespacedKey;
import org.bukkit.entity.Dolphin;
import org.bukkit.entity.Entity;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.generator.BiomeProvider;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
@@ -173,6 +204,17 @@ public class NMSBinding implements INMSBinding {
return null;
}
private RegistryAccess getRegistryAccess(World world) {
try {
var field = getField(Level.class, RegistryAccess.class);
field.setAccessible(true);
return (RegistryAccess) field.get(((CraftWorld) world).getHandle());
} catch (Throwable e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
@Override
public void deserializeTile(CompoundTag c, Location pos) {
((CraftWorld) pos.getWorld()).getHandle().getChunkAt(new BlockPos(pos.getBlockX(), 0, pos.getBlockZ())).setBlockEntityNbt(convert(c));
@@ -256,8 +298,7 @@ public class NMSBinding implements INMSBinding {
@Override
public Object getBiomeBase(World world, Biome biome) {
return biomeToBiomeBase(((CraftWorld) world).getHandle()
.registryAccess().registry(Registries.BIOME).orElse(null), biome);
return biomeToBiomeBase(getRegistryAccess(world).registry(Registries.BIOME).orElse(null), biome);
}
@Override
@@ -294,8 +335,11 @@ public class NMSBinding implements INMSBinding {
public int getBiomeId(Biome biome) {
for (World i : Bukkit.getWorlds()) {
if (i.getEnvironment().equals(World.Environment.NORMAL)) {
Registry<net.minecraft.world.level.biome.Biome> registry = ((CraftWorld) i).getHandle().registryAccess().registry(Registries.BIOME).orElse(null);
return registry.getId((net.minecraft.world.level.biome.Biome) getBiomeBase(registry, biome));
var registry = getRegistryAccess(i).registry(Registries.BIOME).orElse(null);
if (registry != null) {
var holder = (Holder<net.minecraft.world.level.biome.Biome>) getBiomeBase(registry, biome);
return registry.getId(holder.value());
}
}
}
@@ -520,6 +564,139 @@ public class NMSBinding implements INMSBinding {
return ((CraftWorld) location.getWorld()).spawn(location, type.getEntityClass(), null, reason);
}
@Override
public boolean registerDimension(String name, IrisDimension dimension) {
var registry = registry(Registries.DIMENSION_TYPE);
var baseLocation = switch (dimension.getEnvironment()) {
case NORMAL -> new ResourceLocation("minecraft", "overworld");
case NETHER -> new ResourceLocation("minecraft", "the_nether");
case THE_END -> new ResourceLocation("minecraft", "the_end");
case CUSTOM -> throw new IllegalArgumentException("Cannot register custom dimension");
};
var base = registry.getHolder(ResourceKey.create(Registries.DIMENSION_TYPE, baseLocation)).orElse(null);
if (base == null) return false;
var json = encode(DimensionType.CODEC, base).orElse(null);
if (json == null) return false;
var object = json.getAsJsonObject();
var height = dimension.getDimensionHeight();
object.addProperty("min_y", height.getMin());
object.addProperty("height", height.getMax() - height.getMin());
object.addProperty("logical_height", dimension.getLogicalHeight());
var value = decode(DimensionType.CODEC, object.toString()).map(Holder::value).orElse(null);
if (value == null) return false;
return register(Registries.DIMENSION_TYPE, new ResourceLocation("iris", name), value, true);
}
@Override
public boolean registerBiome(String dimensionId, IrisBiomeCustom biome, boolean replace) {
var biomeBase = decode(net.minecraft.world.level.biome.Biome.CODEC, biome.generateJson()).map(Holder::value).orElse(null);
if (biomeBase == null) return false;
return register(Registries.BIOME, new ResourceLocation(dimensionId, biome.getId()), biomeBase, replace);
}
private <T> Optional<T> decode(Codec<T> codec, String json) {
return codec.decode(JsonOps.INSTANCE, GsonHelper.parse(json)).get().left().map(Pair::getFirst);
}
private <T> Optional<JsonElement> encode(Codec<T> codec, T value) {
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.setAccessible(true);
boolean frozen = field.getBoolean(registry);
field.setBoolean(registry, false);
Field valueField = getField(Holder.Reference.class, "T");
valueField.setAccessible(true);
try {
var holder = registry.register(key, value, Lifecycle.stable());
if (frozen) valueField.set(holder, value);
return true;
} finally {
field.setBoolean(registry, frozen);
}
} catch (Throwable e) {
throw new IllegalStateException(e);
}
}
@SuppressWarnings("unchecked")
private <T> boolean replace(ResourceKey<Registry<T>> registryKey, ResourceLocation location, T value) {
Preconditions.checkArgument(registryKey != null, "The registryKey cannot be null!");
Preconditions.checkArgument(location != null, "The location cannot be null!");
Preconditions.checkArgument(value != null, "The value cannot be null!");
var registry = registry(registryKey);
var key = ResourceKey.create(registryKey, location);
try {
var holder = registry.getHolder(key).orElse(null);
if (holder == null) return false;
var oldValue = holder.value();
Field valueField = getField(Holder.Reference.class, "T");
valueField.setAccessible(true);
Field toIdField = getField(MappedRegistry.class, buildType(Reference2IntMap.class, "T"));
toIdField.setAccessible(true);
Field byValueField = getField(MappedRegistry.class, buildType(Map.class, "T", buildType(Holder.Reference.class, "T")));
byValueField.setAccessible(true);
Field lifecyclesField = getField(MappedRegistry.class, buildType(Map.class, "T", Lifecycle.class.getName()));
lifecyclesField.setAccessible(true);
var toId = (Reference2IntMap<T>) toIdField.get(registry);
var byValue = (Map<T, Holder.Reference<T>>) byValueField.get(registry);
var lifecycles = (Map<T, Lifecycle>) lifecyclesField.get(registry);
valueField.set(holder, value);
toId.put(value, toId.removeInt(oldValue));
byValue.put(value, byValue.remove(oldValue));
lifecycles.put(value, lifecycles.remove(oldValue));
return true;
} catch (Throwable e) {
throw new IllegalStateException(e);
}
}
private <T> MappedRegistry<T> registry(ResourceKey<Registry<T>> registryKey) {
var rawRegistry = registry().registry(registryKey).orElse(null);
if (!(rawRegistry instanceof MappedRegistry<T> registry))
throw new IllegalStateException("The Registry is not a mapped Registry!");
return registry;
}
private static String buildType(Class<?> clazz, String... parameterTypes) {
if (parameterTypes.length == 0) return clazz.getName();
var builder = new StringBuilder(clazz.getName())
.append("<");
for (int i = 0; i < parameterTypes.length; i++) {
builder.append(parameterTypes[i]).append(parameterTypes.length - 1 == i ? ">" : ", ");
}
return builder.toString();
}
private static Field getField(Class<?> clazz, String type) throws NoSuchFieldException {
try {
for (Field f : clazz.getDeclaredFields()) {
if (f.getGenericType().getTypeName().equals(type))
return f;
}
throw new NoSuchFieldException(type);
} catch (NoSuchFieldException e) {
Class<?> superClass = clazz.getSuperclass();
if (superClass == null) throw e;
return getField(superClass, type);
}
}
private static Field getField(Class<?> clazz, Class<?> fieldType) throws NoSuchFieldException {
try {
for (Field f : clazz.getDeclaredFields()) {
@@ -540,4 +717,75 @@ public class NMSBinding implements INMSBinding {
public static Holder<net.minecraft.world.level.biome.Biome> biomeToBiomeBase(Registry<net.minecraft.world.level.biome.Biome> registry, Biome biome) {
return registry.getHolderOrThrow(ResourceKey.create(Registries.BIOME, CraftNamespacedKey.toMinecraft(biome.getKey())));
}
public void injectBukkit() {
try {
Iris.info("Injecting Bukkit");
new ByteBuddy()
.redefine(CraftServer.class)
.visit(Advice.to(CraftServerAdvice.class).on(ElementMatchers.isMethod().and(ElementMatchers.takesArguments(WorldCreator.class))))
.make()
.load(CraftServer.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
new ByteBuddy()
.redefine(ServerLevel.class)
.visit(Advice.to(ServerLevelAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(MinecraftServer.class, Executor.class, LevelStorageSource.LevelStorageAccess.class,
PrimaryLevelData.class, ResourceKey.class, LevelStem.class, ChunkProgressListener.class, boolean.class, long.class,
List.class, boolean.class, RandomSequences.class, World.Environment.class, ChunkGenerator.class, BiomeProvider.class))))
.make()
.load(ServerLevel.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
Iris.info("Injected Bukkit Successfully!");
} catch (Exception e) {
Iris.info(C.RED + "Failed to Inject Bukkit!");
e.printStackTrace();
Iris.reportError(e);
}
}
private static class ServerLevelAdvice {
@Advice.OnMethodEnter
static void enter(@Advice.Argument(0) MinecraftServer server, @Advice.Argument(2) LevelStorageSource.LevelStorageAccess access, @Advice.Argument(4) ResourceKey<Level> key, @Advice.Argument(value = 5, readOnly = false) LevelStem levelStem) {
File iris = new File(access.levelDirectory.path().toFile(), "iris");
if (!iris.exists() && !key.location().getPath().startsWith("iris/")) return;
ResourceKey<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) throw new IllegalStateException("Unable to find registry for dimension type " + typeKey);
Holder<DimensionType> holder = registry.getHolder(typeKey).orElse(null);
if (holder == null) throw new IllegalStateException("Unable to find dimension type " + typeKey);
levelStem = new LevelStem(holder, levelStem.generator());
}
}
private static class CraftServerAdvice {
@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
static boolean enter(@Advice.This CraftServer self, @Advice.Argument(0) WorldCreator creator) {
File isIrisWorld = new File(self.getWorldContainer(), creator.name() + "/iris");
boolean isFromIris = false;
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
for (StackTraceElement stack : stackTrace) {
if (stack.getClassName().contains("Iris")) {
isFromIris = true;
break;
}
}
if (isIrisWorld.exists() && !isFromIris) {
var logger = Logger.getLogger("Iris");
logger.warning("detected another Plugin trying to load " + creator.name() + ". This is not supported and will be ignored.");
if (System.getProperty("iris.debug", "false").equals("true")) {
new RuntimeException().printStackTrace();
}
return true;
}
return false;
}
@Advice.OnMethodExit
static void exit(@Advice.Enter boolean bool, @Advice.Return(readOnly = false) World returned) {
if (bool) {
returned = null;
}
}
}
}
@@ -2,6 +2,7 @@ package com.volmit.iris.core.nms.v1_20_R3;
import com.mojang.serialization.Codec;
import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.INMS;
import com.volmit.iris.engine.data.cache.AtomicCache;
import com.volmit.iris.engine.framework.Engine;
import com.volmit.iris.engine.object.IrisBiome;
@@ -124,8 +125,16 @@ public class CustomBiomeSource extends BiomeSource {
for (IrisBiome i : engine.getAllBiomes()) {
if (i.isCustom()) {
for (IrisBiomeCustom j : i.getCustomDerivitives()) {
ResourceLocation resourceLocation = new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId());
Biome biome = customRegistry.get(resourceLocation);
ResourceLocation location = new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId());
Biome biome = customRegistry.get(location);
if (biome == null) {
INMS.get().registerBiome(location.getNamespace(), j, false);
biome = customRegistry.get(location);
if (biome == null) {
Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName());
continue;
}
}
Optional<ResourceKey<Biome>> optionalBiomeKey = customRegistry.getResourceKey(biome);
if (optionalBiomeKey.isEmpty()) {
Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName());
@@ -0,0 +1,256 @@
package com.volmit.iris.core.nms.v1_20_R3;
import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.BiomeBaseInjector;
import com.volmit.iris.core.nms.IHeadless;
import com.volmit.iris.core.nms.v1_20_R3.mca.MCATerrainChunk;
import com.volmit.iris.core.nms.v1_20_R3.mca.RegionFileStorage;
import com.volmit.iris.core.pregenerator.PregenListener;
import com.volmit.iris.engine.framework.Engine;
import com.volmit.iris.engine.framework.EngineStage;
import com.volmit.iris.engine.framework.WrongEngineBroException;
import com.volmit.iris.engine.object.IrisBiome;
import com.volmit.iris.util.collection.KMap;
import com.volmit.iris.util.context.ChunkContext;
import com.volmit.iris.util.context.IrisContext;
import com.volmit.iris.util.documentation.BlockCoordinates;
import com.volmit.iris.util.documentation.RegionCoordinates;
import com.volmit.iris.util.hunk.Hunk;
import com.volmit.iris.util.hunk.view.BiomeGridHunkHolder;
import com.volmit.iris.util.hunk.view.ChunkDataHunkHolder;
import com.volmit.iris.util.mantle.MantleFlag;
import com.volmit.iris.util.math.RNG;
import com.volmit.iris.util.parallel.MultiBurst;
import com.volmit.iris.util.scheduling.Looper;
import com.volmit.iris.util.scheduling.PrecisionStopwatch;
import net.minecraft.core.Holder;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.chunk.ProtoChunk;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.block.data.BlockData;
import java.io.File;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
public class Headless implements IHeadless, LevelHeightAccessor {
private final NMSBinding binding;
private final Engine engine;
private final RegionFileStorage storage;
private final Queue<ProtoChunk> chunkQueue = new ArrayDeque<>();
private final ReentrantLock saveLock = new ReentrantLock();
private final KMap<String, Holder<Biome>> customBiomes = new KMap<>();
private final KMap<NamespacedKey, Holder<Biome>> minecraftBiomes = new KMap<>();
private boolean closed = false;
public Headless(NMSBinding binding, Engine engine) {
this.binding = binding;
this.engine = engine;
this.storage = new RegionFileStorage(new File(engine.getWorld().worldFolder(), "region").toPath(), false);
var queueLooper = new Looper() {
@Override
protected long loop() {
save();
return closed ? -1 : 100;
}
};
queueLooper.setName("Region Save Looper");
queueLooper.start();
var dimKey = engine.getDimension().getLoadKey();
for (var biome : engine.getAllBiomes()) {
if (!biome.isCustom()) continue;
for (var custom : biome.getCustomDerivitives()) {
binding.registerBiome(dimKey, custom, false);
}
}
}
@Override
public boolean exists(int x, int z) {
if (closed) return false;
try {
CompoundTag tag = storage.read(new ChunkPos(x, z));
return tag != null && !"empty".equals(tag.getString("Status"));
} catch (IOException e) {
return false;
}
}
@Override
public void save() {
if (closed) return;
saveLock.lock();
try {
while (!chunkQueue.isEmpty()) {
ChunkAccess chunk = chunkQueue.poll();
if (chunk == null) break;
try {
storage.write(chunk.getPos(), binding.serializeChunk(chunk, this));
} catch (Throwable e) {
Iris.error("Failed to save chunk " + chunk.getPos().x + ", " + chunk.getPos().z);
e.printStackTrace();
}
}
} finally {
saveLock.unlock();
}
}
@Override
public void generateRegion(MultiBurst burst, int x, int z, PregenListener listener) {
if (closed) return;
boolean listening = listener != null;
if (listening) listener.onRegionGenerating(x, z);
CountDownLatch latch = new CountDownLatch(1024);
iterateRegion(x, z, pos -> burst.complete(() -> {
if (listening) listener.onChunkGenerating(pos.x, pos.z);
generateChunk(pos.x, pos.z);
if (listening) listener.onChunkGenerated(pos.x, pos.z);
latch.countDown();
}));
try {
latch.await();
} catch (InterruptedException ignored) {}
if (listening) listener.onRegionGenerated(x, z);
}
@RegionCoordinates
private static void iterateRegion(int x, int z, Consumer<ChunkPos> chunkPos) {
int cX = x << 5;
int cZ = z << 5;
for (int xx = 0; xx < 32; xx++) {
for (int zz = 0; zz < 32; zz++) {
chunkPos.accept(new ChunkPos(cX + xx, cZ + zz));
}
}
}
@Override
public void generateChunk(int x, int z) {
if (closed || exists(x, z)) return;
try {
var pos = new ChunkPos(x, z);
ProtoChunk chunk = binding.createProtoChunk(pos, this);
var tc = new MCATerrainChunk(chunk);
ChunkDataHunkHolder blocks = new ChunkDataHunkHolder(tc);
BiomeGridHunkHolder biomes = new BiomeGridHunkHolder(tc, tc.getMinHeight(), tc.getMaxHeight());
ChunkContext ctx = generate(engine, pos.x << 4, pos.z << 4, blocks, biomes);
blocks.apply();
biomes.apply();
inject(engine, tc.getBiomeBaseInjector(), chunk, ctx); //TODO improve
chunk.setStatus(ChunkStatus.FULL);
chunkQueue.add(chunk);
} catch (Throwable e) {
Iris.error("Failed to generate " + x + ", " + z);
e.printStackTrace();
}
}
@BlockCoordinates
private ChunkContext generate(Engine engine, int x, int z, Hunk<BlockData> vblocks, Hunk<org.bukkit.block.Biome> vbiomes) throws WrongEngineBroException {
if (engine.isClosed()) {
throw new WrongEngineBroException();
}
engine.getContext().touch();
engine.getEngineData().getStatistics().generatedChunk();
ChunkContext ctx = null;
try {
PrecisionStopwatch p = PrecisionStopwatch.start();
Hunk<BlockData> blocks = vblocks.listen((xx, y, zz, t) -> engine.catchBlockUpdates(x + xx, y + engine.getMinHeight(), z + zz, t));
var dimension = engine.getDimension();
if (dimension.isDebugChunkCrossSections() && ((x >> 4) % dimension.getDebugCrossSectionsMod() == 0 || (z >> 4) % dimension.getDebugCrossSectionsMod() == 0)) {
for (int i = 0; i < 16; i++) {
for (int j = 0; j < 16; j++) {
blocks.set(i, 0, j, Material.CRYING_OBSIDIAN.createBlockData());
}
}
} else {
ctx = new ChunkContext(x, z, engine.getComplex());
IrisContext.getOr(engine).setChunkContext(ctx);
for (EngineStage i : engine.getMode().getStages()) {
i.generate(x, z, blocks, vbiomes, false, ctx);
}
}
engine.getMantle().getMantle().flag(x >> 4, z >> 4, MantleFlag.REAL, true);
engine.getMetrics().getTotal().put(p.getMilliseconds());
engine.addGenerated();
} catch (Throwable e) {
Iris.reportError(e);
engine.fail("Failed to generate " + x + ", " + z, e);
}
return ctx;
}
private void inject(Engine engine, BiomeBaseInjector injector, ChunkAccess chunk, ChunkContext ctx) {
var pos = chunk.getPos();
for (int y = engine.getMinHeight(); y < engine.getMaxHeight(); y++) {
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
int wX = pos.getBlockX(x);
int wZ = pos.getBlockZ(z);
try {
injector.setBiome(x, y, z, getNoiseBiome(engine, ctx, x, z, wX, y, wZ));
} catch (Throwable e) {
Iris.error("Failed to inject biome for " + wX + ", " + y + ", " + wZ);
e.printStackTrace();
}
}
}
}
}
private Holder<Biome> getNoiseBiome(Engine engine, ChunkContext ctx, int rX, int rZ, int x, int y, int z) {
RNG rng = new RNG(engine.getSeedManager().getBiome());
int m = (y - engine.getMinHeight()) << 2;
IrisBiome ib = ctx == null ?
engine.getComplex().getTrueBiomeStream().get(x << 2, z << 2) :
ctx.getBiome().get(rX, rZ);
if (ib.isCustom()) {
return customBiomes.computeIfAbsent(ib.getCustomBiome(rng, x << 2, m, z << 2).getId(),
id -> binding.getBiomeHolder(engine.getDimension().getLoadKey(), id));
} else {
return minecraftBiomes.computeIfAbsent(ib.getSkyBiome(rng, x << 2, m, z << 2).getKey(),
id -> binding.getBiomeHolder(id.getNamespace(), id.getKey()));
}
}
@Override
public void close() throws IOException {
if (closed) return;
try {
storage.close();
} finally {
closed = true;
customBiomes.clear();
minecraftBiomes.clear();
}
}
@Override
public int getHeight() {
return engine.getHeight();
}
@Override
public int getMinBuildHeight() {
return engine.getMinHeight();
}
}
@@ -4,14 +4,52 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import java.util.*;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;
import com.google.common.base.Preconditions;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.mojang.datafixers.util.Pair;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps;
import com.mojang.serialization.Lifecycle;
import com.volmit.iris.core.nms.IHeadless;
import com.volmit.iris.core.nms.v1_20_R3.mca.ChunkSerializer;
import com.volmit.iris.engine.object.IrisBiomeCustom;
import com.volmit.iris.engine.object.IrisDimension;
import com.volmit.iris.util.format.C;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
import net.bytebuddy.implementation.bytecode.StackManipulation;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
import net.bytebuddy.matcher.ElementMatchers;
import net.minecraft.core.IdMapper;
import net.minecraft.core.MappedRegistry;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.progress.ChunkProgressListener;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.RandomSequences;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.chunk.UpgradeData;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.storage.LevelStorageSource;
import net.minecraft.world.level.storage.PrimaryLevelData;
import org.bukkit.*;
import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData;
@@ -25,6 +63,7 @@ import org.bukkit.craftbukkit.v1_20_R3.util.CraftNamespacedKey;
import org.bukkit.entity.Dolphin;
import org.bukkit.entity.Entity;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.generator.BiomeProvider;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
@@ -67,6 +106,8 @@ import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.chunk.LevelChunk;
import sun.misc.Unsafe;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
public class NMSBinding implements INMSBinding {
private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
private final BlockData AIR = Material.AIR.createBlockData();
@@ -173,6 +214,17 @@ public class NMSBinding implements INMSBinding {
return null;
}
private RegistryAccess getRegistryAccess(World world) {
try {
var field = getField(Level.class, RegistryAccess.class);
field.setAccessible(true);
return (RegistryAccess) field.get(((CraftWorld) world).getHandle());
} catch (Throwable e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
@Override
public void deserializeTile(CompoundTag c, Location pos) {
((CraftWorld) pos.getWorld()).getHandle().getChunkAt(new BlockPos(pos.getBlockX(), 0, pos.getBlockZ())).setBlockEntityNbt(convert(c));
@@ -256,8 +308,7 @@ public class NMSBinding implements INMSBinding {
@Override
public Object getBiomeBase(World world, Biome biome) {
return biomeToBiomeBase(((CraftWorld) world).getHandle()
.registryAccess().registry(Registries.BIOME).orElse(null), biome);
return biomeToBiomeBase(getRegistryAccess(world).registry(Registries.BIOME).orElse(null), biome);
}
@Override
@@ -294,8 +345,11 @@ public class NMSBinding implements INMSBinding {
public int getBiomeId(Biome biome) {
for (World i : Bukkit.getWorlds()) {
if (i.getEnvironment().equals(World.Environment.NORMAL)) {
Registry<net.minecraft.world.level.biome.Biome> registry = ((CraftWorld) i).getHandle().registryAccess().registry(Registries.BIOME).orElse(null);
return registry.getId((net.minecraft.world.level.biome.Biome) getBiomeBase(registry, biome));
var registry = getRegistryAccess(i).registry(Registries.BIOME).orElse(null);
if (registry != null) {
var holder = (Holder<net.minecraft.world.level.biome.Biome>) getBiomeBase(registry, biome);
return registry.getId(holder.value());
}
}
}
@@ -415,13 +469,13 @@ public class NMSBinding implements INMSBinding {
@Override
public MCAPaletteAccess createPalette() {
MCAIdMapper<BlockState> registry = registryCache.aquireNasty(() -> {
Field cf = net.minecraft.core.IdMapper.class.getDeclaredField("tToId");
Field df = net.minecraft.core.IdMapper.class.getDeclaredField("idToT");
Field bf = net.minecraft.core.IdMapper.class.getDeclaredField("nextId");
Field cf = IdMapper.class.getDeclaredField("tToId");
Field df = IdMapper.class.getDeclaredField("idToT");
Field bf = IdMapper.class.getDeclaredField("nextId");
cf.setAccessible(true);
df.setAccessible(true);
bf.setAccessible(true);
net.minecraft.core.IdMapper<BlockState> blockData = Block.BLOCK_STATE_REGISTRY;
IdMapper<BlockState> blockData = Block.BLOCK_STATE_REGISTRY;
int b = bf.getInt(blockData);
Object2IntMap<BlockState> c = (Object2IntMap<BlockState>) cf.get(blockData);
List<BlockState> d = (List<BlockState>) df.get(blockData);
@@ -515,12 +569,143 @@ public class NMSBinding implements INMSBinding {
return null;
}
@Override
public Entity spawnEntity(Location location, org.bukkit.entity.EntityType type, CreatureSpawnEvent.SpawnReason reason) {
return ((CraftWorld) location.getWorld()).spawn(location, type.getEntityClass(), null, reason);
}
@Override
public boolean registerDimension(String name, IrisDimension dimension) {
var registry = registry(Registries.DIMENSION_TYPE);
var baseLocation = switch (dimension.getEnvironment()) {
case NORMAL -> new ResourceLocation("minecraft", "overworld");
case NETHER -> new ResourceLocation("minecraft", "the_nether");
case THE_END -> new ResourceLocation("minecraft", "the_end");
case CUSTOM -> throw new IllegalArgumentException("Cannot register custom dimension");
};
var base = registry.getHolder(ResourceKey.create(Registries.DIMENSION_TYPE, baseLocation)).orElse(null);
if (base == null) return false;
var json = encode(DimensionType.CODEC, base).orElse(null);
if (json == null) return false;
var object = json.getAsJsonObject();
var height = dimension.getDimensionHeight();
object.addProperty("min_y", height.getMin());
object.addProperty("height", height.getMax() - height.getMin());
object.addProperty("logical_height", dimension.getLogicalHeight());
var value = decode(DimensionType.CODEC, object.toString()).map(Holder::value).orElse(null);
if (value == null) return false;
return register(Registries.DIMENSION_TYPE, new ResourceLocation("iris", name), value, true);
}
@Override
public boolean registerBiome(String dimensionId, IrisBiomeCustom biome, boolean replace) {
var biomeBase = decode(net.minecraft.world.level.biome.Biome.CODEC, biome.generateJson()).map(Holder::value).orElse(null);
if (biomeBase == null) return false;
return register(Registries.BIOME, new ResourceLocation(dimensionId, biome.getId()), biomeBase, replace);
}
private <T> Optional<T> decode(Codec<T> codec, String json) {
return codec.decode(JsonOps.INSTANCE, GsonHelper.parse(json)).get().left().map(Pair::getFirst);
}
private <T> Optional<JsonElement> encode(Codec<T> codec, T value) {
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.setAccessible(true);
boolean frozen = field.getBoolean(registry);
field.setBoolean(registry, false);
Field valueField = getField(Holder.Reference.class, "T");
valueField.setAccessible(true);
try {
var holder = registry.register(key, value, Lifecycle.stable());
if (frozen) valueField.set(holder, value);
return true;
} finally {
field.setBoolean(registry, frozen);
}
} catch (Throwable e) {
throw new IllegalStateException(e);
}
}
@SuppressWarnings("unchecked")
private <T> boolean replace(ResourceKey<Registry<T>> registryKey, ResourceLocation location, T value) {
Preconditions.checkArgument(registryKey != null, "The registryKey cannot be null!");
Preconditions.checkArgument(location != null, "The location cannot be null!");
Preconditions.checkArgument(value != null, "The value cannot be null!");
var registry = registry(registryKey);
var key = ResourceKey.create(registryKey, location);
try {
var holder = registry.getHolder(key).orElse(null);
if (holder == null) return false;
var oldValue = holder.value();
Field valueField = getField(Holder.Reference.class, "T");
valueField.setAccessible(true);
Field toIdField = getField(MappedRegistry.class, buildType(Reference2IntMap.class, "T"));
toIdField.setAccessible(true);
Field byValueField = getField(MappedRegistry.class, buildType(Map.class, "T", buildType(Holder.Reference.class, "T")));
byValueField.setAccessible(true);
Field lifecyclesField = getField(MappedRegistry.class, buildType(Map.class, "T", Lifecycle.class.getName()));
lifecyclesField.setAccessible(true);
var toId = (Reference2IntMap<T>) toIdField.get(registry);
var byValue = (Map<T, Holder.Reference<T>>) byValueField.get(registry);
var lifecycles = (Map<T, Lifecycle>) lifecyclesField.get(registry);
valueField.set(holder, value);
toId.put(value, toId.removeInt(oldValue));
byValue.put(value, byValue.remove(oldValue));
lifecycles.put(value, lifecycles.remove(oldValue));
return true;
} catch (Throwable e) {
throw new IllegalStateException(e);
}
}
private <T> MappedRegistry<T> registry(ResourceKey<Registry<T>> registryKey) {
var rawRegistry = registry().registry(registryKey).orElse(null);
if (!(rawRegistry instanceof MappedRegistry<T> registry))
throw new IllegalStateException("The Registry is not a mapped Registry!");
return registry;
}
private static String buildType(Class<?> clazz, String... parameterTypes) {
if (parameterTypes.length == 0) return clazz.getName();
var builder = new StringBuilder(clazz.getName())
.append("<");
for (int i = 0; i < parameterTypes.length; i++) {
builder.append(parameterTypes[i]).append(parameterTypes.length - 1 == i ? ">" : ", ");
}
return builder.toString();
}
private static Field getField(Class<?> clazz, String type) throws NoSuchFieldException {
try {
for (Field f : clazz.getDeclaredFields()) {
if (f.getGenericType().getTypeName().equals(type))
return f;
}
throw new NoSuchFieldException(type);
} catch (NoSuchFieldException e) {
Class<?> superClass = clazz.getSuperclass();
if (superClass == null) throw e;
return getField(superClass, type);
}
}
private static Field getField(Class<?> clazz, Class<?> fieldType) throws NoSuchFieldException {
try {
for (Field f : clazz.getDeclaredFields()) {
@@ -541,4 +726,92 @@ public class NMSBinding implements INMSBinding {
public static Holder<net.minecraft.world.level.biome.Biome> biomeToBiomeBase(Registry<net.minecraft.world.level.biome.Biome> registry, Biome biome) {
return registry.getHolderOrThrow(ResourceKey.create(Registries.BIOME, CraftNamespacedKey.toMinecraft(biome.getKey())));
}
public void injectBukkit() {
try {
Iris.info("Injecting Bukkit");
new ByteBuddy()
.redefine(CraftServer.class)
.visit(Advice.to(CraftServerAdvice.class).on(ElementMatchers.isMethod().and(ElementMatchers.takesArguments(WorldCreator.class))))
.make()
.load(CraftServer.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
new ByteBuddy()
.redefine(ServerLevel.class)
.visit(Advice.to(ServerLevelAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(MinecraftServer.class, Executor.class, LevelStorageSource.LevelStorageAccess.class,
PrimaryLevelData.class, ResourceKey.class, LevelStem.class, ChunkProgressListener.class, boolean.class, long.class,
List.class, boolean.class, RandomSequences.class, World.Environment.class, ChunkGenerator.class, BiomeProvider.class))))
.make()
.load(ServerLevel.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
Iris.info("Injected Bukkit Successfully!");
} catch (Exception e) {
Iris.info(C.RED + "Failed to Inject Bukkit!");
e.printStackTrace();
Iris.reportError(e);
}
}
@Override
public IHeadless createHeadless(Engine engine) {
return new Headless(this, engine);
}
private static class ServerLevelAdvice {
@Advice.OnMethodEnter
static void enter(@Advice.Argument(0) MinecraftServer server, @Advice.Argument(2) LevelStorageSource.LevelStorageAccess access, @Advice.Argument(4) ResourceKey<Level> key, @Advice.Argument(value = 5, readOnly = false) LevelStem levelStem) {
File iris = new File(access.levelDirectory.path().toFile(), "iris");
if (!iris.exists() && !key.location().getPath().startsWith("iris/")) return;
ResourceKey<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) throw new IllegalStateException("Unable to find registry for dimension type " + typeKey);
Holder<DimensionType> holder = registry.getHolder(typeKey).orElse(null);
if (holder == null) throw new IllegalStateException("Unable to find dimension type " + typeKey);
levelStem = new LevelStem(holder, levelStem.generator());
}
}
private static class CraftServerAdvice {
@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
static boolean enter(@Advice.This CraftServer self, @Advice.Argument(0) WorldCreator creator) {
File isIrisWorld = new File(self.getWorldContainer(), creator.name() + "/iris");
boolean isFromIris = false;
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
for (StackTraceElement stack : stackTrace) {
if (stack.getClassName().contains("Iris")) {
isFromIris = true;
break;
}
}
if (isIrisWorld.exists() && !isFromIris) {
var logger = Logger.getLogger("Iris");
logger.warning("detected another Plugin trying to load " + creator.name() + ". This is not supported and will be ignored.");
if (System.getProperty("iris.debug", "false").equals("true")) {
new RuntimeException().printStackTrace();
}
return true;
}
return false;
}
@Advice.OnMethodExit
static void exit(@Advice.Enter boolean bool, @Advice.Return(readOnly = false) World returned) {
if (bool) {
returned = null;
}
}
}
Holder.Reference<net.minecraft.world.level.biome.Biome> getBiomeHolder(String namespace, String id) {
return getCustomBiomeRegistry().getHolder(ResourceKey.create(Registries.BIOME, new ResourceLocation(namespace, id))).orElse(null);
}
ProtoChunk createProtoChunk(ChunkPos pos, LevelHeightAccessor heightAccessor) {
return new ProtoChunk(pos, UpgradeData.EMPTY, heightAccessor, getCustomBiomeRegistry(), null);
}
net.minecraft.nbt.CompoundTag serializeChunk(ChunkAccess chunkAccess, LevelHeightAccessor heightAccessor) {
return ChunkSerializer.write(chunkAccess, heightAccessor, getCustomBiomeRegistry());
}
}
@@ -0,0 +1,181 @@
package com.volmit.iris.core.nms.v1_20_R3.mca;
import com.mojang.logging.LogUtils;
import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import it.unimi.dsi.fastutil.shorts.ShortList;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map.Entry;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.LongArrayTag;
import net.minecraft.nbt.NbtOps;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.nbt.ShortTag;
import net.minecraft.nbt.Tag;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.chunk.CarvingMask;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.PalettedContainerRO;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.chunk.UpgradeData;
import net.minecraft.world.level.chunk.ChunkAccess.TicksToSave;
import net.minecraft.world.level.levelgen.BelowZeroRetrogen;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.GenerationStep.Carving;
import net.minecraft.world.level.levelgen.Heightmap.Types;
import net.minecraft.world.level.levelgen.blending.BlendingData;
import org.slf4j.Logger;
import static net.minecraft.world.level.chunk.storage.ChunkSerializer.BLOCK_STATE_CODEC;
public class ChunkSerializer {
private static final Logger LOGGER = LogUtils.getLogger();
private static Method CODEC = null;
static {
for (Method method : net.minecraft.world.level.chunk.storage.ChunkSerializer.class.getDeclaredMethods()) {
if (method.getReturnType().equals(Codec.class) && method.getParameterCount() == 1) {
CODEC = method;
CODEC.setAccessible(true);
break;
}
}
}
public static CompoundTag write(ChunkAccess chunk, LevelHeightAccessor heightAccessor, Registry<Biome> biomeRegistry) {
ChunkPos pos = chunk.getPos();
CompoundTag data = NbtUtils.addCurrentDataVersion(new CompoundTag());
data.putInt("xPos", pos.x);
data.putInt("yPos", ((LevelHeightAccessor) chunk).getMinSection());
data.putInt("zPos", pos.z);
data.putLong("LastUpdate", 0L);
data.putLong("InhabitedTime", chunk.getInhabitedTime());
data.putString("Status", BuiltInRegistries.CHUNK_STATUS.getKey(chunk.getStatus()).toString());
BlendingData blendingdata = chunk.getBlendingData();
if (blendingdata != null) {
DataResult<Tag> dataResult = BlendingData.CODEC.encodeStart(NbtOps.INSTANCE, blendingdata);
dataResult.resultOrPartial(LOGGER::error).ifPresent(base -> data.put("blending_data", base));
}
BelowZeroRetrogen belowzeroretrogen = chunk.getBelowZeroRetrogen();
if (belowzeroretrogen != null) {
DataResult<Tag> dataResult = BelowZeroRetrogen.CODEC.encodeStart(NbtOps.INSTANCE, belowzeroretrogen);
dataResult.resultOrPartial(LOGGER::error).ifPresent(base -> data.put("below_zero_retrogen", base));
}
UpgradeData upgradeData = chunk.getUpgradeData();
if (!upgradeData.isEmpty()) {
data.put("UpgradeData", upgradeData.write());
}
LevelChunkSection[] chunkSections = chunk.getSections();
ListTag sections = new ListTag();
Codec<PalettedContainerRO<Holder<Biome>>> codec;
try {
codec = (Codec<PalettedContainerRO<Holder<Biome>>>) CODEC.invoke(null, biomeRegistry);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
int minLightSection = heightAccessor.getMinSection() - 1;
int maxLightSection = minLightSection + heightAccessor.getSectionsCount() + 2;
for (int y = minLightSection; y < maxLightSection; y++) {
int i = ((LevelHeightAccessor) chunk).getSectionIndexFromSectionY(y);
if (i >= 0 && i < chunkSections.length) {
CompoundTag section = new CompoundTag();
LevelChunkSection chunkSection = chunkSections[i];
DataResult<Tag> dataResult = BLOCK_STATE_CODEC.encodeStart(NbtOps.INSTANCE, chunkSection.getStates());
section.put("block_states", dataResult.getOrThrow(false, LOGGER::error));
dataResult = codec.encodeStart(NbtOps.INSTANCE, chunkSection.getBiomes());
section.put("biomes", dataResult.getOrThrow(false, LOGGER::error));
if (!section.isEmpty()) {
section.putByte("Y", (byte)y);
sections.add(section);
}
}
}
data.put("sections", sections);
if (chunk.isLightCorrect()) {
data.putBoolean("isLightOn", true);
}
ListTag blockEntities = new ListTag();
for (BlockPos blockPos : chunk.getBlockEntitiesPos()) {
CompoundTag blockEntityNbt = chunk.getBlockEntityNbtForSaving(blockPos);
if (blockEntityNbt != null) {
blockEntities.add(blockEntityNbt);
}
}
data.put("block_entities", blockEntities);
if (chunk instanceof ProtoChunk protoChunk) {
ListTag entities = new ListTag();
entities.addAll(protoChunk.getEntities());
data.put("entities", entities);
CompoundTag carvingMasks = new CompoundTag();
for (Carving carving : Carving.values()) {
CarvingMask carvingMask = protoChunk.getCarvingMask(carving);
if (carvingMask != null) {
carvingMasks.putLongArray(carving.toString(), carvingMask.toArray());
}
}
data.put("CarvingMasks", carvingMasks);
}
saveTicks(data, chunk.getTicksForSerialization());
data.put("PostProcessing", packOffsets(chunk.getPostProcessing()));
CompoundTag heightmaps = new CompoundTag();
for (Entry<Types, Heightmap> entry : chunk.getHeightmaps()) {
if (chunk.getStatus().heightmapsAfter().contains(entry.getKey())) {
heightmaps.put(entry.getKey().getSerializationKey(), new LongArrayTag(entry.getValue().getRawData()));
}
}
data.put("Heightmaps", heightmaps);
CompoundTag structures = new CompoundTag();
structures.put("starts", new CompoundTag());
structures.put("References", new CompoundTag());
data.put("structures", structures);
if (!chunk.persistentDataContainer.isEmpty()) {
data.put("ChunkBukkitValues", chunk.persistentDataContainer.toTagCompound());
}
return data;
}
private static void saveTicks(CompoundTag compoundTag, TicksToSave ticksToSave) {
compoundTag.put("block_ticks", ticksToSave.blocks().save(0, block -> BuiltInRegistries.BLOCK.getKey(block).toString()));
compoundTag.put("fluid_ticks", ticksToSave.fluids().save(0, fluid -> BuiltInRegistries.FLUID.getKey(fluid).toString()));
}
public static ListTag packOffsets(ShortList[] offsets) {
ListTag tags = new ListTag();
for (ShortList shorts : offsets) {
ListTag listTag = new ListTag();
if (shorts != null) {
for (Short s : shorts) {
listTag.add(ShortTag.valueOf(s));
}
}
tags.add(listTag);
}
return tags;
}
}
@@ -0,0 +1,159 @@
package com.volmit.iris.core.nms.v1_20_R3.mca;
import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.BiomeBaseInjector;
import com.volmit.iris.engine.data.chunk.TerrainChunk;
import com.volmit.iris.util.data.IrisBlockData;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import org.bukkit.Material;
import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData;
import org.bukkit.craftbukkit.v1_20_R3.block.CraftBiome;
import org.bukkit.craftbukkit.v1_20_R3.block.data.CraftBlockData;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.material.MaterialData;
import org.jetbrains.annotations.NotNull;
public record MCATerrainChunk(ChunkAccess chunk) implements TerrainChunk {
@Override
public BiomeBaseInjector getBiomeBaseInjector() {
return (x, y, z, biomeBase) -> chunk.setBiome(x, y, z, (Holder<net.minecraft.world.level.biome.Biome>) biomeBase);
}
@Override
public Biome getBiome(int x, int z) {
return Biome.THE_VOID;
}
@Override
public Biome getBiome(int x, int y, int z) {
return Biome.THE_VOID;
}
@Override
public void setBiome(int x, int z, Biome bio) {
setBiome(x, 0, z, bio);
}
@Override
public void setBiome(int x, int y, int z, Biome bio) {
if (y < 0) return;
y += getMinHeight();
if (y > getMaxHeight()) return;
chunk.setBiome(x & 15, y, z & 15, CraftBiome.bukkitToMinecraftHolder(bio));
}
private LevelHeightAccessor height() {
return chunk;
}
@Override
public int getMinHeight() {
return height().getMinBuildHeight();
}
@Override
public int getMaxHeight() {
return height().getMaxBuildHeight();
}
@Override
public void setBlock(int x, int y, int z, BlockData blockData) {
if (y < 0) return;
y += getMinHeight();
if (y > getMaxHeight()) return;
if (blockData == null) {
Iris.error("NULL BD");
}
if (blockData instanceof IrisBlockData data)
blockData = data.getBase();
if (!(blockData instanceof CraftBlockData craftBlockData))
throw new IllegalArgumentException("Expected CraftBlockData, got " + blockData.getClass().getSimpleName() + " instead");
chunk.setBlockState(new BlockPos(x & 15, y, z & 15), craftBlockData.getState(), false);
}
private BlockState getBlockState(int x, int y, int z) {
if (y < 0) {
y = 0;
}
y += getMinHeight();
if (y > getMaxHeight()) {
y = getMaxHeight();
}
return chunk.getBlockState(new BlockPos(x & 15, y, z & 15));
}
@NotNull
@Override
public org.bukkit.block.data.BlockData getBlockData(int x, int y, int z) {
return CraftBlockData.fromData(getBlockState(x, y, z));
}
@Override
public ChunkGenerator.ChunkData getRaw() {
return null;
}
@Override
public void setRaw(ChunkGenerator.ChunkData data) {
}
@Override
@Deprecated
public void inject(ChunkGenerator.BiomeGrid biome) {
}
@Override
public void setBlock(int x, int y, int z, @NotNull Material material) {
}
@Override
@Deprecated
public void setBlock(int x, int y, int z, @NotNull MaterialData material) {
}
@Override
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, @NotNull Material material) {
}
@Override
@Deprecated
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, @NotNull MaterialData material) {
}
@Override
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, @NotNull BlockData blockData) {
}
@NotNull
@Override
public Material getType(int x, int y, int z) {
return getBlockData(x, y, z).getMaterial();
}
@NotNull
@Override
public MaterialData getTypeAndData(int x, int y, int z) {
return getBlockData(x, y, z).createBlockState().getData();
}
@Override
public byte getData(int x, int y, int z) {
return getTypeAndData(x, y, z).getData();
}
}
@@ -0,0 +1,109 @@
package com.volmit.iris.core.nms.v1_20_R3.mca;
import com.google.common.base.Preconditions;
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import javax.annotation.Nullable;
import net.minecraft.FileUtil;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtAccounter;
import net.minecraft.nbt.NbtIo;
import net.minecraft.nbt.StreamTagVisitor;
import net.minecraft.util.ExceptionCollector;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.storage.RegionFile;
public final class RegionFileStorage implements AutoCloseable {
public final Long2ObjectLinkedOpenHashMap<RegionFile> regionCache = new Long2ObjectLinkedOpenHashMap<>();
private final Path folder;
private final boolean sync;
public RegionFileStorage(Path folder, boolean sync) {
this.folder = folder;
this.sync = sync;
}
public RegionFile getRegionFile(ChunkPos chunkPos, boolean existingOnly) throws IOException {
long id = ChunkPos.asLong(chunkPos.getRegionX(), chunkPos.getRegionZ());
RegionFile regionFile = this.regionCache.getAndMoveToFirst(id);
if (regionFile != null) {
return regionFile;
} else {
if (this.regionCache.size() >= 256) {
this.regionCache.removeLast().close();
}
FileUtil.createDirectoriesSafe(this.folder);
Path path = folder.resolve("r." + chunkPos.getRegionX() + "." + chunkPos.getRegionZ() + ".mca");
if (existingOnly && !Files.exists(path)) {
return null;
} else {
regionFile = new RegionFile(path, this.folder, this.sync);
this.regionCache.putAndMoveToFirst(id, regionFile);
return regionFile;
}
}
}
@Nullable
public CompoundTag read(ChunkPos chunkPos) throws IOException {
RegionFile regionFile = this.getRegionFile(chunkPos, true);
if (regionFile != null) {
try (DataInputStream datainputstream = regionFile.getChunkDataInputStream(chunkPos)) {
if (datainputstream != null) {
return NbtIo.read(datainputstream);
}
}
}
return null;
}
public void scanChunk(ChunkPos chunkPos, StreamTagVisitor visitor) throws IOException {
RegionFile regionFile = this.getRegionFile(chunkPos, true);
if (regionFile != null) {
try (DataInputStream din = regionFile.getChunkDataInputStream(chunkPos)) {
if (din != null) {
NbtIo.parse(din, visitor, NbtAccounter.unlimitedHeap());
}
}
}
}
public void write(ChunkPos chunkPos, @Nullable CompoundTag compound) throws IOException {
RegionFile regionFile = this.getRegionFile(chunkPos, false);
Preconditions.checkArgument(regionFile != null, "Failed to find region file for chunk %s", chunkPos);
if (compound == null) {
regionFile.clear(chunkPos);
} else {
try (DataOutputStream dos = regionFile.getChunkDataOutputStream(chunkPos)) {
NbtIo.write(compound, dos);
}
}
}
@Override
public void close() throws IOException {
ExceptionCollector<IOException> collector = new ExceptionCollector<>();
for (RegionFile regionFile : this.regionCache.values()) {
try {
regionFile.close();
} catch (IOException e) {
collector.add(e);
}
}
collector.throwIfPresent();
}
public void flush() throws IOException {
for (RegionFile regionfile : this.regionCache.values()) {
regionfile.flush();
}
}
}
@@ -2,6 +2,7 @@ package com.volmit.iris.core.nms.v1_20_R4;
import com.mojang.serialization.MapCodec;
import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.INMS;
import com.volmit.iris.engine.data.cache.AtomicCache;
import com.volmit.iris.engine.framework.Engine;
import com.volmit.iris.engine.object.IrisBiome;
@@ -124,8 +125,16 @@ public class CustomBiomeSource extends BiomeSource {
for (IrisBiome i : engine.getAllBiomes()) {
if (i.isCustom()) {
for (IrisBiomeCustom j : i.getCustomDerivitives()) {
ResourceLocation resourceLocation = new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId());
Biome biome = customRegistry.get(resourceLocation);
ResourceLocation location = new ResourceLocation(engine.getDimension().getLoadKey() + ":" + j.getId());
Biome biome = customRegistry.get(location);
if (biome == null) {
INMS.get().registerBiome(location.getNamespace(), j, false);
biome = customRegistry.get(location);
if (biome == null) {
Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName());
continue;
}
}
Optional<ResourceKey<Biome>> optionalBiomeKey = customRegistry.getResourceKey(biome);
if (optionalBiomeKey.isEmpty()) {
Iris.error("Cannot find biome for IrisBiomeCustom " + j.getId() + " from engine " + engine.getName());
@@ -4,18 +4,48 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Vector;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;
import com.google.common.base.Preconditions;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.mojang.datafixers.util.Pair;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps;
import com.volmit.iris.core.nms.datapack.DataVersion;
import com.volmit.iris.engine.object.IrisBiomeCustom;
import com.volmit.iris.engine.object.IrisDimension;
import com.volmit.iris.util.format.C;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
import net.bytebuddy.matcher.ElementMatchers;
import net.minecraft.core.MappedRegistry;
import net.minecraft.core.RegistrationInfo;
import net.minecraft.core.component.DataComponents;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.progress.ChunkProgressListener;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.RandomSequences;
import net.minecraft.world.item.component.CustomData;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.storage.LevelStorageSource;
import net.minecraft.world.level.storage.PrimaryLevelData;
import org.bukkit.*;
import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData;
@@ -29,6 +59,7 @@ import org.bukkit.craftbukkit.v1_20_R4.util.CraftNamespacedKey;
import org.bukkit.entity.Dolphin;
import org.bukkit.entity.Entity;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.generator.BiomeProvider;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
@@ -549,4 +580,203 @@ public class NMSBinding implements INMSBinding {
public DataVersion getDataVersion() {
return DataVersion.V1205;
}
@Override
public boolean registerDimension(String name, IrisDimension dimension) {
var registry = registry(Registries.DIMENSION_TYPE);
var baseLocation = switch (dimension.getEnvironment()) {
case NORMAL -> new ResourceLocation("minecraft", "overworld");
case NETHER -> new ResourceLocation("minecraft", "the_nether");
case THE_END -> new ResourceLocation("minecraft", "the_end");
case CUSTOM -> throw new IllegalArgumentException("Cannot register custom dimension");
};
var base = registry.getHolder(ResourceKey.create(Registries.DIMENSION_TYPE, baseLocation)).orElse(null);
if (base == null) return false;
var json = encode(DimensionType.CODEC, base).orElse(null);
if (json == null) return false;
var object = json.getAsJsonObject();
var height = dimension.getDimensionHeight();
object.addProperty("min_y", height.getMin());
object.addProperty("height", height.getMax() - height.getMin());
object.addProperty("logical_height", dimension.getLogicalHeight());
var value = decode(DimensionType.CODEC, object.toString()).map(Holder::value).orElse(null);
if (value == null) return false;
return register(Registries.DIMENSION_TYPE, new ResourceLocation("iris", name), value, true);
}
@Override
public boolean registerBiome(String dimensionId, IrisBiomeCustom biome, boolean replace) {
var biomeBase = decode(net.minecraft.world.level.biome.Biome.CODEC, biome.generateJson()).map(Holder::value).orElse(null);
if (biomeBase == null) return false;
return register(Registries.BIOME, new ResourceLocation(dimensionId, biome.getId()), biomeBase, replace);
}
private <T> Optional<T> decode(Codec<T> codec, String json) {
return codec.decode(JsonOps.INSTANCE, GsonHelper.parse(json)).result().map(Pair::getFirst);
}
private <T> Optional<JsonElement> encode(Codec<T> codec, T value) {
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.setAccessible(true);
boolean frozen = field.getBoolean(registry);
field.setBoolean(registry, false);
Field valueField = getField(Holder.Reference.class, "T");
valueField.setAccessible(true);
try {
var holder = registry.register(key, value, RegistrationInfo.BUILT_IN);
if (frozen) valueField.set(holder, value);
return true;
} finally {
field.setBoolean(registry, frozen);
}
} catch (Throwable e) {
throw new IllegalStateException(e);
}
}
@SuppressWarnings("unchecked")
private <T> boolean replace(ResourceKey<Registry<T>> registryKey, ResourceLocation location, T value) {
Preconditions.checkArgument(registryKey != null, "The registryKey cannot be null!");
Preconditions.checkArgument(location != null, "The location cannot be null!");
Preconditions.checkArgument(value != null, "The value cannot be null!");
var registry = registry(registryKey);
var key = ResourceKey.create(registryKey, location);
try {
var holder = registry.getHolder(key).orElse(null);
if (holder == null) return false;
var oldValue = holder.value();
Field valueField = getField(Holder.Reference.class, "T");
valueField.setAccessible(true);
Field toIdField = getField(MappedRegistry.class, buildType(Reference2IntMap.class, "T"));
toIdField.setAccessible(true);
Field byValueField = getField(MappedRegistry.class, buildType(Map.class, "T", buildType(Holder.Reference.class, "T")));
byValueField.setAccessible(true);
var toId = (Reference2IntMap<T>) toIdField.get(registry);
var byValue = (Map<T, Holder.Reference<T>>) byValueField.get(registry);
valueField.set(holder, value);
toId.put(value, toId.removeInt(oldValue));
byValue.put(value, byValue.remove(oldValue));
return true;
} catch (Throwable e) {
throw new IllegalStateException(e);
}
}
private <T> MappedRegistry<T> registry(ResourceKey<Registry<T>> registryKey) {
var rawRegistry = registry().registry(registryKey).orElse(null);
if (!(rawRegistry instanceof MappedRegistry<T> registry))
throw new IllegalStateException("The Registry is not a mapped Registry!");
return registry;
}
private static String buildType(Class<?> clazz, String... parameterTypes) {
if (parameterTypes.length == 0) return clazz.getName();
var builder = new StringBuilder(clazz.getName())
.append("<");
for (int i = 0; i < parameterTypes.length; i++) {
builder.append(parameterTypes[i]).append(parameterTypes.length - 1 == i ? ">" : ", ");
}
return builder.toString();
}
private static Field getField(Class<?> clazz, String type) throws NoSuchFieldException {
try {
for (Field f : clazz.getDeclaredFields()) {
if (f.getGenericType().getTypeName().equals(type))
return f;
}
throw new NoSuchFieldException(type);
} catch (NoSuchFieldException e) {
Class<?> superClass = clazz.getSuperclass();
if (superClass == null) throw e;
return getField(superClass, type);
}
}
public void injectBukkit() {
try {
Iris.info("Injecting Bukkit");
new ByteBuddy()
.redefine(CraftServer.class)
.visit(Advice.to(CraftServerAdvice.class).on(ElementMatchers.isMethod().and(ElementMatchers.takesArguments(WorldCreator.class))))
.make()
.load(CraftServer.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
new ByteBuddy()
.redefine(ServerLevel.class)
.visit(Advice.to(ServerLevelAdvice.class).on(ElementMatchers.isConstructor().and(ElementMatchers.takesArguments(MinecraftServer.class, Executor.class, LevelStorageSource.LevelStorageAccess.class,
PrimaryLevelData.class, ResourceKey.class, LevelStem.class, ChunkProgressListener.class, boolean.class, long.class,
List.class, boolean.class, RandomSequences.class, World.Environment.class, ChunkGenerator.class, BiomeProvider.class))))
.make()
.load(ServerLevel.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
Iris.info("Injected Bukkit Successfully!");
} catch (Exception e) {
Iris.info(C.RED + "Failed to Inject Bukkit!");
e.printStackTrace();
Iris.reportError(e);
}
}
private static class ServerLevelAdvice {
@Advice.OnMethodEnter
static void enter(@Advice.Argument(0) MinecraftServer server, @Advice.Argument(2) LevelStorageSource.LevelStorageAccess access, @Advice.Argument(4) ResourceKey<Level> key, @Advice.Argument(value = 5, readOnly = false) LevelStem levelStem) {
File iris = new File(access.levelDirectory.path().toFile(), "iris");
if (!iris.exists() && !key.location().getPath().startsWith("iris/")) return;
ResourceKey<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) throw new IllegalStateException("Unable to find registry for dimension type " + typeKey);
Holder<DimensionType> holder = registry.getHolder(typeKey).orElse(null);
if (holder == null) throw new IllegalStateException("Unable to find dimension type " + typeKey);
levelStem = new LevelStem(holder, levelStem.generator());
}
}
private static class CraftServerAdvice {
@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
static boolean enter(@Advice.This CraftServer self, @Advice.Argument(0) WorldCreator creator) {
File isIrisWorld = new File(self.getWorldContainer(), creator.name() + "/iris");
boolean isFromIris = false;
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
for (StackTraceElement stack : stackTrace) {
if (stack.getClassName().contains("Iris")) {
isFromIris = true;
break;
}
}
if (isIrisWorld.exists() && !isFromIris) {
var logger = Logger.getLogger("Iris");
logger.warning("detected another Plugin trying to load " + creator.name() + ". This is not supported and will be ignored.");
if (System.getProperty("iris.debug", "false").equals("true")) {
new RuntimeException().printStackTrace();
}
return true;
}
return false;
}
@Advice.OnMethodExit
static void exit(@Advice.Enter boolean bool, @Advice.Return(readOnly = false) World returned) {
if (bool) {
returned = null;
}
}
}
}