mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-06-18 14:50:57 +00:00
Revert "Revert "Merge branch 'mca' into jigsaw_dist""
This reverts commit 55017b9a
This commit is contained in:
+25
-2
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
-2
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
-2
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
-2
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
-2
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
-2
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
+181
@@ -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;
|
||||
}
|
||||
}
|
||||
+159
@@ -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();
|
||||
}
|
||||
}
|
||||
+109
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
-2
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user