mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2025-07-04 00:46:08 +00:00
implement MemoryWorlds
This commit is contained in:
parent
0101130d7a
commit
093d77bf8a
@ -0,0 +1,15 @@
|
|||||||
|
package com.volmit.iris.core.nms;
|
||||||
|
|
||||||
|
import org.bukkit.Chunk;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.generator.ChunkGenerator;
|
||||||
|
|
||||||
|
public interface IMemoryWorld extends Listener, AutoCloseable {
|
||||||
|
|
||||||
|
World getBukkit();
|
||||||
|
|
||||||
|
Chunk getChunk(int x, int z);
|
||||||
|
|
||||||
|
ChunkGenerator.ChunkData getChunkData(int x, int z);
|
||||||
|
}
|
@ -24,7 +24,6 @@ import com.volmit.iris.core.nms.datapack.DataVersion;
|
|||||||
import com.volmit.iris.engine.framework.Engine;
|
import com.volmit.iris.engine.framework.Engine;
|
||||||
import com.volmit.iris.util.collection.KList;
|
import com.volmit.iris.util.collection.KList;
|
||||||
import com.volmit.iris.util.collection.KMap;
|
import com.volmit.iris.util.collection.KMap;
|
||||||
import com.volmit.iris.util.hunk.Hunk;
|
|
||||||
import com.volmit.iris.util.mantle.Mantle;
|
import com.volmit.iris.util.mantle.Mantle;
|
||||||
import com.volmit.iris.util.math.Vector3d;
|
import com.volmit.iris.util.math.Vector3d;
|
||||||
import com.volmit.iris.util.nbt.mca.palette.MCABiomeContainer;
|
import com.volmit.iris.util.nbt.mca.palette.MCABiomeContainer;
|
||||||
@ -43,6 +42,7 @@ import org.bukkit.inventory.ItemStack;
|
|||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
public interface INMSBinding {
|
public interface INMSBinding {
|
||||||
boolean hasTile(Material material);
|
boolean hasTile(Material material);
|
||||||
@ -99,10 +99,6 @@ public interface INMSBinding {
|
|||||||
|
|
||||||
int countCustomBiomes();
|
int countCustomBiomes();
|
||||||
|
|
||||||
default void setBiomes(int cx, int cz, World world, Hunk<Object> biomes) {
|
|
||||||
Iris.error("Unsupported version!");
|
|
||||||
}
|
|
||||||
|
|
||||||
void forceBiomeInto(int x, int y, int z, Object somethingVeryDirty, ChunkGenerator.BiomeGrid chunk);
|
void forceBiomeInto(int x, int y, int z, Object somethingVeryDirty, ChunkGenerator.BiomeGrid chunk);
|
||||||
|
|
||||||
default boolean supportsDataPacks() {
|
default boolean supportsDataPacks() {
|
||||||
@ -137,4 +133,15 @@ public interface INMSBinding {
|
|||||||
Iris.error("Unsupported version!");
|
Iris.error("Unsupported version!");
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
default IMemoryWorld createMemoryWorld(WorldCreator creator) throws IOException {
|
||||||
|
return createMemoryWorld(switch (creator.environment()) {
|
||||||
|
case NORMAL -> NamespacedKey.minecraft("overworld");
|
||||||
|
case NETHER -> NamespacedKey.minecraft("the_nether");
|
||||||
|
case THE_END -> NamespacedKey.minecraft("the_end");
|
||||||
|
default -> throw new IllegalArgumentException("Illegal dimension (" + creator.environment() + ")");
|
||||||
|
}, creator);
|
||||||
|
}
|
||||||
|
|
||||||
|
IMemoryWorld createMemoryWorld(NamespacedKey levelType, WorldCreator creator) throws IOException;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,351 @@
|
|||||||
|
package com.volmit.iris.core.nms.v1_19_R1;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.mojang.serialization.Lifecycle;
|
||||||
|
import com.volmit.iris.Iris;
|
||||||
|
import com.volmit.iris.core.nms.IMemoryWorld;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.core.Registry;
|
||||||
|
import net.minecraft.resources.ResourceKey;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.server.dedicated.DedicatedServerProperties;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.util.GsonHelper;
|
||||||
|
import net.minecraft.world.Difficulty;
|
||||||
|
import net.minecraft.world.entity.ai.village.VillageSiege;
|
||||||
|
import net.minecraft.world.entity.npc.CatSpawner;
|
||||||
|
import net.minecraft.world.entity.npc.WanderingTraderSpawner;
|
||||||
|
import net.minecraft.world.level.*;
|
||||||
|
import net.minecraft.world.level.biome.BiomeManager;
|
||||||
|
import net.minecraft.world.level.block.Blocks;
|
||||||
|
import net.minecraft.world.level.block.EntityBlock;
|
||||||
|
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 net.minecraft.world.level.dimension.LevelStem;
|
||||||
|
import net.minecraft.world.level.levelgen.PatrolSpawner;
|
||||||
|
import net.minecraft.world.level.levelgen.PhantomSpawner;
|
||||||
|
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;
|
||||||
|
import org.bukkit.craftbukkit.v1_19_R1.CraftChunk;
|
||||||
|
import org.bukkit.craftbukkit.v1_19_R1.CraftServer;
|
||||||
|
import org.bukkit.craftbukkit.v1_19_R1.block.CraftBlock;
|
||||||
|
import org.bukkit.craftbukkit.v1_19_R1.block.data.CraftBlockData;
|
||||||
|
import org.bukkit.craftbukkit.v1_19_R1.generator.CraftWorldInfo;
|
||||||
|
import org.bukkit.craftbukkit.v1_19_R1.util.CraftMagicNumbers;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.world.WorldUnloadEvent;
|
||||||
|
import org.bukkit.generator.ChunkGenerator;
|
||||||
|
import org.bukkit.material.MaterialData;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
public class MemoryWorld implements IMemoryWorld {
|
||||||
|
private static final AtomicLong C = new AtomicLong();
|
||||||
|
private static final Field WORLDS_FIELD;
|
||||||
|
|
||||||
|
private final AtomicReference<ServerLevel> level = new AtomicReference<>();
|
||||||
|
|
||||||
|
public MemoryWorld(NamespacedKey levelType, WorldCreator creator) throws IOException {
|
||||||
|
var name = "memory_world"+C.getAndIncrement();
|
||||||
|
while (Bukkit.getWorld(name) != null) {
|
||||||
|
name = "memory_world"+C.getAndIncrement();
|
||||||
|
}
|
||||||
|
|
||||||
|
var generator = creator.generator();
|
||||||
|
var biomeProvider = creator.biomeProvider();
|
||||||
|
var hardcore = creator.hardcore();
|
||||||
|
var server = getServer();
|
||||||
|
|
||||||
|
var tempDir = Files.createTempDirectory("MemoryGenerator");
|
||||||
|
LevelStorageSource source = LevelStorageSource.createDefault(tempDir);
|
||||||
|
ResourceKey<LevelStem> stemKey = ResourceKey.create(Registry.LEVEL_STEM_REGISTRY, new ResourceLocation(levelType.getKey(), levelType.getNamespace()));
|
||||||
|
var access = source.createAccess(name, stemKey);
|
||||||
|
|
||||||
|
var registry = server.registryAccess().registryOrThrow(Registry.LEVEL_STEM_REGISTRY);
|
||||||
|
var properties = new DedicatedServerProperties.WorldGenProperties(Objects.toString(creator.seed()), GsonHelper.parse(creator.generatorSettings().isEmpty() ? "{}" : creator.generatorSettings()), creator.generateStructures(), creator.type().name().toLowerCase(Locale.ROOT));
|
||||||
|
var settings = properties.create(server.registryAccess());
|
||||||
|
var worldSettings = new LevelSettings(name, GameType.byId(Bukkit.getDefaultGameMode().getValue()), hardcore, Difficulty.EASY, false, new GameRules(), server.datapackconfiguration);
|
||||||
|
var worldData = new PrimaryLevelData(worldSettings, settings, Lifecycle.stable());
|
||||||
|
|
||||||
|
long obfSeed = BiomeManager.obfuscateSeed(creator.seed());
|
||||||
|
var list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(worldData));
|
||||||
|
var levelStem = registry.get(stemKey);
|
||||||
|
if (levelStem == null)
|
||||||
|
throw new IllegalStateException("Unknown dimension type: " + stemKey);
|
||||||
|
|
||||||
|
var worldInfo = new CraftWorldInfo(worldData, access, creator.environment(), levelStem.typeHolder().value());
|
||||||
|
if (biomeProvider == null && generator != null) {
|
||||||
|
biomeProvider = generator.getDefaultBiomeProvider(worldInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
var levelKey = ResourceKey.create(Registry.DIMENSION_REGISTRY, new ResourceLocation(name));
|
||||||
|
var level = new ServerLevel(
|
||||||
|
server,
|
||||||
|
server.executor,
|
||||||
|
access,
|
||||||
|
worldData,
|
||||||
|
levelKey,
|
||||||
|
levelStem,
|
||||||
|
server.progressListenerFactory.create(0),
|
||||||
|
worldData.worldGenSettings().isDebug(),
|
||||||
|
obfSeed,
|
||||||
|
creator.environment() == World.Environment.NORMAL ? list : ImmutableList.of(),
|
||||||
|
true,
|
||||||
|
creator.environment(),
|
||||||
|
generator,
|
||||||
|
biomeProvider
|
||||||
|
);
|
||||||
|
level.keepSpawnInMemory = false;
|
||||||
|
Iris.instance.registerListener(this);
|
||||||
|
this.level.set(level);
|
||||||
|
}
|
||||||
|
|
||||||
|
public World getBukkit() {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
|
||||||
|
return level.getWorld();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Chunk getChunk(int x, int z) {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
return new CraftChunk(level, x, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChunkGenerator.ChunkData getChunkData(int x, int z) {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
return new MemoryChunkData(x, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||||
|
public void onWorldUnload(WorldUnloadEvent event) {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null || event.getWorld() != level.getWorld())
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.level.set(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isClosed() {
|
||||||
|
return this.level.get() == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws Exception {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null || !this.level.compareAndSet(level, null))
|
||||||
|
return;
|
||||||
|
|
||||||
|
level.getChunkSource().close(false);
|
||||||
|
level.entityManager.close(false);
|
||||||
|
level.convertable.deleteLevel();
|
||||||
|
level.convertable.close();
|
||||||
|
|
||||||
|
var map = (Map<String, World>) WORLDS_FIELD.get(Bukkit.getServer());
|
||||||
|
map.remove(level.dimension().location().getPath());
|
||||||
|
getServer().removeLevel(level);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MinecraftServer getServer() {
|
||||||
|
return ((CraftServer) Bukkit.getServer()).getServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
WORLDS_FIELD = CraftServer.class.getDeclaredField("worlds");
|
||||||
|
WORLDS_FIELD.setAccessible(true);
|
||||||
|
} catch (NoSuchFieldException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MemoryChunkData implements ChunkGenerator.ChunkData {
|
||||||
|
private final int maxHeight;
|
||||||
|
private final int minHeight;
|
||||||
|
private final ChunkPos pos;
|
||||||
|
private WeakReference<LevelChunk> chunk;
|
||||||
|
|
||||||
|
private MemoryChunkData(int x, int z) {
|
||||||
|
var level = MemoryWorld.this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
|
||||||
|
var chunk = level.getChunk(x, z);
|
||||||
|
this.minHeight = chunk.getMinBuildHeight();
|
||||||
|
this.maxHeight = chunk.getMaxBuildHeight();
|
||||||
|
this.pos = new ChunkPos(x, z);
|
||||||
|
this.chunk = new WeakReference<>(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LevelChunk getHandle() {
|
||||||
|
LevelChunk chunk = this.chunk.get();
|
||||||
|
if (chunk == null) {
|
||||||
|
var level = MemoryWorld.this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
|
||||||
|
chunk = level.getChunk(this.pos.x, this.pos.z);
|
||||||
|
this.chunk = new WeakReference<>(chunk);
|
||||||
|
}
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMinHeight() {
|
||||||
|
return minHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxHeight() {
|
||||||
|
return maxHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Biome getBiome(int x, int y, int z) {
|
||||||
|
return CraftBlock.biomeBaseToBiome(this.getHandle().biomeRegistry, this.getHandle().getNoiseBiome(x >> 2, y >> 2, z >> 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlock(int x, int y, int z, @NotNull Material material) {
|
||||||
|
setBlock(x, y, z, material.createBlockData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlock(int x, int y, int z, @NotNull MaterialData material) {
|
||||||
|
setBlock(x, y, z, CraftMagicNumbers.getBlock(material));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlock(int x, int y, int z, @NotNull BlockData blockData) {
|
||||||
|
setBlock(x, y, z, ((CraftBlockData) blockData).getState());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, Material material) {
|
||||||
|
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, material.createBlockData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, MaterialData material) {
|
||||||
|
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, CraftMagicNumbers.getBlock(material));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockData blockData) {
|
||||||
|
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, ((CraftBlockData)blockData).getState());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Material getType(int x, int y, int z) {
|
||||||
|
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z).getBlock());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public MaterialData getTypeAndData(int x, int y, int z) {
|
||||||
|
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public BlockData getBlockData(int x, int y, int z) {
|
||||||
|
return CraftBlockData.fromData(this.getTypeId(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
@Override
|
||||||
|
public byte getData(int x, int y, int z) {
|
||||||
|
return CraftMagicNumbers.toLegacyData(this.getTypeId(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockState type) {
|
||||||
|
if (xMin <= 15 && yMin < this.maxHeight && zMin <= 15) {
|
||||||
|
if (xMin < 0) {
|
||||||
|
xMin = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yMin < this.minHeight) {
|
||||||
|
yMin = this.minHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zMin < 0) {
|
||||||
|
zMin = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xMax > 16) {
|
||||||
|
xMax = 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yMax > this.maxHeight) {
|
||||||
|
yMax = this.maxHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zMax > 16) {
|
||||||
|
zMax = 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xMin < xMax && yMin < yMax && zMin < zMax) {
|
||||||
|
for(int y = yMin; y < yMax; ++y) {
|
||||||
|
for(int x = xMin; x < xMax; ++x) {
|
||||||
|
for(int z = zMin; z < zMax; ++z) {
|
||||||
|
this.setBlock(x, y, z, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BlockState getTypeId(int x, int y, int z) {
|
||||||
|
if (x == (x & 15) && y >= this.minHeight && y < this.maxHeight && z == (z & 15)) {
|
||||||
|
ChunkAccess access = this.getHandle();
|
||||||
|
return access.getBlockState(new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z));
|
||||||
|
} else {
|
||||||
|
return Blocks.AIR.defaultBlockState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setBlock(int x, int y, int z, BlockState type) {
|
||||||
|
if (x == (x & 15) && y >= this.minHeight && y < this.maxHeight && z == (z & 15)) {
|
||||||
|
ChunkAccess access = this.getHandle();
|
||||||
|
BlockPos blockPosition = new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z);
|
||||||
|
BlockState oldBlockData = access.setBlockState(blockPosition, type, false);
|
||||||
|
if (type.hasBlockEntity()) {
|
||||||
|
BlockEntity tileEntity = ((EntityBlock)type.getBlock()).newBlockEntity(blockPosition, type);
|
||||||
|
if (tileEntity == null) {
|
||||||
|
access.removeBlockEntity(blockPosition);
|
||||||
|
} else {
|
||||||
|
access.setBlockEntity(tileEntity);
|
||||||
|
}
|
||||||
|
} else if (oldBlockData != null && oldBlockData.hasBlockEntity()) {
|
||||||
|
access.removeBlockEntity(blockPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,7 @@
|
|||||||
package com.volmit.iris.core.nms.v1_19_R1;
|
package com.volmit.iris.core.nms.v1_19_R1;
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.*;
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.DataInputStream;
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
@ -15,6 +12,7 @@ import java.util.Vector;
|
|||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import com.mojang.datafixers.util.Pair;
|
import com.mojang.datafixers.util.Pair;
|
||||||
|
import com.volmit.iris.core.nms.IMemoryWorld;
|
||||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||||
import com.volmit.iris.util.scheduling.J;
|
import com.volmit.iris.util.scheduling.J;
|
||||||
import net.minecraft.nbt.*;
|
import net.minecraft.nbt.*;
|
||||||
@ -607,6 +605,11 @@ public class NMSBinding implements INMSBinding {
|
|||||||
return keys;
|
return keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IMemoryWorld createMemoryWorld(NamespacedKey levelType, WorldCreator creator) throws IOException {
|
||||||
|
return new MemoryWorld(levelType, creator);
|
||||||
|
}
|
||||||
|
|
||||||
private static Field getField(Class<?> clazz, Class<?> fieldType) throws NoSuchFieldException {
|
private static Field getField(Class<?> clazz, Class<?> fieldType) throws NoSuchFieldException {
|
||||||
try {
|
try {
|
||||||
for (Field f : clazz.getDeclaredFields()) {
|
for (Field f : clazz.getDeclaredFields()) {
|
||||||
|
@ -0,0 +1,360 @@
|
|||||||
|
package com.volmit.iris.core.nms.v1_19_R2;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.mojang.serialization.Lifecycle;
|
||||||
|
import com.volmit.iris.Iris;
|
||||||
|
import com.volmit.iris.core.nms.IMemoryWorld;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.core.registries.Registries;
|
||||||
|
import net.minecraft.resources.ResourceKey;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.server.dedicated.DedicatedServerProperties;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.util.GsonHelper;
|
||||||
|
import net.minecraft.world.Difficulty;
|
||||||
|
import net.minecraft.world.entity.ai.village.VillageSiege;
|
||||||
|
import net.minecraft.world.entity.npc.CatSpawner;
|
||||||
|
import net.minecraft.world.entity.npc.WanderingTraderSpawner;
|
||||||
|
import net.minecraft.world.level.ChunkPos;
|
||||||
|
import net.minecraft.world.level.GameRules;
|
||||||
|
import net.minecraft.world.level.GameType;
|
||||||
|
import net.minecraft.world.level.LevelSettings;
|
||||||
|
import net.minecraft.world.level.biome.BiomeManager;
|
||||||
|
import net.minecraft.world.level.block.Blocks;
|
||||||
|
import net.minecraft.world.level.block.EntityBlock;
|
||||||
|
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 net.minecraft.world.level.dimension.LevelStem;
|
||||||
|
import net.minecraft.world.level.levelgen.PatrolSpawner;
|
||||||
|
import net.minecraft.world.level.levelgen.PhantomSpawner;
|
||||||
|
import net.minecraft.world.level.levelgen.WorldOptions;
|
||||||
|
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;
|
||||||
|
import org.bukkit.craftbukkit.v1_19_R2.CraftChunk;
|
||||||
|
import org.bukkit.craftbukkit.v1_19_R2.CraftServer;
|
||||||
|
import org.bukkit.craftbukkit.v1_19_R2.block.CraftBlock;
|
||||||
|
import org.bukkit.craftbukkit.v1_19_R2.block.data.CraftBlockData;
|
||||||
|
import org.bukkit.craftbukkit.v1_19_R2.generator.CraftWorldInfo;
|
||||||
|
import org.bukkit.craftbukkit.v1_19_R2.util.CraftMagicNumbers;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.world.WorldUnloadEvent;
|
||||||
|
import org.bukkit.generator.ChunkGenerator;
|
||||||
|
import org.bukkit.material.MaterialData;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
public class MemoryWorld implements IMemoryWorld {
|
||||||
|
private static final AtomicLong C = new AtomicLong();
|
||||||
|
private static final Field WORLDS_FIELD;
|
||||||
|
|
||||||
|
private final AtomicReference<ServerLevel> level = new AtomicReference<>();
|
||||||
|
|
||||||
|
public MemoryWorld(NamespacedKey levelType, WorldCreator creator) throws IOException {
|
||||||
|
var name = "memory_world"+C.getAndIncrement();
|
||||||
|
while (Bukkit.getWorld(name) != null) {
|
||||||
|
name = "memory_world"+C.getAndIncrement();
|
||||||
|
}
|
||||||
|
|
||||||
|
var generator = creator.generator();
|
||||||
|
var biomeProvider = creator.biomeProvider();
|
||||||
|
var hardcore = creator.hardcore();
|
||||||
|
var server = getServer();
|
||||||
|
|
||||||
|
var tempDir = Files.createTempDirectory("MemoryGenerator");
|
||||||
|
LevelStorageSource source = LevelStorageSource.createDefault(tempDir);
|
||||||
|
ResourceKey<LevelStem> stemKey = ResourceKey.create(Registries.LEVEL_STEM, new ResourceLocation(levelType.getKey(), levelType.getNamespace()));
|
||||||
|
var access = source.createAccess(name, stemKey);
|
||||||
|
|
||||||
|
var worldLoader = server.worldLoader;
|
||||||
|
var registry = server.registryAccess().registryOrThrow(Registries.LEVEL_STEM);
|
||||||
|
var options = new WorldOptions(creator.seed(), creator.generateStructures(), false);
|
||||||
|
var properties = new DedicatedServerProperties.WorldDimensionData(GsonHelper.parse(creator.generatorSettings().isEmpty() ? "{}" : creator.generatorSettings()), creator.type().name().toLowerCase(Locale.ROOT));
|
||||||
|
var settings = new LevelSettings(name, GameType.byId(Bukkit.getDefaultGameMode().getValue()), hardcore, Difficulty.EASY, false, new GameRules(), worldLoader.dataConfiguration());
|
||||||
|
var dimension = properties.create(worldLoader.datapackWorldgen()).bake(registry);
|
||||||
|
Lifecycle lifecycle = dimension.lifecycle().add(worldLoader.datapackWorldgen().allRegistriesLifecycle());
|
||||||
|
|
||||||
|
var worldData = new PrimaryLevelData(settings, options, dimension.specialWorldProperty(), lifecycle);
|
||||||
|
worldData.customDimensions = registry;
|
||||||
|
|
||||||
|
long obfSeed = BiomeManager.obfuscateSeed(creator.seed());
|
||||||
|
var list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(worldData));
|
||||||
|
var levelStem = registry.get(stemKey);
|
||||||
|
if (levelStem == null)
|
||||||
|
throw new IllegalStateException("Unknown dimension type: " + stemKey);
|
||||||
|
|
||||||
|
var worldInfo = new CraftWorldInfo(worldData, access, creator.environment(), levelStem.type().value());
|
||||||
|
if (biomeProvider == null && generator != null) {
|
||||||
|
biomeProvider = generator.getDefaultBiomeProvider(worldInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
var levelKey = ResourceKey.create(Registries.DIMENSION, new ResourceLocation(name));
|
||||||
|
var level = new ServerLevel(
|
||||||
|
server,
|
||||||
|
server.executor,
|
||||||
|
access,
|
||||||
|
worldData,
|
||||||
|
levelKey,
|
||||||
|
levelStem,
|
||||||
|
server.progressListenerFactory.create(0),
|
||||||
|
worldData.isDebugWorld(),
|
||||||
|
obfSeed,
|
||||||
|
creator.environment() == World.Environment.NORMAL ? list : ImmutableList.of(),
|
||||||
|
true,
|
||||||
|
creator.environment(),
|
||||||
|
generator,
|
||||||
|
biomeProvider
|
||||||
|
);
|
||||||
|
level.keepSpawnInMemory = false;
|
||||||
|
Iris.instance.registerListener(this);
|
||||||
|
this.level.set(level);
|
||||||
|
}
|
||||||
|
|
||||||
|
public World getBukkit() {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
|
||||||
|
return level.getWorld();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Chunk getChunk(int x, int z) {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
return new CraftChunk(level, x, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChunkGenerator.ChunkData getChunkData(int x, int z) {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
return new MemoryChunkData(x, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||||
|
public void onWorldUnload(WorldUnloadEvent event) {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null || event.getWorld() != level.getWorld())
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.level.set(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isClosed() {
|
||||||
|
return this.level.get() == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws Exception {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null || !this.level.compareAndSet(level, null))
|
||||||
|
return;
|
||||||
|
|
||||||
|
level.getChunkSource().close(false);
|
||||||
|
level.entityManager.close(false);
|
||||||
|
level.convertable.deleteLevel();
|
||||||
|
level.convertable.close();
|
||||||
|
|
||||||
|
var map = (Map<String, World>) WORLDS_FIELD.get(Bukkit.getServer());
|
||||||
|
map.remove(level.dimension().location().getPath());
|
||||||
|
getServer().removeLevel(level);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MinecraftServer getServer() {
|
||||||
|
return ((CraftServer) Bukkit.getServer()).getServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
WORLDS_FIELD = CraftServer.class.getDeclaredField("worlds");
|
||||||
|
WORLDS_FIELD.setAccessible(true);
|
||||||
|
} catch (NoSuchFieldException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MemoryChunkData implements ChunkGenerator.ChunkData {
|
||||||
|
private final int maxHeight;
|
||||||
|
private final int minHeight;
|
||||||
|
private final ChunkPos pos;
|
||||||
|
private WeakReference<LevelChunk> chunk;
|
||||||
|
|
||||||
|
private MemoryChunkData(int x, int z) {
|
||||||
|
var level = MemoryWorld.this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
|
||||||
|
var chunk = level.getChunk(x, z);
|
||||||
|
this.minHeight = chunk.getMinBuildHeight();
|
||||||
|
this.maxHeight = chunk.getMaxBuildHeight();
|
||||||
|
this.pos = new ChunkPos(x, z);
|
||||||
|
this.chunk = new WeakReference<>(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LevelChunk getHandle() {
|
||||||
|
LevelChunk chunk = this.chunk.get();
|
||||||
|
if (chunk == null) {
|
||||||
|
var level = MemoryWorld.this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
|
||||||
|
chunk = level.getChunk(this.pos.x, this.pos.z);
|
||||||
|
this.chunk = new WeakReference<>(chunk);
|
||||||
|
}
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMinHeight() {
|
||||||
|
return minHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxHeight() {
|
||||||
|
return maxHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Biome getBiome(int x, int y, int z) {
|
||||||
|
return CraftBlock.biomeBaseToBiome(this.getHandle().biomeRegistry, this.getHandle().getNoiseBiome(x >> 2, y >> 2, z >> 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlock(int x, int y, int z, @NotNull Material material) {
|
||||||
|
setBlock(x, y, z, material.createBlockData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlock(int x, int y, int z, @NotNull MaterialData material) {
|
||||||
|
setBlock(x, y, z, CraftMagicNumbers.getBlock(material));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlock(int x, int y, int z, @NotNull BlockData blockData) {
|
||||||
|
setBlock(x, y, z, ((CraftBlockData) blockData).getState());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, Material material) {
|
||||||
|
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, material.createBlockData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, MaterialData material) {
|
||||||
|
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, CraftMagicNumbers.getBlock(material));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockData blockData) {
|
||||||
|
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, ((CraftBlockData)blockData).getState());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Material getType(int x, int y, int z) {
|
||||||
|
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z).getBlock());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public MaterialData getTypeAndData(int x, int y, int z) {
|
||||||
|
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public BlockData getBlockData(int x, int y, int z) {
|
||||||
|
return CraftBlockData.fromData(this.getTypeId(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
@Override
|
||||||
|
public byte getData(int x, int y, int z) {
|
||||||
|
return CraftMagicNumbers.toLegacyData(this.getTypeId(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockState type) {
|
||||||
|
if (xMin <= 15 && yMin < this.maxHeight && zMin <= 15) {
|
||||||
|
if (xMin < 0) {
|
||||||
|
xMin = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yMin < this.minHeight) {
|
||||||
|
yMin = this.minHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zMin < 0) {
|
||||||
|
zMin = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xMax > 16) {
|
||||||
|
xMax = 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yMax > this.maxHeight) {
|
||||||
|
yMax = this.maxHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zMax > 16) {
|
||||||
|
zMax = 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xMin < xMax && yMin < yMax && zMin < zMax) {
|
||||||
|
for(int y = yMin; y < yMax; ++y) {
|
||||||
|
for(int x = xMin; x < xMax; ++x) {
|
||||||
|
for(int z = zMin; z < zMax; ++z) {
|
||||||
|
this.setBlock(x, y, z, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private BlockState getTypeId(int x, int y, int z) {
|
||||||
|
if (x == (x & 15) && y >= this.minHeight && y < this.maxHeight && z == (z & 15)) {
|
||||||
|
ChunkAccess access = this.getHandle();
|
||||||
|
return access.getBlockState(new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z));
|
||||||
|
} else {
|
||||||
|
return Blocks.AIR.defaultBlockState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setBlock(int x, int y, int z, BlockState type) {
|
||||||
|
if (x == (x & 15) && y >= this.minHeight && y < this.maxHeight && z == (z & 15)) {
|
||||||
|
ChunkAccess access = this.getHandle();
|
||||||
|
BlockPos blockPosition = new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z);
|
||||||
|
BlockState oldBlockData = access.setBlockState(blockPosition, type, false);
|
||||||
|
if (type.hasBlockEntity()) {
|
||||||
|
BlockEntity tileEntity = ((EntityBlock)type.getBlock()).newBlockEntity(blockPosition, type);
|
||||||
|
if (tileEntity == null) {
|
||||||
|
access.removeBlockEntity(blockPosition);
|
||||||
|
} else {
|
||||||
|
access.setBlockEntity(tileEntity);
|
||||||
|
}
|
||||||
|
} else if (oldBlockData != null && oldBlockData.hasBlockEntity()) {
|
||||||
|
access.removeBlockEntity(blockPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,7 @@
|
|||||||
package com.volmit.iris.core.nms.v1_19_R2;
|
package com.volmit.iris.core.nms.v1_19_R2;
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.*;
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.DataInputStream;
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
@ -15,6 +12,7 @@ import java.util.Vector;
|
|||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import com.mojang.datafixers.util.Pair;
|
import com.mojang.datafixers.util.Pair;
|
||||||
|
import com.volmit.iris.core.nms.IMemoryWorld;
|
||||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||||
import com.volmit.iris.util.scheduling.J;
|
import com.volmit.iris.util.scheduling.J;
|
||||||
import net.minecraft.nbt.*;
|
import net.minecraft.nbt.*;
|
||||||
@ -609,6 +607,11 @@ public class NMSBinding implements INMSBinding {
|
|||||||
return keys;
|
return keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IMemoryWorld createMemoryWorld(NamespacedKey levelType, WorldCreator creator) throws IOException {
|
||||||
|
return new MemoryWorld(levelType, creator);
|
||||||
|
}
|
||||||
|
|
||||||
private static Field getField(Class<?> clazz, Class<?> fieldType) throws NoSuchFieldException {
|
private static Field getField(Class<?> clazz, Class<?> fieldType) throws NoSuchFieldException {
|
||||||
try {
|
try {
|
||||||
for (Field f : clazz.getDeclaredFields()) {
|
for (Field f : clazz.getDeclaredFields()) {
|
||||||
|
@ -0,0 +1,360 @@
|
|||||||
|
package com.volmit.iris.core.nms.v1_19_R3;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.mojang.serialization.Lifecycle;
|
||||||
|
import com.volmit.iris.Iris;
|
||||||
|
import com.volmit.iris.core.nms.IMemoryWorld;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.core.registries.Registries;
|
||||||
|
import net.minecraft.resources.ResourceKey;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.server.dedicated.DedicatedServerProperties;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.util.GsonHelper;
|
||||||
|
import net.minecraft.world.Difficulty;
|
||||||
|
import net.minecraft.world.entity.ai.village.VillageSiege;
|
||||||
|
import net.minecraft.world.entity.npc.CatSpawner;
|
||||||
|
import net.minecraft.world.entity.npc.WanderingTraderSpawner;
|
||||||
|
import net.minecraft.world.level.ChunkPos;
|
||||||
|
import net.minecraft.world.level.GameRules;
|
||||||
|
import net.minecraft.world.level.GameType;
|
||||||
|
import net.minecraft.world.level.LevelSettings;
|
||||||
|
import net.minecraft.world.level.biome.BiomeManager;
|
||||||
|
import net.minecraft.world.level.block.Blocks;
|
||||||
|
import net.minecraft.world.level.block.EntityBlock;
|
||||||
|
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 net.minecraft.world.level.dimension.LevelStem;
|
||||||
|
import net.minecraft.world.level.levelgen.PatrolSpawner;
|
||||||
|
import net.minecraft.world.level.levelgen.PhantomSpawner;
|
||||||
|
import net.minecraft.world.level.levelgen.WorldOptions;
|
||||||
|
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;
|
||||||
|
import org.bukkit.craftbukkit.v1_19_R3.CraftChunk;
|
||||||
|
import org.bukkit.craftbukkit.v1_19_R3.CraftServer;
|
||||||
|
import org.bukkit.craftbukkit.v1_19_R3.block.CraftBlock;
|
||||||
|
import org.bukkit.craftbukkit.v1_19_R3.block.data.CraftBlockData;
|
||||||
|
import org.bukkit.craftbukkit.v1_19_R3.generator.CraftWorldInfo;
|
||||||
|
import org.bukkit.craftbukkit.v1_19_R3.util.CraftMagicNumbers;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.world.WorldUnloadEvent;
|
||||||
|
import org.bukkit.generator.ChunkGenerator;
|
||||||
|
import org.bukkit.material.MaterialData;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
public class MemoryWorld implements IMemoryWorld {
|
||||||
|
private static final AtomicLong C = new AtomicLong();
|
||||||
|
private static final Field WORLDS_FIELD;
|
||||||
|
|
||||||
|
private final AtomicReference<ServerLevel> level = new AtomicReference<>();
|
||||||
|
|
||||||
|
public MemoryWorld(NamespacedKey levelType, WorldCreator creator) throws IOException {
|
||||||
|
var name = "memory_world"+C.getAndIncrement();
|
||||||
|
while (Bukkit.getWorld(name) != null) {
|
||||||
|
name = "memory_world"+C.getAndIncrement();
|
||||||
|
}
|
||||||
|
|
||||||
|
var generator = creator.generator();
|
||||||
|
var biomeProvider = creator.biomeProvider();
|
||||||
|
var hardcore = creator.hardcore();
|
||||||
|
var server = getServer();
|
||||||
|
|
||||||
|
var tempDir = Files.createTempDirectory("MemoryGenerator");
|
||||||
|
LevelStorageSource source = LevelStorageSource.createDefault(tempDir);
|
||||||
|
ResourceKey<LevelStem> stemKey = ResourceKey.create(Registries.LEVEL_STEM, new ResourceLocation(levelType.getKey(), levelType.getNamespace()));
|
||||||
|
var access = source.createAccess(name, stemKey);
|
||||||
|
|
||||||
|
var worldLoader = server.worldLoader;
|
||||||
|
var registry = server.registryAccess().registryOrThrow(Registries.LEVEL_STEM);
|
||||||
|
var options = new WorldOptions(creator.seed(), creator.generateStructures(), false);
|
||||||
|
var properties = new DedicatedServerProperties.WorldDimensionData(GsonHelper.parse(creator.generatorSettings().isEmpty() ? "{}" : creator.generatorSettings()), creator.type().name().toLowerCase(Locale.ROOT));
|
||||||
|
var settings = new LevelSettings(name, GameType.byId(Bukkit.getDefaultGameMode().getValue()), hardcore, Difficulty.EASY, false, new GameRules(), worldLoader.dataConfiguration());
|
||||||
|
var dimension = properties.create(worldLoader.datapackWorldgen()).bake(registry);
|
||||||
|
Lifecycle lifecycle = dimension.lifecycle().add(worldLoader.datapackWorldgen().allRegistriesLifecycle());
|
||||||
|
|
||||||
|
var worldData = new PrimaryLevelData(settings, options, dimension.specialWorldProperty(), lifecycle);
|
||||||
|
worldData.customDimensions = registry;
|
||||||
|
|
||||||
|
long obfSeed = BiomeManager.obfuscateSeed(creator.seed());
|
||||||
|
var list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(worldData));
|
||||||
|
var levelStem = registry.get(stemKey);
|
||||||
|
if (levelStem == null)
|
||||||
|
throw new IllegalStateException("Unknown dimension type: " + stemKey);
|
||||||
|
|
||||||
|
var worldInfo = new CraftWorldInfo(worldData, access, creator.environment(), levelStem.type().value());
|
||||||
|
if (biomeProvider == null && generator != null) {
|
||||||
|
biomeProvider = generator.getDefaultBiomeProvider(worldInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
var levelKey = ResourceKey.create(Registries.DIMENSION, new ResourceLocation(name));
|
||||||
|
var level = new ServerLevel(
|
||||||
|
server,
|
||||||
|
server.executor,
|
||||||
|
access,
|
||||||
|
worldData,
|
||||||
|
levelKey,
|
||||||
|
levelStem,
|
||||||
|
server.progressListenerFactory.create(0),
|
||||||
|
worldData.isDebugWorld(),
|
||||||
|
obfSeed,
|
||||||
|
creator.environment() == World.Environment.NORMAL ? list : ImmutableList.of(),
|
||||||
|
true,
|
||||||
|
creator.environment(),
|
||||||
|
generator,
|
||||||
|
biomeProvider
|
||||||
|
);
|
||||||
|
level.keepSpawnInMemory = false;
|
||||||
|
Iris.instance.registerListener(this);
|
||||||
|
this.level.set(level);
|
||||||
|
}
|
||||||
|
|
||||||
|
public World getBukkit() {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
|
||||||
|
return level.getWorld();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Chunk getChunk(int x, int z) {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
return new CraftChunk(level, x, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChunkGenerator.ChunkData getChunkData(int x, int z) {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
return new MemoryChunkData(x, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||||
|
public void onWorldUnload(WorldUnloadEvent event) {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null || event.getWorld() != level.getWorld())
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.level.set(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isClosed() {
|
||||||
|
return this.level.get() == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws Exception {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null || !this.level.compareAndSet(level, null))
|
||||||
|
return;
|
||||||
|
|
||||||
|
level.getChunkSource().close(false);
|
||||||
|
level.entityManager.close(false);
|
||||||
|
level.convertable.deleteLevel();
|
||||||
|
level.convertable.close();
|
||||||
|
|
||||||
|
var map = (Map<String, World>) WORLDS_FIELD.get(Bukkit.getServer());
|
||||||
|
map.remove(level.dimension().location().getPath());
|
||||||
|
getServer().removeLevel(level);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MinecraftServer getServer() {
|
||||||
|
return ((CraftServer) Bukkit.getServer()).getServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
WORLDS_FIELD = CraftServer.class.getDeclaredField("worlds");
|
||||||
|
WORLDS_FIELD.setAccessible(true);
|
||||||
|
} catch (NoSuchFieldException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MemoryChunkData implements ChunkGenerator.ChunkData {
|
||||||
|
private final int maxHeight;
|
||||||
|
private final int minHeight;
|
||||||
|
private final ChunkPos pos;
|
||||||
|
private WeakReference<LevelChunk> chunk;
|
||||||
|
|
||||||
|
private MemoryChunkData(int x, int z) {
|
||||||
|
var level = MemoryWorld.this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
|
||||||
|
var chunk = level.getChunk(x, z);
|
||||||
|
this.minHeight = chunk.getMinBuildHeight();
|
||||||
|
this.maxHeight = chunk.getMaxBuildHeight();
|
||||||
|
this.pos = new ChunkPos(x, z);
|
||||||
|
this.chunk = new WeakReference<>(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LevelChunk getHandle() {
|
||||||
|
LevelChunk chunk = this.chunk.get();
|
||||||
|
if (chunk == null) {
|
||||||
|
var level = MemoryWorld.this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
|
||||||
|
chunk = level.getChunk(this.pos.x, this.pos.z);
|
||||||
|
this.chunk = new WeakReference<>(chunk);
|
||||||
|
}
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMinHeight() {
|
||||||
|
return minHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxHeight() {
|
||||||
|
return maxHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Biome getBiome(int x, int y, int z) {
|
||||||
|
return CraftBlock.biomeBaseToBiome(this.getHandle().biomeRegistry, this.getHandle().getNoiseBiome(x >> 2, y >> 2, z >> 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlock(int x, int y, int z, @NotNull Material material) {
|
||||||
|
setBlock(x, y, z, material.createBlockData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlock(int x, int y, int z, @NotNull MaterialData material) {
|
||||||
|
setBlock(x, y, z, CraftMagicNumbers.getBlock(material));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlock(int x, int y, int z, @NotNull BlockData blockData) {
|
||||||
|
setBlock(x, y, z, ((CraftBlockData) blockData).getState());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, Material material) {
|
||||||
|
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, material.createBlockData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, MaterialData material) {
|
||||||
|
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, CraftMagicNumbers.getBlock(material));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockData blockData) {
|
||||||
|
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, ((CraftBlockData)blockData).getState());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Material getType(int x, int y, int z) {
|
||||||
|
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z).getBlock());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public MaterialData getTypeAndData(int x, int y, int z) {
|
||||||
|
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public BlockData getBlockData(int x, int y, int z) {
|
||||||
|
return CraftBlockData.fromData(this.getTypeId(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
@Override
|
||||||
|
public byte getData(int x, int y, int z) {
|
||||||
|
return CraftMagicNumbers.toLegacyData(this.getTypeId(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockState type) {
|
||||||
|
if (xMin <= 15 && yMin < this.maxHeight && zMin <= 15) {
|
||||||
|
if (xMin < 0) {
|
||||||
|
xMin = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yMin < this.minHeight) {
|
||||||
|
yMin = this.minHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zMin < 0) {
|
||||||
|
zMin = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xMax > 16) {
|
||||||
|
xMax = 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yMax > this.maxHeight) {
|
||||||
|
yMax = this.maxHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zMax > 16) {
|
||||||
|
zMax = 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xMin < xMax && yMin < yMax && zMin < zMax) {
|
||||||
|
for(int y = yMin; y < yMax; ++y) {
|
||||||
|
for(int x = xMin; x < xMax; ++x) {
|
||||||
|
for(int z = zMin; z < zMax; ++z) {
|
||||||
|
this.setBlock(x, y, z, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private BlockState getTypeId(int x, int y, int z) {
|
||||||
|
if (x == (x & 15) && y >= this.minHeight && y < this.maxHeight && z == (z & 15)) {
|
||||||
|
ChunkAccess access = this.getHandle();
|
||||||
|
return access.getBlockState(new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z));
|
||||||
|
} else {
|
||||||
|
return Blocks.AIR.defaultBlockState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setBlock(int x, int y, int z, BlockState type) {
|
||||||
|
if (x == (x & 15) && y >= this.minHeight && y < this.maxHeight && z == (z & 15)) {
|
||||||
|
ChunkAccess access = this.getHandle();
|
||||||
|
BlockPos blockPosition = new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z);
|
||||||
|
BlockState oldBlockData = access.setBlockState(blockPosition, type, false);
|
||||||
|
if (type.hasBlockEntity()) {
|
||||||
|
BlockEntity tileEntity = ((EntityBlock)type.getBlock()).newBlockEntity(blockPosition, type);
|
||||||
|
if (tileEntity == null) {
|
||||||
|
access.removeBlockEntity(blockPosition);
|
||||||
|
} else {
|
||||||
|
access.setBlockEntity(tileEntity);
|
||||||
|
}
|
||||||
|
} else if (oldBlockData != null && oldBlockData.hasBlockEntity()) {
|
||||||
|
access.removeBlockEntity(blockPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,7 @@
|
|||||||
package com.volmit.iris.core.nms.v1_19_R3;
|
package com.volmit.iris.core.nms.v1_19_R3;
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.*;
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.DataInputStream;
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
@ -15,6 +12,7 @@ import java.util.Vector;
|
|||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import com.mojang.datafixers.util.Pair;
|
import com.mojang.datafixers.util.Pair;
|
||||||
|
import com.volmit.iris.core.nms.IMemoryWorld;
|
||||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||||
import com.volmit.iris.util.scheduling.J;
|
import com.volmit.iris.util.scheduling.J;
|
||||||
import net.minecraft.nbt.*;
|
import net.minecraft.nbt.*;
|
||||||
@ -619,6 +617,11 @@ public class NMSBinding implements INMSBinding {
|
|||||||
return keys;
|
return keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IMemoryWorld createMemoryWorld(NamespacedKey levelType, WorldCreator creator) throws IOException {
|
||||||
|
return new MemoryWorld(levelType, creator);
|
||||||
|
}
|
||||||
|
|
||||||
private static Field getField(Class<?> clazz, Class<?> fieldType) throws NoSuchFieldException {
|
private static Field getField(Class<?> clazz, Class<?> fieldType) throws NoSuchFieldException {
|
||||||
try {
|
try {
|
||||||
for (Field f : clazz.getDeclaredFields()) {
|
for (Field f : clazz.getDeclaredFields()) {
|
||||||
|
@ -0,0 +1,361 @@
|
|||||||
|
package com.volmit.iris.core.nms.v1_20_R1;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.mojang.serialization.Lifecycle;
|
||||||
|
import com.volmit.iris.Iris;
|
||||||
|
import com.volmit.iris.core.nms.IMemoryWorld;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.core.registries.Registries;
|
||||||
|
import net.minecraft.resources.ResourceKey;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.server.dedicated.DedicatedServerProperties;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.util.GsonHelper;
|
||||||
|
import net.minecraft.world.Difficulty;
|
||||||
|
import net.minecraft.world.entity.ai.village.VillageSiege;
|
||||||
|
import net.minecraft.world.entity.npc.CatSpawner;
|
||||||
|
import net.minecraft.world.entity.npc.WanderingTraderSpawner;
|
||||||
|
import net.minecraft.world.level.ChunkPos;
|
||||||
|
import net.minecraft.world.level.GameRules;
|
||||||
|
import net.minecraft.world.level.GameType;
|
||||||
|
import net.minecraft.world.level.LevelSettings;
|
||||||
|
import net.minecraft.world.level.biome.BiomeManager;
|
||||||
|
import net.minecraft.world.level.block.Blocks;
|
||||||
|
import net.minecraft.world.level.block.EntityBlock;
|
||||||
|
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 net.minecraft.world.level.dimension.LevelStem;
|
||||||
|
import net.minecraft.world.level.levelgen.PatrolSpawner;
|
||||||
|
import net.minecraft.world.level.levelgen.PhantomSpawner;
|
||||||
|
import net.minecraft.world.level.levelgen.WorldOptions;
|
||||||
|
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;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R1.CraftChunk;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R1.CraftServer;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R1.block.CraftBlock;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R1.block.data.CraftBlockData;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R1.generator.CraftWorldInfo;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R1.util.CraftMagicNumbers;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.world.WorldUnloadEvent;
|
||||||
|
import org.bukkit.generator.ChunkGenerator;
|
||||||
|
import org.bukkit.material.MaterialData;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
public class MemoryWorld implements IMemoryWorld {
|
||||||
|
private static final AtomicLong C = new AtomicLong();
|
||||||
|
private static final Field WORLDS_FIELD;
|
||||||
|
|
||||||
|
private final AtomicReference<ServerLevel> level = new AtomicReference<>();
|
||||||
|
|
||||||
|
public MemoryWorld(NamespacedKey levelType, WorldCreator creator) throws IOException {
|
||||||
|
var name = "memory_world"+C.getAndIncrement();
|
||||||
|
while (Bukkit.getWorld(name) != null) {
|
||||||
|
name = "memory_world"+C.getAndIncrement();
|
||||||
|
}
|
||||||
|
|
||||||
|
var generator = creator.generator();
|
||||||
|
var biomeProvider = creator.biomeProvider();
|
||||||
|
var hardcore = creator.hardcore();
|
||||||
|
var server = getServer();
|
||||||
|
|
||||||
|
var tempDir = Files.createTempDirectory("MemoryGenerator");
|
||||||
|
LevelStorageSource source = LevelStorageSource.createDefault(tempDir);
|
||||||
|
ResourceKey<LevelStem> stemKey = ResourceKey.create(Registries.LEVEL_STEM, new ResourceLocation(levelType.getKey(), levelType.getNamespace()));
|
||||||
|
var access = source.createAccess(name, stemKey);
|
||||||
|
|
||||||
|
var worldLoader = server.worldLoader;
|
||||||
|
var registry = server.registryAccess().registryOrThrow(Registries.LEVEL_STEM);
|
||||||
|
var options = new WorldOptions(creator.seed(), creator.generateStructures(), false);
|
||||||
|
var properties = new DedicatedServerProperties.WorldDimensionData(GsonHelper.parse(creator.generatorSettings().isEmpty() ? "{}" : creator.generatorSettings()), creator.type().name().toLowerCase(Locale.ROOT));
|
||||||
|
var settings = new LevelSettings(name, GameType.byId(Bukkit.getDefaultGameMode().getValue()), hardcore, Difficulty.EASY, false, new GameRules(), worldLoader.dataConfiguration());
|
||||||
|
var dimension = properties.create(worldLoader.datapackWorldgen()).bake(registry);
|
||||||
|
Lifecycle lifecycle = dimension.lifecycle().add(worldLoader.datapackWorldgen().allRegistriesLifecycle());
|
||||||
|
|
||||||
|
var worldData = new PrimaryLevelData(settings, options, dimension.specialWorldProperty(), lifecycle);
|
||||||
|
worldData.customDimensions = registry;
|
||||||
|
|
||||||
|
long obfSeed = BiomeManager.obfuscateSeed(creator.seed());
|
||||||
|
var list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(worldData));
|
||||||
|
var levelStem = registry.get(stemKey);
|
||||||
|
if (levelStem == null)
|
||||||
|
throw new IllegalStateException("Unknown dimension type: " + stemKey);
|
||||||
|
|
||||||
|
var worldInfo = new CraftWorldInfo(worldData, access, creator.environment(), levelStem.type().value());
|
||||||
|
if (biomeProvider == null && generator != null) {
|
||||||
|
biomeProvider = generator.getDefaultBiomeProvider(worldInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
var levelKey = ResourceKey.create(Registries.DIMENSION, new ResourceLocation(name));
|
||||||
|
var level = new ServerLevel(
|
||||||
|
server,
|
||||||
|
server.executor,
|
||||||
|
access,
|
||||||
|
worldData,
|
||||||
|
levelKey,
|
||||||
|
levelStem,
|
||||||
|
server.progressListenerFactory.create(0),
|
||||||
|
worldData.isDebugWorld(),
|
||||||
|
obfSeed,
|
||||||
|
creator.environment() == World.Environment.NORMAL ? list : ImmutableList.of(),
|
||||||
|
true,
|
||||||
|
server.overworld().getRandomSequences(),
|
||||||
|
creator.environment(),
|
||||||
|
generator,
|
||||||
|
biomeProvider
|
||||||
|
);
|
||||||
|
level.keepSpawnInMemory = false;
|
||||||
|
Iris.instance.registerListener(this);
|
||||||
|
this.level.set(level);
|
||||||
|
}
|
||||||
|
|
||||||
|
public World getBukkit() {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
|
||||||
|
return level.getWorld();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Chunk getChunk(int x, int z) {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
return new CraftChunk(level, x, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChunkGenerator.ChunkData getChunkData(int x, int z) {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
return new MemoryChunkData(x, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||||
|
public void onWorldUnload(WorldUnloadEvent event) {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null || event.getWorld() != level.getWorld())
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.level.set(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isClosed() {
|
||||||
|
return this.level.get() == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws Exception {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null || !this.level.compareAndSet(level, null))
|
||||||
|
return;
|
||||||
|
|
||||||
|
level.getChunkSource().close(false);
|
||||||
|
level.entityManager.close(false);
|
||||||
|
level.convertable.deleteLevel();
|
||||||
|
level.convertable.close();
|
||||||
|
|
||||||
|
var map = (Map<String, World>) WORLDS_FIELD.get(Bukkit.getServer());
|
||||||
|
map.remove(level.dimension().location().getPath());
|
||||||
|
getServer().removeLevel(level);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MinecraftServer getServer() {
|
||||||
|
return ((CraftServer) Bukkit.getServer()).getServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
WORLDS_FIELD = CraftServer.class.getDeclaredField("worlds");
|
||||||
|
WORLDS_FIELD.setAccessible(true);
|
||||||
|
} catch (NoSuchFieldException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MemoryChunkData implements ChunkGenerator.ChunkData {
|
||||||
|
private final int maxHeight;
|
||||||
|
private final int minHeight;
|
||||||
|
private final ChunkPos pos;
|
||||||
|
private WeakReference<LevelChunk> chunk;
|
||||||
|
|
||||||
|
private MemoryChunkData(int x, int z) {
|
||||||
|
var level = MemoryWorld.this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
|
||||||
|
var chunk = level.getChunk(x, z);
|
||||||
|
this.minHeight = chunk.getMinBuildHeight();
|
||||||
|
this.maxHeight = chunk.getMaxBuildHeight();
|
||||||
|
this.pos = new ChunkPos(x, z);
|
||||||
|
this.chunk = new WeakReference<>(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LevelChunk getHandle() {
|
||||||
|
LevelChunk chunk = this.chunk.get();
|
||||||
|
if (chunk == null) {
|
||||||
|
var level = MemoryWorld.this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
|
||||||
|
chunk = level.getChunk(this.pos.x, this.pos.z);
|
||||||
|
this.chunk = new WeakReference<>(chunk);
|
||||||
|
}
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMinHeight() {
|
||||||
|
return minHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxHeight() {
|
||||||
|
return maxHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Biome getBiome(int x, int y, int z) {
|
||||||
|
return CraftBlock.biomeBaseToBiome(this.getHandle().biomeRegistry, this.getHandle().getNoiseBiome(x >> 2, y >> 2, z >> 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlock(int x, int y, int z, @NotNull Material material) {
|
||||||
|
setBlock(x, y, z, material.createBlockData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlock(int x, int y, int z, @NotNull MaterialData material) {
|
||||||
|
setBlock(x, y, z, CraftMagicNumbers.getBlock(material));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlock(int x, int y, int z, @NotNull BlockData blockData) {
|
||||||
|
setBlock(x, y, z, ((CraftBlockData) blockData).getState());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, Material material) {
|
||||||
|
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, material.createBlockData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, MaterialData material) {
|
||||||
|
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, CraftMagicNumbers.getBlock(material));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockData blockData) {
|
||||||
|
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, ((CraftBlockData)blockData).getState());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Material getType(int x, int y, int z) {
|
||||||
|
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z).getBlock());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public MaterialData getTypeAndData(int x, int y, int z) {
|
||||||
|
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public BlockData getBlockData(int x, int y, int z) {
|
||||||
|
return CraftBlockData.fromData(this.getTypeId(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
@Override
|
||||||
|
public byte getData(int x, int y, int z) {
|
||||||
|
return CraftMagicNumbers.toLegacyData(this.getTypeId(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockState type) {
|
||||||
|
if (xMin <= 15 && yMin < this.maxHeight && zMin <= 15) {
|
||||||
|
if (xMin < 0) {
|
||||||
|
xMin = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yMin < this.minHeight) {
|
||||||
|
yMin = this.minHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zMin < 0) {
|
||||||
|
zMin = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xMax > 16) {
|
||||||
|
xMax = 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yMax > this.maxHeight) {
|
||||||
|
yMax = this.maxHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zMax > 16) {
|
||||||
|
zMax = 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xMin < xMax && yMin < yMax && zMin < zMax) {
|
||||||
|
for(int y = yMin; y < yMax; ++y) {
|
||||||
|
for(int x = xMin; x < xMax; ++x) {
|
||||||
|
for(int z = zMin; z < zMax; ++z) {
|
||||||
|
this.setBlock(x, y, z, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private BlockState getTypeId(int x, int y, int z) {
|
||||||
|
if (x == (x & 15) && y >= this.minHeight && y < this.maxHeight && z == (z & 15)) {
|
||||||
|
ChunkAccess access = this.getHandle();
|
||||||
|
return access.getBlockState(new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z));
|
||||||
|
} else {
|
||||||
|
return Blocks.AIR.defaultBlockState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setBlock(int x, int y, int z, BlockState type) {
|
||||||
|
if (x == (x & 15) && y >= this.minHeight && y < this.maxHeight && z == (z & 15)) {
|
||||||
|
ChunkAccess access = this.getHandle();
|
||||||
|
BlockPos blockPosition = new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z);
|
||||||
|
BlockState oldBlockData = access.setBlockState(blockPosition, type, false);
|
||||||
|
if (type.hasBlockEntity()) {
|
||||||
|
BlockEntity tileEntity = ((EntityBlock)type.getBlock()).newBlockEntity(blockPosition, type);
|
||||||
|
if (tileEntity == null) {
|
||||||
|
access.removeBlockEntity(blockPosition);
|
||||||
|
} else {
|
||||||
|
access.setBlockEntity(tileEntity);
|
||||||
|
}
|
||||||
|
} else if (oldBlockData != null && oldBlockData.hasBlockEntity()) {
|
||||||
|
access.removeBlockEntity(blockPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@ package com.volmit.iris.core.nms.v1_20_R1;
|
|||||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||||
import com.mojang.datafixers.util.Pair;
|
import com.mojang.datafixers.util.Pair;
|
||||||
import com.volmit.iris.Iris;
|
import com.volmit.iris.Iris;
|
||||||
|
import com.volmit.iris.core.nms.IMemoryWorld;
|
||||||
import com.volmit.iris.core.nms.INMSBinding;
|
import com.volmit.iris.core.nms.INMSBinding;
|
||||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||||
import com.volmit.iris.engine.data.cache.AtomicCache;
|
import com.volmit.iris.engine.data.cache.AtomicCache;
|
||||||
@ -66,10 +67,7 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
import sun.misc.Unsafe;
|
import sun.misc.Unsafe;
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.*;
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.DataInputStream;
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
@ -612,6 +610,11 @@ public class NMSBinding implements INMSBinding {
|
|||||||
return keys;
|
return keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IMemoryWorld createMemoryWorld(NamespacedKey levelType, WorldCreator creator) throws IOException {
|
||||||
|
return new MemoryWorld(levelType, creator);
|
||||||
|
}
|
||||||
|
|
||||||
private static Field getField(Class<?> clazz, Class<?> fieldType) throws NoSuchFieldException {
|
private static Field getField(Class<?> clazz, Class<?> fieldType) throws NoSuchFieldException {
|
||||||
try {
|
try {
|
||||||
for (Field f : clazz.getDeclaredFields()) {
|
for (Field f : clazz.getDeclaredFields()) {
|
||||||
|
@ -0,0 +1,361 @@
|
|||||||
|
package com.volmit.iris.core.nms.v1_20_R2;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.mojang.serialization.Lifecycle;
|
||||||
|
import com.volmit.iris.Iris;
|
||||||
|
import com.volmit.iris.core.nms.IMemoryWorld;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.core.registries.Registries;
|
||||||
|
import net.minecraft.resources.ResourceKey;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.server.dedicated.DedicatedServerProperties;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.util.GsonHelper;
|
||||||
|
import net.minecraft.world.Difficulty;
|
||||||
|
import net.minecraft.world.entity.ai.village.VillageSiege;
|
||||||
|
import net.minecraft.world.entity.npc.CatSpawner;
|
||||||
|
import net.minecraft.world.entity.npc.WanderingTraderSpawner;
|
||||||
|
import net.minecraft.world.level.ChunkPos;
|
||||||
|
import net.minecraft.world.level.GameRules;
|
||||||
|
import net.minecraft.world.level.GameType;
|
||||||
|
import net.minecraft.world.level.LevelSettings;
|
||||||
|
import net.minecraft.world.level.biome.BiomeManager;
|
||||||
|
import net.minecraft.world.level.block.Blocks;
|
||||||
|
import net.minecraft.world.level.block.EntityBlock;
|
||||||
|
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 net.minecraft.world.level.dimension.LevelStem;
|
||||||
|
import net.minecraft.world.level.levelgen.PatrolSpawner;
|
||||||
|
import net.minecraft.world.level.levelgen.PhantomSpawner;
|
||||||
|
import net.minecraft.world.level.levelgen.WorldOptions;
|
||||||
|
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;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R2.CraftChunk;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R2.CraftServer;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R2.block.CraftBiome;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R2.block.data.CraftBlockData;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R2.generator.CraftWorldInfo;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R2.util.CraftMagicNumbers;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.world.WorldUnloadEvent;
|
||||||
|
import org.bukkit.generator.ChunkGenerator;
|
||||||
|
import org.bukkit.material.MaterialData;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
public class MemoryWorld implements IMemoryWorld {
|
||||||
|
private static final AtomicLong C = new AtomicLong();
|
||||||
|
private static final Field WORLDS_FIELD;
|
||||||
|
|
||||||
|
private final AtomicReference<ServerLevel> level = new AtomicReference<>();
|
||||||
|
|
||||||
|
public MemoryWorld(NamespacedKey levelType, WorldCreator creator) throws IOException {
|
||||||
|
var name = "memory_world"+C.getAndIncrement();
|
||||||
|
while (Bukkit.getWorld(name) != null) {
|
||||||
|
name = "memory_world"+C.getAndIncrement();
|
||||||
|
}
|
||||||
|
|
||||||
|
var generator = creator.generator();
|
||||||
|
var biomeProvider = creator.biomeProvider();
|
||||||
|
var hardcore = creator.hardcore();
|
||||||
|
var server = getServer();
|
||||||
|
|
||||||
|
var tempDir = Files.createTempDirectory("MemoryGenerator");
|
||||||
|
LevelStorageSource source = LevelStorageSource.createDefault(tempDir);
|
||||||
|
ResourceKey<LevelStem> stemKey = ResourceKey.create(Registries.LEVEL_STEM, new ResourceLocation(levelType.getKey(), levelType.getNamespace()));
|
||||||
|
var access = source.createAccess(name, stemKey);
|
||||||
|
|
||||||
|
var worldLoader = server.worldLoader;
|
||||||
|
var registry = server.registryAccess().registryOrThrow(Registries.LEVEL_STEM);
|
||||||
|
var options = new WorldOptions(creator.seed(), creator.generateStructures(), false);
|
||||||
|
var properties = new DedicatedServerProperties.WorldDimensionData(GsonHelper.parse(creator.generatorSettings().isEmpty() ? "{}" : creator.generatorSettings()), creator.type().name().toLowerCase(Locale.ROOT));
|
||||||
|
var settings = new LevelSettings(name, GameType.byId(Bukkit.getDefaultGameMode().getValue()), hardcore, Difficulty.EASY, false, new GameRules(), worldLoader.dataConfiguration());
|
||||||
|
var dimension = properties.create(worldLoader.datapackWorldgen()).bake(registry);
|
||||||
|
Lifecycle lifecycle = dimension.lifecycle().add(worldLoader.datapackWorldgen().allRegistriesLifecycle());
|
||||||
|
|
||||||
|
var worldData = new PrimaryLevelData(settings, options, dimension.specialWorldProperty(), lifecycle);
|
||||||
|
worldData.customDimensions = registry;
|
||||||
|
|
||||||
|
long obfSeed = BiomeManager.obfuscateSeed(creator.seed());
|
||||||
|
var list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(worldData));
|
||||||
|
var levelStem = registry.get(stemKey);
|
||||||
|
if (levelStem == null)
|
||||||
|
throw new IllegalStateException("Unknown dimension type: " + stemKey);
|
||||||
|
|
||||||
|
var worldInfo = new CraftWorldInfo(worldData, access, creator.environment(), levelStem.type().value());
|
||||||
|
if (biomeProvider == null && generator != null) {
|
||||||
|
biomeProvider = generator.getDefaultBiomeProvider(worldInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
var levelKey = ResourceKey.create(Registries.DIMENSION, new ResourceLocation(name));
|
||||||
|
var level = new ServerLevel(
|
||||||
|
server,
|
||||||
|
server.executor,
|
||||||
|
access,
|
||||||
|
worldData,
|
||||||
|
levelKey,
|
||||||
|
levelStem,
|
||||||
|
server.progressListenerFactory.create(0),
|
||||||
|
worldData.isDebugWorld(),
|
||||||
|
obfSeed,
|
||||||
|
creator.environment() == World.Environment.NORMAL ? list : ImmutableList.of(),
|
||||||
|
true,
|
||||||
|
server.overworld().getRandomSequences(),
|
||||||
|
creator.environment(),
|
||||||
|
generator,
|
||||||
|
biomeProvider
|
||||||
|
);
|
||||||
|
level.keepSpawnInMemory = false;
|
||||||
|
Iris.instance.registerListener(this);
|
||||||
|
this.level.set(level);
|
||||||
|
}
|
||||||
|
|
||||||
|
public World getBukkit() {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
|
||||||
|
return level.getWorld();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Chunk getChunk(int x, int z) {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
return new CraftChunk(level, x, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChunkGenerator.ChunkData getChunkData(int x, int z) {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
return new MemoryChunkData(x, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||||
|
public void onWorldUnload(WorldUnloadEvent event) {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null || event.getWorld() != level.getWorld())
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.level.set(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isClosed() {
|
||||||
|
return this.level.get() == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws Exception {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null || !this.level.compareAndSet(level, null))
|
||||||
|
return;
|
||||||
|
|
||||||
|
level.getChunkSource().close(false);
|
||||||
|
level.entityManager.close(false);
|
||||||
|
level.convertable.deleteLevel();
|
||||||
|
level.convertable.close();
|
||||||
|
|
||||||
|
var map = (Map<String, World>) WORLDS_FIELD.get(Bukkit.getServer());
|
||||||
|
map.remove(level.dimension().location().getPath());
|
||||||
|
getServer().removeLevel(level);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MinecraftServer getServer() {
|
||||||
|
return ((CraftServer) Bukkit.getServer()).getServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
WORLDS_FIELD = CraftServer.class.getDeclaredField("worlds");
|
||||||
|
WORLDS_FIELD.setAccessible(true);
|
||||||
|
} catch (NoSuchFieldException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MemoryChunkData implements ChunkGenerator.ChunkData {
|
||||||
|
private final int maxHeight;
|
||||||
|
private final int minHeight;
|
||||||
|
private final ChunkPos pos;
|
||||||
|
private WeakReference<LevelChunk> chunk;
|
||||||
|
|
||||||
|
private MemoryChunkData(int x, int z) {
|
||||||
|
var level = MemoryWorld.this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
|
||||||
|
var chunk = level.getChunk(x, z);
|
||||||
|
this.minHeight = chunk.getMinBuildHeight();
|
||||||
|
this.maxHeight = chunk.getMaxBuildHeight();
|
||||||
|
this.pos = new ChunkPos(x, z);
|
||||||
|
this.chunk = new WeakReference<>(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LevelChunk getHandle() {
|
||||||
|
LevelChunk chunk = this.chunk.get();
|
||||||
|
if (chunk == null) {
|
||||||
|
var level = MemoryWorld.this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
|
||||||
|
chunk = level.getChunk(this.pos.x, this.pos.z);
|
||||||
|
this.chunk = new WeakReference<>(chunk);
|
||||||
|
}
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMinHeight() {
|
||||||
|
return minHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxHeight() {
|
||||||
|
return maxHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Biome getBiome(int x, int y, int z) {
|
||||||
|
return CraftBiome.minecraftHolderToBukkit(getHandle().getNoiseBiome(x >> 2, y >> 2, z >> 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlock(int x, int y, int z, @NotNull Material material) {
|
||||||
|
setBlock(x, y, z, material.createBlockData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlock(int x, int y, int z, @NotNull MaterialData material) {
|
||||||
|
setBlock(x, y, z, CraftMagicNumbers.getBlock(material));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlock(int x, int y, int z, @NotNull BlockData blockData) {
|
||||||
|
setBlock(x, y, z, ((CraftBlockData) blockData).getState());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, Material material) {
|
||||||
|
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, material.createBlockData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, MaterialData material) {
|
||||||
|
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, CraftMagicNumbers.getBlock(material));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockData blockData) {
|
||||||
|
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, ((CraftBlockData)blockData).getState());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Material getType(int x, int y, int z) {
|
||||||
|
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z).getBlock());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public MaterialData getTypeAndData(int x, int y, int z) {
|
||||||
|
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public BlockData getBlockData(int x, int y, int z) {
|
||||||
|
return CraftBlockData.fromData(this.getTypeId(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
@Override
|
||||||
|
public byte getData(int x, int y, int z) {
|
||||||
|
return CraftMagicNumbers.toLegacyData(this.getTypeId(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockState type) {
|
||||||
|
if (xMin <= 15 && yMin < this.maxHeight && zMin <= 15) {
|
||||||
|
if (xMin < 0) {
|
||||||
|
xMin = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yMin < this.minHeight) {
|
||||||
|
yMin = this.minHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zMin < 0) {
|
||||||
|
zMin = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xMax > 16) {
|
||||||
|
xMax = 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yMax > this.maxHeight) {
|
||||||
|
yMax = this.maxHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zMax > 16) {
|
||||||
|
zMax = 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xMin < xMax && yMin < yMax && zMin < zMax) {
|
||||||
|
for(int y = yMin; y < yMax; ++y) {
|
||||||
|
for(int x = xMin; x < xMax; ++x) {
|
||||||
|
for(int z = zMin; z < zMax; ++z) {
|
||||||
|
this.setBlock(x, y, z, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private BlockState getTypeId(int x, int y, int z) {
|
||||||
|
if (x == (x & 15) && y >= this.minHeight && y < this.maxHeight && z == (z & 15)) {
|
||||||
|
ChunkAccess access = this.getHandle();
|
||||||
|
return access.getBlockState(new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z));
|
||||||
|
} else {
|
||||||
|
return Blocks.AIR.defaultBlockState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setBlock(int x, int y, int z, BlockState type) {
|
||||||
|
if (x == (x & 15) && y >= this.minHeight && y < this.maxHeight && z == (z & 15)) {
|
||||||
|
ChunkAccess access = this.getHandle();
|
||||||
|
BlockPos blockPosition = new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z);
|
||||||
|
BlockState oldBlockData = access.setBlockState(blockPosition, type, false);
|
||||||
|
if (type.hasBlockEntity()) {
|
||||||
|
BlockEntity tileEntity = ((EntityBlock)type.getBlock()).newBlockEntity(blockPosition, type);
|
||||||
|
if (tileEntity == null) {
|
||||||
|
access.removeBlockEntity(blockPosition);
|
||||||
|
} else {
|
||||||
|
access.setBlockEntity(tileEntity);
|
||||||
|
}
|
||||||
|
} else if (oldBlockData != null && oldBlockData.hasBlockEntity()) {
|
||||||
|
access.removeBlockEntity(blockPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,7 @@
|
|||||||
package com.volmit.iris.core.nms.v1_20_R2;
|
package com.volmit.iris.core.nms.v1_20_R2;
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.*;
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.DataInputStream;
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
@ -15,6 +12,7 @@ import java.util.Vector;
|
|||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import com.mojang.datafixers.util.Pair;
|
import com.mojang.datafixers.util.Pair;
|
||||||
|
import com.volmit.iris.core.nms.IMemoryWorld;
|
||||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||||
import com.volmit.iris.util.scheduling.J;
|
import com.volmit.iris.util.scheduling.J;
|
||||||
import net.minecraft.nbt.*;
|
import net.minecraft.nbt.*;
|
||||||
@ -610,6 +608,11 @@ public class NMSBinding implements INMSBinding {
|
|||||||
return keys;
|
return keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IMemoryWorld createMemoryWorld(NamespacedKey levelType, WorldCreator creator) throws IOException {
|
||||||
|
return new MemoryWorld(levelType, creator);
|
||||||
|
}
|
||||||
|
|
||||||
private static Field getField(Class<?> clazz, Class<?> fieldType) throws NoSuchFieldException {
|
private static Field getField(Class<?> clazz, Class<?> fieldType) throws NoSuchFieldException {
|
||||||
try {
|
try {
|
||||||
for (Field f : clazz.getDeclaredFields()) {
|
for (Field f : clazz.getDeclaredFields()) {
|
||||||
|
@ -0,0 +1,365 @@
|
|||||||
|
package com.volmit.iris.core.nms.v1_20_R3;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.mojang.serialization.Lifecycle;
|
||||||
|
import com.volmit.iris.Iris;
|
||||||
|
import com.volmit.iris.core.nms.IMemoryWorld;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.core.registries.Registries;
|
||||||
|
import net.minecraft.resources.ResourceKey;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.server.dedicated.DedicatedServerProperties;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.util.GsonHelper;
|
||||||
|
import net.minecraft.world.Difficulty;
|
||||||
|
import net.minecraft.world.entity.ai.village.VillageSiege;
|
||||||
|
import net.minecraft.world.entity.npc.CatSpawner;
|
||||||
|
import net.minecraft.world.entity.npc.WanderingTraderSpawner;
|
||||||
|
import net.minecraft.world.level.ChunkPos;
|
||||||
|
import net.minecraft.world.level.GameRules;
|
||||||
|
import net.minecraft.world.level.GameType;
|
||||||
|
import net.minecraft.world.level.LevelSettings;
|
||||||
|
import net.minecraft.world.level.biome.BiomeManager;
|
||||||
|
import net.minecraft.world.level.block.Blocks;
|
||||||
|
import net.minecraft.world.level.block.EntityBlock;
|
||||||
|
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 net.minecraft.world.level.dimension.LevelStem;
|
||||||
|
import net.minecraft.world.level.levelgen.PatrolSpawner;
|
||||||
|
import net.minecraft.world.level.levelgen.PhantomSpawner;
|
||||||
|
import net.minecraft.world.level.levelgen.WorldOptions;
|
||||||
|
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;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R3.CraftChunk;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R3.CraftServer;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R3.block.CraftBiome;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R3.block.CraftBlockType;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R3.block.data.CraftBlockData;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R3.generator.CraftChunkData;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R3.generator.CraftWorldInfo;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R3.util.CraftMagicNumbers;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.world.WorldUnloadEvent;
|
||||||
|
import org.bukkit.generator.ChunkGenerator;
|
||||||
|
import org.bukkit.material.MaterialData;
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
public class MemoryWorld implements IMemoryWorld {
|
||||||
|
private static final AtomicLong C = new AtomicLong();
|
||||||
|
private static final Field WORLDS_FIELD;
|
||||||
|
|
||||||
|
private final AtomicReference<ServerLevel> level = new AtomicReference<>();
|
||||||
|
|
||||||
|
public MemoryWorld(NamespacedKey levelType, WorldCreator creator) throws IOException {
|
||||||
|
var name = "memory_world"+C.getAndIncrement();
|
||||||
|
while (Bukkit.getWorld(name) != null) {
|
||||||
|
name = "memory_world"+C.getAndIncrement();
|
||||||
|
}
|
||||||
|
|
||||||
|
var generator = creator.generator();
|
||||||
|
var biomeProvider = creator.biomeProvider();
|
||||||
|
var hardcore = creator.hardcore();
|
||||||
|
var server = getServer();
|
||||||
|
|
||||||
|
var tempDir = Files.createTempDirectory("MemoryGenerator");
|
||||||
|
LevelStorageSource source = LevelStorageSource.createDefault(tempDir);
|
||||||
|
ResourceKey<LevelStem> stemKey = ResourceKey.create(Registries.LEVEL_STEM, new ResourceLocation(levelType.getKey(), levelType.getNamespace()));
|
||||||
|
var access = source.createAccess(name, stemKey);
|
||||||
|
|
||||||
|
var worldLoader = server.worldLoader;
|
||||||
|
var registry = server.registryAccess().registryOrThrow(Registries.LEVEL_STEM);
|
||||||
|
var options = new WorldOptions(creator.seed(), creator.generateStructures(), false);
|
||||||
|
var properties = new DedicatedServerProperties.WorldDimensionData(GsonHelper.parse(creator.generatorSettings().isEmpty() ? "{}" : creator.generatorSettings()), creator.type().name().toLowerCase(Locale.ROOT));
|
||||||
|
var settings = new LevelSettings(name, GameType.byId(Bukkit.getDefaultGameMode().getValue()), hardcore, Difficulty.EASY, false, new GameRules(), worldLoader.dataConfiguration());
|
||||||
|
var dimension = properties.create(worldLoader.datapackWorldgen()).bake(registry);
|
||||||
|
Lifecycle lifecycle = dimension.lifecycle().add(worldLoader.datapackWorldgen().allRegistriesLifecycle());
|
||||||
|
|
||||||
|
var worldData = new PrimaryLevelData(settings, options, dimension.specialWorldProperty(), lifecycle);
|
||||||
|
worldData.customDimensions = registry;
|
||||||
|
|
||||||
|
long obfSeed = BiomeManager.obfuscateSeed(creator.seed());
|
||||||
|
var list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(worldData));
|
||||||
|
var levelStem = registry.get(stemKey);
|
||||||
|
if (levelStem == null)
|
||||||
|
throw new IllegalStateException("Unknown dimension type: " + stemKey);
|
||||||
|
|
||||||
|
var worldInfo = new CraftWorldInfo(worldData, access, creator.environment(), levelStem.type().value());
|
||||||
|
if (biomeProvider == null && generator != null) {
|
||||||
|
biomeProvider = generator.getDefaultBiomeProvider(worldInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
var levelKey = ResourceKey.create(Registries.DIMENSION, new ResourceLocation(name));
|
||||||
|
var level = new ServerLevel(
|
||||||
|
server,
|
||||||
|
server.executor,
|
||||||
|
access,
|
||||||
|
worldData,
|
||||||
|
levelKey,
|
||||||
|
levelStem,
|
||||||
|
server.progressListenerFactory.create(0),
|
||||||
|
worldData.isDebugWorld(),
|
||||||
|
obfSeed,
|
||||||
|
creator.environment() == World.Environment.NORMAL ? list : ImmutableList.of(),
|
||||||
|
true,
|
||||||
|
server.overworld().getRandomSequences(),
|
||||||
|
creator.environment(),
|
||||||
|
generator,
|
||||||
|
biomeProvider
|
||||||
|
);
|
||||||
|
level.keepSpawnInMemory = false;
|
||||||
|
Iris.instance.registerListener(this);
|
||||||
|
this.level.set(level);
|
||||||
|
}
|
||||||
|
|
||||||
|
public World getBukkit() {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
|
||||||
|
return level.getWorld();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Chunk getChunk(int x, int z) {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
return new CraftChunk(level, x, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChunkGenerator.ChunkData getChunkData(int x, int z) {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
return new MemoryChunkData(x, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||||
|
public void onWorldUnload(WorldUnloadEvent event) {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null || event.getWorld() != level.getWorld())
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.level.set(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isClosed() {
|
||||||
|
return this.level.get() == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws Exception {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null || !this.level.compareAndSet(level, null))
|
||||||
|
return;
|
||||||
|
|
||||||
|
level.getChunkSource().close(false);
|
||||||
|
level.entityManager.close(false);
|
||||||
|
level.convertable.deleteLevel();
|
||||||
|
level.convertable.close();
|
||||||
|
|
||||||
|
var map = (Map<String, World>) WORLDS_FIELD.get(Bukkit.getServer());
|
||||||
|
map.remove(level.dimension().location().getPath());
|
||||||
|
getServer().removeLevel(level);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MinecraftServer getServer() {
|
||||||
|
return ((CraftServer) Bukkit.getServer()).getServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
WORLDS_FIELD = CraftServer.class.getDeclaredField("worlds");
|
||||||
|
WORLDS_FIELD.setAccessible(true);
|
||||||
|
} catch (NoSuchFieldException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MemoryChunkData implements ChunkGenerator.ChunkData {
|
||||||
|
private final int maxHeight;
|
||||||
|
private final int minHeight;
|
||||||
|
private final ChunkPos pos;
|
||||||
|
private WeakReference<LevelChunk> chunk;
|
||||||
|
|
||||||
|
private MemoryChunkData(int x, int z) {
|
||||||
|
var level = MemoryWorld.this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
|
||||||
|
var chunk = level.getChunk(x, z);
|
||||||
|
this.minHeight = chunk.getMinBuildHeight();
|
||||||
|
this.maxHeight = chunk.getMaxBuildHeight();
|
||||||
|
this.pos = new ChunkPos(x, z);
|
||||||
|
this.chunk = new WeakReference<>(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LevelChunk getHandle() {
|
||||||
|
LevelChunk chunk = this.chunk.get();
|
||||||
|
if (chunk == null) {
|
||||||
|
var level = MemoryWorld.this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
|
||||||
|
chunk = level.getChunk(this.pos.x, this.pos.z);
|
||||||
|
this.chunk = new WeakReference<>(chunk);
|
||||||
|
}
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMinHeight() {
|
||||||
|
return minHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxHeight() {
|
||||||
|
return maxHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Biome getBiome(int x, int y, int z) {
|
||||||
|
return CraftBiome.minecraftHolderToBukkit(getHandle().getNoiseBiome(x >> 2, y >> 2, z >> 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlock(int x, int y, int z, @NotNull Material material) {
|
||||||
|
setBlock(x, y, z, material.createBlockData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlock(int x, int y, int z, @NotNull MaterialData material) {
|
||||||
|
setBlock(x, y, z, CraftMagicNumbers.getBlock(material));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlock(int x, int y, int z, @NotNull BlockData blockData) {
|
||||||
|
setBlock(x, y, z, ((CraftBlockData) blockData).getState());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, Material material) {
|
||||||
|
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, material.createBlockData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, MaterialData material) {
|
||||||
|
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, CraftMagicNumbers.getBlock(material));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockData blockData) {
|
||||||
|
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, ((CraftBlockData)blockData).getState());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Material getType(int x, int y, int z) {
|
||||||
|
return CraftBlockType.minecraftToBukkit(this.getTypeId(x, y, z).getBlock());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public MaterialData getTypeAndData(int x, int y, int z) {
|
||||||
|
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public BlockData getBlockData(int x, int y, int z) {
|
||||||
|
return CraftBlockData.fromData(this.getTypeId(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
@Override
|
||||||
|
public byte getData(int x, int y, int z) {
|
||||||
|
return CraftMagicNumbers.toLegacyData(this.getTypeId(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockState type) {
|
||||||
|
if (xMin <= 15 && yMin < this.maxHeight && zMin <= 15) {
|
||||||
|
if (xMin < 0) {
|
||||||
|
xMin = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yMin < this.minHeight) {
|
||||||
|
yMin = this.minHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zMin < 0) {
|
||||||
|
zMin = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xMax > 16) {
|
||||||
|
xMax = 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yMax > this.maxHeight) {
|
||||||
|
yMax = this.maxHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zMax > 16) {
|
||||||
|
zMax = 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xMin < xMax && yMin < yMax && zMin < zMax) {
|
||||||
|
for(int y = yMin; y < yMax; ++y) {
|
||||||
|
for(int x = xMin; x < xMax; ++x) {
|
||||||
|
for(int z = zMin; z < zMax; ++z) {
|
||||||
|
this.setBlock(x, y, z, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private BlockState getTypeId(int x, int y, int z) {
|
||||||
|
if (x == (x & 15) && y >= this.minHeight && y < this.maxHeight && z == (z & 15)) {
|
||||||
|
ChunkAccess access = this.getHandle();
|
||||||
|
return access.getBlockState(new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z));
|
||||||
|
} else {
|
||||||
|
return Blocks.AIR.defaultBlockState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setBlock(int x, int y, int z, BlockState type) {
|
||||||
|
if (x == (x & 15) && y >= this.minHeight && y < this.maxHeight && z == (z & 15)) {
|
||||||
|
ChunkAccess access = this.getHandle();
|
||||||
|
BlockPos blockPosition = new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z);
|
||||||
|
BlockState oldBlockData = access.setBlockState(blockPosition, type, false);
|
||||||
|
if (type.hasBlockEntity()) {
|
||||||
|
BlockEntity tileEntity = ((EntityBlock)type.getBlock()).newBlockEntity(blockPosition, type);
|
||||||
|
if (tileEntity == null) {
|
||||||
|
access.removeBlockEntity(blockPosition);
|
||||||
|
} else {
|
||||||
|
access.setBlockEntity(tileEntity);
|
||||||
|
}
|
||||||
|
} else if (oldBlockData != null && oldBlockData.hasBlockEntity()) {
|
||||||
|
access.removeBlockEntity(blockPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package com.volmit.iris.core.nms.v1_20_R3;
|
package com.volmit.iris.core.nms.v1_20_R3;
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
|
import java.io.*;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
@ -11,6 +12,7 @@ import java.util.Vector;
|
|||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import com.mojang.datafixers.util.Pair;
|
import com.mojang.datafixers.util.Pair;
|
||||||
|
import com.volmit.iris.core.nms.IMemoryWorld;
|
||||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||||
import com.volmit.iris.util.data.B;
|
import com.volmit.iris.util.data.B;
|
||||||
import com.volmit.iris.util.scheduling.J;
|
import com.volmit.iris.util.scheduling.J;
|
||||||
@ -29,8 +31,10 @@ 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.CraftBlockState;
|
||||||
import org.bukkit.craftbukkit.v1_20_R3.block.CraftBlockStates;
|
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.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.inventory.CraftItemStack;
|
||||||
import org.bukkit.craftbukkit.v1_20_R3.util.CraftNamespacedKey;
|
import org.bukkit.craftbukkit.v1_20_R3.util.CraftNamespacedKey;
|
||||||
|
import org.bukkit.entity.Dolphin;
|
||||||
import org.bukkit.entity.Entity;
|
import org.bukkit.entity.Entity;
|
||||||
import org.bukkit.event.entity.CreatureSpawnEvent;
|
import org.bukkit.event.entity.CreatureSpawnEvent;
|
||||||
import org.bukkit.generator.ChunkGenerator;
|
import org.bukkit.generator.ChunkGenerator;
|
||||||
@ -50,6 +54,7 @@ import com.volmit.iris.util.json.JSONObject;
|
|||||||
import com.volmit.iris.util.mantle.Mantle;
|
import com.volmit.iris.util.mantle.Mantle;
|
||||||
import com.volmit.iris.util.math.Vector3d;
|
import com.volmit.iris.util.math.Vector3d;
|
||||||
import com.volmit.iris.util.matter.MatterBiomeInject;
|
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.NBTWorld;
|
||||||
import com.volmit.iris.util.nbt.mca.palette.*;
|
import com.volmit.iris.util.nbt.mca.palette.*;
|
||||||
import com.volmit.iris.util.nbt.tag.CompoundTag;
|
import com.volmit.iris.util.nbt.tag.CompoundTag;
|
||||||
@ -64,12 +69,14 @@ import net.minecraft.resources.ResourceKey;
|
|||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.server.level.ServerLevel;
|
import net.minecraft.server.level.ServerLevel;
|
||||||
import net.minecraft.world.entity.EntityType;
|
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.Block;
|
||||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
import net.minecraft.world.level.block.state.BlockState;
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||||
import net.minecraft.world.level.chunk.ChunkStatus;
|
import net.minecraft.world.level.chunk.ChunkStatus;
|
||||||
import net.minecraft.world.level.chunk.LevelChunk;
|
import net.minecraft.world.level.chunk.LevelChunk;
|
||||||
|
import sun.misc.Unsafe;
|
||||||
|
|
||||||
public class NMSBinding implements INMSBinding {
|
public class NMSBinding implements INMSBinding {
|
||||||
private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
|
private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
|
||||||
@ -277,11 +284,7 @@ public class NMSBinding implements INMSBinding {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getTrueBiomeBaseId(Object biomeBase) {
|
public int getTrueBiomeBaseId(Object biomeBase) {
|
||||||
if (biomeBase instanceof net.minecraft.world.level.biome.Biome) {
|
return getCustomBiomeRegistry().getId(((Holder<net.minecraft.world.level.biome.Biome>) biomeBase).value());
|
||||||
net.minecraft.world.level.biome.Biome biome = (net.minecraft.world.level.biome.Biome) biomeBase;
|
|
||||||
return getCustomBiomeRegistry().getId(biome);
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException("Invalid biomeBase type: " + biomeBase.getClass().getName());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -310,15 +313,7 @@ public class NMSBinding implements INMSBinding {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getKeyForBiomeBase(Object biomeBase) {
|
public String getKeyForBiomeBase(Object biomeBase) {
|
||||||
if (biomeBase instanceof net.minecraft.core.Holder<?>) {
|
return getCustomBiomeRegistry().getKey((net.minecraft.world.level.biome.Biome) biomeBase).getPath(); // something, not something:something
|
||||||
net.minecraft.core.Holder<?> holder = (net.minecraft.core.Holder<?>) biomeBase;
|
|
||||||
|
|
||||||
if (holder.value() instanceof net.minecraft.world.level.biome.Biome) {
|
|
||||||
net.minecraft.world.level.biome.Biome biome = (net.minecraft.world.level.biome.Biome) holder.value();
|
|
||||||
return getCustomBiomeRegistry().getKey(biome).getPath();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException("Invalid biomeBase type: " + biomeBase.getClass().getName());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -441,7 +436,6 @@ public class NMSBinding implements INMSBinding {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setBiomes(int cx, int cz, World world, Hunk<Object> biomes) {
|
public void setBiomes(int cx, int cz, World world, Hunk<Object> biomes) {
|
||||||
LevelChunk c = ((CraftWorld) world).getHandle().getChunk(cx, cz);
|
LevelChunk c = ((CraftWorld) world).getHandle().getChunk(cx, cz);
|
||||||
biomes.iterateSync((x, y, z, b) -> c.setBiome(x, y, z, (Holder<net.minecraft.world.level.biome.Biome>) b));
|
biomes.iterateSync((x, y, z, b) -> c.setBiome(x, y, z, (Holder<net.minecraft.world.level.biome.Biome>) b));
|
||||||
@ -664,4 +658,9 @@ public class NMSBinding implements INMSBinding {
|
|||||||
|
|
||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IMemoryWorld createMemoryWorld(NamespacedKey levelType, WorldCreator creator) throws IOException {
|
||||||
|
return new MemoryWorld(levelType, creator);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,362 @@
|
|||||||
|
package com.volmit.iris.core.nms.v1_20_R4;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.mojang.serialization.Lifecycle;
|
||||||
|
import com.volmit.iris.Iris;
|
||||||
|
import com.volmit.iris.core.nms.IMemoryWorld;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.core.registries.Registries;
|
||||||
|
import net.minecraft.resources.ResourceKey;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.server.dedicated.DedicatedServerProperties;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.util.GsonHelper;
|
||||||
|
import net.minecraft.world.Difficulty;
|
||||||
|
import net.minecraft.world.entity.ai.village.VillageSiege;
|
||||||
|
import net.minecraft.world.entity.npc.CatSpawner;
|
||||||
|
import net.minecraft.world.entity.npc.WanderingTraderSpawner;
|
||||||
|
import net.minecraft.world.level.ChunkPos;
|
||||||
|
import net.minecraft.world.level.GameRules;
|
||||||
|
import net.minecraft.world.level.GameType;
|
||||||
|
import net.minecraft.world.level.LevelSettings;
|
||||||
|
import net.minecraft.world.level.biome.BiomeManager;
|
||||||
|
import net.minecraft.world.level.block.Blocks;
|
||||||
|
import net.minecraft.world.level.block.EntityBlock;
|
||||||
|
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 net.minecraft.world.level.dimension.LevelStem;
|
||||||
|
import net.minecraft.world.level.levelgen.PatrolSpawner;
|
||||||
|
import net.minecraft.world.level.levelgen.PhantomSpawner;
|
||||||
|
import net.minecraft.world.level.levelgen.WorldOptions;
|
||||||
|
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;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R4.CraftChunk;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R4.CraftServer;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R4.block.CraftBiome;
|
||||||
|
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.generator.CraftWorldInfo;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R4.util.CraftMagicNumbers;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.world.WorldUnloadEvent;
|
||||||
|
import org.bukkit.generator.ChunkGenerator;
|
||||||
|
import org.bukkit.material.MaterialData;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
public class MemoryWorld implements IMemoryWorld {
|
||||||
|
private static final AtomicLong C = new AtomicLong();
|
||||||
|
private static final Field WORLDS_FIELD;
|
||||||
|
|
||||||
|
private final AtomicReference<ServerLevel> level = new AtomicReference<>();
|
||||||
|
|
||||||
|
public MemoryWorld(NamespacedKey levelType, WorldCreator creator) throws IOException {
|
||||||
|
var name = "memory_world"+C.getAndIncrement();
|
||||||
|
while (Bukkit.getWorld(name) != null) {
|
||||||
|
name = "memory_world"+C.getAndIncrement();
|
||||||
|
}
|
||||||
|
|
||||||
|
var generator = creator.generator();
|
||||||
|
var biomeProvider = creator.biomeProvider();
|
||||||
|
var hardcore = creator.hardcore();
|
||||||
|
var server = getServer();
|
||||||
|
|
||||||
|
var tempDir = Files.createTempDirectory("MemoryGenerator");
|
||||||
|
LevelStorageSource source = LevelStorageSource.createDefault(tempDir);
|
||||||
|
ResourceKey<LevelStem> stemKey = ResourceKey.create(Registries.LEVEL_STEM, new ResourceLocation(levelType.getKey(), levelType.getNamespace()));
|
||||||
|
var access = source.createAccess(name, stemKey);
|
||||||
|
|
||||||
|
var worldLoader = server.worldLoader;
|
||||||
|
var registry = server.registryAccess().registryOrThrow(Registries.LEVEL_STEM);
|
||||||
|
var options = new WorldOptions(creator.seed(), creator.generateStructures(), false);
|
||||||
|
var properties = new DedicatedServerProperties.WorldDimensionData(GsonHelper.parse(creator.generatorSettings().isEmpty() ? "{}" : creator.generatorSettings()), creator.type().name().toLowerCase(Locale.ROOT));
|
||||||
|
var settings = new LevelSettings(name, GameType.byId(Bukkit.getDefaultGameMode().getValue()), hardcore, Difficulty.EASY, false, new GameRules(), worldLoader.dataConfiguration());
|
||||||
|
var dimension = properties.create(worldLoader.datapackWorldgen()).bake(registry);
|
||||||
|
Lifecycle lifecycle = dimension.lifecycle().add(worldLoader.datapackWorldgen().allRegistriesLifecycle());
|
||||||
|
|
||||||
|
var worldData = new PrimaryLevelData(settings, options, dimension.specialWorldProperty(), lifecycle);
|
||||||
|
worldData.customDimensions = registry;
|
||||||
|
worldData.getGameRules().getRule(GameRules.RULE_SPAWN_CHUNK_RADIUS).set(0, null);
|
||||||
|
|
||||||
|
long obfSeed = BiomeManager.obfuscateSeed(creator.seed());
|
||||||
|
var list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(worldData));
|
||||||
|
var levelStem = registry.get(stemKey);
|
||||||
|
if (levelStem == null)
|
||||||
|
throw new IllegalStateException("Unknown dimension type: " + stemKey);
|
||||||
|
|
||||||
|
var worldInfo = new CraftWorldInfo(worldData, access, creator.environment(), levelStem.type().value());
|
||||||
|
if (biomeProvider == null && generator != null) {
|
||||||
|
biomeProvider = generator.getDefaultBiomeProvider(worldInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
var levelKey = ResourceKey.create(Registries.DIMENSION, new ResourceLocation(name));
|
||||||
|
var level = new ServerLevel(
|
||||||
|
server,
|
||||||
|
server.executor,
|
||||||
|
access,
|
||||||
|
worldData,
|
||||||
|
levelKey,
|
||||||
|
levelStem,
|
||||||
|
server.progressListenerFactory.create(0),
|
||||||
|
worldData.isDebugWorld(),
|
||||||
|
obfSeed,
|
||||||
|
creator.environment() == World.Environment.NORMAL ? list : ImmutableList.of(),
|
||||||
|
true,
|
||||||
|
server.overworld().getRandomSequences(),
|
||||||
|
creator.environment(),
|
||||||
|
generator,
|
||||||
|
biomeProvider
|
||||||
|
);
|
||||||
|
Iris.instance.registerListener(this);
|
||||||
|
this.level.set(level);
|
||||||
|
}
|
||||||
|
|
||||||
|
public World getBukkit() {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
|
||||||
|
return level.getWorld();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Chunk getChunk(int x, int z) {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
return new CraftChunk(level, x, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChunkGenerator.ChunkData getChunkData(int x, int z) {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
return new MemoryChunkData(x, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||||
|
public void onWorldUnload(WorldUnloadEvent event) {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null || event.getWorld() != level.getWorld())
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.level.set(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isClosed() {
|
||||||
|
return this.level.get() == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws Exception {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null || !this.level.compareAndSet(level, null))
|
||||||
|
return;
|
||||||
|
|
||||||
|
level.getChunkSource().close(false);
|
||||||
|
level.entityManager.close(false);
|
||||||
|
level.convertable.deleteLevel();
|
||||||
|
level.convertable.close();
|
||||||
|
|
||||||
|
var map = (Map<String, World>) WORLDS_FIELD.get(Bukkit.getServer());
|
||||||
|
map.remove(level.dimension().location().getPath());
|
||||||
|
getServer().removeLevel(level);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MinecraftServer getServer() {
|
||||||
|
return ((CraftServer) Bukkit.getServer()).getServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
WORLDS_FIELD = CraftServer.class.getDeclaredField("worlds");
|
||||||
|
WORLDS_FIELD.setAccessible(true);
|
||||||
|
} catch (NoSuchFieldException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MemoryChunkData implements ChunkGenerator.ChunkData {
|
||||||
|
private final int maxHeight;
|
||||||
|
private final int minHeight;
|
||||||
|
private final ChunkPos pos;
|
||||||
|
private WeakReference<LevelChunk> chunk;
|
||||||
|
|
||||||
|
private MemoryChunkData(int x, int z) {
|
||||||
|
var level = MemoryWorld.this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
|
||||||
|
var chunk = level.getChunk(x, z);
|
||||||
|
this.minHeight = chunk.getMinBuildHeight();
|
||||||
|
this.maxHeight = chunk.getMaxBuildHeight();
|
||||||
|
this.pos = new ChunkPos(x, z);
|
||||||
|
this.chunk = new WeakReference<>(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LevelChunk getHandle() {
|
||||||
|
LevelChunk chunk = this.chunk.get();
|
||||||
|
if (chunk == null) {
|
||||||
|
var level = MemoryWorld.this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
|
||||||
|
chunk = level.getChunk(this.pos.x, this.pos.z);
|
||||||
|
this.chunk = new WeakReference<>(chunk);
|
||||||
|
}
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMinHeight() {
|
||||||
|
return minHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxHeight() {
|
||||||
|
return maxHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Biome getBiome(int x, int y, int z) {
|
||||||
|
return CraftBiome.minecraftHolderToBukkit(getHandle().getNoiseBiome(x >> 2, y >> 2, z >> 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlock(int x, int y, int z, @NotNull Material material) {
|
||||||
|
setBlock(x, y, z, material.createBlockData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlock(int x, int y, int z, @NotNull MaterialData material) {
|
||||||
|
setBlock(x, y, z, CraftMagicNumbers.getBlock(material));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlock(int x, int y, int z, @NotNull BlockData blockData) {
|
||||||
|
setBlock(x, y, z, ((CraftBlockData) blockData).getState());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, Material material) {
|
||||||
|
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, material.createBlockData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, MaterialData material) {
|
||||||
|
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, CraftMagicNumbers.getBlock(material));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockData blockData) {
|
||||||
|
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, ((CraftBlockData)blockData).getState());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Material getType(int x, int y, int z) {
|
||||||
|
return CraftBlockType.minecraftToBukkit(this.getTypeId(x, y, z).getBlock());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public MaterialData getTypeAndData(int x, int y, int z) {
|
||||||
|
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public BlockData getBlockData(int x, int y, int z) {
|
||||||
|
return CraftBlockData.fromData(this.getTypeId(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
@Override
|
||||||
|
public byte getData(int x, int y, int z) {
|
||||||
|
return CraftMagicNumbers.toLegacyData(this.getTypeId(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockState type) {
|
||||||
|
if (xMin <= 15 && yMin < this.maxHeight && zMin <= 15) {
|
||||||
|
if (xMin < 0) {
|
||||||
|
xMin = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yMin < this.minHeight) {
|
||||||
|
yMin = this.minHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zMin < 0) {
|
||||||
|
zMin = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xMax > 16) {
|
||||||
|
xMax = 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yMax > this.maxHeight) {
|
||||||
|
yMax = this.maxHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zMax > 16) {
|
||||||
|
zMax = 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xMin < xMax && yMin < yMax && zMin < zMax) {
|
||||||
|
for(int y = yMin; y < yMax; ++y) {
|
||||||
|
for(int x = xMin; x < xMax; ++x) {
|
||||||
|
for(int z = zMin; z < zMax; ++z) {
|
||||||
|
this.setBlock(x, y, z, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private BlockState getTypeId(int x, int y, int z) {
|
||||||
|
if (x == (x & 15) && y >= this.minHeight && y < this.maxHeight && z == (z & 15)) {
|
||||||
|
ChunkAccess access = this.getHandle();
|
||||||
|
return access.getBlockState(new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z));
|
||||||
|
} else {
|
||||||
|
return Blocks.AIR.defaultBlockState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setBlock(int x, int y, int z, BlockState type) {
|
||||||
|
if (x == (x & 15) && y >= this.minHeight && y < this.maxHeight && z == (z & 15)) {
|
||||||
|
ChunkAccess access = this.getHandle();
|
||||||
|
BlockPos blockPosition = new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z);
|
||||||
|
BlockState oldBlockData = access.setBlockState(blockPosition, type, false);
|
||||||
|
if (type.hasBlockEntity()) {
|
||||||
|
BlockEntity tileEntity = ((EntityBlock)type.getBlock()).newBlockEntity(blockPosition, type);
|
||||||
|
if (tileEntity == null) {
|
||||||
|
access.removeBlockEntity(blockPosition);
|
||||||
|
} else {
|
||||||
|
access.setBlockEntity(tileEntity);
|
||||||
|
}
|
||||||
|
} else if (oldBlockData != null && oldBlockData.hasBlockEntity()) {
|
||||||
|
access.removeBlockEntity(blockPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package com.volmit.iris.core.nms.v1_20_R4;
|
package com.volmit.iris.core.nms.v1_20_R4;
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
@ -8,6 +9,7 @@ import java.util.*;
|
|||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import com.mojang.datafixers.util.Pair;
|
import com.mojang.datafixers.util.Pair;
|
||||||
|
import com.volmit.iris.core.nms.IMemoryWorld;
|
||||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||||
import com.volmit.iris.core.nms.datapack.DataVersion;
|
import com.volmit.iris.core.nms.datapack.DataVersion;
|
||||||
import com.volmit.iris.util.nbt.tag.CompoundTag;
|
import com.volmit.iris.util.nbt.tag.CompoundTag;
|
||||||
@ -650,4 +652,9 @@ public class NMSBinding implements INMSBinding {
|
|||||||
|
|
||||||
return keys;
|
return keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IMemoryWorld createMemoryWorld(NamespacedKey levelType, WorldCreator creator) throws IOException {
|
||||||
|
return new MemoryWorld(levelType, creator);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,362 @@
|
|||||||
|
package com.volmit.iris.core.nms.v1_21_R1;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.mojang.serialization.Lifecycle;
|
||||||
|
import com.volmit.iris.Iris;
|
||||||
|
import com.volmit.iris.core.nms.IMemoryWorld;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.core.registries.Registries;
|
||||||
|
import net.minecraft.resources.ResourceKey;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.server.dedicated.DedicatedServerProperties;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.util.GsonHelper;
|
||||||
|
import net.minecraft.world.Difficulty;
|
||||||
|
import net.minecraft.world.entity.ai.village.VillageSiege;
|
||||||
|
import net.minecraft.world.entity.npc.CatSpawner;
|
||||||
|
import net.minecraft.world.entity.npc.WanderingTraderSpawner;
|
||||||
|
import net.minecraft.world.level.ChunkPos;
|
||||||
|
import net.minecraft.world.level.GameRules;
|
||||||
|
import net.minecraft.world.level.GameType;
|
||||||
|
import net.minecraft.world.level.LevelSettings;
|
||||||
|
import net.minecraft.world.level.biome.BiomeManager;
|
||||||
|
import net.minecraft.world.level.block.Blocks;
|
||||||
|
import net.minecraft.world.level.block.EntityBlock;
|
||||||
|
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 net.minecraft.world.level.dimension.LevelStem;
|
||||||
|
import net.minecraft.world.level.levelgen.PatrolSpawner;
|
||||||
|
import net.minecraft.world.level.levelgen.PhantomSpawner;
|
||||||
|
import net.minecraft.world.level.levelgen.WorldOptions;
|
||||||
|
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;
|
||||||
|
import org.bukkit.craftbukkit.v1_21_R1.CraftChunk;
|
||||||
|
import org.bukkit.craftbukkit.v1_21_R1.CraftServer;
|
||||||
|
import org.bukkit.craftbukkit.v1_21_R1.block.CraftBiome;
|
||||||
|
import org.bukkit.craftbukkit.v1_21_R1.block.CraftBlockType;
|
||||||
|
import org.bukkit.craftbukkit.v1_21_R1.block.data.CraftBlockData;
|
||||||
|
import org.bukkit.craftbukkit.v1_21_R1.generator.CraftWorldInfo;
|
||||||
|
import org.bukkit.craftbukkit.v1_21_R1.util.CraftMagicNumbers;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.world.WorldUnloadEvent;
|
||||||
|
import org.bukkit.generator.ChunkGenerator;
|
||||||
|
import org.bukkit.material.MaterialData;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
public class MemoryWorld implements IMemoryWorld {
|
||||||
|
private static final AtomicLong C = new AtomicLong();
|
||||||
|
private static final Field WORLDS_FIELD;
|
||||||
|
|
||||||
|
private final AtomicReference<ServerLevel> level = new AtomicReference<>();
|
||||||
|
|
||||||
|
public MemoryWorld(NamespacedKey levelType, WorldCreator creator) throws IOException {
|
||||||
|
var name = "memory_world"+C.getAndIncrement();
|
||||||
|
while (Bukkit.getWorld(name) != null) {
|
||||||
|
name = "memory_world"+C.getAndIncrement();
|
||||||
|
}
|
||||||
|
|
||||||
|
var generator = creator.generator();
|
||||||
|
var biomeProvider = creator.biomeProvider();
|
||||||
|
var hardcore = creator.hardcore();
|
||||||
|
var server = getServer();
|
||||||
|
|
||||||
|
var tempDir = Files.createTempDirectory("MemoryGenerator");
|
||||||
|
LevelStorageSource source = LevelStorageSource.createDefault(tempDir);
|
||||||
|
ResourceKey<LevelStem> stemKey = ResourceKey.create(Registries.LEVEL_STEM, ResourceLocation.fromNamespaceAndPath(levelType.getKey(), levelType.getNamespace()));
|
||||||
|
var access = source.createAccess(name, stemKey);
|
||||||
|
|
||||||
|
var worldLoader = server.worldLoader;
|
||||||
|
var registry = server.registryAccess().registryOrThrow(Registries.LEVEL_STEM);
|
||||||
|
var options = new WorldOptions(creator.seed(), creator.generateStructures(), false);
|
||||||
|
var properties = new DedicatedServerProperties.WorldDimensionData(GsonHelper.parse(creator.generatorSettings().isEmpty() ? "{}" : creator.generatorSettings()), creator.type().name().toLowerCase(Locale.ROOT));
|
||||||
|
var settings = new LevelSettings(name, GameType.byId(Bukkit.getDefaultGameMode().getValue()), hardcore, Difficulty.EASY, false, new GameRules(), worldLoader.dataConfiguration());
|
||||||
|
var dimension = properties.create(worldLoader.datapackWorldgen()).bake(registry);
|
||||||
|
Lifecycle lifecycle = dimension.lifecycle().add(worldLoader.datapackWorldgen().allRegistriesLifecycle());
|
||||||
|
|
||||||
|
var worldData = new PrimaryLevelData(settings, options, dimension.specialWorldProperty(), lifecycle);
|
||||||
|
worldData.customDimensions = registry;
|
||||||
|
worldData.getGameRules().getRule(GameRules.RULE_SPAWN_CHUNK_RADIUS).set(0, null);
|
||||||
|
|
||||||
|
long obfSeed = BiomeManager.obfuscateSeed(creator.seed());
|
||||||
|
var list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(worldData));
|
||||||
|
var levelStem = registry.get(stemKey);
|
||||||
|
if (levelStem == null)
|
||||||
|
throw new IllegalStateException("Unknown dimension type: " + stemKey);
|
||||||
|
|
||||||
|
var worldInfo = new CraftWorldInfo(worldData, access, creator.environment(), levelStem.type().value());
|
||||||
|
if (biomeProvider == null && generator != null) {
|
||||||
|
biomeProvider = generator.getDefaultBiomeProvider(worldInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
var levelKey = ResourceKey.create(Registries.DIMENSION, ResourceLocation.withDefaultNamespace(name));
|
||||||
|
var level = new ServerLevel(
|
||||||
|
server,
|
||||||
|
server.executor,
|
||||||
|
access,
|
||||||
|
worldData,
|
||||||
|
levelKey,
|
||||||
|
levelStem,
|
||||||
|
server.progressListenerFactory.create(0),
|
||||||
|
worldData.isDebugWorld(),
|
||||||
|
obfSeed,
|
||||||
|
creator.environment() == World.Environment.NORMAL ? list : ImmutableList.of(),
|
||||||
|
true,
|
||||||
|
server.overworld().getRandomSequences(),
|
||||||
|
creator.environment(),
|
||||||
|
generator,
|
||||||
|
biomeProvider
|
||||||
|
);
|
||||||
|
Iris.instance.registerListener(this);
|
||||||
|
this.level.set(level);
|
||||||
|
}
|
||||||
|
|
||||||
|
public World getBukkit() {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
|
||||||
|
return level.getWorld();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Chunk getChunk(int x, int z) {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
return new CraftChunk(level, x, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChunkGenerator.ChunkData getChunkData(int x, int z) {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
return new MemoryChunkData(x, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||||
|
public void onWorldUnload(WorldUnloadEvent event) {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null || event.getWorld() != level.getWorld())
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.level.set(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isClosed() {
|
||||||
|
return this.level.get() == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws Exception {
|
||||||
|
var level = this.level.get();
|
||||||
|
if (level == null || !this.level.compareAndSet(level, null))
|
||||||
|
return;
|
||||||
|
|
||||||
|
level.getChunkSource().close(false);
|
||||||
|
level.entityManager.close(false);
|
||||||
|
level.convertable.deleteLevel();
|
||||||
|
level.convertable.close();
|
||||||
|
|
||||||
|
var map = (Map<String, World>) WORLDS_FIELD.get(Bukkit.getServer());
|
||||||
|
map.remove(level.dimension().location().getPath());
|
||||||
|
getServer().removeLevel(level);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MinecraftServer getServer() {
|
||||||
|
return ((CraftServer) Bukkit.getServer()).getServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
WORLDS_FIELD = CraftServer.class.getDeclaredField("worlds");
|
||||||
|
WORLDS_FIELD.setAccessible(true);
|
||||||
|
} catch (NoSuchFieldException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MemoryChunkData implements ChunkGenerator.ChunkData {
|
||||||
|
private final int maxHeight;
|
||||||
|
private final int minHeight;
|
||||||
|
private final ChunkPos pos;
|
||||||
|
private WeakReference<LevelChunk> chunk;
|
||||||
|
|
||||||
|
private MemoryChunkData(int x, int z) {
|
||||||
|
var level = MemoryWorld.this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
|
||||||
|
var chunk = level.getChunk(x, z);
|
||||||
|
this.minHeight = chunk.getMinBuildHeight();
|
||||||
|
this.maxHeight = chunk.getMaxBuildHeight();
|
||||||
|
this.pos = new ChunkPos(x, z);
|
||||||
|
this.chunk = new WeakReference<>(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LevelChunk getHandle() {
|
||||||
|
LevelChunk chunk = this.chunk.get();
|
||||||
|
if (chunk == null) {
|
||||||
|
var level = MemoryWorld.this.level.get();
|
||||||
|
if (level == null)
|
||||||
|
throw new IllegalStateException("World is not loaded");
|
||||||
|
|
||||||
|
chunk = level.getChunk(this.pos.x, this.pos.z);
|
||||||
|
this.chunk = new WeakReference<>(chunk);
|
||||||
|
}
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMinHeight() {
|
||||||
|
return minHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxHeight() {
|
||||||
|
return maxHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Biome getBiome(int x, int y, int z) {
|
||||||
|
return CraftBiome.minecraftHolderToBukkit(getHandle().getNoiseBiome(x >> 2, y >> 2, z >> 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlock(int x, int y, int z, @NotNull Material material) {
|
||||||
|
setBlock(x, y, z, material.createBlockData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlock(int x, int y, int z, @NotNull MaterialData material) {
|
||||||
|
setBlock(x, y, z, CraftMagicNumbers.getBlock(material));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlock(int x, int y, int z, @NotNull BlockData blockData) {
|
||||||
|
setBlock(x, y, z, ((CraftBlockData) blockData).getState());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, Material material) {
|
||||||
|
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, material.createBlockData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, MaterialData material) {
|
||||||
|
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, CraftMagicNumbers.getBlock(material));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockData blockData) {
|
||||||
|
this.setRegion(xMin, yMin, zMin, xMax, yMax, zMax, ((CraftBlockData)blockData).getState());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Material getType(int x, int y, int z) {
|
||||||
|
return CraftBlockType.minecraftToBukkit(this.getTypeId(x, y, z).getBlock());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public MaterialData getTypeAndData(int x, int y, int z) {
|
||||||
|
return CraftMagicNumbers.getMaterial(this.getTypeId(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public BlockData getBlockData(int x, int y, int z) {
|
||||||
|
return CraftBlockData.fromData(this.getTypeId(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
@Override
|
||||||
|
public byte getData(int x, int y, int z) {
|
||||||
|
return CraftMagicNumbers.toLegacyData(this.getTypeId(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, BlockState type) {
|
||||||
|
if (xMin <= 15 && yMin < this.maxHeight && zMin <= 15) {
|
||||||
|
if (xMin < 0) {
|
||||||
|
xMin = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yMin < this.minHeight) {
|
||||||
|
yMin = this.minHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zMin < 0) {
|
||||||
|
zMin = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xMax > 16) {
|
||||||
|
xMax = 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yMax > this.maxHeight) {
|
||||||
|
yMax = this.maxHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zMax > 16) {
|
||||||
|
zMax = 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xMin < xMax && yMin < yMax && zMin < zMax) {
|
||||||
|
for(int y = yMin; y < yMax; ++y) {
|
||||||
|
for(int x = xMin; x < xMax; ++x) {
|
||||||
|
for(int z = zMin; z < zMax; ++z) {
|
||||||
|
this.setBlock(x, y, z, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private BlockState getTypeId(int x, int y, int z) {
|
||||||
|
if (x == (x & 15) && y >= this.minHeight && y < this.maxHeight && z == (z & 15)) {
|
||||||
|
ChunkAccess access = this.getHandle();
|
||||||
|
return access.getBlockState(new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z));
|
||||||
|
} else {
|
||||||
|
return Blocks.AIR.defaultBlockState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setBlock(int x, int y, int z, BlockState type) {
|
||||||
|
if (x == (x & 15) && y >= this.minHeight && y < this.maxHeight && z == (z & 15)) {
|
||||||
|
ChunkAccess access = this.getHandle();
|
||||||
|
BlockPos blockPosition = new BlockPos(access.getPos().getMinBlockX() + x, y, access.getPos().getMinBlockZ() + z);
|
||||||
|
BlockState oldBlockData = access.setBlockState(blockPosition, type, false);
|
||||||
|
if (type.hasBlockEntity()) {
|
||||||
|
BlockEntity tileEntity = ((EntityBlock)type.getBlock()).newBlockEntity(blockPosition, type);
|
||||||
|
if (tileEntity == null) {
|
||||||
|
access.removeBlockEntity(blockPosition);
|
||||||
|
} else {
|
||||||
|
access.setBlockEntity(tileEntity);
|
||||||
|
}
|
||||||
|
} else if (oldBlockData != null && oldBlockData.hasBlockEntity()) {
|
||||||
|
access.removeBlockEntity(blockPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,7 @@
|
|||||||
package com.volmit.iris.core.nms.v1_21_R1;
|
package com.volmit.iris.core.nms.v1_21_R1;
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.*;
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.DataInputStream;
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
@ -12,6 +9,7 @@ import java.util.*;
|
|||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import com.mojang.datafixers.util.Pair;
|
import com.mojang.datafixers.util.Pair;
|
||||||
|
import com.volmit.iris.core.nms.IMemoryWorld;
|
||||||
import com.volmit.iris.core.nms.container.BiomeColor;
|
import com.volmit.iris.core.nms.container.BiomeColor;
|
||||||
import com.volmit.iris.core.nms.datapack.DataVersion;
|
import com.volmit.iris.core.nms.datapack.DataVersion;
|
||||||
import com.volmit.iris.util.scheduling.J;
|
import com.volmit.iris.util.scheduling.J;
|
||||||
@ -657,4 +655,9 @@ public class NMSBinding implements INMSBinding {
|
|||||||
|
|
||||||
return keys;
|
return keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IMemoryWorld createMemoryWorld(NamespacedKey levelType, WorldCreator creator) throws IOException {
|
||||||
|
return new MemoryWorld(levelType, creator);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user