implement MemoryWorlds

This commit is contained in:
Julian Krings 2024-10-01 19:29:29 +02:00
parent 0101130d7a
commit 093d77bf8a
18 changed files with 2972 additions and 44 deletions

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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);
}
}
}
}
}

View File

@ -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()) {

View File

@ -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);
}
}
}
}
}

View File

@ -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()) {

View File

@ -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);
}
}
}
}
}

View File

@ -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()) {

View File

@ -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);
}
}
}
}
}

View File

@ -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()) {

View File

@ -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);
}
}
}
}
}

View File

@ -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()) {

View File

@ -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);
}
}
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}
}
}

View File

@ -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);
}
}