9
0
mirror of https://github.com/VolmitSoftware/Iris.git synced 2025-12-30 04:29:05 +00:00

add missing methods to 1.21 bindings

This commit is contained in:
Julian Krings
2024-07-29 18:29:28 +02:00
parent 295fe16f8f
commit 482fa9b11e

View File

@@ -1,26 +1,48 @@
package com.volmit.iris.core.nms.v1_21_R1;
import java.awt.Color;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.*;
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.volmit.iris.core.nms.container.BiomeColor;
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.*;
import net.minecraft.core.Registry;
import net.minecraft.core.component.DataComponents;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ChunkMap;
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.LevelReader;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.chunk.status.WorldGenContext;
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;
@@ -34,6 +56,7 @@ import org.bukkit.craftbukkit.v1_21_R1.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;
@@ -56,10 +79,6 @@ import com.volmit.iris.util.nbt.mca.palette.*;
import com.volmit.iris.util.nbt.tag.CompoundTag;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
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;
@@ -581,4 +600,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 -> ResourceLocation.fromNamespaceAndPath("minecraft", "overworld");
case NETHER -> ResourceLocation.fromNamespaceAndPath("minecraft", "the_nether");
case THE_END -> ResourceLocation.fromNamespaceAndPath("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, ResourceLocation.fromNamespaceAndPath("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, ResourceLocation.fromNamespaceAndPath(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, ResourceLocation.fromNamespaceAndPath("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;
}
}
}
}