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.util.collection.KList;
|
||||
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.math.Vector3d;
|
||||
import com.volmit.iris.util.nbt.mca.palette.MCABiomeContainer;
|
||||
@ -43,6 +42,7 @@ import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.Color;
|
||||
import java.io.IOException;
|
||||
|
||||
public interface INMSBinding {
|
||||
boolean hasTile(Material material);
|
||||
@ -99,10 +99,6 @@ public interface INMSBinding {
|
||||
|
||||
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);
|
||||
|
||||
default boolean supportsDataPacks() {
|
||||
@ -137,4 +133,15 @@ public interface INMSBinding {
|
||||
Iris.error("Unsupported version!");
|
||||
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;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
@ -15,6 +12,7 @@ import java.util.Vector;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
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.util.scheduling.J;
|
||||
import net.minecraft.nbt.*;
|
||||
@ -607,6 +605,11 @@ public class NMSBinding implements INMSBinding {
|
||||
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 {
|
||||
try {
|
||||
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;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
@ -15,6 +12,7 @@ import java.util.Vector;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
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.util.scheduling.J;
|
||||
import net.minecraft.nbt.*;
|
||||
@ -609,6 +607,11 @@ public class NMSBinding implements INMSBinding {
|
||||
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 {
|
||||
try {
|
||||
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;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
@ -15,6 +12,7 @@ import java.util.Vector;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
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.util.scheduling.J;
|
||||
import net.minecraft.nbt.*;
|
||||
@ -619,6 +617,11 @@ public class NMSBinding implements INMSBinding {
|
||||
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 {
|
||||
try {
|
||||
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.datafixers.util.Pair;
|
||||
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.container.BiomeColor;
|
||||
import com.volmit.iris.engine.data.cache.AtomicCache;
|
||||
@ -66,10 +67,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import sun.misc.Unsafe;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
@ -612,6 +610,11 @@ public class NMSBinding implements INMSBinding {
|
||||
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 {
|
||||
try {
|
||||
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;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
@ -15,6 +12,7 @@ import java.util.Vector;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
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.util.scheduling.J;
|
||||
import net.minecraft.nbt.*;
|
||||
@ -610,6 +608,11 @@ public class NMSBinding implements INMSBinding {
|
||||
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 {
|
||||
try {
|
||||
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;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
@ -11,6 +12,7 @@ import java.util.Vector;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
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.util.data.B;
|
||||
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.CraftBlockStates;
|
||||
import org.bukkit.craftbukkit.v1_20_R3.block.data.CraftBlockData;
|
||||
import org.bukkit.craftbukkit.v1_20_R3.entity.CraftDolphin;
|
||||
import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.v1_20_R3.util.CraftNamespacedKey;
|
||||
import org.bukkit.entity.Dolphin;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.event.entity.CreatureSpawnEvent;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
@ -50,6 +54,7 @@ import com.volmit.iris.util.json.JSONObject;
|
||||
import com.volmit.iris.util.mantle.Mantle;
|
||||
import com.volmit.iris.util.math.Vector3d;
|
||||
import com.volmit.iris.util.matter.MatterBiomeInject;
|
||||
import com.volmit.iris.util.nbt.io.NBTUtil;
|
||||
import com.volmit.iris.util.nbt.mca.NBTWorld;
|
||||
import com.volmit.iris.util.nbt.mca.palette.*;
|
||||
import com.volmit.iris.util.nbt.tag.CompoundTag;
|
||||
@ -64,12 +69,14 @@ import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.level.biome.BiomeSource;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.ChunkStatus;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import sun.misc.Unsafe;
|
||||
|
||||
public class NMSBinding implements INMSBinding {
|
||||
private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
|
||||
@ -277,11 +284,7 @@ public class NMSBinding implements INMSBinding {
|
||||
|
||||
@Override
|
||||
public int getTrueBiomeBaseId(Object biomeBase) {
|
||||
if (biomeBase instanceof net.minecraft.world.level.biome.Biome) {
|
||||
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());
|
||||
return getCustomBiomeRegistry().getId(((Holder<net.minecraft.world.level.biome.Biome>) biomeBase).value());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -310,15 +313,7 @@ public class NMSBinding implements INMSBinding {
|
||||
|
||||
@Override
|
||||
public String getKeyForBiomeBase(Object biomeBase) {
|
||||
if (biomeBase instanceof net.minecraft.core.Holder<?>) {
|
||||
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());
|
||||
return getCustomBiomeRegistry().getKey((net.minecraft.world.level.biome.Biome) biomeBase).getPath(); // something, not something:something
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -441,7 +436,6 @@ public class NMSBinding implements INMSBinding {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBiomes(int cx, int cz, World world, Hunk<Object> biomes) {
|
||||
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));
|
||||
@ -664,4 +658,9 @@ public class NMSBinding implements INMSBinding {
|
||||
|
||||
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;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
@ -8,6 +9,7 @@ import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
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.datapack.DataVersion;
|
||||
import com.volmit.iris.util.nbt.tag.CompoundTag;
|
||||
@ -650,4 +652,9 @@ public class NMSBinding implements INMSBinding {
|
||||
|
||||
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;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
@ -12,6 +9,7 @@ import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
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.datapack.DataVersion;
|
||||
import com.volmit.iris.util.scheduling.J;
|
||||
@ -657,4 +655,9 @@ public class NMSBinding implements INMSBinding {
|
||||
|
||||
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