Scissors/patches/server/0001-AdvancedSlimePaper-Server-Changes.patch

1828 lines
74 KiB
Diff
Raw Normal View History

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
2023-10-19 05:03:42 +00:00
From: Telesphoreo <me@telesphoreo.me>
Date: Thu, 19 Oct 2023 00:00:51 -0500
Subject: [PATCH] AdvancedSlimePaper Server Changes
diff --git a/src/main/java/com/infernalsuite/aswm/Converter.java b/src/main/java/com/infernalsuite/aswm/Converter.java
new file mode 100644
index 0000000000000000000000000000000000000000..7d1753c0b7e89bbf0c245a0231b62773eca2779e
--- /dev/null
+++ b/src/main/java/com/infernalsuite/aswm/Converter.java
@@ -0,0 +1,118 @@
+package com.infernalsuite.aswm;
+
+import com.flowpowered.nbt.CompoundMap;
+import com.flowpowered.nbt.TagType;
+import com.infernalsuite.aswm.api.utils.NibbleArray;
+import net.minecraft.nbt.*;
+import net.minecraft.world.level.chunk.DataLayer;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Converter {
+
+ private static final Logger LOGGER = LogManager.getLogger("SWM Converter");
+
+ static DataLayer convertArray(NibbleArray array) {
+ return new DataLayer(array.getBacking());
+ }
+
+ public static NibbleArray convertArray(DataLayer array) {
+ if(array == null) {
+ return null;
+ }
+
+ return new NibbleArray(array.getData());
+ }
+
+ public static Tag convertTag(com.flowpowered.nbt.Tag tag) {
+ try {
+ switch(tag.getType()) {
+ case TAG_BYTE:
+ return ByteTag.valueOf(((com.flowpowered.nbt.ByteTag) tag).getValue());
+ case TAG_SHORT:
+ return ShortTag.valueOf(((com.flowpowered.nbt.ShortTag) tag).getValue());
+ case TAG_INT:
+ return IntTag.valueOf(((com.flowpowered.nbt.IntTag) tag).getValue());
+ case TAG_LONG:
+ return LongTag.valueOf(((com.flowpowered.nbt.LongTag) tag).getValue());
+ case TAG_FLOAT:
+ return FloatTag.valueOf(((com.flowpowered.nbt.FloatTag) tag).getValue());
+ case TAG_DOUBLE:
+ return DoubleTag.valueOf(((com.flowpowered.nbt.DoubleTag) tag).getValue());
+ case TAG_BYTE_ARRAY:
+ return new ByteArrayTag(((com.flowpowered.nbt.ByteArrayTag) tag).getValue());
+ case TAG_STRING:
+ return StringTag.valueOf(((com.flowpowered.nbt.StringTag) tag).getValue());
+ case TAG_LIST:
+ ListTag list = new ListTag();
+ ((com.flowpowered.nbt.ListTag<?>) tag).getValue().stream().map(Converter::convertTag).forEach(list::add);
+
+ return list;
+ case TAG_COMPOUND:
+ CompoundTag compound = new CompoundTag();
+
+ ((com.flowpowered.nbt.CompoundTag) tag).getValue().forEach((key, value) -> compound.put(key, convertTag(value)));
+ return compound;
+ case TAG_INT_ARRAY:
+ return new IntArrayTag(((com.flowpowered.nbt.IntArrayTag) tag).getValue());
+ case TAG_LONG_ARRAY:
+ return new LongArrayTag(((com.flowpowered.nbt.LongArrayTag) tag).getValue());
+ default:
+ throw new IllegalArgumentException("Invalid tag type " + tag.getType().name());
+ }
+ } catch(Exception ex) {
+ LOGGER.error("Failed to convert NBT object:");
+ LOGGER.error(tag.toString());
+
+ throw ex;
+ }
+ }
+
+ public static com.flowpowered.nbt.Tag convertTag(String name, Tag base) {
+ switch(base.getId()) {
+ case Tag.TAG_BYTE:
+ return new com.flowpowered.nbt.ByteTag(name, ((ByteTag) base).getAsByte());
+ case Tag.TAG_SHORT:
+ return new com.flowpowered.nbt.ShortTag(name, ((ShortTag) base).getAsShort());
+ case Tag.TAG_INT:
+ return new com.flowpowered.nbt.IntTag(name, ((IntTag) base).getAsInt());
+ case Tag.TAG_LONG:
+ return new com.flowpowered.nbt.LongTag(name, ((LongTag) base).getAsLong());
+ case Tag.TAG_FLOAT:
+ return new com.flowpowered.nbt.FloatTag(name, ((FloatTag) base).getAsFloat());
+ case Tag.TAG_DOUBLE:
+ return new com.flowpowered.nbt.DoubleTag(name, ((DoubleTag) base).getAsDouble());
+ case Tag.TAG_BYTE_ARRAY:
+ return new com.flowpowered.nbt.ByteArrayTag(name, ((ByteArrayTag) base).getAsByteArray());
+ case Tag.TAG_STRING:
+ return new com.flowpowered.nbt.StringTag(name, ((StringTag) base).getAsString());
+ case Tag.TAG_LIST:
+ List<com.flowpowered.nbt.Tag> list = new ArrayList<>();
+ ListTag originalList = ((ListTag) base);
+
+ for(Tag entry : originalList) {
+ list.add(convertTag("", entry));
+ }
+
+ return new com.flowpowered.nbt.ListTag(name, TagType.getById(originalList.getElementType()), list);
+ case Tag.TAG_COMPOUND:
+ CompoundTag originalCompound = ((CompoundTag) base);
+ com.flowpowered.nbt.CompoundTag compound = new com.flowpowered.nbt.CompoundTag(name, new CompoundMap());
+
+ for(String key : originalCompound.getAllKeys()) {
+ compound.getValue().put(key, convertTag(key, originalCompound.get(key)));
+ }
+
+ return compound;
+ case Tag.TAG_INT_ARRAY:
+ return new com.flowpowered.nbt.IntArrayTag(name, ((IntArrayTag) base).getAsIntArray());
+ case Tag.TAG_LONG_ARRAY:
+ return new com.flowpowered.nbt.LongArrayTag(name, ((LongArrayTag) base).getAsLongArray());
+ default:
+ throw new IllegalArgumentException("Invalid tag type " + base.getId());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/infernalsuite/aswm/InternalPlugin.java b/src/main/java/com/infernalsuite/aswm/InternalPlugin.java
new file mode 100644
index 0000000000000000000000000000000000000000..61518ab2b68e7a41500f3c8c8a5ec1230597f0e5
--- /dev/null
+++ b/src/main/java/com/infernalsuite/aswm/InternalPlugin.java
@@ -0,0 +1,28 @@
+package com.infernalsuite.aswm;
+
+import net.minecraft.server.MinecraftServer;
+import org.bukkit.Server;
+import org.bukkit.craftbukkit.scheduler.MinecraftInternalPlugin;
+import org.bukkit.plugin.PluginLogger;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.logging.LogRecord;
+
+public class InternalPlugin extends MinecraftInternalPlugin {
+
+ @Override
+ public @NotNull Server getServer() {
+ return MinecraftServer.getServer().server;
+ }
+
+ @Override
+ public @NotNull PluginLogger getLogger() {
+ return new PluginLogger(new InternalPlugin()) {
+ @Override
+ public void log(@NotNull LogRecord logRecord) {
+ MinecraftServer.LOGGER.info(logRecord.getMessage());
+ }
+ };
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/infernalsuite/aswm/SimpleDataFixerConverter.java b/src/main/java/com/infernalsuite/aswm/SimpleDataFixerConverter.java
new file mode 100644
2023-10-19 05:03:42 +00:00
index 0000000000000000000000000000000000000000..7dcfe5e080f567ab7d0cd6d0c47a6aaa4daae55f
--- /dev/null
+++ b/src/main/java/com/infernalsuite/aswm/SimpleDataFixerConverter.java
@@ -0,0 +1,101 @@
+package com.infernalsuite.aswm;
+
+import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
+import ca.spottedleaf.dataconverter.minecraft.walkers.generic.WalkerUtils;
+import ca.spottedleaf.dataconverter.types.nbt.NBTMapType;
+import com.flowpowered.nbt.CompoundTag;
2023-10-19 05:03:42 +00:00
+import com.infernalsuite.aswm.api.world.SlimeChunk;
+import com.infernalsuite.aswm.api.world.SlimeChunkSection;
+import com.infernalsuite.aswm.api.world.SlimeWorld;
+import com.infernalsuite.aswm.serialization.SlimeWorldReader;
+import com.infernalsuite.aswm.skeleton.SkeletonSlimeWorld;
+import com.infernalsuite.aswm.skeleton.SlimeChunkSectionSkeleton;
+import com.infernalsuite.aswm.skeleton.SlimeChunkSkeleton;
+import net.minecraft.SharedConstants;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+class SimpleDataFixerConverter implements SlimeWorldReader<SlimeWorld> {
+
+ @Override
+ public SlimeWorld readFromData(SlimeWorld data) {
+ int newVersion = SharedConstants.getCurrentVersion().getDataVersion().getVersion();
+ int currentVersion = data.getDataVersion();
+ // Already fixed
+ if (currentVersion == newVersion) {
+ return data;
+ }
+
+ Map<com.infernalsuite.aswm.ChunkPos, SlimeChunk> chunks = new HashMap<>();
+ for (SlimeChunk chunk : data.getChunkStorage()) {
+ List<CompoundTag> entities = new ArrayList<>();
+ List<CompoundTag> blockEntities = new ArrayList<>();
+ for (CompoundTag upgradeEntity : chunk.getTileEntities()) {
+ blockEntities.add(
+ convertAndBack(upgradeEntity, (tag) -> MCTypeRegistry.TILE_ENTITY.convert(new NBTMapType(tag), currentVersion, newVersion))
+ );
+ }
+ for (CompoundTag upgradeEntity : chunk.getEntities()) {
+ entities.add(
+ convertAndBack(upgradeEntity, (tag) -> MCTypeRegistry.ENTITY.convert(new NBTMapType(tag), currentVersion, newVersion))
+ );
+ }
+
+ SlimeChunkSection[] sections = new SlimeChunkSection[chunk.getSections().length];
+ for (int i = 0; i < sections.length; i++) {
+ SlimeChunkSection dataSection = chunk.getSections()[i];
+
2023-10-19 05:03:42 +00:00
+ CompoundTag blockStateTag = blockStateTag = convertAndBack(dataSection.getBlockStatesTag(), (tag) -> {
+ WalkerUtils.convertList(MCTypeRegistry.BLOCK_STATE, new NBTMapType(tag), "palette", currentVersion, newVersion);
+ });
+
2023-10-19 05:03:42 +00:00
+ CompoundTag biomeTag = convertAndBack(dataSection.getBiomeTag(), (tag) -> {
+ WalkerUtils.convertList(MCTypeRegistry.BIOME, new NBTMapType(tag), "palette", currentVersion, newVersion);
+ });
+
+ sections[i] = new SlimeChunkSectionSkeleton(
+ blockStateTag,
+ biomeTag,
+ dataSection.getBlockLight(),
+ dataSection.getSkyLight()
+ );
+
+ chunks.put(new ChunkPos(chunk.getX(), chunk.getZ()), new SlimeChunkSkeleton(
+ chunk.getX(),
+ chunk.getZ(),
+ sections,
+ chunk.getHeightMaps(),
+ blockEntities,
+ entities
+ ));
+ }
+
+ }
+
+ return new SkeletonSlimeWorld(
+ data.getName(),
+ data.getLoader(),
+ data.isReadOnly(),
+ chunks,
+ data.getExtraData(),
+ data.getPropertyMap(),
+ newVersion
+ );
+ }
+
+
2023-10-19 05:03:42 +00:00
+ private static CompoundTag convertAndBack(CompoundTag value, Consumer<net.minecraft.nbt.CompoundTag> acceptor) {
+ if (value == null) {
+ return null;
+ }
+
+ net.minecraft.nbt.CompoundTag converted = (net.minecraft.nbt.CompoundTag) Converter.convertTag(value);
+ acceptor.accept(converted);
+
2023-10-19 05:03:42 +00:00
+ return (CompoundTag) Converter.convertTag(value.getName(), converted);
+ }
+}
diff --git a/src/main/java/com/infernalsuite/aswm/SlimeNMSBridgeImpl.java b/src/main/java/com/infernalsuite/aswm/SlimeNMSBridgeImpl.java
new file mode 100644
2023-10-19 05:03:42 +00:00
index 0000000000000000000000000000000000000000..d014c27b896ea862bb3f7ff7d39df476513bb5f8
--- /dev/null
+++ b/src/main/java/com/infernalsuite/aswm/SlimeNMSBridgeImpl.java
2023-10-19 05:03:42 +00:00
@@ -0,0 +1,205 @@
+package com.infernalsuite.aswm;
+
+import com.infernalsuite.aswm.api.SlimeNMSBridge;
+import com.infernalsuite.aswm.api.world.SlimeWorld;
+import com.infernalsuite.aswm.api.world.SlimeWorldInstance;
+import com.infernalsuite.aswm.api.world.properties.SlimeProperties;
+import com.infernalsuite.aswm.level.SlimeBootstrap;
+import com.infernalsuite.aswm.level.SlimeInMemoryWorld;
+import com.infernalsuite.aswm.level.SlimeLevelInstance;
+import com.mojang.serialization.Lifecycle;
+import net.minecraft.SharedConstants;
+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.DedicatedServer;
+import net.minecraft.server.dedicated.DedicatedServerProperties;
+import net.minecraft.world.level.GameRules;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.LevelSettings;
+import net.minecraft.world.level.dimension.LevelStem;
+import net.minecraft.world.level.levelgen.WorldOptions;
+import net.minecraft.world.level.storage.CommandStorage;
+import net.minecraft.world.level.storage.DimensionDataStorage;
+import net.minecraft.world.level.storage.PrimaryLevelData;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.bukkit.Bukkit;
+import org.bukkit.World;
+import org.bukkit.craftbukkit.CraftWorld;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.IOException;
+import java.util.Locale;
+
+public class SlimeNMSBridgeImpl implements SlimeNMSBridge {
+
+ private static final SimpleDataFixerConverter DATA_FIXER_CONVERTER = new SimpleDataFixerConverter();
+
+ private static final Logger LOGGER = LogManager.getLogger("SWM");
+
+ private SlimeWorld defaultWorld;
+ private SlimeWorld defaultNetherWorld;
+ private SlimeWorld defaultEndWorld;
+
+ public static SlimeNMSBridgeImpl instance() {
+ return (SlimeNMSBridgeImpl) SlimeNMSBridge.instance();
+ }
+
+ @Override
+ public boolean loadOverworldOverride() {
+ if (defaultWorld == null) {
+ return false;
+ }
+
+ // See MinecraftServer loading logic
+ // Some stuff is needed when loading overworld world
+ SlimeLevelInstance instance = ((SlimeInMemoryWorld) this.loadInstance(defaultWorld, Level.OVERWORLD)).getInstance();
+ DimensionDataStorage worldpersistentdata = instance.getDataStorage();
+ instance.getCraftServer().scoreboardManager = new org.bukkit.craftbukkit.scoreboard.CraftScoreboardManager(instance.getServer(), instance.getScoreboard());
+ instance.getServer().commandStorage = new CommandStorage(worldpersistentdata);
+
+ return true;
+ }
+
+ @Override
+ public boolean loadNetherOverride() {
+ if (defaultNetherWorld == null) {
+ return false;
+ }
+
+ this.loadInstance(defaultNetherWorld, Level.NETHER);
+
+ return true;
+ }
+
+ @Override
+ public boolean loadEndOverride() {
+ if (defaultEndWorld == null) {
+ return false;
+ }
+
+ this.loadInstance(defaultEndWorld, Level.END);
+
+ return true;
+ }
+
+ @Override
+ public void setDefaultWorlds(SlimeWorld normalWorld, SlimeWorld netherWorld, SlimeWorld endWorld) {
+ if (normalWorld != null) {
+ normalWorld.getPropertyMap().setValue(SlimeProperties.ENVIRONMENT, World.Environment.NORMAL.toString().toLowerCase());
+ defaultWorld = normalWorld;
+ }
+
+ if (netherWorld != null) {
+ netherWorld.getPropertyMap().setValue(SlimeProperties.ENVIRONMENT, World.Environment.NETHER.toString().toLowerCase());
+ defaultNetherWorld = netherWorld;
+ }
+
+ if (endWorld != null) {
+ endWorld.getPropertyMap().setValue(SlimeProperties.ENVIRONMENT, World.Environment.THE_END.toString().toLowerCase());
+ defaultEndWorld = endWorld;
+ }
+
+ }
+
+ @Override
+ public SlimeWorldInstance loadInstance(SlimeWorld slimeWorld) {
+ return this.loadInstance(slimeWorld, null);
+ }
+
+ public SlimeWorldInstance loadInstance(SlimeWorld slimeWorld, @Nullable ResourceKey<Level> dimensionOverride) {
+ String worldName = slimeWorld.getName();
+
+ if (Bukkit.getWorld(worldName) != null) {
+ throw new IllegalArgumentException("World " + worldName + " already exists! Maybe it's an outdated SlimeWorld object?");
+ }
+
+ SlimeLevelInstance server = createCustomWorld(slimeWorld, dimensionOverride);
+ registerWorld(server);
+ return server.getSlimeInstance();
+ }
+
+ @Override
+ public SlimeWorldInstance getInstance(World world) {
+ CraftWorld craftWorld = (CraftWorld) world;
+
+ if (!(craftWorld.getHandle() instanceof SlimeLevelInstance worldServer)) {
+ return null;
+ }
+
+ return worldServer.getSlimeInstance();
+ }
+
+ @Override
+ public SlimeWorld applyDataFixers(SlimeWorld world) {
+ return DATA_FIXER_CONVERTER.readFromData(world);
+ }
+
+
+ @Override
+ public int getCurrentVersion() {
+ return SharedConstants.getCurrentVersion().getDataVersion().getVersion();
+ }
+
+ public void registerWorld(SlimeLevelInstance server) {
+ MinecraftServer mcServer = MinecraftServer.getServer();
+ mcServer.initWorld(server, server.serverLevelData, mcServer.getWorldData(), server.serverLevelData.worldGenOptions());
+
+ mcServer.addLevel(server);
+ }
+
+ private SlimeLevelInstance createCustomWorld(SlimeWorld world, @Nullable ResourceKey<Level> dimensionOverride) {
+ SlimeBootstrap bootstrap = new SlimeBootstrap(world);
+ String worldName = world.getName();
+
+ PrimaryLevelData worldDataServer = createWorldData(world);
+ World.Environment environment = getEnvironment(world);
+ ResourceKey<LevelStem> dimension = switch (environment) {
+ case NORMAL -> LevelStem.OVERWORLD;
+ case NETHER -> LevelStem.NETHER;
+ case THE_END -> LevelStem.END;
+ default -> throw new IllegalArgumentException("Unknown dimension supplied");
+ };
+
+ ResourceKey<Level> worldKey = dimensionOverride == null ? ResourceKey.create(Registries.DIMENSION, new ResourceLocation(worldName.toLowerCase(Locale.ENGLISH))) : dimensionOverride;
+ LevelStem stem = MinecraftServer.getServer().registries().compositeAccess().registryOrThrow(Registries.LEVEL_STEM).get(dimension);
+
+ SlimeLevelInstance level;
+
+ try {
+ level = new SlimeLevelInstance(bootstrap, worldDataServer, worldKey, dimension, stem, environment);
+ } catch (IOException ex) {
+ throw new RuntimeException(ex); // TODO do something better with this?
+ }
+
+ // level.setReady(true);
+ level.setSpawnSettings(world.getPropertyMap().getValue(SlimeProperties.ALLOW_MONSTERS), world.getPropertyMap().getValue(SlimeProperties.ALLOW_ANIMALS));
+
+ return level;
+ }
+
+ private World.Environment getEnvironment(SlimeWorld world) {
+ return World.Environment.valueOf(world.getPropertyMap().getValue(SlimeProperties.ENVIRONMENT).toUpperCase());
+ }
+
+ private PrimaryLevelData createWorldData(SlimeWorld world) {
+ MinecraftServer mcServer = MinecraftServer.getServer();
+ DedicatedServerProperties serverProps = ((DedicatedServer) mcServer).getProperties();
+ String worldName = world.getName();
+
+ LevelSettings worldsettings = new LevelSettings(worldName, serverProps.gamemode, false, serverProps.difficulty,
+ true, new GameRules(), mcServer.worldLoader.dataConfiguration());
+
+ WorldOptions worldoptions = new WorldOptions(0, false, false);
+
+ PrimaryLevelData data = new PrimaryLevelData(worldsettings, worldoptions, PrimaryLevelData.SpecialWorldProperty.FLAT, Lifecycle.stable());
+ data.checkName(worldName);
+ data.setModdedInfo(mcServer.getServerModName(), mcServer.getModdedStatus().shouldReportAsModified());
+ data.setInitialized(true);
+
+ return data;
+ }
+
+}
diff --git a/src/main/java/com/infernalsuite/aswm/level/ChunkDataLoadTask.java b/src/main/java/com/infernalsuite/aswm/level/ChunkDataLoadTask.java
new file mode 100644
2023-10-19 05:03:42 +00:00
index 0000000000000000000000000000000000000000..66093eaed9a45c7b714471915c8bd38f23ac7894
--- /dev/null
+++ b/src/main/java/com/infernalsuite/aswm/level/ChunkDataLoadTask.java
2023-10-19 05:03:42 +00:00
@@ -0,0 +1,113 @@
+package com.infernalsuite.aswm.level;
+
+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor;
+import com.infernalsuite.aswm.api.world.SlimeChunk;
+import com.mojang.logging.LogUtils;
+import io.papermc.paper.chunk.system.scheduling.ChunkLoadTask;
+import io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler;
+import io.papermc.paper.chunk.system.scheduling.GenericDataLoadTask;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.world.level.ChunkPos;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.chunk.ChunkAccess;
+import net.minecraft.world.level.chunk.ImposterProtoChunk;
+import net.minecraft.world.level.chunk.LevelChunk;
+import net.minecraft.world.level.chunk.UpgradeData;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.ticks.LevelChunkTicks;
+import org.slf4j.Logger;
+
+import java.util.function.Consumer;
+
+public final class ChunkDataLoadTask implements CommonLoadTask {
+
+ private static final Logger LOGGER = LogUtils.getClassLogger();
+
+ private final ChunkTaskScheduler scheduler;
+ private final ServerLevel world;
+ private final int chunkX;
+ private final int chunkZ;
+ private Consumer<GenericDataLoadTask.TaskResult<ChunkAccess, Throwable>> onRun;
+
+ private PrioritisedExecutor.PrioritisedTask task;
+
+ private final ChunkLoadTask chunkLoadTask;
+
+ protected ChunkDataLoadTask(ChunkLoadTask chunkLoadTask, final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX,
+ final int chunkZ, final PrioritisedExecutor.Priority priority, final Consumer<GenericDataLoadTask.TaskResult<ChunkAccess, Throwable>> onRun) {
+ this.chunkLoadTask = chunkLoadTask;
+ this.scheduler = scheduler;
+ this.world = world;
+ this.chunkX = chunkX;
+ this.chunkZ = chunkZ;
+ this.onRun = onRun;
+
+ this.task = this.scheduler.createChunkTask(this.chunkX, this.chunkZ, () -> {
+ try {
+ SlimeChunk chunk = ((SlimeLevelInstance) this.world).slimeInstance.getChunk(this.chunkX, this.chunkZ);
+ this.onRun.accept(new GenericDataLoadTask.TaskResult<>(runOnMain(chunk), null));
+ } catch (Throwable e) {
+ LOGGER.error("ERROR", e);
+ this.onRun.accept(new GenericDataLoadTask.TaskResult<>(null, e));
+ }
+ }, priority);
+ }
+
+ private ChunkAccess getEmptyChunk() {
+ LevelChunkTicks<Block> blockLevelChunkTicks = new LevelChunkTicks<>();
+ LevelChunkTicks<Fluid> fluidLevelChunkTicks = new LevelChunkTicks<>();
+
+ return new ImposterProtoChunk(new LevelChunk(this.world, new ChunkPos(this.chunkX, this.chunkZ), UpgradeData.EMPTY, blockLevelChunkTicks, fluidLevelChunkTicks,
+ 0L, null, null, null), true);
+ }
+
+ protected ChunkAccess runOnMain(final SlimeChunk data) {
+ // have tasks to run (at this point, it's just the POI consistency checking)
+ try {
+ // if (data.tasks != null) {
+ // for (int i = 0, len = data.tasks.size(); i < len; i) {
+ // data.tasks.poll().run();
+ // }
+ // }
+
+ LevelChunk chunk = this.world.slimeInstance.promote(chunkX, chunkZ, data);
+
+ return new ImposterProtoChunk(chunk, false);
+ } catch (final ThreadDeath death) {
+ throw death;
+ } catch (final Throwable thr2) {
+ LOGGER.error("Failed to parse main tasks for task " + this.toString() + ", chunk data will be lost", thr2);
+ return this.getEmptyChunk();
+ }
+ }
+
+ @Override
+ public PrioritisedExecutor.Priority getPriority() {
+ return this.task.getPriority();
+ }
+
+ @Override
+ public void setPriority(PrioritisedExecutor.Priority priority) {
+ this.task.setPriority(priority);
+ }
+
+ @Override
+ public void raisePriority(PrioritisedExecutor.Priority priority) {
+ this.task.raisePriority(priority);
+ }
+
+ @Override
+ public void lowerPriority(PrioritisedExecutor.Priority priority) {
+ this.task.lowerPriority(priority);
+ }
+
+ @Override
+ public boolean cancel() {
+ return this.task.cancel();
+ }
+
+ public boolean schedule(boolean schedule) {
+ this.scheduler.scheduleChunkTask(chunkX, chunkZ, this.task::execute);
+ return true;
+ }
+}
diff --git a/src/main/java/com/infernalsuite/aswm/level/CommonLoadTask.java b/src/main/java/com/infernalsuite/aswm/level/CommonLoadTask.java
new file mode 100644
index 0000000000000000000000000000000000000000..fc6e46972bcc77134ed718c8c157ec3893d4bcdf
--- /dev/null
+++ b/src/main/java/com/infernalsuite/aswm/level/CommonLoadTask.java
@@ -0,0 +1,18 @@
+package com.infernalsuite.aswm.level;
+
+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor;
+
+public interface CommonLoadTask {
+
+ boolean schedule(boolean schedule);
+
+ PrioritisedExecutor.Priority getPriority();
+
+ boolean cancel();
+
+ void lowerPriority(PrioritisedExecutor.Priority priority);
+
+ void raisePriority(PrioritisedExecutor.Priority priority);
+
+ void setPriority(PrioritisedExecutor.Priority priority);
+}
\ No newline at end of file
diff --git a/src/main/java/com/infernalsuite/aswm/level/FastChunkPruner.java b/src/main/java/com/infernalsuite/aswm/level/FastChunkPruner.java
new file mode 100644
index 0000000000000000000000000000000000000000..c0e47f25e9be33da374dc737c96d8d3c2bb1cd0f
--- /dev/null
+++ b/src/main/java/com/infernalsuite/aswm/level/FastChunkPruner.java
@@ -0,0 +1,59 @@
+package com.infernalsuite.aswm.level;
+
+import com.infernalsuite.aswm.api.world.SlimeWorld;
+import com.infernalsuite.aswm.api.world.properties.SlimeProperties;
+import com.infernalsuite.aswm.api.world.properties.SlimePropertyMap;
+import io.papermc.paper.world.ChunkEntitySlices;
+import net.minecraft.world.level.chunk.LevelChunk;
+import net.minecraft.world.level.chunk.LevelChunkSection;
+
+public class FastChunkPruner {
+
+ public static boolean canBePruned(SlimeWorld world, LevelChunk chunk) {
+ // Kenox <muranelp@gmail.com>
+ // It's not safe to assume that the chunk can be pruned
+ // if there isn't a loaded chunk there
+ if (chunk == null || chunk.getChunkHolder() == null) {
+ return false;
+ }
+
+ SlimePropertyMap propertyMap = world.getPropertyMap();
+ if (propertyMap.getValue(SlimeProperties.SHOULD_LIMIT_SAVE)) {
+ int minX = propertyMap.getValue(SlimeProperties.SAVE_MIN_X);
+ int maxX = propertyMap.getValue(SlimeProperties.SAVE_MAX_X);
+
+ int minZ = propertyMap.getValue(SlimeProperties.SAVE_MIN_Z);
+ int maxZ = propertyMap.getValue(SlimeProperties.SAVE_MAX_Z);
+
+ int chunkX = chunk.locX;
+ int chunkZ = chunk.locZ;
+
+ if (chunkX < minX || chunkX > maxX) {
+ return true;
+ }
+
+ if (chunkZ < minZ || chunkZ > maxZ) {
+ return true;
+ }
+ }
+
+ String pruningSetting = world.getPropertyMap().getValue(SlimeProperties.CHUNK_PRUNING);
+ if (pruningSetting.equals("aggressive")) {
+ ChunkEntitySlices slices = chunk.getChunkHolder().getEntityChunk();
+
+ return chunk.blockEntities.isEmpty() && (slices == null || slices.isEmpty()) && areSectionsEmpty(chunk);
+ }
+
+ return false;
+ }
+
+ private static boolean areSectionsEmpty(LevelChunk chunk) {
+ for (LevelChunkSection section : chunk.getSections()) {
+ if (!section.hasOnlyAir()) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/infernalsuite/aswm/level/NMSSlimeChunk.java b/src/main/java/com/infernalsuite/aswm/level/NMSSlimeChunk.java
new file mode 100644
2023-10-19 05:03:42 +00:00
index 0000000000000000000000000000000000000000..c94ee5460d3859d373ae81e9d3623db071d6c38b
--- /dev/null
+++ b/src/main/java/com/infernalsuite/aswm/level/NMSSlimeChunk.java
@@ -0,0 +1,203 @@
+package com.infernalsuite.aswm.level;
+
+import com.flowpowered.nbt.CompoundMap;
+import com.flowpowered.nbt.CompoundTag;
+import com.flowpowered.nbt.LongArrayTag;
+import com.google.common.collect.Lists;
+import com.infernalsuite.aswm.Converter;
+import com.infernalsuite.aswm.api.utils.NibbleArray;
+import com.infernalsuite.aswm.api.world.SlimeChunk;
+import com.infernalsuite.aswm.api.world.SlimeChunkSection;
2023-10-19 05:03:42 +00:00
+import com.infernalsuite.aswm.skeleton.SlimeChunkSectionSkeleton;
+import com.mojang.logging.LogUtils;
+import com.mojang.serialization.Codec;
+import io.papermc.paper.world.ChunkEntitySlices;
+import net.minecraft.core.Holder;
+import net.minecraft.core.Registry;
+import net.minecraft.core.SectionPos;
+import net.minecraft.core.registries.Registries;
+import net.minecraft.nbt.NbtOps;
+import net.minecraft.nbt.Tag;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.level.LightLayer;
+import net.minecraft.world.level.biome.Biome;
+import net.minecraft.world.level.biome.Biomes;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.level.block.entity.BlockEntity;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.chunk.LevelChunk;
+import net.minecraft.world.level.chunk.LevelChunkSection;
+import net.minecraft.world.level.chunk.PalettedContainer;
+import net.minecraft.world.level.chunk.PalettedContainerRO;
+import net.minecraft.world.level.chunk.storage.ChunkSerializer;
+import net.minecraft.world.level.levelgen.Heightmap;
+import net.minecraft.world.level.lighting.LevelLightEngine;
+import org.slf4j.Logger;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class NMSSlimeChunk implements SlimeChunk {
+ private static final Logger LOGGER = LogUtils.getClassLogger();
+
+ private static final CompoundTag EMPTY_BLOCK_STATE_PALETTE;
+ private static final CompoundTag EMPTY_BIOME_PALETTE;
+
+ // Optimized empty section serialization
+ static {
+ {
+ PalettedContainer<BlockState> empty = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES, null);
+ Tag tag = ChunkSerializer.BLOCK_STATE_CODEC.encodeStart(NbtOps.INSTANCE, empty).getOrThrow(false, (error) -> {
+ throw new AssertionError(error);
+ });
+
+ EMPTY_BLOCK_STATE_PALETTE = (CompoundTag) Converter.convertTag("", tag);
+ }
+ {
+ Registry<Biome> biomes = net.minecraft.server.MinecraftServer.getServer().registryAccess().registryOrThrow(Registries.BIOME);
+ PalettedContainer<Holder<Biome>> empty = new PalettedContainer<>(biomes.asHolderIdMap(), biomes.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES, null);
+ Tag tag = ChunkSerializer.makeBiomeCodec(biomes).encodeStart(NbtOps.INSTANCE, empty).getOrThrow(false, (error) -> {
+ throw new AssertionError(error);
+ });
+
+ EMPTY_BIOME_PALETTE = (CompoundTag) Converter.convertTag("", tag);
+ }
+ }
+
+ private LevelChunk chunk;
+
+ public NMSSlimeChunk(LevelChunk chunk) {
+ this.chunk = chunk;
+ }
+
+ @Override
+ public int getX() {
+ return chunk.getPos().x;
+ }
+
+ @Override
+ public int getZ() {
+ return chunk.getPos().z;
+ }
+
+ @Override
+ public SlimeChunkSection[] getSections() {
+ SlimeChunkSection[] sections = new SlimeChunkSection[this.chunk.getSectionsCount()];
+ LevelLightEngine lightEngine = chunk.getLevel().getChunkSource().getLightEngine();
+
+ Registry<Biome> biomeRegistry = chunk.getLevel().registryAccess().registryOrThrow(Registries.BIOME);
+
+ // Ignore deprecation, spigot only method
+ Codec<PalettedContainerRO<Holder<Biome>>> codec = PalettedContainer.codecRO(biomeRegistry.asHolderIdMap(), biomeRegistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomeRegistry.getHolderOrThrow(Biomes.PLAINS));
+
+ for (int sectionId = 0; sectionId < chunk.getSections().length; sectionId++) {
+ LevelChunkSection section = chunk.getSections()[sectionId];
+ // Sections CANNOT be null in 1.18
+
+ // Block Light Nibble Array
+ NibbleArray blockLightArray = Converter.convertArray(lightEngine.getLayerListener(LightLayer.BLOCK).getDataLayerData(SectionPos.of(chunk.getPos(), sectionId)));
+
+ // Sky light Nibble Array
+ NibbleArray skyLightArray = Converter.convertArray(lightEngine.getLayerListener(LightLayer.SKY).getDataLayerData(SectionPos.of(chunk.getPos(), sectionId)));
+
+ // Tile/Entity Data
+
+ // Block Data
+ CompoundTag blockStateTag;
+ if (section.hasOnlyAir()) {
+ blockStateTag = EMPTY_BLOCK_STATE_PALETTE;
+ } else {
+ Tag data = ChunkSerializer.BLOCK_STATE_CODEC.encodeStart(NbtOps.INSTANCE, section.getStates()).getOrThrow(false, System.err::println); // todo error handling
+ blockStateTag = (CompoundTag) Converter.convertTag("", data);
+ }
+
+
+ CompoundTag biomeTag;
+ PalettedContainer<Holder<Biome>> biomes = (PalettedContainer<Holder<Biome>>) section.getBiomes();
+ if (biomes.data.palette().getSize() == 1 && biomes.data.palette().maybeHas((h) -> h.is(Biomes.PLAINS))) {
+ biomeTag = EMPTY_BIOME_PALETTE;
+ } else {
+ Tag biomeData = codec.encodeStart(NbtOps.INSTANCE, section.getBiomes()).getOrThrow(false, System.err::println); // todo error handling
+ biomeTag = (CompoundTag) Converter.convertTag("", biomeData);
+ }
+
+ sections[sectionId] = new SlimeChunkSectionSkeleton(blockStateTag, biomeTag, blockLightArray, skyLightArray);
+ }
+
+ return sections;
+ }
+
+ @Override
+ public CompoundTag getHeightMaps() {
+ // HeightMap
+ CompoundMap heightMaps = new CompoundMap();
+
+ for (Map.Entry<Heightmap.Types, Heightmap> entry : chunk.heightmaps.entrySet()) {
+ if (!entry.getKey().keepAfterWorldgen()) {
+ continue;
+ }
+
+ Heightmap.Types type = entry.getKey();
+ Heightmap map = entry.getValue();
+
+ heightMaps.put(type.name(), new LongArrayTag(type.name(), map.getRawData()));
+ }
+
+ return new CompoundTag("", heightMaps);
+ }
+
+ @Override
+ public List<CompoundTag> getTileEntities() {
+ List<net.minecraft.nbt.CompoundTag> tileEntities = new ArrayList<>();
+
+ for (BlockEntity entity : chunk.blockEntities.values()) {
+ net.minecraft.nbt.CompoundTag entityNbt = entity.saveWithFullMetadata();
+ tileEntities.add(entityNbt);
+ }
+
+ return Lists.transform(tileEntities, (compound) -> {
+ return (CompoundTag) Converter.convertTag("", compound);
+ });
+ }
+
+ @Override
+ public List<CompoundTag> getEntities() {
+ List<net.minecraft.nbt.CompoundTag> entities = new ArrayList<>();
+
+ if(this.chunk == null || this.chunk.getChunkHolder() == null) {
+ return new ArrayList<>();
+ }
+
+ ChunkEntitySlices slices = this.chunk.getChunkHolder().getEntityChunk();
+ if (slices == null) {
+ return new ArrayList<>();
+ }
+
+ // Work by <gunther@gameslabs.net>
+ for (Entity entity : slices.entities) {
+ net.minecraft.nbt.CompoundTag entityNbt = new net.minecraft.nbt.CompoundTag();
+ try {
+ if (entity.save(entityNbt)) {
+ entities.add(entityNbt);
+ }
+ } catch (Exception e) {
+ LOGGER.error("Could not save the entity = {}, exception = {}", entity, e);
+ }
+ }
+
+ return Lists.transform(entities, (compound) -> {
+ return (CompoundTag) Converter.convertTag("", compound);
+ });
+ }
+
+ public LevelChunk getChunk() {
+ return chunk;
+ }
+
+ public void setChunk(LevelChunk chunk) {
+ this.chunk = chunk;
+ }
+
+}
diff --git a/src/main/java/com/infernalsuite/aswm/level/NMSSlimeWorld.java b/src/main/java/com/infernalsuite/aswm/level/NMSSlimeWorld.java
new file mode 100644
index 0000000000000000000000000000000000000000..9a27369c00345bbb94aa19f77687269dc94c0b0a
--- /dev/null
+++ b/src/main/java/com/infernalsuite/aswm/level/NMSSlimeWorld.java
@@ -0,0 +1,91 @@
+package com.infernalsuite.aswm.level;
+
+import com.flowpowered.nbt.CompoundTag;
+import com.infernalsuite.aswm.api.exceptions.WorldAlreadyExistsException;
+import com.infernalsuite.aswm.api.loaders.SlimeLoader;
+import com.infernalsuite.aswm.api.world.SlimeChunk;
+import com.infernalsuite.aswm.api.world.SlimeWorld;
+import com.infernalsuite.aswm.api.world.properties.SlimePropertyMap;
+import net.minecraft.SharedConstants;
+import net.minecraft.server.level.ChunkHolder;
+import net.minecraft.world.level.chunk.LevelChunk;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+public class NMSSlimeWorld implements SlimeWorld {
+
+ private final SlimeInMemoryWorld memoryWorld;
+ private final SlimeLevelInstance instance;
+
+ public NMSSlimeWorld(SlimeInMemoryWorld memoryWorld) {
+ this.instance = memoryWorld.getInstance();
+ this.memoryWorld = memoryWorld;
+ }
+
+ @Override
+ public String getName() {
+ return this.instance.getMinecraftWorld().serverLevelData.getLevelName();
+ }
+
+ @Override
+ public SlimeLoader getLoader() {
+ return this.instance.slimeInstance.getSaveStrategy();
+ }
+
+ @Override
+ public SlimeChunk getChunk(int x, int z) {
+ LevelChunk chunk = this.instance.getChunkIfLoaded(x, z);
+ if (chunk == null) {
+ return null;
+ }
+
+ return new NMSSlimeChunk(chunk);
+ }
+
+ @Override
+ public Collection<SlimeChunk> getChunkStorage() {
+ List<ChunkHolder> chunks = io.papermc.paper.chunk.system.ChunkSystem.getVisibleChunkHolders(this.instance); // Paper
+ return chunks.stream().map(ChunkHolder::getFullChunkNow).filter(Objects::nonNull)
+ .map(NMSSlimeChunk::new)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public CompoundTag getExtraData() {
+ return this.instance.slimeInstance.getExtraData();
+ }
+
+ @Override
+ public Collection<CompoundTag> getWorldMaps() {
+ return List.of();
+ }
+
+ @Override
+ public SlimePropertyMap getPropertyMap() {
+ return this.instance.slimeInstance.getPropertyMap();
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ return this.getLoader() == null;
+ }
+
+ @Override
+ public SlimeWorld clone(String worldName) {
+ return this.memoryWorld.clone(worldName);
+ }
+
+ @Override
+ public SlimeWorld clone(String worldName, SlimeLoader loader) throws WorldAlreadyExistsException, IOException {
+ return this.memoryWorld.clone(worldName, loader);
+ }
+
+ @Override
+ public int getDataVersion() {
+ return SharedConstants.getCurrentVersion().getDataVersion().getVersion();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/infernalsuite/aswm/level/SafeNmsChunkWrapper.java b/src/main/java/com/infernalsuite/aswm/level/SafeNmsChunkWrapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..b20a037679182e3c4a8bf31f084078f6d7e4ff46
--- /dev/null
+++ b/src/main/java/com/infernalsuite/aswm/level/SafeNmsChunkWrapper.java
@@ -0,0 +1,83 @@
+package com.infernalsuite.aswm.level;
+
+import com.flowpowered.nbt.CompoundTag;
+import com.infernalsuite.aswm.api.world.SlimeChunk;
+import com.infernalsuite.aswm.api.world.SlimeChunkSection;
+
+import java.util.List;
+
+public class SafeNmsChunkWrapper implements SlimeChunk {
+
+ private final NMSSlimeChunk wrapper;
+ private final SlimeChunk safety;
+
+ public SafeNmsChunkWrapper(NMSSlimeChunk wrapper, SlimeChunk safety) {
+ this.wrapper = wrapper;
+ this.safety = safety;
+ }
+
+ @Override
+ public int getX() {
+ return this.wrapper.getX();
+ }
+
+ @Override
+ public int getZ() {
+ return this.wrapper.getZ();
+ }
+
+ @Override
+ public SlimeChunkSection[] getSections() {
+ if (shouldDefaultBackToSlimeChunk()) {
+ return this.safety.getSections();
+ }
+
+ return this.wrapper.getSections();
+ }
+
+ @Override
+ public CompoundTag getHeightMaps() {
+ if (shouldDefaultBackToSlimeChunk()) {
+ return this.safety.getHeightMaps();
+ }
+
+ return this.wrapper.getHeightMaps();
+ }
+
+ @Override
+ public List<CompoundTag> getTileEntities() {
+ if (shouldDefaultBackToSlimeChunk()) {
+ return this.safety.getTileEntities();
+ }
+
+ return this.wrapper.getTileEntities();
+ }
+
+ @Override
+ public List<CompoundTag> getEntities() {
+ if (shouldDefaultBackToSlimeChunk()) {
+ return this.safety.getEntities();
+ }
+
+ return this.wrapper.getEntities();
+ }
+
+ /*
+Slime chunks can still be requested but not actually loaded, this caused
+some things to not properly save because they are not "loaded" into the chunk.
+See ChunkMap#protoChunkToFullChunk
+anything in the if statement will not be loaded and is stuck inside the runnable.
+Inorder to possibly not corrupt the state, simply refer back to the slime saved object.
+*/
+ public boolean shouldDefaultBackToSlimeChunk() {
+ return this.safety != null && !this.wrapper.getChunk().loaded;
+ }
+
+ public NMSSlimeChunk getWrapper() {
+ return wrapper;
+ }
+
+ public SlimeChunk getSafety() {
+ return safety;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/infernalsuite/aswm/level/SlimeBootstrap.java b/src/main/java/com/infernalsuite/aswm/level/SlimeBootstrap.java
new file mode 100644
index 0000000000000000000000000000000000000000..8853088c5c6306511716bbffac9bf73c633b61bb
--- /dev/null
+++ b/src/main/java/com/infernalsuite/aswm/level/SlimeBootstrap.java
@@ -0,0 +1,8 @@
+package com.infernalsuite.aswm.level;
+
+import com.infernalsuite.aswm.api.world.SlimeWorld;
+
+public record SlimeBootstrap(
+ SlimeWorld initial
+) {
+}
\ No newline at end of file
diff --git a/src/main/java/com/infernalsuite/aswm/level/SlimeChunkConverter.java b/src/main/java/com/infernalsuite/aswm/level/SlimeChunkConverter.java
new file mode 100644
index 0000000000000000000000000000000000000000..91a7f41db47c7df3ecc301e0827a1d07305f604e
--- /dev/null
+++ b/src/main/java/com/infernalsuite/aswm/level/SlimeChunkConverter.java
@@ -0,0 +1,164 @@
+package com.infernalsuite.aswm.level;
+
+import ca.spottedleaf.starlight.common.light.SWMRNibbleArray;
+import com.flowpowered.nbt.CompoundMap;
+import com.flowpowered.nbt.CompoundTag;
+import com.flowpowered.nbt.LongArrayTag;
+import com.infernalsuite.aswm.Converter;
+import com.infernalsuite.aswm.api.utils.NibbleArray;
+import com.infernalsuite.aswm.api.world.SlimeChunk;
+import com.infernalsuite.aswm.api.world.SlimeChunkSection;
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.DataResult;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.Holder;
+import net.minecraft.core.Registry;
+import net.minecraft.core.registries.Registries;
+import net.minecraft.nbt.NbtOps;
+import net.minecraft.world.level.ChunkPos;
+import net.minecraft.world.level.biome.Biome;
+import net.minecraft.world.level.biome.Biomes;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.level.block.entity.BlockEntity;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.chunk.LevelChunk;
+import net.minecraft.world.level.chunk.LevelChunkSection;
+import net.minecraft.world.level.chunk.PalettedContainer;
+import net.minecraft.world.level.chunk.UpgradeData;
+import net.minecraft.world.level.chunk.storage.ChunkSerializer;
+import net.minecraft.world.level.levelgen.Heightmap;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.ticks.LevelChunkTicks;
+
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Optional;
+
+public class SlimeChunkConverter {
+
+ static SlimeChunkLevel deserializeSlimeChunk(SlimeLevelInstance instance, SlimeChunk chunk) {
+ int x = chunk.getX();
+ int z = chunk.getZ();
+
+ ChunkPos pos = new ChunkPos(x, z);
+
+ // Chunk sections
+ LevelChunkSection[] sections = new LevelChunkSection[instance.getSectionsCount()];
+
+ SWMRNibbleArray[] blockNibbles = ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(instance);
+ SWMRNibbleArray[] skyNibbles = ca.spottedleaf.starlight.common.light.StarLightEngine.getFilledEmptyLight(instance);
+ instance.getServer().scheduleOnMain(() -> {
+ instance.getLightEngine().retainData(pos, true);
+ });
+
+ Registry<Biome> biomeRegistry = instance.registryAccess().registryOrThrow(Registries.BIOME);
+ // Ignore deprecated method
+
+ Codec<PalettedContainer<Holder<Biome>>> codec = PalettedContainer.codecRW(biomeRegistry.asHolderIdMap(), biomeRegistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomeRegistry.getHolderOrThrow(Biomes.PLAINS), null);
+
+ for (int sectionId = 0; sectionId < chunk.getSections().length; sectionId++) {
+ SlimeChunkSection slimeSection = chunk.getSections()[sectionId];
+
+ if (slimeSection != null) {
+ NibbleArray blockLight = slimeSection.getBlockLight();
+ if (blockLight != null) {
+ blockNibbles[sectionId] = new SWMRNibbleArray(blockLight.getBacking());
+ }
+
+ NibbleArray skyLight = slimeSection.getSkyLight();
+ if (skyLight != null) {
+ skyNibbles[sectionId] = new SWMRNibbleArray(skyLight.getBacking());
+ }
+
+ PalettedContainer<BlockState> blockPalette;
+ if (slimeSection.getBlockStatesTag() != null) {
+ DataResult<PalettedContainer<BlockState>> dataresult = ChunkSerializer.BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, Converter.convertTag(slimeSection.getBlockStatesTag())).promotePartial((s) -> {
+ System.out.println("Recoverable error when parsing section " + x + "," + z + ": " + s); // todo proper logging
+ });
+ blockPalette = dataresult.getOrThrow(false, System.err::println); // todo proper logging
+ } else {
+ blockPalette = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES, null);
+ }
+
+ PalettedContainer<Holder<Biome>> biomePalette;
+
+ if (slimeSection.getBiomeTag() != null) {
+ DataResult<PalettedContainer<Holder<Biome>>> dataresult = codec.parse(NbtOps.INSTANCE, Converter.convertTag(slimeSection.getBiomeTag())).promotePartial((s) -> {
+ System.out.println("Recoverable error when parsing section " + x + "," + z + ": " + s); // todo proper logging
+ });
+ biomePalette = dataresult.getOrThrow(false, System.err::println); // todo proper logging
+ } else {
+ biomePalette = new PalettedContainer<>(biomeRegistry.asHolderIdMap(), biomeRegistry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES, null);
+ }
+
+ if (sectionId < sections.length) {
+ LevelChunkSection section = new LevelChunkSection(blockPalette, biomePalette);
+ sections[sectionId] = section;
+ }
+ }
+ }
+
+ // Keep the chunk loaded at level 33 to avoid light glitches
+ // Such a high level will let the server not tick the chunk,
+ // but at the same time it won't be completely unloaded from memory
+ // getChunkProvider().addTicket(SWM_TICKET, pos, 33, Unit.INSTANCE);
+
+
+ LevelChunk.PostLoadProcessor loadEntities = (nmsChunk) -> {
+
+ // TODO
+ // Load tile entities
+ List<CompoundTag> tileEntities = chunk.getTileEntities();
+
+ if (tileEntities != null) {
+ for (CompoundTag tag : tileEntities) {
+ Optional<String> type = tag.getStringValue("id");
+
+ // Sometimes null tile entities are saved
+ if (type.isPresent()) {
+ BlockPos blockPosition = new BlockPos(tag.getIntValue("x").get(), tag.getIntValue("y").get(), tag.getIntValue("z").get());
+ BlockState blockData = nmsChunk.getBlockState(blockPosition);
+ BlockEntity entity = BlockEntity.loadStatic(blockPosition, blockData, (net.minecraft.nbt.CompoundTag) Converter.convertTag(tag));
+
+ if (entity != null) {
+ nmsChunk.setBlockEntity(entity);
+ }
+ }
+ }
+ }
+ };
+
+ LevelChunkTicks<Block> blockLevelChunkTicks = new LevelChunkTicks<>();
+ LevelChunkTicks<Fluid> fluidLevelChunkTicks = new LevelChunkTicks<>();
+ SlimeChunkLevel nmsChunk = new SlimeChunkLevel(instance, pos, UpgradeData.EMPTY, blockLevelChunkTicks, fluidLevelChunkTicks, 0L, sections, loadEntities, null);
+
+ // Height Maps
+ EnumSet<Heightmap.Types> heightMapTypes = nmsChunk.getStatus().heightmapsAfter();
+ CompoundMap heightMaps = chunk.getHeightMaps().getValue();
+ EnumSet<Heightmap.Types> unsetHeightMaps = EnumSet.noneOf(Heightmap.Types.class);
+
+ // Light
+ nmsChunk.setBlockNibbles(blockNibbles);
+ nmsChunk.setSkyNibbles(skyNibbles);
+
+ for (Heightmap.Types type : heightMapTypes) {
+ String name = type.getSerializedName();
+
+ if (heightMaps.containsKey(name)) {
+ LongArrayTag heightMap = (LongArrayTag) heightMaps.get(name);
+ nmsChunk.setHeightmap(type, heightMap.getValue());
+ } else {
+ unsetHeightMaps.add(type);
+ }
+ }
+
+ // Don't try to populate heightmaps if there are none.
+ // Does a crazy amount of block lookups
+ if (!unsetHeightMaps.isEmpty()) {
+ Heightmap.primeHeightmaps(nmsChunk, unsetHeightMaps);
+ }
+
+ return nmsChunk;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/infernalsuite/aswm/level/SlimeChunkLevel.java b/src/main/java/com/infernalsuite/aswm/level/SlimeChunkLevel.java
new file mode 100644
index 0000000000000000000000000000000000000000..b159fc8751e9840b311cc1eda01e496e2dbc5f2e
--- /dev/null
+++ b/src/main/java/com/infernalsuite/aswm/level/SlimeChunkLevel.java
@@ -0,0 +1,27 @@
+package com.infernalsuite.aswm.level;
+
+import net.minecraft.world.level.ChunkPos;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.chunk.LevelChunk;
+import net.minecraft.world.level.chunk.LevelChunkSection;
+import net.minecraft.world.level.chunk.UpgradeData;
+import net.minecraft.world.level.levelgen.blending.BlendingData;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.ticks.LevelChunkTicks;
+import org.jetbrains.annotations.Nullable;
+
+public class SlimeChunkLevel extends LevelChunk {
+
+ private final SlimeInMemoryWorld inMemoryWorld;
+
+ public SlimeChunkLevel(SlimeLevelInstance world, ChunkPos pos, UpgradeData upgradeData, LevelChunkTicks<Block> blockTickScheduler, LevelChunkTicks<Fluid> fluidTickScheduler, long inhabitedTime, @Nullable LevelChunkSection[] sectionArrayInitializer, @Nullable LevelChunk.PostLoadProcessor entityLoader, @Nullable BlendingData blendingData) {
+ super(world, pos, upgradeData, blockTickScheduler, fluidTickScheduler, inhabitedTime, sectionArrayInitializer, entityLoader, blendingData);
+ this.inMemoryWorld = world.slimeInstance;
+ }
+
+ @Override
+ public void unloadCallback() {
+ super.unloadCallback();
+ this.inMemoryWorld.unload(this);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/infernalsuite/aswm/level/SlimeInMemoryWorld.java b/src/main/java/com/infernalsuite/aswm/level/SlimeInMemoryWorld.java
new file mode 100644
2023-10-19 05:03:42 +00:00
index 0000000000000000000000000000000000000000..043de6fba8387ce851d1d54c501cd834a1760c60
--- /dev/null
+++ b/src/main/java/com/infernalsuite/aswm/level/SlimeInMemoryWorld.java
2023-10-19 05:03:42 +00:00
@@ -0,0 +1,247 @@
+package com.infernalsuite.aswm.level;
+
+import com.flowpowered.nbt.CompoundTag;
+import com.infernalsuite.aswm.ChunkPos;
+import com.infernalsuite.aswm.api.exceptions.WorldAlreadyExistsException;
+import com.infernalsuite.aswm.api.loaders.SlimeLoader;
+import com.infernalsuite.aswm.api.world.SlimeChunk;
+import com.infernalsuite.aswm.api.world.SlimeWorld;
+import com.infernalsuite.aswm.api.world.SlimeWorldInstance;
+import com.infernalsuite.aswm.api.world.properties.SlimePropertyMap;
2023-10-19 05:03:42 +00:00
+import com.infernalsuite.aswm.serialization.slime.SlimeSerializer;
+import com.infernalsuite.aswm.skeleton.SkeletonCloning;
+import com.infernalsuite.aswm.skeleton.SkeletonSlimeWorld;
+import com.infernalsuite.aswm.skeleton.SlimeChunkSkeleton;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.chunk.LevelChunk;
+import net.minecraft.world.level.chunk.UpgradeData;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.ticks.LevelChunkTicks;
+import org.bukkit.World;
+
+import java.io.IOException;
2023-10-19 05:03:42 +00:00
+import java.util.*;
+
+/*
+The concept of this is a bit flawed, since ideally this should be a 1:1 representation of the MC world.
+However, due to the complexity of the chunk system we essentially need to wrap around it.
+This stores slime chunks, and when unloaded, will properly convert it to a normal slime chunk for storage.
+ */
+public class SlimeInMemoryWorld implements SlimeWorld, SlimeWorldInstance {
+
+ private final SlimeLevelInstance instance;
+ private final SlimeWorld liveWorld;
+
+ private final CompoundTag extra;
+ private final SlimePropertyMap propertyMap;
+ private final SlimeLoader loader;
+
+ private final Map<ChunkPos, SlimeChunk> chunkStorage = new HashMap<>();
+ private boolean readOnly;
+ // private final Map<ChunkPos, List<CompoundTag>> entityStorage = new HashMap<>();
+
+ public SlimeInMemoryWorld(SlimeBootstrap bootstrap, SlimeLevelInstance instance) {
+ this.instance = instance;
+ this.extra = bootstrap.initial().getExtraData();
+ this.propertyMap = bootstrap.initial().getPropertyMap();
+ this.loader = bootstrap.initial().getLoader();
+ this.readOnly = bootstrap.initial().isReadOnly();
+
+ for (SlimeChunk initial : bootstrap.initial().getChunkStorage()) {
+ ChunkPos pos = new ChunkPos(initial.getX(), initial.getZ());
+ List<CompoundTag> tags = new ArrayList<>(initial.getEntities());
+
+ // this.entityStorage.put(pos, tags);
+ this.chunkStorage.put(pos, initial);
+ }
+
+ this.liveWorld = new NMSSlimeWorld(this);
+ }
+
+ @Override
+ public String getName() {
+ return this.instance.getMinecraftWorld().serverLevelData.getLevelName();
+ }
+
+ @Override
+ public SlimeLoader getLoader() {
+ return this.loader;
+ }
+
+ public LevelChunk promote(int x, int z, SlimeChunk chunk) {
+ SlimeChunkLevel levelChunk;
+ if (chunk == null) {
+ net.minecraft.world.level.ChunkPos pos = new net.minecraft.world.level.ChunkPos(x, z);
+ LevelChunkTicks<Block> blockLevelChunkTicks = new LevelChunkTicks<>();
+ LevelChunkTicks<Fluid> fluidLevelChunkTicks = new LevelChunkTicks<>();
+
+ levelChunk = new SlimeChunkLevel(this.instance, pos, UpgradeData.EMPTY, blockLevelChunkTicks, fluidLevelChunkTicks,
+ 0L, null, null, null);
+
+ chunk = new NMSSlimeChunk(levelChunk);
+
+ } else {
+ levelChunk = SlimeChunkConverter.deserializeSlimeChunk(this.instance, chunk);
+ chunk = new SafeNmsChunkWrapper(new NMSSlimeChunk(levelChunk), chunk);
+ }
+ this.chunkStorage.put(new ChunkPos(x, z), chunk);
+
+ return levelChunk;
+ }
+
+ // Authored by: Kenox <muranelp@gmail.com>
+ // Don't use the NMS live chunk in the chunk map
+ public void unload(LevelChunk providedChunk) {
+ final int x = providedChunk.locX;
+ final int z = providedChunk.locZ;
+
+ SlimeChunk chunk = new NMSSlimeChunk(providedChunk);
+
+ if (FastChunkPruner.canBePruned(this.liveWorld, providedChunk)) {
+ this.chunkStorage.remove(new ChunkPos(x, z));
+ return;
+ }
+
+ this.chunkStorage.put(new ChunkPos(x, z),
+ new SlimeChunkSkeleton(chunk.getX(), chunk.getZ(), chunk.getSections(),
+ chunk.getHeightMaps(), chunk.getTileEntities(), chunk.getEntities()));
+ }
+
+ @Override
+ public SlimeChunk getChunk(int x, int z) {
+ return this.chunkStorage.get(new ChunkPos(x, z));
+ }
+
+ @Override
+ public Collection<SlimeChunk> getChunkStorage() {
+ return this.chunkStorage.values();
+ }
+
+ @Override
+ public World getBukkitWorld() {
+ return this.instance.getWorld();
+ }
+
+ @Override
+ public SlimeWorld getSlimeWorldMirror() {
+ return this.liveWorld;
+ }
+
+ @Override
+ public SlimePropertyMap getPropertyMap() {
+ return this.propertyMap;
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ return this.getSaveStrategy() == null || this.readOnly;
+ }
+
+ @Override
+ public SlimeWorld clone(String worldName) {
+ try {
+ return clone(worldName, null);
+ } catch (WorldAlreadyExistsException | IOException ignored) {
+ return null; // Never going to happen
+ }
+ }
+
+ @Override
+ public SlimeWorld clone(String worldName, SlimeLoader loader) throws WorldAlreadyExistsException, IOException {
+ if (this.getName().equals(worldName)) {
+ throw new IllegalArgumentException("The clone world cannot have the same name as the original world!");
+ }
+
+ if (worldName == null) {
+ throw new IllegalArgumentException("The world name cannot be null!");
+ }
+ if (loader != null) {
+ if (loader.worldExists(worldName)) {
+ throw new WorldAlreadyExistsException(worldName);
+ }
+ }
+
+ SlimeWorld cloned = SkeletonCloning.fullClone(worldName, this, loader);
+ if (loader != null) {
+ loader.saveWorld(worldName, SlimeSerializer.serialize(cloned));
+ }
+
+ return cloned;
+ }
+
+ @Override
+ public int getDataVersion() {
+ return this.liveWorld.getDataVersion();
+ }
+
+ @Override
+ public SlimeLoader getSaveStrategy() {
+ return this.loader;
+ }
+
+ @Override
+ public CompoundTag getExtraData() {
+ return this.extra;
+ }
+
+ @Override
+ public Collection<CompoundTag> getWorldMaps() {
+ return List.of();
+ }
+
+ // public Map<ChunkPos, List<CompoundTag>> getEntityStorage() {
+ // return entityStorage;
+ // }
+
+ public SlimeWorld getForSerialization() {
+ SlimeWorld world = SkeletonCloning.weakCopy(this);
+
+ Map<ChunkPos, SlimeChunk> cloned = new HashMap<>();
+ for (Map.Entry<ChunkPos, SlimeChunk> entry : this.chunkStorage.entrySet()) {
+ SlimeChunk clonedChunk = entry.getValue();
+ // NMS "live" chunks need to be converted
+ {
+ LevelChunk chunk = null;
+ if (clonedChunk instanceof SafeNmsChunkWrapper safeNmsChunkWrapper) {
+ if (safeNmsChunkWrapper.shouldDefaultBackToSlimeChunk()) {
+ clonedChunk = safeNmsChunkWrapper.getSafety();
+ } else {
+ chunk = safeNmsChunkWrapper.getWrapper().getChunk();
+ }
+ } else if (clonedChunk instanceof NMSSlimeChunk nmsSlimeChunk) {
+ chunk = nmsSlimeChunk.getChunk();
+ }
+
+ if (chunk != null) {
+ if (FastChunkPruner.canBePruned(world, chunk)) {
+ continue;
+ }
+
+ clonedChunk = new SlimeChunkSkeleton(
+ clonedChunk.getX(),
+ clonedChunk.getZ(),
+ clonedChunk.getSections(),
+ clonedChunk.getHeightMaps(),
+ clonedChunk.getTileEntities(),
+ clonedChunk.getEntities()
+ );
+ }
+ }
+
+ cloned.put(entry.getKey(), clonedChunk);
+ }
+
+ return new SkeletonSlimeWorld(world.getName(),
+ world.getLoader(),
+ world.isReadOnly(),
+ cloned,
+ world.getExtraData(),
+ world.getPropertyMap(),
+ world.getDataVersion()
+ );
+ }
+
+ public SlimeLevelInstance getInstance() {
+ return instance;
+ }
+}
diff --git a/src/main/java/com/infernalsuite/aswm/level/SlimeLevelGenerator.java b/src/main/java/com/infernalsuite/aswm/level/SlimeLevelGenerator.java
new file mode 100644
index 0000000000000000000000000000000000000000..4f48b7a1a41aabc78cc9276fbf9f372cb117003f
--- /dev/null
+++ b/src/main/java/com/infernalsuite/aswm/level/SlimeLevelGenerator.java
@@ -0,0 +1,39 @@
+package com.infernalsuite.aswm.level;
+
+import com.mojang.serialization.Codec;
+import net.minecraft.core.Holder;
+import net.minecraft.world.level.biome.Biome;
+import net.minecraft.world.level.biome.BiomeSource;
+import net.minecraft.world.level.biome.Climate;
+import net.minecraft.world.level.levelgen.FlatLevelSource;
+import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+public class SlimeLevelGenerator extends FlatLevelSource {
+
+ public SlimeLevelGenerator(Holder<Biome> biome) {
+ super(new FlatLevelGeneratorSettings(Optional.empty(), biome, List.of()), getSource(biome));
+ }
+
+ private static BiomeSource getSource(Holder<Biome> biome) {
+ return new BiomeSource() {
+ @Override
+ protected Codec<? extends BiomeSource> codec() {
+ return null;
+ }
+
+ @Override
+ protected Stream<Holder<Biome>> collectPossibleBiomes() {
+ return Stream.of(biome);
+ }
+
+ @Override
+ public Holder<Biome> getNoiseBiome(int x, int y, int z, Climate.Sampler noise) {
+ return biome;
+ }
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/infernalsuite/aswm/level/SlimeLevelInstance.java b/src/main/java/com/infernalsuite/aswm/level/SlimeLevelInstance.java
new file mode 100644
2023-10-19 05:03:42 +00:00
index 0000000000000000000000000000000000000000..10fde8e966dd89b8371c764f0c6fc08a0e28ac44
--- /dev/null
+++ b/src/main/java/com/infernalsuite/aswm/level/SlimeLevelInstance.java
@@ -0,0 +1,195 @@
+package com.infernalsuite.aswm.level;
+
+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import com.infernalsuite.aswm.Converter;
+import com.infernalsuite.aswm.api.world.SlimeChunk;
+import com.infernalsuite.aswm.api.world.SlimeWorld;
+import com.infernalsuite.aswm.api.world.SlimeWorldInstance;
+import com.infernalsuite.aswm.api.world.properties.SlimeProperties;
+import com.infernalsuite.aswm.api.world.properties.SlimePropertyMap;
2023-10-19 05:03:42 +00:00
+import com.infernalsuite.aswm.serialization.slime.SlimeSerializer;
+import io.papermc.paper.chunk.system.scheduling.ChunkLoadTask;
+import io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler;
+import io.papermc.paper.chunk.system.scheduling.GenericDataLoadTask;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.Holder;
+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.level.ServerLevel;
+import net.minecraft.server.level.TicketType;
+import net.minecraft.util.ProgressListener;
+import net.minecraft.util.Unit;
+import net.minecraft.util.datafix.DataFixers;
+import net.minecraft.world.Difficulty;
+import net.minecraft.world.entity.EntityType;
2023-10-19 05:03:42 +00:00
+import net.minecraft.world.level.ChunkPos;
+import net.minecraft.world.level.biome.Biome;
+import net.minecraft.world.level.chunk.ChunkAccess;
+import net.minecraft.world.level.chunk.ChunkGenerator;
+import net.minecraft.world.level.dimension.LevelStem;
+import net.minecraft.world.level.storage.LevelStorageSource;
+import net.minecraft.world.level.storage.PrimaryLevelData;
+import net.minecraft.world.level.validation.DirectoryValidator;
+import org.apache.commons.io.FileUtils;
+import org.bukkit.Bukkit;
+import org.bukkit.event.world.WorldSaveEvent;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.UUID;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.function.Consumer;
+import java.util.logging.Level;
+import java.util.stream.Collectors;
+
+public class SlimeLevelInstance extends ServerLevel {
+
+
+ public static LevelStorageSource CUSTOM_LEVEL_STORAGE;
+
+ static {
+ try {
+ Path path = Files.createTempDirectory("swm-" + UUID.randomUUID().toString().substring(0, 5)).toAbsolutePath();
+ DirectoryValidator directoryvalidator = LevelStorageSource.parseValidator(path.resolve("allowed_symlinks.txt"));
+ CUSTOM_LEVEL_STORAGE = new LevelStorageSource(path, path, directoryvalidator, DataFixers.getDataFixer());
+
+ FileUtils.forceDeleteOnExit(path.toFile());
+
+ } catch (IOException ex) {
+ throw new IllegalStateException("Couldn't create dummy file directory.", ex);
+ }
+ }
+
+ private static final ExecutorService WORLD_SAVER_SERVICE = Executors.newFixedThreadPool(4, new ThreadFactoryBuilder()
+ .setNameFormat("SWM Pool Thread #%1$d").build());
+ private static final TicketType<Unit> SWM_TICKET = TicketType.create("swm-chunk", (a, b) -> 0);
+
+ private final Object saveLock = new Object();
+
+ private boolean ready = false;
+
+ public SlimeLevelInstance(SlimeBootstrap slimeBootstrap, PrimaryLevelData primaryLevelData,
+ ResourceKey<net.minecraft.world.level.Level> worldKey,
+ ResourceKey<LevelStem> dimensionKey, LevelStem worldDimension,
+ org.bukkit.World.Environment environment) throws IOException {
+
+ super(slimeBootstrap, MinecraftServer.getServer(), MinecraftServer.getServer().executor,
+ CUSTOM_LEVEL_STORAGE.createAccess(slimeBootstrap.initial().getName() + UUID.randomUUID(), dimensionKey),
+ primaryLevelData, worldKey, worldDimension,
2023-10-19 05:03:42 +00:00
+ MinecraftServer.getServer().progressListenerFactory.create(11), false, null, 0,
+ Collections.emptyList(), true, environment, null, null);
+ this.slimeInstance = new SlimeInMemoryWorld(slimeBootstrap, this);
+
+
+ SlimePropertyMap propertyMap = slimeBootstrap.initial().getPropertyMap();
+
+ this.serverLevelData.setDifficulty(Difficulty.valueOf(propertyMap.getValue(SlimeProperties.DIFFICULTY).toUpperCase()));
+ this.serverLevelData.setSpawn(new BlockPos(propertyMap.getValue(SlimeProperties.SPAWN_X), propertyMap.getValue(SlimeProperties.SPAWN_Y), propertyMap.getValue(SlimeProperties.SPAWN_Z)), 0);
+ super.setSpawnSettings(propertyMap.getValue(SlimeProperties.ALLOW_MONSTERS), propertyMap.getValue(SlimeProperties.ALLOW_ANIMALS));
+
+ this.pvpMode = propertyMap.getValue(SlimeProperties.PVP);
+
+ this.keepSpawnInMemory = false;
+ }
+
+ @Override
+ public ChunkGenerator getGenerator(SlimeBootstrap slimeBootstrap) {
+ String biomeStr = slimeBootstrap.initial().getPropertyMap().getValue(SlimeProperties.DEFAULT_BIOME);
+ ResourceKey<Biome> biomeKey = ResourceKey.create(Registries.BIOME, new ResourceLocation(biomeStr));
+ Holder<Biome> defaultBiome = MinecraftServer.getServer().registryAccess().registryOrThrow(Registries.BIOME).getHolder(biomeKey).orElseThrow();
+ return new SlimeLevelGenerator(defaultBiome);
+ }
+
+ @Override
+ public void save(@Nullable ProgressListener progressUpdate, boolean forceSave, boolean savingDisabled, boolean close) {
+ try {
+ if (!this.slimeInstance.isReadOnly() && !savingDisabled) {
+ Bukkit.getPluginManager().callEvent(new WorldSaveEvent(getWorld()));
+
+ //this.getChunkSource().save(forceSave);
+ this.serverLevelData.setWorldBorder(this.getWorldBorder().createSettings());
+ this.serverLevelData.setCustomBossEvents(MinecraftServer.getServer().getCustomBossEvents().save());
+
+ // Update level data
+ net.minecraft.nbt.CompoundTag compound = new net.minecraft.nbt.CompoundTag();
+ net.minecraft.nbt.CompoundTag nbtTagCompound = this.serverLevelData.createTag(MinecraftServer.getServer().registryAccess(), compound);
+
+ if (MinecraftServer.getServer().isStopped()) { // Make sure the world gets saved before stopping the server by running it from the main thread
+ save().get(); // Async wait for it to finish
+ this.slimeInstance.getLoader().unlockWorld(this.slimeInstance.getName()); // Unlock
+ } else {
+ this.save();
+ //WORLD_SAVER_SERVICE.execute(this::save);
+ }
+ }
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void saveIncrementally(boolean doFull) {
+ if (doFull) {
+ this.save(null, false, false);
+ }
+ }
+
+ private Future<?> save() {
+ synchronized (saveLock) { // Don't want to save the SlimeWorld from multiple threads simultaneously
+ SlimeWorldInstance slimeWorld = this.slimeInstance;
+ Bukkit.getLogger().log(Level.INFO, "Saving world " + this.slimeInstance.getName() + "...");
+ long start = System.currentTimeMillis();
+
+ Bukkit.getLogger().log(Level.INFO, "CONVERTING NMS -> SKELETON");
+ SlimeWorld world = this.slimeInstance.getForSerialization();
+ Bukkit.getLogger().log(Level.INFO, "CONVERTED TO SKELETON, PUSHING OFF-THREAD");
+ return WORLD_SAVER_SERVICE.submit(() -> {
+ try {
+ byte[] serializedWorld = SlimeSerializer.serialize(world);
+ long saveStart = System.currentTimeMillis();
+ slimeWorld.getSaveStrategy().saveWorld(slimeWorld.getName(), serializedWorld);
+ Bukkit.getLogger().log(Level.INFO, "World " + slimeWorld.getName() + " serialized in " + (saveStart - start) + "ms and saved in " + (System.currentTimeMillis() - saveStart) + "ms.");
+ } catch (IOException | IllegalStateException ex) {
+ ex.printStackTrace();
+ }
+ });
+
+ }
+ }
+
+ public SlimeWorldInstance getSlimeInstance() {
+ return this.slimeInstance;
+ }
+
+ public ChunkDataLoadTask getLoadTask(ChunkLoadTask task, ChunkTaskScheduler scheduler, ServerLevel world, int chunkX, int chunkZ, PrioritisedExecutor.Priority priority, Consumer<GenericDataLoadTask.TaskResult<ChunkAccess, Throwable>> onRun) {
+ return new ChunkDataLoadTask(task, scheduler, world, chunkX, chunkZ, priority, onRun);
+ }
+
+ public void loadEntities(int chunkX, int chunkZ) {
+ SlimeChunk slimeChunk = this.slimeInstance.getChunk(chunkX, chunkZ);
+ if (slimeChunk != null) {
+ this.getEntityLookup().addLegacyChunkEntities(new ArrayList<>(
+ EntityType.loadEntitiesRecursive(slimeChunk.getEntities()
+ .stream()
+ .map((tag) -> (net.minecraft.nbt.CompoundTag) Converter.convertTag(tag))
+ .collect(Collectors.toList()), this)
+ .toList()
2023-10-19 05:03:42 +00:00
+ ), new ChunkPos(chunkX, chunkZ));
+ }
+ }
+
+ // @Override
+ // public void unload(LevelChunk chunk) {
+ // this.slimeInstance.unload(chunk);
+ // super.unload(chunk);
+ // }
+}
diff --git a/src/main/java/com/infernalsuite/aswm/util/NmsUtil.java b/src/main/java/com/infernalsuite/aswm/util/NmsUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..3500005bb09dc484bc333f1e0799613d097a37d3
--- /dev/null
+++ b/src/main/java/com/infernalsuite/aswm/util/NmsUtil.java
@@ -0,0 +1,9 @@
+package com.infernalsuite.aswm.util;
+
+public class NmsUtil {
+
+ public static long asLong(int chunkX, int chunkZ) {
+ return (((long) chunkZ) * Integer.MAX_VALUE + ((long) chunkX));
+ //return (long)chunkX & 4294967295L | ((long)chunkZ & 4294967295L) << 32;
+ }
+}
\ No newline at end of file