mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2025-07-21 11:43:27 +00:00
replace world load context injection with bytecode injections
This commit is contained in:
parent
ab4770400e
commit
9c151abac7
10
build.gradle
10
build.gradle
@ -97,6 +97,7 @@ NMS_BINDINGS.forEach { key, value ->
|
||||
systemProperty("disable.watchdog", "")
|
||||
systemProperty("net.kyori.ansi.colorLevel", COLOR)
|
||||
systemProperty("com.mojang.eula.agree", true)
|
||||
jvmArgs("-javaagent:${tasks.shadowJar.archiveFile.get().asFile.absolutePath}")
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,6 +114,12 @@ shadowJar {
|
||||
relocate 'net.kyori', 'com.volmit.iris.util.kyori'
|
||||
relocate 'org.bstats', 'com.volmit.util.metrics'
|
||||
archiveFileName.set("Iris-${project.version}.jar")
|
||||
manifest.attributes(
|
||||
'Agent-Class': 'com.volmit.iris.util.agent.Installer',
|
||||
'Premain-Class': 'com.volmit.iris.util.agent.Installer',
|
||||
'Can-Redefine-Classes': true,
|
||||
'Can-Retransform-Classes': true
|
||||
)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@ -173,6 +180,9 @@ allprojects {
|
||||
compileOnly 'com.github.ben-manes.caffeine:caffeine:3.0.6'
|
||||
compileOnly 'org.apache.commons:commons-lang3:3.12.0'
|
||||
compileOnly 'com.github.oshi:oshi-core:6.6.5'
|
||||
|
||||
compileOnly("net.bytebuddy:byte-buddy:1.17.5")
|
||||
compileOnly("net.bytebuddy:byte-buddy-agent:1.17.5")
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -32,10 +32,8 @@ import com.volmit.iris.core.nms.v1X.NMSBinding1X;
|
||||
import com.volmit.iris.core.pregenerator.LazyPregenerator;
|
||||
import com.volmit.iris.core.service.StudioSVC;
|
||||
import com.volmit.iris.core.tools.IrisToolbelt;
|
||||
import com.volmit.iris.core.tools.IrisWorldCreator;
|
||||
import com.volmit.iris.engine.EnginePanic;
|
||||
import com.volmit.iris.engine.object.IrisCompat;
|
||||
import com.volmit.iris.engine.object.IrisContextInjector;
|
||||
import com.volmit.iris.engine.object.IrisDimension;
|
||||
import com.volmit.iris.engine.object.IrisWorld;
|
||||
import com.volmit.iris.engine.platform.BukkitChunkGenerator;
|
||||
@ -457,11 +455,9 @@ public class Iris extends VolmitPlugin implements Listener {
|
||||
services = new KMap<>();
|
||||
setupAudience();
|
||||
initialize("com.volmit.iris.core.service").forEach((i) -> services.put((Class<? extends IrisService>) i.getClass(), (IrisService) i));
|
||||
INMS.get();
|
||||
IO.delete(new File("iris"));
|
||||
compat = IrisCompat.configured(getDataFile("compat.json"));
|
||||
ServerConfigurator.configure();
|
||||
new IrisContextInjector();
|
||||
IrisSafeguard.IrisSafeguardSystem();
|
||||
getSender().setTag(getTag());
|
||||
IrisSafeguard.earlySplash();
|
||||
|
@ -18,7 +18,6 @@
|
||||
|
||||
package com.volmit.iris.core.nms;
|
||||
|
||||
import com.volmit.iris.core.nms.container.AutoClosing;
|
||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||
import com.volmit.iris.core.nms.datapack.DataVersion;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
@ -89,13 +88,10 @@ public interface INMSBinding {
|
||||
MCABiomeContainer newBiomeContainer(int min, int max);
|
||||
|
||||
default World createWorld(WorldCreator c) {
|
||||
if (missingDimensionTypes(true, true, true))
|
||||
if (missingDimensionTypes(c.environment()))
|
||||
throw new IllegalStateException("Missing dimenstion types to create world");
|
||||
|
||||
try (var ignored = injectLevelStems()) {
|
||||
ignored.storeContext();
|
||||
return c.createWorld();
|
||||
}
|
||||
return c.createWorld();
|
||||
}
|
||||
|
||||
int countCustomBiomes();
|
||||
@ -130,13 +126,20 @@ public interface INMSBinding {
|
||||
|
||||
KList<String> getStructureKeys();
|
||||
|
||||
AutoClosing injectLevelStems();
|
||||
|
||||
default AutoClosing injectUncached(boolean overworld, boolean nether, boolean end) {
|
||||
return null;
|
||||
}
|
||||
KMap<String, World.Environment> getMainWorlds();
|
||||
|
||||
boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end);
|
||||
|
||||
void removeCustomDimensions(World world);
|
||||
default boolean missingDimensionTypes(World.Environment env) {
|
||||
return switch (env) {
|
||||
case NORMAL -> missingDimensionTypes(true, false, false);
|
||||
case NETHER -> missingDimensionTypes(false, true, false);
|
||||
case THE_END -> missingDimensionTypes(false, false, true);
|
||||
default -> true;
|
||||
};
|
||||
}
|
||||
|
||||
default boolean injectBukkit() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -121,13 +121,8 @@ public class NMSBinding1X implements INMSBinding {
|
||||
}
|
||||
|
||||
@Override
|
||||
public AutoClosing injectLevelStems() {
|
||||
return new AutoClosing(() -> {});
|
||||
}
|
||||
|
||||
@Override
|
||||
public AutoClosing injectUncached(boolean overworld, boolean nether, boolean end) {
|
||||
return injectLevelStems();
|
||||
public KMap<String, World.Environment> getMainWorlds() {
|
||||
return new KMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -135,11 +130,6 @@ public class NMSBinding1X implements INMSBinding {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCustomDimensions(World world) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag serializeEntity(Entity location) {
|
||||
return null;
|
||||
|
@ -3,8 +3,12 @@ package com.volmit.iris.core.safeguard;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.INMS;
|
||||
import com.volmit.iris.core.nms.v1X.NMSBinding1X;
|
||||
import com.volmit.iris.engine.object.IrisContextInjector;
|
||||
import com.volmit.iris.util.agent.Agent;
|
||||
import com.volmit.iris.util.misc.ServerProperties;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import javax.tools.JavaCompiler;
|
||||
@ -15,10 +19,8 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static com.volmit.iris.Iris.getJavaVersion;
|
||||
import static com.volmit.iris.core.safeguard.IrisSafeguard.*;
|
||||
@ -31,6 +33,8 @@ public class ServerBootSFG {
|
||||
public static boolean hasPrivileges = true;
|
||||
public static boolean unsuportedversion = false;
|
||||
public static boolean missingDimensionTypes = false;
|
||||
public static boolean missingAgent = false;
|
||||
public static boolean failedInjection = false;
|
||||
protected static boolean safeguardPassed;
|
||||
public static boolean passedserversoftware = true;
|
||||
protected static int count;
|
||||
@ -112,10 +116,21 @@ public class ServerBootSFG {
|
||||
severityMedium++;
|
||||
}
|
||||
|
||||
if (IrisContextInjector.isMissingDimensionTypes()) {
|
||||
missingDimensionTypes = true;
|
||||
joiner.add("Missing Dimension Types");
|
||||
if (!Agent.install()) {
|
||||
missingAgent = true;
|
||||
joiner.add("Missing Java Agent");
|
||||
severityHigh++;
|
||||
} else {
|
||||
if (missingDimensionTypes()) {
|
||||
missingDimensionTypes = true;
|
||||
joiner.add("Missing Dimension Types");
|
||||
severityHigh++;
|
||||
}
|
||||
if (!INMS.get().injectBukkit()) {
|
||||
failedInjection = true;
|
||||
joiner.add("Failed Bukkit Injection");
|
||||
severityHigh++;
|
||||
}
|
||||
}
|
||||
|
||||
allIncompatibilities = joiner.toString();
|
||||
@ -173,4 +188,31 @@ public class ServerBootSFG {
|
||||
return !path.isEmpty() && (new File(path, "javac").exists() || new File(path, "javac.exe").exists());
|
||||
}
|
||||
|
||||
private static boolean missingDimensionTypes() {
|
||||
var irisWorlds = irisWorlds();
|
||||
if (irisWorlds.isEmpty()) return false;
|
||||
|
||||
var worlds = INMS.get().getMainWorlds();
|
||||
worlds.keySet().removeIf(Predicate.not(irisWorlds::contains));
|
||||
|
||||
boolean overworld = worlds.containsValue(World.Environment.NORMAL) || worlds.containsValue(World.Environment.CUSTOM);
|
||||
boolean nether = worlds.containsValue(World.Environment.NETHER);
|
||||
boolean end = worlds.containsValue(World.Environment.THE_END);
|
||||
|
||||
if (overworld || nether || end)
|
||||
return INMS.get().missingDimensionTypes(overworld, nether, end);
|
||||
return false;
|
||||
}
|
||||
|
||||
private static List<String> irisWorlds() {
|
||||
var config = YamlConfiguration.loadConfiguration(ServerProperties.BUKKIT_YML);
|
||||
ConfigurationSection section = config.getConfigurationSection("worlds");
|
||||
if (section == null) return List.of();
|
||||
|
||||
return section.getKeys(false)
|
||||
.stream()
|
||||
.filter(k -> section.getString(k + ".generator", "").startsWith("Iris"))
|
||||
.toList();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -44,6 +44,11 @@ public class UtilsSFG {
|
||||
Iris.safeguard(C.RED + "- Required Iris dimension types were not loaded.");
|
||||
Iris.safeguard(C.RED + "- If this still happens after a restart please contact support.");
|
||||
}
|
||||
if (ServerBootSFG.missingAgent) {
|
||||
Iris.safeguard(C.RED + "Java Agent");
|
||||
Iris.safeguard(C.RED + "- Please enable dynamic agent loading by adding -XX:+EnableDynamicAgentLoading to your jvm arguments.");
|
||||
Iris.safeguard(C.RED + "- or add the jvm argument -javaagent:plugins/" + Iris.instance.getJarFile().getName());
|
||||
}
|
||||
if (!ServerBootSFG.passedserversoftware) {
|
||||
Iris.safeguard(C.YELLOW + "Unsupported Server Software");
|
||||
Iris.safeguard(C.YELLOW + "- Please consider using Paper or Purpur instead.");
|
||||
|
@ -1,66 +0,0 @@
|
||||
package com.volmit.iris.engine.object;
|
||||
|
||||
import com.volmit.iris.core.nms.INMS;
|
||||
import com.volmit.iris.core.nms.container.AutoClosing;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.misc.ServerProperties;
|
||||
import lombok.Getter;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.world.WorldInitEvent;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.volmit.iris.Iris.instance;
|
||||
|
||||
public class IrisContextInjector implements Listener {
|
||||
@Getter
|
||||
private static boolean missingDimensionTypes = false;
|
||||
private AutoClosing autoClosing = null;
|
||||
|
||||
public IrisContextInjector() {
|
||||
if (!Bukkit.getWorlds().isEmpty()) return;
|
||||
|
||||
String levelName = ServerProperties.LEVEL_NAME;
|
||||
List<String> irisWorlds = irisWorlds();
|
||||
boolean overworld = irisWorlds.contains(levelName);
|
||||
boolean nether = irisWorlds.contains(levelName + "_nether");
|
||||
boolean end = irisWorlds.contains(levelName + "_end");
|
||||
|
||||
if (INMS.get().missingDimensionTypes(overworld, nether, end)) {
|
||||
missingDimensionTypes = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (overworld || nether || end) {
|
||||
autoClosing = INMS.get().injectUncached(overworld, nether, end);
|
||||
}
|
||||
|
||||
instance.registerListener(this);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void on(WorldInitEvent event) {
|
||||
if (autoClosing != null) {
|
||||
autoClosing.close();
|
||||
autoClosing = null;
|
||||
}
|
||||
instance.unregisterListener(this);
|
||||
}
|
||||
|
||||
private List<String> irisWorlds() {
|
||||
var config = YamlConfiguration.loadConfiguration(ServerProperties.BUKKIT_YML);
|
||||
ConfigurationSection section = config.getConfigurationSection("worlds");
|
||||
if (section == null) return List.of();
|
||||
|
||||
return section.getKeys(false)
|
||||
.stream()
|
||||
.filter(k -> section.getString(k + ".generator", "").startsWith("Iris"))
|
||||
.toList();
|
||||
}
|
||||
}
|
@ -130,7 +130,6 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
||||
if (initialized || !world.name().equals(event.getWorld().getName()))
|
||||
return;
|
||||
AutoClosing.closeContext();
|
||||
INMS.get().removeCustomDimensions(event.getWorld());
|
||||
world.setRawWorldSeed(event.getWorld().getSeed());
|
||||
Engine engine = getEngine(event.getWorld());
|
||||
if (engine == null) {
|
||||
|
41
core/src/main/java/com/volmit/iris/util/agent/Agent.java
Normal file
41
core/src/main/java/com/volmit/iris/util/agent/Agent.java
Normal file
@ -0,0 +1,41 @@
|
||||
package com.volmit.iris.util.agent;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import net.bytebuddy.agent.ByteBuddyAgent;
|
||||
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
|
||||
|
||||
import java.lang.instrument.Instrumentation;
|
||||
|
||||
public class Agent {
|
||||
private static final String NAME = "com.volmit.iris.util.agent.Installer";
|
||||
|
||||
public static ClassReloadingStrategy installed() {
|
||||
return ClassReloadingStrategy.of(getInstrumentation());
|
||||
}
|
||||
|
||||
public static Instrumentation getInstrumentation() {
|
||||
Instrumentation instrumentation = doGetInstrumentation();
|
||||
if (instrumentation == null) throw new IllegalStateException("The agent is not initialized or unavailable");
|
||||
return instrumentation;
|
||||
}
|
||||
|
||||
public static boolean install() {
|
||||
if (doGetInstrumentation() != null)
|
||||
return true;
|
||||
try {
|
||||
Iris.info("Installing Java Agent...");
|
||||
ByteBuddyAgent.attach(Iris.instance.getJarFile(), ByteBuddyAgent.ProcessProvider.ForCurrentVm.INSTANCE);
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return doGetInstrumentation() != null;
|
||||
}
|
||||
|
||||
private static Instrumentation doGetInstrumentation() {
|
||||
try {
|
||||
return (Instrumentation) Class.forName(NAME, true, ClassLoader.getSystemClassLoader()).getMethod("getInstrumentation").invoke(null);
|
||||
} catch (Exception ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
29
core/src/main/java/com/volmit/iris/util/agent/Installer.java
Normal file
29
core/src/main/java/com/volmit/iris/util/agent/Installer.java
Normal file
@ -0,0 +1,29 @@
|
||||
package com.volmit.iris.util.agent;
|
||||
|
||||
import java.lang.instrument.Instrumentation;
|
||||
|
||||
public class Installer {
|
||||
private static volatile Instrumentation instrumentation;
|
||||
|
||||
public static Instrumentation getInstrumentation() {
|
||||
Instrumentation instrumentation = Installer.instrumentation;
|
||||
if (instrumentation == null) {
|
||||
throw new IllegalStateException("The agent is not loaded or this method is not called via the system class loader");
|
||||
}
|
||||
return instrumentation;
|
||||
}
|
||||
|
||||
public static void premain(String arguments, Instrumentation instrumentation) {
|
||||
doMain(instrumentation);
|
||||
}
|
||||
|
||||
public static void agentmain(String arguments, Instrumentation instrumentation) {
|
||||
doMain(instrumentation);
|
||||
}
|
||||
|
||||
private static synchronized void doMain(Instrumentation instrumentation) {
|
||||
if (Installer.instrumentation != null)
|
||||
return;
|
||||
Installer.instrumentation = instrumentation;
|
||||
}
|
||||
}
|
@ -20,6 +20,8 @@ libraries:
|
||||
- bsf:bsf:2.4.0
|
||||
- org.lz4:lz4-java:1.8.0
|
||||
- com.github.oshi:oshi-core:6.6.5
|
||||
- net.bytebuddy:byte-buddy:1.17.5
|
||||
- net.bytebuddy:byte-buddy-agent:1.17.5
|
||||
commands:
|
||||
iris:
|
||||
aliases: [ ir, irs ]
|
||||
|
@ -2,27 +2,30 @@ package com.volmit.iris.core.nms.v1_20_R1;
|
||||
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import com.mojang.serialization.Lifecycle;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.INMSBinding;
|
||||
import com.volmit.iris.core.nms.container.AutoClosing;
|
||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||
import com.volmit.iris.engine.data.cache.AtomicCache;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.util.agent.Agent;
|
||||
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;
|
||||
import com.volmit.iris.util.math.Vector3d;
|
||||
import com.volmit.iris.util.matter.MatterBiomeInject;
|
||||
import com.volmit.iris.util.nbt.io.NBTUtil;
|
||||
import com.volmit.iris.util.misc.ServerProperties;
|
||||
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 com.volmit.iris.util.scheduling.J;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import lombok.SneakyThrows;
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.matcher.ElementMatchers;
|
||||
import net.minecraft.core.*;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
@ -31,13 +34,12 @@ import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.WorldLoader;
|
||||
import net.minecraft.server.commands.data.BlockDataAccessor;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.progress.ChunkProgressListener;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.entity.EntityDimensions;
|
||||
import net.minecraft.world.RandomSequences;
|
||||
import net.minecraft.world.level.LevelReader;
|
||||
import net.minecraft.world.level.biome.BiomeSource;
|
||||
import net.minecraft.world.level.biome.Biomes;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
@ -52,6 +54,8 @@ import net.minecraft.world.level.dimension.LevelStem;
|
||||
import net.minecraft.world.level.levelgen.FlatLevelSource;
|
||||
import net.minecraft.world.level.levelgen.flat.FlatLayerInfo;
|
||||
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
|
||||
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;
|
||||
@ -63,31 +67,23 @@ import org.bukkit.craftbukkit.v1_20_R1.block.CraftBlock;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.block.CraftBlockState;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.block.CraftBlockStates;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.block.data.CraftBlockData;
|
||||
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.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import sun.misc.Unsafe;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class NMSBinding implements INMSBinding {
|
||||
|
||||
@ -95,11 +91,11 @@ public class NMSBinding implements INMSBinding {
|
||||
private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
|
||||
private final BlockData AIR = Material.AIR.createBlockData();
|
||||
private final AtomicCache<MCAIdMap<net.minecraft.world.level.biome.Biome>> biomeMapCache = new AtomicCache<>();
|
||||
private final AtomicCache<WorldLoader.DataLoadContext> dataLoadContext = new AtomicCache<>();
|
||||
private final AtomicBoolean injected = new AtomicBoolean();
|
||||
private final AtomicCache<MCAIdMapper<BlockState>> registryCache = new AtomicCache<>();
|
||||
private final AtomicCache<MCAPalette<BlockState>> globalCache = new AtomicCache<>();
|
||||
private final AtomicCache<RegistryAccess> registryAccess = new AtomicCache<>();
|
||||
private final ReentrantLock dataContextLock = new ReentrantLock(true);
|
||||
private final KMap<World.Environment, LevelStem> stems = new KMap<>();
|
||||
private final AtomicCache<Method> byIdRef = new AtomicCache<>();
|
||||
private Field biomeStorageCache = null;
|
||||
|
||||
@ -645,44 +641,28 @@ public class NMSBinding implements INMSBinding {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public AutoClosing injectLevelStems() {
|
||||
if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!");
|
||||
@SuppressWarnings("all")
|
||||
public KMap<String, World.Environment> getMainWorlds() {
|
||||
String levelName = ServerProperties.LEVEL_NAME;
|
||||
KMap<String, World.Environment> worlds = new KMap<>();
|
||||
for (var key : registry().registryOrThrow(Registries.LEVEL_STEM).registryKeySet()) {
|
||||
World.Environment env = World.Environment.NORMAL;
|
||||
if (key == LevelStem.NETHER) {
|
||||
if (!Bukkit.getAllowNether())
|
||||
continue;
|
||||
env = World.Environment.NETHER;
|
||||
} else if (key == LevelStem.END) {
|
||||
if (!Bukkit.getAllowEnd())
|
||||
continue;
|
||||
env = World.Environment.THE_END;
|
||||
} else if (key != LevelStem.OVERWORLD) {
|
||||
env = World.Environment.CUSTOM;
|
||||
}
|
||||
|
||||
var server = ((CraftServer) Bukkit.getServer());
|
||||
var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class);
|
||||
var nmsServer = server.getServer();
|
||||
var old = nmsServer.worldLoader;
|
||||
|
||||
field.setAccessible(true);
|
||||
field.set(nmsServer, dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext(
|
||||
old.resources(),
|
||||
old.dataConfiguration(),
|
||||
old.datapackWorldgen(),
|
||||
createRegistryAccess(old.datapackDimensions(), false, true, true, true)
|
||||
)));
|
||||
|
||||
return new AutoClosing(() -> {
|
||||
field.set(nmsServer, old);
|
||||
dataContextLock.unlock();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public AutoClosing injectUncached(boolean overworld, boolean nether, boolean end) {
|
||||
var reg = registry();
|
||||
var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class);
|
||||
field.setAccessible(true);
|
||||
|
||||
var access = createRegistryAccess(((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions(), true, overworld, nether, end);
|
||||
var injected = access.registryOrThrow(Registries.LEVEL_STEM);
|
||||
var old = (Map<ResourceKey<? extends Registry<?>>, Registry<?>>) field.get(reg);
|
||||
var fake = new HashMap<>(old);
|
||||
fake.put(Registries.LEVEL_STEM, injected);
|
||||
field.set(reg, fake);
|
||||
|
||||
return new AutoClosing(() -> field.set(reg, old));
|
||||
String worldType = env == World.Environment.CUSTOM ? key.location().getNamespace() + "_" + key.location().getPath() : env.toString().toLowerCase(Locale.ROOT);
|
||||
worlds.put(key == LevelStem.OVERWORLD ? levelName : levelName + "_" + worldType, env);
|
||||
}
|
||||
return worlds;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -695,53 +675,74 @@ public class NMSBinding implements INMSBinding {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCustomDimensions(World world) {
|
||||
((CraftWorld) world).getHandle().K.customDimensions = null;
|
||||
}
|
||||
|
||||
private RegistryAccess.Frozen createRegistryAccess(RegistryAccess.Frozen datapack, boolean copy, boolean overworld, boolean nether, boolean end) {
|
||||
var access = registry();
|
||||
var dimensions = access.registryOrThrow(Registries.DIMENSION_TYPE);
|
||||
|
||||
var settings = new FlatLevelGeneratorSettings(
|
||||
Optional.empty(),
|
||||
access.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.THE_VOID),
|
||||
List.of()
|
||||
);
|
||||
settings.getLayersInfo().add(new FlatLayerInfo(1, Blocks.AIR));
|
||||
settings.updateLayers();
|
||||
|
||||
var source = new FlatLevelSource(settings);
|
||||
var fake = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.experimental());
|
||||
if (overworld) register(fake, dimensions, source, LevelStem.OVERWORLD);
|
||||
if (nether) register(fake, dimensions, source, LevelStem.NETHER);
|
||||
if (end) register(fake, dimensions, source, LevelStem.END);
|
||||
copy(fake, datapack.registry(Registries.LEVEL_STEM).orElse(null));
|
||||
|
||||
if (copy) copy(fake, access.registryOrThrow(Registries.LEVEL_STEM));
|
||||
|
||||
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake)).freeze();
|
||||
}
|
||||
|
||||
private void register(MappedRegistry<LevelStem> target, Registry<DimensionType> dimensions, FlatLevelSource source, ResourceKey<LevelStem> key) {
|
||||
var loc = createIrisKey(key);
|
||||
target.register(key, new LevelStem(
|
||||
dimensions.getHolder(ResourceKey.create(Registries.DIMENSION_TYPE, loc)).orElseThrow(() -> new IllegalStateException("Missing dimension type " + loc + " in " + dimensions.keySet())),
|
||||
source
|
||||
), Lifecycle.stable());
|
||||
}
|
||||
|
||||
private void copy(MappedRegistry<LevelStem> target, Registry<LevelStem> source) {
|
||||
if (source == null) return;
|
||||
source.registryKeySet().forEach(key -> {
|
||||
var value = source.get(key);
|
||||
var info = source.lifecycle(value);
|
||||
if (value != null && info != null && !target.containsKey(key))
|
||||
target.register(key, value, info);
|
||||
});
|
||||
public boolean injectBukkit() {
|
||||
if (injected.getAndSet(true))
|
||||
return true;
|
||||
try {
|
||||
Iris.info("Injecting Bukkit");
|
||||
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(), Agent.installed());
|
||||
return true;
|
||||
} catch (Throwable e) {
|
||||
Iris.error(C.RED + "Failed to inject Bukkit");
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private ResourceLocation createIrisKey(ResourceKey<LevelStem> key) {
|
||||
return new ResourceLocation("iris", key.location().getPath());
|
||||
}
|
||||
|
||||
public LevelStem levelStem(RegistryAccess access, World.Environment env) {
|
||||
if (env == World.Environment.CUSTOM)
|
||||
env = World.Environment.NORMAL;
|
||||
return stems.computeIfAbsent(env, key -> new LevelStem(dimensionType(access, key), chunkGenerator(access)));
|
||||
}
|
||||
|
||||
private Holder.Reference<DimensionType> dimensionType(RegistryAccess access, World.Environment env) {
|
||||
return access.registryOrThrow(Registries.DIMENSION_TYPE).getHolderOrThrow(ResourceKey.create(Registries.DIMENSION_TYPE, new ResourceLocation("iris", switch (env) {
|
||||
case NORMAL, CUSTOM -> "overworld";
|
||||
case NETHER -> "the_nether";
|
||||
case THE_END -> "the_end";
|
||||
})));
|
||||
}
|
||||
|
||||
private net.minecraft.world.level.chunk.ChunkGenerator chunkGenerator(RegistryAccess access) {
|
||||
var settings = new FlatLevelGeneratorSettings(Optional.empty(), access.registryOrThrow(Registries.BIOME).getHolderOrThrow(Biomes.THE_VOID), List.of());
|
||||
settings.getLayersInfo().add(new FlatLayerInfo(1, Blocks.AIR));
|
||||
settings.updateLayers();
|
||||
return new FlatLevelSource(settings);
|
||||
}
|
||||
|
||||
private static class ServerLevelAdvice {
|
||||
@SneakyThrows
|
||||
@Advice.OnMethodEnter
|
||||
static void enter(
|
||||
@Advice.Argument(0) MinecraftServer server,
|
||||
@Advice.Argument(3) PrimaryLevelData levelData,
|
||||
@Advice.Argument(value = 5, readOnly = false) LevelStem levelStem,
|
||||
@Advice.Argument(12) World.Environment env,
|
||||
@Advice.Argument(value = 13) ChunkGenerator gen
|
||||
) {
|
||||
if (gen == null || !gen.getClass().getPackageName().startsWith("com.volmit.iris"))
|
||||
return;
|
||||
|
||||
Object bindings = Class.forName("com.volmit.iris.core.nms.INMS", true, Bukkit.getPluginManager().getPlugin("Iris")
|
||||
.getClass()
|
||||
.getClassLoader())
|
||||
.getDeclaredMethod("get")
|
||||
.invoke(null);
|
||||
levelStem = (LevelStem) bindings.getClass()
|
||||
.getDeclaredMethod("levelStem", RegistryAccess.class, World.Environment.class)
|
||||
.invoke(bindings, server.registryAccess(), env);
|
||||
levelData.customDimensions = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,32 +1,33 @@
|
||||
package com.volmit.iris.core.nms.v1_20_R2;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import com.mojang.serialization.Lifecycle;
|
||||
import com.volmit.iris.core.nms.container.AutoClosing;
|
||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||
import com.volmit.iris.util.agent.Agent;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import com.volmit.iris.util.misc.ServerProperties;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import lombok.SneakyThrows;
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.matcher.ElementMatchers;
|
||||
import net.minecraft.core.*;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.nbt.*;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.WorldLoader;
|
||||
import net.minecraft.server.commands.data.BlockDataAccessor;
|
||||
import net.minecraft.server.level.progress.ChunkProgressListener;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.RandomSequences;
|
||||
import net.minecraft.world.level.LevelReader;
|
||||
import net.minecraft.world.level.biome.Biomes;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
@ -36,6 +37,8 @@ import net.minecraft.world.level.dimension.LevelStem;
|
||||
import net.minecraft.world.level.levelgen.FlatLevelSource;
|
||||
import net.minecraft.world.level.levelgen.flat.FlatLayerInfo;
|
||||
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
|
||||
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||
import net.minecraft.world.level.storage.PrimaryLevelData;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
@ -45,12 +48,11 @@ import org.bukkit.craftbukkit.v1_20_R2.CraftWorld;
|
||||
import org.bukkit.craftbukkit.v1_20_R2.block.CraftBlockState;
|
||||
import org.bukkit.craftbukkit.v1_20_R2.block.CraftBlockStates;
|
||||
import org.bukkit.craftbukkit.v1_20_R2.block.data.CraftBlockData;
|
||||
import org.bukkit.craftbukkit.v1_20_R2.entity.CraftDolphin;
|
||||
import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftItemStack;
|
||||
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.Contract;
|
||||
@ -68,7 +70,6 @@ import com.volmit.iris.util.json.JSONObject;
|
||||
import com.volmit.iris.util.mantle.Mantle;
|
||||
import com.volmit.iris.util.math.Vector3d;
|
||||
import com.volmit.iris.util.matter.MatterBiomeInject;
|
||||
import com.volmit.iris.util.nbt.io.NBTUtil;
|
||||
import com.volmit.iris.util.nbt.mca.NBTWorld;
|
||||
import com.volmit.iris.util.nbt.mca.palette.*;
|
||||
import com.volmit.iris.util.nbt.tag.CompoundTag;
|
||||
@ -79,24 +80,22 @@ import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.level.biome.BiomeSource;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
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 sun.misc.Unsafe;
|
||||
|
||||
public class NMSBinding implements INMSBinding {
|
||||
private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
|
||||
private final BlockData AIR = Material.AIR.createBlockData();
|
||||
private final AtomicCache<MCAIdMap<net.minecraft.world.level.biome.Biome>> biomeMapCache = new AtomicCache<>();
|
||||
private final AtomicCache<WorldLoader.DataLoadContext> dataLoadContext = new AtomicCache<>();
|
||||
private final AtomicBoolean injected = new AtomicBoolean();
|
||||
private final AtomicCache<MCAIdMapper<BlockState>> registryCache = new AtomicCache<>();
|
||||
private final AtomicCache<MCAPalette<BlockState>> globalCache = new AtomicCache<>();
|
||||
private final AtomicCache<RegistryAccess> registryAccess = new AtomicCache<>();
|
||||
private final ReentrantLock dataContextLock = new ReentrantLock(true);
|
||||
private final KMap<World.Environment, LevelStem> stems = new KMap<>();
|
||||
private final AtomicCache<Method> byIdRef = new AtomicCache<>();
|
||||
private Field biomeStorageCache = null;
|
||||
|
||||
@ -646,44 +645,28 @@ public class NMSBinding implements INMSBinding {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public AutoClosing injectLevelStems() {
|
||||
if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!");
|
||||
@SuppressWarnings("all")
|
||||
public KMap<String, World.Environment> getMainWorlds() {
|
||||
String levelName = ServerProperties.LEVEL_NAME;
|
||||
KMap<String, World.Environment> worlds = new KMap<>();
|
||||
for (var key : registry().registryOrThrow(Registries.LEVEL_STEM).registryKeySet()) {
|
||||
World.Environment env = World.Environment.NORMAL;
|
||||
if (key == LevelStem.NETHER) {
|
||||
if (!Bukkit.getAllowNether())
|
||||
continue;
|
||||
env = World.Environment.NETHER;
|
||||
} else if (key == LevelStem.END) {
|
||||
if (!Bukkit.getAllowEnd())
|
||||
continue;
|
||||
env = World.Environment.THE_END;
|
||||
} else if (key != LevelStem.OVERWORLD) {
|
||||
env = World.Environment.CUSTOM;
|
||||
}
|
||||
|
||||
var server = ((CraftServer) Bukkit.getServer());
|
||||
var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class);
|
||||
var nmsServer = server.getServer();
|
||||
var old = nmsServer.worldLoader;
|
||||
|
||||
field.setAccessible(true);
|
||||
field.set(nmsServer, dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext(
|
||||
old.resources(),
|
||||
old.dataConfiguration(),
|
||||
old.datapackWorldgen(),
|
||||
createRegistryAccess(old.datapackDimensions(), false, true, true, true)
|
||||
)));
|
||||
|
||||
return new AutoClosing(() -> {
|
||||
field.set(nmsServer, old);
|
||||
dataContextLock.unlock();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public AutoClosing injectUncached(boolean overworld, boolean nether, boolean end) {
|
||||
var reg = registry();
|
||||
var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class);
|
||||
field.setAccessible(true);
|
||||
|
||||
var access = createRegistryAccess(((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions(), true, overworld, nether, end);
|
||||
var injected = access.registryOrThrow(Registries.LEVEL_STEM);
|
||||
var old = (Map<ResourceKey<? extends Registry<?>>, Registry<?>>) field.get(reg);
|
||||
var fake = new HashMap<>(old);
|
||||
fake.put(Registries.LEVEL_STEM, injected);
|
||||
field.set(reg, fake);
|
||||
|
||||
return new AutoClosing(() -> field.set(reg, old));
|
||||
String worldType = env == World.Environment.CUSTOM ? key.location().getNamespace() + "_" + key.location().getPath() : env.toString().toLowerCase(Locale.ROOT);
|
||||
worlds.put(key == LevelStem.OVERWORLD ? levelName : levelName + "_" + worldType, env);
|
||||
}
|
||||
return worlds;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -696,53 +679,74 @@ public class NMSBinding implements INMSBinding {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCustomDimensions(World world) {
|
||||
((CraftWorld) world).getHandle().K.customDimensions = null;
|
||||
}
|
||||
|
||||
private RegistryAccess.Frozen createRegistryAccess(RegistryAccess.Frozen datapack, boolean copy, boolean overworld, boolean nether, boolean end) {
|
||||
var access = registry();
|
||||
var dimensions = access.registryOrThrow(Registries.DIMENSION_TYPE);
|
||||
|
||||
var settings = new FlatLevelGeneratorSettings(
|
||||
Optional.empty(),
|
||||
access.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.THE_VOID),
|
||||
List.of()
|
||||
);
|
||||
settings.getLayersInfo().add(new FlatLayerInfo(1, Blocks.AIR));
|
||||
settings.updateLayers();
|
||||
|
||||
var source = new FlatLevelSource(settings);
|
||||
var fake = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.experimental());
|
||||
if (overworld) register(fake, dimensions, source, LevelStem.OVERWORLD);
|
||||
if (nether) register(fake, dimensions, source, LevelStem.NETHER);
|
||||
if (end) register(fake, dimensions, source, LevelStem.END);
|
||||
copy(fake, datapack.registry(Registries.LEVEL_STEM).orElse(null));
|
||||
|
||||
if (copy) copy(fake, access.registryOrThrow(Registries.LEVEL_STEM));
|
||||
|
||||
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake)).freeze();
|
||||
}
|
||||
|
||||
private void register(MappedRegistry<LevelStem> target, Registry<DimensionType> dimensions, FlatLevelSource source, ResourceKey<LevelStem> key) {
|
||||
var loc = createIrisKey(key);
|
||||
target.register(key, new LevelStem(
|
||||
dimensions.getHolder(ResourceKey.create(Registries.DIMENSION_TYPE, loc)).orElseThrow(() -> new IllegalStateException("Missing dimension type " + loc + " in " + dimensions.keySet())),
|
||||
source
|
||||
), Lifecycle.stable());
|
||||
}
|
||||
|
||||
private void copy(MappedRegistry<LevelStem> target, Registry<LevelStem> source) {
|
||||
if (source == null) return;
|
||||
source.registryKeySet().forEach(key -> {
|
||||
var value = source.get(key);
|
||||
var info = source.lifecycle(value);
|
||||
if (value != null && info != null && !target.containsKey(key))
|
||||
target.register(key, value, info);
|
||||
});
|
||||
public boolean injectBukkit() {
|
||||
if (injected.getAndSet(true))
|
||||
return true;
|
||||
try {
|
||||
Iris.info("Injecting Bukkit");
|
||||
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(), Agent.installed());
|
||||
return true;
|
||||
} catch (Throwable e) {
|
||||
Iris.error(C.RED + "Failed to inject Bukkit");
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private ResourceLocation createIrisKey(ResourceKey<LevelStem> key) {
|
||||
return new ResourceLocation("iris", key.location().getPath());
|
||||
}
|
||||
|
||||
public LevelStem levelStem(RegistryAccess access, World.Environment env) {
|
||||
if (env == World.Environment.CUSTOM)
|
||||
env = World.Environment.NORMAL;
|
||||
return stems.computeIfAbsent(env, key -> new LevelStem(dimensionType(access, key), chunkGenerator(access)));
|
||||
}
|
||||
|
||||
private Holder.Reference<DimensionType> dimensionType(RegistryAccess access, World.Environment env) {
|
||||
return access.registryOrThrow(Registries.DIMENSION_TYPE).getHolderOrThrow(ResourceKey.create(Registries.DIMENSION_TYPE, new ResourceLocation("iris", switch (env) {
|
||||
case NORMAL, CUSTOM -> "overworld";
|
||||
case NETHER -> "the_nether";
|
||||
case THE_END -> "the_end";
|
||||
})));
|
||||
}
|
||||
|
||||
private net.minecraft.world.level.chunk.ChunkGenerator chunkGenerator(RegistryAccess access) {
|
||||
var settings = new FlatLevelGeneratorSettings(Optional.empty(), access.registryOrThrow(Registries.BIOME).getHolderOrThrow(Biomes.THE_VOID), List.of());
|
||||
settings.getLayersInfo().add(new FlatLayerInfo(1, Blocks.AIR));
|
||||
settings.updateLayers();
|
||||
return new FlatLevelSource(settings);
|
||||
}
|
||||
|
||||
private static class ServerLevelAdvice {
|
||||
@SneakyThrows
|
||||
@Advice.OnMethodEnter
|
||||
static void enter(
|
||||
@Advice.Argument(0) MinecraftServer server,
|
||||
@Advice.Argument(3) PrimaryLevelData levelData,
|
||||
@Advice.Argument(value = 5, readOnly = false) LevelStem levelStem,
|
||||
@Advice.Argument(12) World.Environment env,
|
||||
@Advice.Argument(value = 13) ChunkGenerator gen
|
||||
) {
|
||||
if (gen == null || !gen.getClass().getPackageName().startsWith("com.volmit.iris"))
|
||||
return;
|
||||
|
||||
Object bindings = Class.forName("com.volmit.iris.core.nms.INMS", true, Bukkit.getPluginManager().getPlugin("Iris")
|
||||
.getClass()
|
||||
.getClassLoader())
|
||||
.getDeclaredMethod("get")
|
||||
.invoke(null);
|
||||
levelStem = (LevelStem) bindings.getClass()
|
||||
.getDeclaredMethod("levelStem", RegistryAccess.class, World.Environment.class)
|
||||
.invoke(bindings, server.registryAccess(), env);
|
||||
levelData.customDimensions = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,32 +1,33 @@
|
||||
package com.volmit.iris.core.nms.v1_20_R3;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import com.mojang.serialization.Lifecycle;
|
||||
import com.volmit.iris.core.nms.container.AutoClosing;
|
||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||
import com.volmit.iris.util.agent.Agent;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import com.volmit.iris.util.misc.ServerProperties;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import lombok.SneakyThrows;
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.matcher.ElementMatchers;
|
||||
import net.minecraft.core.*;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.nbt.*;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.WorldLoader;
|
||||
import net.minecraft.server.commands.data.BlockDataAccessor;
|
||||
import net.minecraft.server.level.progress.ChunkProgressListener;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.RandomSequences;
|
||||
import net.minecraft.world.level.LevelReader;
|
||||
import net.minecraft.world.level.biome.Biomes;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
@ -36,6 +37,8 @@ import net.minecraft.world.level.dimension.LevelStem;
|
||||
import net.minecraft.world.level.levelgen.FlatLevelSource;
|
||||
import net.minecraft.world.level.levelgen.flat.FlatLayerInfo;
|
||||
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
|
||||
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||
import net.minecraft.world.level.storage.PrimaryLevelData;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
@ -45,12 +48,11 @@ import org.bukkit.craftbukkit.v1_20_R3.CraftWorld;
|
||||
import org.bukkit.craftbukkit.v1_20_R3.block.CraftBlockState;
|
||||
import org.bukkit.craftbukkit.v1_20_R3.block.CraftBlockStates;
|
||||
import org.bukkit.craftbukkit.v1_20_R3.block.data.CraftBlockData;
|
||||
import org.bukkit.craftbukkit.v1_20_R3.entity.CraftDolphin;
|
||||
import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftItemStack;
|
||||
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.Contract;
|
||||
@ -68,7 +70,6 @@ import com.volmit.iris.util.json.JSONObject;
|
||||
import com.volmit.iris.util.mantle.Mantle;
|
||||
import com.volmit.iris.util.math.Vector3d;
|
||||
import com.volmit.iris.util.matter.MatterBiomeInject;
|
||||
import com.volmit.iris.util.nbt.io.NBTUtil;
|
||||
import com.volmit.iris.util.nbt.mca.NBTWorld;
|
||||
import com.volmit.iris.util.nbt.mca.palette.*;
|
||||
import com.volmit.iris.util.nbt.tag.CompoundTag;
|
||||
@ -79,24 +80,22 @@ import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.level.biome.BiomeSource;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
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 sun.misc.Unsafe;
|
||||
|
||||
public class NMSBinding implements INMSBinding {
|
||||
private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
|
||||
private final BlockData AIR = Material.AIR.createBlockData();
|
||||
private final AtomicCache<MCAIdMap<net.minecraft.world.level.biome.Biome>> biomeMapCache = new AtomicCache<>();
|
||||
private final AtomicCache<WorldLoader.DataLoadContext> dataLoadContext = new AtomicCache<>();
|
||||
private final AtomicBoolean injected = new AtomicBoolean();
|
||||
private final AtomicCache<MCAIdMapper<BlockState>> registryCache = new AtomicCache<>();
|
||||
private final AtomicCache<MCAPalette<BlockState>> globalCache = new AtomicCache<>();
|
||||
private final AtomicCache<RegistryAccess> registryAccess = new AtomicCache<>();
|
||||
private final ReentrantLock dataContextLock = new ReentrantLock(true);
|
||||
private final KMap<World.Environment, LevelStem> stems = new KMap<>();
|
||||
private final AtomicCache<Method> byIdRef = new AtomicCache<>();
|
||||
private Field biomeStorageCache = null;
|
||||
|
||||
@ -647,44 +646,28 @@ public class NMSBinding implements INMSBinding {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public AutoClosing injectLevelStems() {
|
||||
if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!");
|
||||
@SuppressWarnings("all")
|
||||
public KMap<String, World.Environment> getMainWorlds() {
|
||||
String levelName = ServerProperties.LEVEL_NAME;
|
||||
KMap<String, World.Environment> worlds = new KMap<>();
|
||||
for (var key : registry().registryOrThrow(Registries.LEVEL_STEM).registryKeySet()) {
|
||||
World.Environment env = World.Environment.NORMAL;
|
||||
if (key == LevelStem.NETHER) {
|
||||
if (!Bukkit.getAllowNether())
|
||||
continue;
|
||||
env = World.Environment.NETHER;
|
||||
} else if (key == LevelStem.END) {
|
||||
if (!Bukkit.getAllowEnd())
|
||||
continue;
|
||||
env = World.Environment.THE_END;
|
||||
} else if (key != LevelStem.OVERWORLD) {
|
||||
env = World.Environment.CUSTOM;
|
||||
}
|
||||
|
||||
var server = ((CraftServer) Bukkit.getServer());
|
||||
var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class);
|
||||
var nmsServer = server.getServer();
|
||||
var old = nmsServer.worldLoader;
|
||||
|
||||
field.setAccessible(true);
|
||||
field.set(nmsServer, dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext(
|
||||
old.resources(),
|
||||
old.dataConfiguration(),
|
||||
old.datapackWorldgen(),
|
||||
createRegistryAccess(old.datapackDimensions(), false, true, true, true)
|
||||
)));
|
||||
|
||||
return new AutoClosing(() -> {
|
||||
field.set(nmsServer, old);
|
||||
dataContextLock.unlock();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public AutoClosing injectUncached(boolean overworld, boolean nether, boolean end) {
|
||||
var reg = registry();
|
||||
var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class);
|
||||
field.setAccessible(true);
|
||||
|
||||
var access = createRegistryAccess(((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions(), true, overworld, nether, end);
|
||||
var injected = access.registryOrThrow(Registries.LEVEL_STEM);
|
||||
var old = (Map<ResourceKey<? extends Registry<?>>, Registry<?>>) field.get(reg);
|
||||
var fake = new HashMap<>(old);
|
||||
fake.put(Registries.LEVEL_STEM, injected);
|
||||
field.set(reg, fake);
|
||||
|
||||
return new AutoClosing(() -> field.set(reg, old));
|
||||
String worldType = env == World.Environment.CUSTOM ? key.location().getNamespace() + "_" + key.location().getPath() : env.toString().toLowerCase(Locale.ROOT);
|
||||
worlds.put(key == LevelStem.OVERWORLD ? levelName : levelName + "_" + worldType, env);
|
||||
}
|
||||
return worlds;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -697,53 +680,74 @@ public class NMSBinding implements INMSBinding {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCustomDimensions(World world) {
|
||||
((CraftWorld) world).getHandle().K.customDimensions = null;
|
||||
}
|
||||
|
||||
private RegistryAccess.Frozen createRegistryAccess(RegistryAccess.Frozen datapack, boolean copy, boolean overworld, boolean nether, boolean end) {
|
||||
var access = registry();
|
||||
var dimensions = access.registryOrThrow(Registries.DIMENSION_TYPE);
|
||||
|
||||
var settings = new FlatLevelGeneratorSettings(
|
||||
Optional.empty(),
|
||||
access.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.THE_VOID),
|
||||
List.of()
|
||||
);
|
||||
settings.getLayersInfo().add(new FlatLayerInfo(1, Blocks.AIR));
|
||||
settings.updateLayers();
|
||||
|
||||
var source = new FlatLevelSource(settings);
|
||||
var fake = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.experimental());
|
||||
if (overworld) register(fake, dimensions, source, LevelStem.OVERWORLD);
|
||||
if (nether) register(fake, dimensions, source, LevelStem.NETHER);
|
||||
if (end) register(fake, dimensions, source, LevelStem.END);
|
||||
copy(fake, datapack.registry(Registries.LEVEL_STEM).orElse(null));
|
||||
|
||||
if (copy) copy(fake, access.registryOrThrow(Registries.LEVEL_STEM));
|
||||
|
||||
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake)).freeze();
|
||||
}
|
||||
|
||||
private void register(MappedRegistry<LevelStem> target, Registry<DimensionType> dimensions, FlatLevelSource source, ResourceKey<LevelStem> key) {
|
||||
var loc = createIrisKey(key);
|
||||
target.register(key, new LevelStem(
|
||||
dimensions.getHolder(ResourceKey.create(Registries.DIMENSION_TYPE, loc)).orElseThrow(() -> new IllegalStateException("Missing dimension type " + loc + " in " + dimensions.keySet())),
|
||||
source
|
||||
), Lifecycle.stable());
|
||||
}
|
||||
|
||||
private void copy(MappedRegistry<LevelStem> target, Registry<LevelStem> source) {
|
||||
if (source == null) return;
|
||||
source.registryKeySet().forEach(key -> {
|
||||
var value = source.get(key);
|
||||
var info = source.lifecycle(value);
|
||||
if (value != null && info != null && !target.containsKey(key))
|
||||
target.register(key, value, info);
|
||||
});
|
||||
public boolean injectBukkit() {
|
||||
if (injected.getAndSet(true))
|
||||
return true;
|
||||
try {
|
||||
Iris.info("Injecting Bukkit");
|
||||
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(), Agent.installed());
|
||||
return true;
|
||||
} catch (Throwable e) {
|
||||
Iris.error(C.RED + "Failed to inject Bukkit");
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private ResourceLocation createIrisKey(ResourceKey<LevelStem> key) {
|
||||
return new ResourceLocation("iris", key.location().getPath());
|
||||
}
|
||||
|
||||
public LevelStem levelStem(RegistryAccess access, World.Environment env) {
|
||||
if (env == World.Environment.CUSTOM)
|
||||
env = World.Environment.NORMAL;
|
||||
return stems.computeIfAbsent(env, key -> new LevelStem(dimensionType(access, key), chunkGenerator(access)));
|
||||
}
|
||||
|
||||
private Holder.Reference<DimensionType> dimensionType(RegistryAccess access, World.Environment env) {
|
||||
return access.registryOrThrow(Registries.DIMENSION_TYPE).getHolderOrThrow(ResourceKey.create(Registries.DIMENSION_TYPE, new ResourceLocation("iris", switch (env) {
|
||||
case NORMAL, CUSTOM -> "overworld";
|
||||
case NETHER -> "the_nether";
|
||||
case THE_END -> "the_end";
|
||||
})));
|
||||
}
|
||||
|
||||
private net.minecraft.world.level.chunk.ChunkGenerator chunkGenerator(RegistryAccess access) {
|
||||
var settings = new FlatLevelGeneratorSettings(Optional.empty(), access.registryOrThrow(Registries.BIOME).getHolderOrThrow(Biomes.THE_VOID), List.of());
|
||||
settings.getLayersInfo().add(new FlatLayerInfo(1, Blocks.AIR));
|
||||
settings.updateLayers();
|
||||
return new FlatLevelSource(settings);
|
||||
}
|
||||
|
||||
private static class ServerLevelAdvice {
|
||||
@SneakyThrows
|
||||
@Advice.OnMethodEnter
|
||||
static void enter(
|
||||
@Advice.Argument(0) MinecraftServer server,
|
||||
@Advice.Argument(3) PrimaryLevelData levelData,
|
||||
@Advice.Argument(value = 5, readOnly = false) LevelStem levelStem,
|
||||
@Advice.Argument(12) World.Environment env,
|
||||
@Advice.Argument(value = 13) ChunkGenerator gen
|
||||
) {
|
||||
if (gen == null || !gen.getClass().getPackageName().startsWith("com.volmit.iris"))
|
||||
return;
|
||||
|
||||
Object bindings = Class.forName("com.volmit.iris.core.nms.INMS", true, Bukkit.getPluginManager().getPlugin("Iris")
|
||||
.getClass()
|
||||
.getClassLoader())
|
||||
.getDeclaredMethod("get")
|
||||
.invoke(null);
|
||||
levelStem = (LevelStem) bindings.getClass()
|
||||
.getDeclaredMethod("levelStem", RegistryAccess.class, World.Environment.class)
|
||||
.invoke(bindings, server.registryAccess(), env);
|
||||
levelData.customDimensions = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,18 +5,22 @@ import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import com.mojang.serialization.Lifecycle;
|
||||
import com.volmit.iris.core.nms.container.AutoClosing;
|
||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||
import com.volmit.iris.core.nms.datapack.DataVersion;
|
||||
import com.volmit.iris.util.agent.Agent;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import com.volmit.iris.util.misc.ServerProperties;
|
||||
import com.volmit.iris.util.nbt.tag.CompoundTag;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import lombok.SneakyThrows;
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.matcher.ElementMatchers;
|
||||
import net.minecraft.core.*;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
@ -31,23 +35,24 @@ import net.minecraft.nbt.ShortTag;
|
||||
import net.minecraft.nbt.StringTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.WorldLoader;
|
||||
import net.minecraft.server.commands.data.BlockDataAccessor;
|
||||
import net.minecraft.server.commands.data.DataCommands;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.progress.ChunkProgressListener;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.RandomSequences;
|
||||
import net.minecraft.world.item.component.CustomData;
|
||||
import net.minecraft.world.level.LevelReader;
|
||||
import net.minecraft.world.level.biome.Biomes;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.EntityBlock;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
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.levelgen.FlatLevelSource;
|
||||
import net.minecraft.world.level.levelgen.flat.FlatLayerInfo;
|
||||
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
|
||||
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;
|
||||
@ -56,15 +61,12 @@ import org.bukkit.craftbukkit.v1_20_R4.CraftServer;
|
||||
import org.bukkit.craftbukkit.v1_20_R4.CraftWorld;
|
||||
import org.bukkit.craftbukkit.v1_20_R4.block.CraftBlockState;
|
||||
import org.bukkit.craftbukkit.v1_20_R4.block.CraftBlockStates;
|
||||
import org.bukkit.craftbukkit.v1_20_R4.block.CraftBlockType;
|
||||
import org.bukkit.craftbukkit.v1_20_R4.block.data.CraftBlockData;
|
||||
import org.bukkit.craftbukkit.v1_20_R4.entity.CraftDolphin;
|
||||
import org.bukkit.craftbukkit.v1_20_R4.generator.CustomChunkGenerator;
|
||||
import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack;
|
||||
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.Contract;
|
||||
@ -100,11 +102,11 @@ public class NMSBinding implements INMSBinding {
|
||||
private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
|
||||
private final BlockData AIR = Material.AIR.createBlockData();
|
||||
private final AtomicCache<MCAIdMap<net.minecraft.world.level.biome.Biome>> biomeMapCache = new AtomicCache<>();
|
||||
private final AtomicCache<WorldLoader.DataLoadContext> dataLoadContext = new AtomicCache<>();
|
||||
private final AtomicBoolean injected = new AtomicBoolean();
|
||||
private final AtomicCache<MCAIdMapper<BlockState>> registryCache = new AtomicCache<>();
|
||||
private final AtomicCache<MCAPalette<BlockState>> globalCache = new AtomicCache<>();
|
||||
private final AtomicCache<RegistryAccess> registryAccess = new AtomicCache<>();
|
||||
private final ReentrantLock dataContextLock = new ReentrantLock(true);
|
||||
private final KMap<World.Environment, LevelStem> stems = new KMap<>();
|
||||
private final AtomicCache<Method> byIdRef = new AtomicCache<>();
|
||||
private Field biomeStorageCache = null;
|
||||
|
||||
@ -672,44 +674,28 @@ public class NMSBinding implements INMSBinding {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public AutoClosing injectLevelStems() {
|
||||
if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!");
|
||||
@SuppressWarnings("all")
|
||||
public KMap<String, World.Environment> getMainWorlds() {
|
||||
String levelName = ServerProperties.LEVEL_NAME;
|
||||
KMap<String, World.Environment> worlds = new KMap<>();
|
||||
for (var key : registry().registryOrThrow(Registries.LEVEL_STEM).registryKeySet()) {
|
||||
World.Environment env = World.Environment.NORMAL;
|
||||
if (key == LevelStem.NETHER) {
|
||||
if (!Bukkit.getAllowNether())
|
||||
continue;
|
||||
env = World.Environment.NETHER;
|
||||
} else if (key == LevelStem.END) {
|
||||
if (!Bukkit.getAllowEnd())
|
||||
continue;
|
||||
env = World.Environment.THE_END;
|
||||
} else if (key != LevelStem.OVERWORLD) {
|
||||
env = World.Environment.CUSTOM;
|
||||
}
|
||||
|
||||
var server = ((CraftServer) Bukkit.getServer());
|
||||
var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class);
|
||||
var nmsServer = server.getServer();
|
||||
var old = nmsServer.worldLoader;
|
||||
|
||||
field.setAccessible(true);
|
||||
field.set(nmsServer, dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext(
|
||||
old.resources(),
|
||||
old.dataConfiguration(),
|
||||
old.datapackWorldgen(),
|
||||
createRegistryAccess(old.datapackDimensions(), false, true, true, true)
|
||||
)));
|
||||
|
||||
return new AutoClosing(() -> {
|
||||
field.set(nmsServer, old);
|
||||
dataContextLock.unlock();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public AutoClosing injectUncached(boolean overworld, boolean nether, boolean end) {
|
||||
var reg = registry();
|
||||
var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class);
|
||||
field.setAccessible(true);
|
||||
|
||||
var access = createRegistryAccess(((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions(), true, overworld, nether, end);
|
||||
var injected = access.registryOrThrow(Registries.LEVEL_STEM);
|
||||
var old = (Map<ResourceKey<? extends Registry<?>>, Registry<?>>) field.get(reg);
|
||||
var fake = new HashMap<>(old);
|
||||
fake.put(Registries.LEVEL_STEM, injected);
|
||||
field.set(reg, fake);
|
||||
|
||||
return new AutoClosing(() -> field.set(reg, old));
|
||||
String worldType = env == World.Environment.CUSTOM ? key.location().getNamespace() + "_" + key.location().getPath() : env.toString().toLowerCase(Locale.ROOT);
|
||||
worlds.put(key == LevelStem.OVERWORLD ? levelName : levelName + "_" + worldType, env);
|
||||
}
|
||||
return worlds;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -722,53 +708,74 @@ public class NMSBinding implements INMSBinding {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCustomDimensions(World world) {
|
||||
((CraftWorld) world).getHandle().K.customDimensions = null;
|
||||
}
|
||||
|
||||
private RegistryAccess.Frozen createRegistryAccess(RegistryAccess.Frozen datapack, boolean copy, boolean overworld, boolean nether, boolean end) {
|
||||
var access = registry();
|
||||
var dimensions = access.registryOrThrow(Registries.DIMENSION_TYPE);
|
||||
|
||||
var settings = new FlatLevelGeneratorSettings(
|
||||
Optional.empty(),
|
||||
access.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.THE_VOID),
|
||||
List.of()
|
||||
);
|
||||
settings.getLayersInfo().add(new FlatLayerInfo(1, Blocks.AIR));
|
||||
settings.updateLayers();
|
||||
|
||||
var source = new FlatLevelSource(settings);
|
||||
var fake = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.experimental());
|
||||
if (overworld) register(fake, dimensions, source, LevelStem.OVERWORLD);
|
||||
if (nether) register(fake, dimensions, source, LevelStem.NETHER);
|
||||
if (end) register(fake, dimensions, source, LevelStem.END);
|
||||
copy(fake, datapack.registry(Registries.LEVEL_STEM).orElse(null));
|
||||
|
||||
if (copy) copy(fake, access.registryOrThrow(Registries.LEVEL_STEM));
|
||||
|
||||
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake)).freeze();
|
||||
}
|
||||
|
||||
private void register(MappedRegistry<LevelStem> target, Registry<DimensionType> dimensions, FlatLevelSource source, ResourceKey<LevelStem> key) {
|
||||
var loc = createIrisKey(key);
|
||||
target.register(key, new LevelStem(
|
||||
dimensions.getHolder(ResourceKey.create(Registries.DIMENSION_TYPE, loc)).orElseThrow(() -> new IllegalStateException("Missing dimension type " + loc + " in " + dimensions.keySet())),
|
||||
source
|
||||
), RegistrationInfo.BUILT_IN);
|
||||
}
|
||||
|
||||
private void copy(MappedRegistry<LevelStem> target, Registry<LevelStem> source) {
|
||||
if (source == null) return;
|
||||
source.registryKeySet().forEach(key -> {
|
||||
var value = source.get(key);
|
||||
var info = source.registrationInfo(key).orElse(null);
|
||||
if (value != null && info != null && !target.containsKey(key))
|
||||
target.register(key, value, info);
|
||||
});
|
||||
public boolean injectBukkit() {
|
||||
if (injected.getAndSet(true))
|
||||
return true;
|
||||
try {
|
||||
Iris.info("Injecting Bukkit");
|
||||
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(), Agent.installed());
|
||||
return true;
|
||||
} catch (Throwable e) {
|
||||
Iris.error(C.RED + "Failed to inject Bukkit");
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private ResourceLocation createIrisKey(ResourceKey<LevelStem> key) {
|
||||
return new ResourceLocation("iris", key.location().getPath());
|
||||
}
|
||||
|
||||
public LevelStem levelStem(RegistryAccess access, World.Environment env) {
|
||||
if (env == World.Environment.CUSTOM)
|
||||
env = World.Environment.NORMAL;
|
||||
return stems.computeIfAbsent(env, key -> new LevelStem(dimensionType(access, key), chunkGenerator(access)));
|
||||
}
|
||||
|
||||
private Holder.Reference<DimensionType> dimensionType(RegistryAccess access, World.Environment env) {
|
||||
return access.registryOrThrow(Registries.DIMENSION_TYPE).getHolderOrThrow(ResourceKey.create(Registries.DIMENSION_TYPE, new ResourceLocation("iris", switch (env) {
|
||||
case NORMAL, CUSTOM -> "overworld";
|
||||
case NETHER -> "the_nether";
|
||||
case THE_END -> "the_end";
|
||||
})));
|
||||
}
|
||||
|
||||
private net.minecraft.world.level.chunk.ChunkGenerator chunkGenerator(RegistryAccess access) {
|
||||
var settings = new FlatLevelGeneratorSettings(Optional.empty(), access.registryOrThrow(Registries.BIOME).getHolderOrThrow(Biomes.THE_VOID), List.of());
|
||||
settings.getLayersInfo().add(new FlatLayerInfo(1, Blocks.AIR));
|
||||
settings.updateLayers();
|
||||
return new FlatLevelSource(settings);
|
||||
}
|
||||
|
||||
private static class ServerLevelAdvice {
|
||||
@SneakyThrows
|
||||
@Advice.OnMethodEnter
|
||||
static void enter(
|
||||
@Advice.Argument(0) MinecraftServer server,
|
||||
@Advice.Argument(3) PrimaryLevelData levelData,
|
||||
@Advice.Argument(value = 5, readOnly = false) LevelStem levelStem,
|
||||
@Advice.Argument(12) World.Environment env,
|
||||
@Advice.Argument(value = 13) ChunkGenerator gen
|
||||
) {
|
||||
if (gen == null || !gen.getClass().getPackageName().startsWith("com.volmit.iris"))
|
||||
return;
|
||||
|
||||
Object bindings = Class.forName("com.volmit.iris.core.nms.INMS", true, Bukkit.getPluginManager().getPlugin("Iris")
|
||||
.getClass()
|
||||
.getClassLoader())
|
||||
.getDeclaredMethod("get")
|
||||
.invoke(null);
|
||||
levelStem = (LevelStem) bindings.getClass()
|
||||
.getDeclaredMethod("levelStem", RegistryAccess.class, World.Environment.class)
|
||||
.invoke(bindings, server.registryAccess(), env);
|
||||
levelData.customDimensions = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,35 +1,35 @@
|
||||
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.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import com.mojang.serialization.Lifecycle;
|
||||
import com.volmit.iris.core.nms.container.AutoClosing;
|
||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||
import com.volmit.iris.core.nms.datapack.DataVersion;
|
||||
import com.volmit.iris.util.agent.Agent;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import com.volmit.iris.util.misc.ServerProperties;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import lombok.SneakyThrows;
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.matcher.ElementMatchers;
|
||||
import net.minecraft.core.*;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.nbt.*;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.WorldLoader;
|
||||
import net.minecraft.server.commands.data.BlockDataAccessor;
|
||||
import net.minecraft.server.level.ChunkMap;
|
||||
import net.minecraft.server.level.progress.ChunkProgressListener;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.RandomSequences;
|
||||
import net.minecraft.world.item.component.CustomData;
|
||||
import net.minecraft.world.level.LevelReader;
|
||||
import net.minecraft.world.level.biome.Biomes;
|
||||
@ -42,6 +42,8 @@ import net.minecraft.world.level.dimension.LevelStem;
|
||||
import net.minecraft.world.level.levelgen.FlatLevelSource;
|
||||
import net.minecraft.world.level.levelgen.flat.FlatLayerInfo;
|
||||
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
|
||||
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;
|
||||
@ -51,12 +53,11 @@ import org.bukkit.craftbukkit.v1_21_R1.CraftWorld;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.block.CraftBlockState;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.block.CraftBlockStates;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.block.data.CraftBlockData;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.entity.CraftDolphin;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftItemStack;
|
||||
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.Contract;
|
||||
@ -74,7 +75,6 @@ import com.volmit.iris.util.json.JSONObject;
|
||||
import com.volmit.iris.util.mantle.Mantle;
|
||||
import com.volmit.iris.util.math.Vector3d;
|
||||
import com.volmit.iris.util.matter.MatterBiomeInject;
|
||||
import com.volmit.iris.util.nbt.io.NBTUtil;
|
||||
import com.volmit.iris.util.nbt.mca.NBTWorld;
|
||||
import com.volmit.iris.util.nbt.mca.palette.*;
|
||||
import com.volmit.iris.util.nbt.tag.CompoundTag;
|
||||
@ -85,23 +85,21 @@ import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.level.biome.BiomeSource;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import sun.misc.Unsafe;
|
||||
|
||||
public class NMSBinding implements INMSBinding {
|
||||
private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
|
||||
private final BlockData AIR = Material.AIR.createBlockData();
|
||||
private final AtomicCache<MCAIdMap<net.minecraft.world.level.biome.Biome>> biomeMapCache = new AtomicCache<>();
|
||||
private final AtomicCache<WorldLoader.DataLoadContext> dataLoadContext = new AtomicCache<>();
|
||||
private final AtomicBoolean injected = new AtomicBoolean();
|
||||
private final AtomicCache<MCAIdMapper<BlockState>> registryCache = new AtomicCache<>();
|
||||
private final AtomicCache<MCAPalette<BlockState>> globalCache = new AtomicCache<>();
|
||||
private final AtomicCache<RegistryAccess> registryAccess = new AtomicCache<>();
|
||||
private final ReentrantLock dataContextLock = new ReentrantLock(true);
|
||||
private final KMap<World.Environment, LevelStem> stems = new KMap<>();
|
||||
private final AtomicCache<Method> byIdRef = new AtomicCache<>();
|
||||
private Field biomeStorageCache = null;
|
||||
|
||||
@ -676,44 +674,28 @@ public class NMSBinding implements INMSBinding {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public AutoClosing injectLevelStems() {
|
||||
if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!");
|
||||
@SuppressWarnings("all")
|
||||
public KMap<String, World.Environment> getMainWorlds() {
|
||||
String levelName = ServerProperties.LEVEL_NAME;
|
||||
KMap<String, World.Environment> worlds = new KMap<>();
|
||||
for (var key : registry().registryOrThrow(Registries.LEVEL_STEM).registryKeySet()) {
|
||||
World.Environment env = World.Environment.NORMAL;
|
||||
if (key == LevelStem.NETHER) {
|
||||
if (!Bukkit.getAllowNether())
|
||||
continue;
|
||||
env = World.Environment.NETHER;
|
||||
} else if (key == LevelStem.END) {
|
||||
if (!Bukkit.getAllowEnd())
|
||||
continue;
|
||||
env = World.Environment.THE_END;
|
||||
} else if (key != LevelStem.OVERWORLD) {
|
||||
env = World.Environment.CUSTOM;
|
||||
}
|
||||
|
||||
var server = ((CraftServer) Bukkit.getServer());
|
||||
var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class);
|
||||
var nmsServer = server.getServer();
|
||||
var old = nmsServer.worldLoader;
|
||||
|
||||
field.setAccessible(true);
|
||||
field.set(nmsServer, dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext(
|
||||
old.resources(),
|
||||
old.dataConfiguration(),
|
||||
old.datapackWorldgen(),
|
||||
createRegistryAccess(old.datapackDimensions(), false, true, true, true)
|
||||
)));
|
||||
|
||||
return new AutoClosing(() -> {
|
||||
field.set(nmsServer, old);
|
||||
dataContextLock.unlock();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public AutoClosing injectUncached(boolean overworld, boolean nether, boolean end) {
|
||||
var reg = registry();
|
||||
var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class);
|
||||
field.setAccessible(true);
|
||||
|
||||
var access = createRegistryAccess(((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions(), true, overworld, nether, end);
|
||||
var injected = access.registryOrThrow(Registries.LEVEL_STEM);
|
||||
var old = (Map<ResourceKey<? extends Registry<?>>, Registry<?>>) field.get(reg);
|
||||
var fake = new HashMap<>(old);
|
||||
fake.put(Registries.LEVEL_STEM, injected);
|
||||
field.set(reg, fake);
|
||||
|
||||
return new AutoClosing(() -> field.set(reg, old));
|
||||
String worldType = env == World.Environment.CUSTOM ? key.location().getNamespace() + "_" + key.location().getPath() : env.toString().toLowerCase(Locale.ROOT);
|
||||
worlds.put(key == LevelStem.OVERWORLD ? levelName : levelName + "_" + worldType, env);
|
||||
}
|
||||
return worlds;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -726,53 +708,74 @@ public class NMSBinding implements INMSBinding {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCustomDimensions(World world) {
|
||||
((CraftWorld) world).getHandle().K.customDimensions = null;
|
||||
}
|
||||
|
||||
private RegistryAccess.Frozen createRegistryAccess(RegistryAccess.Frozen datapack, boolean copy, boolean overworld, boolean nether, boolean end) {
|
||||
var access = registry();
|
||||
var dimensions = access.registryOrThrow(Registries.DIMENSION_TYPE);
|
||||
|
||||
var settings = new FlatLevelGeneratorSettings(
|
||||
Optional.empty(),
|
||||
access.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.THE_VOID),
|
||||
List.of()
|
||||
);
|
||||
settings.getLayersInfo().add(new FlatLayerInfo(1, Blocks.AIR));
|
||||
settings.updateLayers();
|
||||
|
||||
var source = new FlatLevelSource(settings);
|
||||
var fake = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.experimental());
|
||||
if (overworld) register(fake, dimensions, source, LevelStem.OVERWORLD);
|
||||
if (nether) register(fake, dimensions, source, LevelStem.NETHER);
|
||||
if (end) register(fake, dimensions, source, LevelStem.END);
|
||||
copy(fake, datapack.registry(Registries.LEVEL_STEM).orElse(null));
|
||||
|
||||
if (copy) copy(fake, access.registryOrThrow(Registries.LEVEL_STEM));
|
||||
|
||||
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake)).freeze();
|
||||
}
|
||||
|
||||
private void register(MappedRegistry<LevelStem> target, Registry<DimensionType> dimensions, FlatLevelSource source, ResourceKey<LevelStem> key) {
|
||||
var loc = createIrisKey(key);
|
||||
target.register(key, new LevelStem(
|
||||
dimensions.getHolder(ResourceKey.create(Registries.DIMENSION_TYPE, loc)).orElseThrow(() -> new IllegalStateException("Missing dimension type " + loc + " in " + dimensions.keySet())),
|
||||
source
|
||||
), RegistrationInfo.BUILT_IN);
|
||||
}
|
||||
|
||||
private void copy(MappedRegistry<LevelStem> target, Registry<LevelStem> source) {
|
||||
if (source == null) return;
|
||||
source.registryKeySet().forEach(key -> {
|
||||
var value = source.get(key);
|
||||
var info = source.registrationInfo(key).orElse(null);
|
||||
if (value != null && info != null && !target.containsKey(key))
|
||||
target.register(key, value, info);
|
||||
});
|
||||
public boolean injectBukkit() {
|
||||
if (injected.getAndSet(true))
|
||||
return true;
|
||||
try {
|
||||
Iris.info("Injecting Bukkit");
|
||||
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(), Agent.installed());
|
||||
return true;
|
||||
} catch (Throwable e) {
|
||||
Iris.error(C.RED + "Failed to inject Bukkit");
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private ResourceLocation createIrisKey(ResourceKey<LevelStem> key) {
|
||||
return ResourceLocation.fromNamespaceAndPath("iris", key.location().getPath());
|
||||
}
|
||||
|
||||
public LevelStem levelStem(RegistryAccess access, World.Environment env) {
|
||||
if (env == World.Environment.CUSTOM)
|
||||
env = World.Environment.NORMAL;
|
||||
return stems.computeIfAbsent(env, key -> new LevelStem(dimensionType(access, key), chunkGenerator(access)));
|
||||
}
|
||||
|
||||
private Holder.Reference<DimensionType> dimensionType(RegistryAccess access, World.Environment env) {
|
||||
return access.registryOrThrow(Registries.DIMENSION_TYPE).getHolderOrThrow(ResourceKey.create(Registries.DIMENSION_TYPE, ResourceLocation.fromNamespaceAndPath("iris", switch (env) {
|
||||
case NORMAL, CUSTOM -> "overworld";
|
||||
case NETHER -> "the_nether";
|
||||
case THE_END -> "the_end";
|
||||
})));
|
||||
}
|
||||
|
||||
private net.minecraft.world.level.chunk.ChunkGenerator chunkGenerator(RegistryAccess access) {
|
||||
var settings = new FlatLevelGeneratorSettings(Optional.empty(), access.registryOrThrow(Registries.BIOME).getHolderOrThrow(Biomes.THE_VOID), List.of());
|
||||
settings.getLayersInfo().add(new FlatLayerInfo(1, Blocks.AIR));
|
||||
settings.updateLayers();
|
||||
return new FlatLevelSource(settings);
|
||||
}
|
||||
|
||||
private static class ServerLevelAdvice {
|
||||
@SneakyThrows
|
||||
@Advice.OnMethodEnter
|
||||
static void enter(
|
||||
@Advice.Argument(0) MinecraftServer server,
|
||||
@Advice.Argument(3) PrimaryLevelData levelData,
|
||||
@Advice.Argument(value = 5, readOnly = false) LevelStem levelStem,
|
||||
@Advice.Argument(12) World.Environment env,
|
||||
@Advice.Argument(value = 13) ChunkGenerator gen
|
||||
) {
|
||||
if (gen == null || !gen.getClass().getPackageName().startsWith("com.volmit.iris"))
|
||||
return;
|
||||
|
||||
Object bindings = Class.forName("com.volmit.iris.core.nms.INMS", true, Bukkit.getPluginManager().getPlugin("Iris")
|
||||
.getClass()
|
||||
.getClassLoader())
|
||||
.getDeclaredMethod("get")
|
||||
.invoke(null);
|
||||
levelStem = (LevelStem) bindings.getClass()
|
||||
.getDeclaredMethod("levelStem", RegistryAccess.class, World.Environment.class)
|
||||
.invoke(bindings, server.registryAccess(), env);
|
||||
levelData.customDimensions = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,26 +5,30 @@ import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.mojang.serialization.Lifecycle;
|
||||
import com.volmit.iris.core.nms.container.AutoClosing;
|
||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||
import com.volmit.iris.core.nms.container.Pair;
|
||||
import com.volmit.iris.core.nms.datapack.DataVersion;
|
||||
import com.volmit.iris.util.agent.Agent;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import com.volmit.iris.util.misc.ServerProperties;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
import lombok.SneakyThrows;
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.matcher.ElementMatchers;
|
||||
import net.minecraft.core.*;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.nbt.*;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.WorldLoader;
|
||||
import net.minecraft.server.commands.data.BlockDataAccessor;
|
||||
import net.minecraft.server.level.progress.ChunkProgressListener;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.RandomSequences;
|
||||
import net.minecraft.world.item.component.CustomData;
|
||||
import net.minecraft.world.level.LevelReader;
|
||||
import net.minecraft.world.level.biome.Biomes;
|
||||
@ -37,6 +41,8 @@ import net.minecraft.world.level.dimension.LevelStem;
|
||||
import net.minecraft.world.level.levelgen.FlatLevelSource;
|
||||
import net.minecraft.world.level.levelgen.flat.FlatLayerInfo;
|
||||
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
|
||||
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;
|
||||
@ -50,6 +56,7 @@ import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.v1_21_R2.util.CraftNamespacedKey;
|
||||
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.Contract;
|
||||
@ -87,11 +94,11 @@ public class NMSBinding implements INMSBinding {
|
||||
private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
|
||||
private final BlockData AIR = Material.AIR.createBlockData();
|
||||
private final AtomicCache<MCAIdMap<net.minecraft.world.level.biome.Biome>> biomeMapCache = new AtomicCache<>();
|
||||
private final AtomicCache<WorldLoader.DataLoadContext> dataLoadContext = new AtomicCache<>();
|
||||
private final AtomicBoolean injected = new AtomicBoolean();
|
||||
private final AtomicCache<MCAIdMapper<BlockState>> registryCache = new AtomicCache<>();
|
||||
private final AtomicCache<MCAPalette<BlockState>> globalCache = new AtomicCache<>();
|
||||
private final AtomicCache<RegistryAccess> registryAccess = new AtomicCache<>();
|
||||
private final ReentrantLock dataContextLock = new ReentrantLock(true);
|
||||
private final KMap<World.Environment, LevelStem> stems = new KMap<>();
|
||||
private final AtomicCache<Method> byIdRef = new AtomicCache<>();
|
||||
private Field biomeStorageCache = null;
|
||||
|
||||
@ -666,44 +673,28 @@ public class NMSBinding implements INMSBinding {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public AutoClosing injectLevelStems() {
|
||||
if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!");
|
||||
@SuppressWarnings("all")
|
||||
public KMap<String, World.Environment> getMainWorlds() {
|
||||
String levelName = ServerProperties.LEVEL_NAME;
|
||||
KMap<String, World.Environment> worlds = new KMap<>();
|
||||
for (var key : registry().lookupOrThrow(Registries.LEVEL_STEM).registryKeySet()) {
|
||||
World.Environment env = World.Environment.NORMAL;
|
||||
if (key == LevelStem.NETHER) {
|
||||
if (!Bukkit.getAllowNether())
|
||||
continue;
|
||||
env = World.Environment.NETHER;
|
||||
} else if (key == LevelStem.END) {
|
||||
if (!Bukkit.getAllowEnd())
|
||||
continue;
|
||||
env = World.Environment.THE_END;
|
||||
} else if (key != LevelStem.OVERWORLD) {
|
||||
env = World.Environment.CUSTOM;
|
||||
}
|
||||
|
||||
var server = ((CraftServer) Bukkit.getServer());
|
||||
var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class);
|
||||
var nmsServer = server.getServer();
|
||||
var old = nmsServer.worldLoader;
|
||||
|
||||
field.setAccessible(true);
|
||||
field.set(nmsServer, dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext(
|
||||
old.resources(),
|
||||
old.dataConfiguration(),
|
||||
old.datapackWorldgen(),
|
||||
createRegistryAccess(old.datapackDimensions(), false, true, true, true)
|
||||
)));
|
||||
|
||||
return new AutoClosing(() -> {
|
||||
field.set(nmsServer, old);
|
||||
dataContextLock.unlock();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public AutoClosing injectUncached(boolean overworld, boolean nether, boolean end) {
|
||||
var reg = registry();
|
||||
var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class);
|
||||
field.setAccessible(true);
|
||||
|
||||
var access = createRegistryAccess(((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions(), true, overworld, nether, end);
|
||||
var injected = access.lookupOrThrow(Registries.LEVEL_STEM);
|
||||
var old = (Map<ResourceKey<? extends Registry<?>>, Registry<?>>) field.get(reg);
|
||||
var fake = new HashMap<>(old);
|
||||
fake.put(Registries.LEVEL_STEM, injected);
|
||||
field.set(reg, fake);
|
||||
|
||||
return new AutoClosing(() -> field.set(reg, old));
|
||||
String worldType = env == World.Environment.CUSTOM ? key.location().getNamespace() + "_" + key.location().getPath() : env.toString().toLowerCase(Locale.ROOT);
|
||||
worlds.put(key == LevelStem.OVERWORLD ? levelName : levelName + "_" + worldType, env);
|
||||
}
|
||||
return worlds;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -716,53 +707,74 @@ public class NMSBinding implements INMSBinding {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCustomDimensions(World world) {
|
||||
((CraftWorld) world).getHandle().L.customDimensions = null;
|
||||
}
|
||||
|
||||
private RegistryAccess.Frozen createRegistryAccess(RegistryAccess.Frozen datapack, boolean copy, boolean overworld, boolean nether, boolean end) {
|
||||
var access = registry();
|
||||
var dimensions = access.lookupOrThrow(Registries.DIMENSION_TYPE);
|
||||
|
||||
var settings = new FlatLevelGeneratorSettings(
|
||||
Optional.empty(),
|
||||
access.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.THE_VOID),
|
||||
List.of()
|
||||
);
|
||||
settings.getLayersInfo().add(new FlatLayerInfo(1, Blocks.AIR));
|
||||
settings.updateLayers();
|
||||
|
||||
var source = new FlatLevelSource(settings);
|
||||
var fake = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.experimental());
|
||||
if (overworld) register(fake, dimensions, source, LevelStem.OVERWORLD);
|
||||
if (nether) register(fake, dimensions, source, LevelStem.NETHER);
|
||||
if (end) register(fake, dimensions, source, LevelStem.END);
|
||||
copy(fake, datapack.lookup(Registries.LEVEL_STEM).orElse(null));
|
||||
|
||||
if (copy) copy(fake, access.lookupOrThrow(Registries.LEVEL_STEM));
|
||||
|
||||
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake)).freeze();
|
||||
}
|
||||
|
||||
private void register(MappedRegistry<LevelStem> target, Registry<DimensionType> dimensions, FlatLevelSource source, ResourceKey<LevelStem> key) {
|
||||
var loc = createIrisKey(key);
|
||||
target.register(key, new LevelStem(
|
||||
dimensions.get(loc).orElseThrow(() -> new IllegalStateException("Missing dimension type " + loc + " in " + dimensions.keySet())),
|
||||
source
|
||||
), RegistrationInfo.BUILT_IN);
|
||||
}
|
||||
|
||||
private void copy(MappedRegistry<LevelStem> target, Registry<LevelStem> source) {
|
||||
if (source == null) return;
|
||||
source.listElementIds().forEach(key -> {
|
||||
var value = source.getValue(key);
|
||||
var info = source.registrationInfo(key).orElse(null);
|
||||
if (value != null && info != null && !target.containsKey(key))
|
||||
target.register(key, value, info);
|
||||
});
|
||||
public boolean injectBukkit() {
|
||||
if (injected.getAndSet(true))
|
||||
return true;
|
||||
try {
|
||||
Iris.info("Injecting Bukkit");
|
||||
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(), Agent.installed());
|
||||
return true;
|
||||
} catch (Throwable e) {
|
||||
Iris.error(C.RED + "Failed to inject Bukkit");
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private ResourceLocation createIrisKey(ResourceKey<LevelStem> key) {
|
||||
return ResourceLocation.fromNamespaceAndPath("iris", key.location().getPath());
|
||||
}
|
||||
|
||||
public LevelStem levelStem(RegistryAccess access, World.Environment env) {
|
||||
if (env == World.Environment.CUSTOM)
|
||||
env = World.Environment.NORMAL;
|
||||
return stems.computeIfAbsent(env, key -> new LevelStem(dimensionType(access, key), chunkGenerator(access)));
|
||||
}
|
||||
|
||||
private Holder.Reference<DimensionType> dimensionType(RegistryAccess access, World.Environment env) {
|
||||
return access.lookupOrThrow(Registries.DIMENSION_TYPE).getOrThrow(ResourceKey.create(Registries.DIMENSION_TYPE, ResourceLocation.fromNamespaceAndPath("iris", switch (env) {
|
||||
case NORMAL, CUSTOM -> "overworld";
|
||||
case NETHER -> "the_nether";
|
||||
case THE_END -> "the_end";
|
||||
})));
|
||||
}
|
||||
|
||||
private net.minecraft.world.level.chunk.ChunkGenerator chunkGenerator(RegistryAccess access) {
|
||||
var settings = new FlatLevelGeneratorSettings(Optional.empty(), access.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.THE_VOID), List.of());
|
||||
settings.getLayersInfo().add(new FlatLayerInfo(1, Blocks.AIR));
|
||||
settings.updateLayers();
|
||||
return new FlatLevelSource(settings);
|
||||
}
|
||||
|
||||
private static class ServerLevelAdvice {
|
||||
@SneakyThrows
|
||||
@Advice.OnMethodEnter
|
||||
static void enter(
|
||||
@Advice.Argument(0) MinecraftServer server,
|
||||
@Advice.Argument(3) PrimaryLevelData levelData,
|
||||
@Advice.Argument(value = 5, readOnly = false) LevelStem levelStem,
|
||||
@Advice.Argument(12) World.Environment env,
|
||||
@Advice.Argument(value = 13) ChunkGenerator gen
|
||||
) {
|
||||
if (gen == null || !gen.getClass().getPackageName().startsWith("com.volmit.iris"))
|
||||
return;
|
||||
|
||||
Object bindings = Class.forName("com.volmit.iris.core.nms.INMS", true, Bukkit.getPluginManager().getPlugin("Iris")
|
||||
.getClass()
|
||||
.getClassLoader())
|
||||
.getDeclaredMethod("get")
|
||||
.invoke(null);
|
||||
levelStem = (LevelStem) bindings.getClass()
|
||||
.getDeclaredMethod("levelStem", RegistryAccess.class, World.Environment.class)
|
||||
.invoke(bindings, server.registryAccess(), env);
|
||||
levelData.customDimensions = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,28 +1,31 @@
|
||||
package com.volmit.iris.core.nms.v1_21_R3;
|
||||
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.mojang.serialization.Lifecycle;
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.INMSBinding;
|
||||
import com.volmit.iris.core.nms.container.AutoClosing;
|
||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||
import com.volmit.iris.core.nms.container.Pair;
|
||||
import com.volmit.iris.core.nms.datapack.DataVersion;
|
||||
import com.volmit.iris.engine.data.cache.AtomicCache;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.util.agent.Agent;
|
||||
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;
|
||||
import com.volmit.iris.util.math.Vector3d;
|
||||
import com.volmit.iris.util.matter.MatterBiomeInject;
|
||||
import com.volmit.iris.util.misc.ServerProperties;
|
||||
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 com.volmit.iris.util.scheduling.J;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import lombok.SneakyThrows;
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.matcher.ElementMatchers;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.core.*;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
@ -32,10 +35,11 @@ import net.minecraft.nbt.*;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.WorldLoader;
|
||||
import net.minecraft.server.commands.data.BlockDataAccessor;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.progress.ChunkProgressListener;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.RandomSequences;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.item.component.CustomData;
|
||||
import net.minecraft.world.level.LevelReader;
|
||||
@ -54,6 +58,8 @@ import net.minecraft.world.level.dimension.LevelStem;
|
||||
import net.minecraft.world.level.levelgen.FlatLevelSource;
|
||||
import net.minecraft.world.level.levelgen.flat.FlatLayerInfo;
|
||||
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
|
||||
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;
|
||||
@ -67,6 +73,7 @@ import org.bukkit.craftbukkit.v1_21_R3.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.v1_21_R3.util.CraftNamespacedKey;
|
||||
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.Contract;
|
||||
@ -78,19 +85,19 @@ import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class NMSBinding implements INMSBinding {
|
||||
private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
|
||||
private final BlockData AIR = Material.AIR.createBlockData();
|
||||
private final AtomicCache<MCAIdMap<net.minecraft.world.level.biome.Biome>> biomeMapCache = new AtomicCache<>();
|
||||
private final AtomicCache<WorldLoader.DataLoadContext> dataLoadContext = new AtomicCache<>();
|
||||
private final AtomicBoolean injected = new AtomicBoolean();
|
||||
private final AtomicCache<MCAIdMapper<BlockState>> registryCache = new AtomicCache<>();
|
||||
private final AtomicCache<MCAPalette<BlockState>> globalCache = new AtomicCache<>();
|
||||
private final AtomicCache<RegistryAccess> registryAccess = new AtomicCache<>();
|
||||
private final ReentrantLock dataContextLock = new ReentrantLock(true);
|
||||
private final KMap<World.Environment, LevelStem> stems = new KMap<>();
|
||||
private final AtomicCache<Method> byIdRef = new AtomicCache<>();
|
||||
private Field biomeStorageCache = null;
|
||||
|
||||
@ -665,44 +672,28 @@ public class NMSBinding implements INMSBinding {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public AutoClosing injectLevelStems() {
|
||||
if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!");
|
||||
@SuppressWarnings("all")
|
||||
public KMap<String, World.Environment> getMainWorlds() {
|
||||
String levelName = ServerProperties.LEVEL_NAME;
|
||||
KMap<String, World.Environment> worlds = new KMap<>();
|
||||
for (var key : registry().lookupOrThrow(Registries.LEVEL_STEM).registryKeySet()) {
|
||||
World.Environment env = World.Environment.NORMAL;
|
||||
if (key == LevelStem.NETHER) {
|
||||
if (!Bukkit.getAllowNether())
|
||||
continue;
|
||||
env = World.Environment.NETHER;
|
||||
} else if (key == LevelStem.END) {
|
||||
if (!Bukkit.getAllowEnd())
|
||||
continue;
|
||||
env = World.Environment.THE_END;
|
||||
} else if (key != LevelStem.OVERWORLD) {
|
||||
env = World.Environment.CUSTOM;
|
||||
}
|
||||
|
||||
var server = ((CraftServer) Bukkit.getServer());
|
||||
var field = getField(MinecraftServer.class, WorldLoader.DataLoadContext.class);
|
||||
var nmsServer = server.getServer();
|
||||
var old = nmsServer.worldLoader;
|
||||
|
||||
field.setAccessible(true);
|
||||
field.set(nmsServer, dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext(
|
||||
old.resources(),
|
||||
old.dataConfiguration(),
|
||||
old.datapackWorldgen(),
|
||||
createRegistryAccess(old.datapackDimensions(), false, true, true, true)
|
||||
)));
|
||||
|
||||
return new AutoClosing(() -> {
|
||||
field.set(nmsServer, old);
|
||||
dataContextLock.unlock();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public AutoClosing injectUncached(boolean overworld, boolean nether, boolean end) {
|
||||
var reg = registry();
|
||||
var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class);
|
||||
field.setAccessible(true);
|
||||
|
||||
var access = createRegistryAccess(((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions(), true, overworld, nether, end);
|
||||
var injected = access.lookupOrThrow(Registries.LEVEL_STEM);
|
||||
var old = (Map<ResourceKey<? extends Registry<?>>, Registry<?>>) field.get(reg);
|
||||
var fake = new HashMap<>(old);
|
||||
fake.put(Registries.LEVEL_STEM, injected);
|
||||
field.set(reg, fake);
|
||||
|
||||
return new AutoClosing(() -> field.set(reg, old));
|
||||
String worldType = env == World.Environment.CUSTOM ? key.location().getNamespace() + "_" + key.location().getPath() : env.toString().toLowerCase(Locale.ROOT);
|
||||
worlds.put(key == LevelStem.OVERWORLD ? levelName : levelName + "_" + worldType, env);
|
||||
}
|
||||
return worlds;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -715,53 +706,74 @@ public class NMSBinding implements INMSBinding {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCustomDimensions(World world) {
|
||||
((CraftWorld) world).getHandle().L.customDimensions = null;
|
||||
}
|
||||
|
||||
private RegistryAccess.Frozen createRegistryAccess(RegistryAccess.Frozen datapack, boolean copy, boolean overworld, boolean nether, boolean end) {
|
||||
var access = registry();
|
||||
var dimensions = access.lookupOrThrow(Registries.DIMENSION_TYPE);
|
||||
|
||||
var settings = new FlatLevelGeneratorSettings(
|
||||
Optional.empty(),
|
||||
access.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.THE_VOID),
|
||||
List.of()
|
||||
);
|
||||
settings.getLayersInfo().add(new FlatLayerInfo(1, Blocks.AIR));
|
||||
settings.updateLayers();
|
||||
|
||||
var source = new FlatLevelSource(settings);
|
||||
var fake = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.experimental());
|
||||
if (overworld) register(fake, dimensions, source, LevelStem.OVERWORLD);
|
||||
if (nether) register(fake, dimensions, source, LevelStem.NETHER);
|
||||
if (end) register(fake, dimensions, source, LevelStem.END);
|
||||
copy(fake, datapack.lookup(Registries.LEVEL_STEM).orElse(null));
|
||||
|
||||
if (copy) copy(fake, access.lookupOrThrow(Registries.LEVEL_STEM));
|
||||
|
||||
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake)).freeze();
|
||||
}
|
||||
|
||||
private void register(MappedRegistry<LevelStem> target, Registry<DimensionType> dimensions, FlatLevelSource source, ResourceKey<LevelStem> key) {
|
||||
var loc = createIrisKey(key);
|
||||
target.register(key, new LevelStem(
|
||||
dimensions.get(loc).orElseThrow(() -> new IllegalStateException("Missing dimension type " + loc + " in " + dimensions.keySet())),
|
||||
source
|
||||
), RegistrationInfo.BUILT_IN);
|
||||
}
|
||||
|
||||
private void copy(MappedRegistry<LevelStem> target, Registry<LevelStem> source) {
|
||||
if (source == null) return;
|
||||
source.listElementIds().forEach(key -> {
|
||||
var value = source.getValue(key);
|
||||
var info = source.registrationInfo(key).orElse(null);
|
||||
if (value != null && info != null && !target.containsKey(key))
|
||||
target.register(key, value, info);
|
||||
});
|
||||
public boolean injectBukkit() {
|
||||
if (injected.getAndSet(true))
|
||||
return true;
|
||||
try {
|
||||
Iris.info("Injecting Bukkit");
|
||||
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(), Agent.installed());
|
||||
return true;
|
||||
} catch (Throwable e) {
|
||||
Iris.error(C.RED + "Failed to inject Bukkit");
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private ResourceLocation createIrisKey(ResourceKey<LevelStem> key) {
|
||||
return ResourceLocation.fromNamespaceAndPath("iris", key.location().getPath());
|
||||
}
|
||||
|
||||
public LevelStem levelStem(RegistryAccess access, World.Environment env) {
|
||||
if (env == World.Environment.CUSTOM)
|
||||
env = World.Environment.NORMAL;
|
||||
return stems.computeIfAbsent(env, key -> new LevelStem(dimensionType(access, key), chunkGenerator(access)));
|
||||
}
|
||||
|
||||
private Holder.Reference<DimensionType> dimensionType(RegistryAccess access, World.Environment env) {
|
||||
return access.lookupOrThrow(Registries.DIMENSION_TYPE).getOrThrow(ResourceKey.create(Registries.DIMENSION_TYPE, ResourceLocation.fromNamespaceAndPath("iris", switch (env) {
|
||||
case NORMAL, CUSTOM -> "overworld";
|
||||
case NETHER -> "the_nether";
|
||||
case THE_END -> "the_end";
|
||||
})));
|
||||
}
|
||||
|
||||
private net.minecraft.world.level.chunk.ChunkGenerator chunkGenerator(RegistryAccess access) {
|
||||
var settings = new FlatLevelGeneratorSettings(Optional.empty(), access.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.THE_VOID), List.of());
|
||||
settings.getLayersInfo().add(new FlatLayerInfo(1, Blocks.AIR));
|
||||
settings.updateLayers();
|
||||
return new FlatLevelSource(settings);
|
||||
}
|
||||
|
||||
private static class ServerLevelAdvice {
|
||||
@SneakyThrows
|
||||
@Advice.OnMethodEnter
|
||||
static void enter(
|
||||
@Advice.Argument(0) MinecraftServer server,
|
||||
@Advice.Argument(3) PrimaryLevelData levelData,
|
||||
@Advice.Argument(value = 5, readOnly = false) LevelStem levelStem,
|
||||
@Advice.Argument(12) World.Environment env,
|
||||
@Advice.Argument(value = 13) ChunkGenerator gen
|
||||
) {
|
||||
if (gen == null || !gen.getClass().getPackageName().startsWith("com.volmit.iris"))
|
||||
return;
|
||||
|
||||
Object bindings = Class.forName("com.volmit.iris.core.nms.INMS", true, Bukkit.getPluginManager().getPlugin("Iris")
|
||||
.getClass()
|
||||
.getClassLoader())
|
||||
.getDeclaredMethod("get")
|
||||
.invoke(null);
|
||||
levelStem = (LevelStem) bindings.getClass()
|
||||
.getDeclaredMethod("levelStem", RegistryAccess.class, World.Environment.class)
|
||||
.invoke(bindings, server.registryAccess(), env);
|
||||
levelData.customDimensions = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user