mirror of
https://github.com/AtlasMediaGroup/Scissors.git
synced 2024-11-01 04:37:09 +00:00
2817 lines
136 KiB
Diff
2817 lines
136 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
|
|
Date: Mon, 26 Dec 2022 11:25:35 -0500
|
|
Subject: [PATCH] AdvancedSlimePaper Server Changes
|
|
|
|
AdvancedSlimePaper
|
|
Copyright (C) 2023 InfernalSuite
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
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
|
|
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;
|
|
+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];
|
|
+
|
|
+ CompoundTag blockStateTag = blockStateTag = convertAndBack(dataSection.getBlockStatesTag(), (tag) -> {
|
|
+ WalkerUtils.convertList(MCTypeRegistry.BLOCK_STATE, new NBTMapType(tag), "palette", currentVersion, newVersion);
|
|
+ });
|
|
+
|
|
+ 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
|
|
+ );
|
|
+ }
|
|
+
|
|
+
|
|
+ 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);
|
|
+
|
|
+ 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
|
|
index 0000000000000000000000000000000000000000..d014c27b896ea862bb3f7ff7d39df476513bb5f8
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/infernalsuite/aswm/SlimeNMSBridgeImpl.java
|
|
@@ -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
|
|
index 0000000000000000000000000000000000000000..66093eaed9a45c7b714471915c8bd38f23ac7894
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/infernalsuite/aswm/level/ChunkDataLoadTask.java
|
|
@@ -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
|
|
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;
|
|
+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
|
|
index 0000000000000000000000000000000000000000..043de6fba8387ce851d1d54c501cd834a1760c60
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/infernalsuite/aswm/level/SlimeInMemoryWorld.java
|
|
@@ -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;
|
|
+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;
|
|
+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
|
|
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;
|
|
+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;
|
|
+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,
|
|
+ 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()
|
|
+ ), 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
|
|
diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java
|
|
index abd0217cf0bff183c8e262edc173a53403797c1a..42170127cc1ed35cb2aec6bf7eb473a0137c63bb 100644
|
|
--- a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java
|
|
+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java
|
|
@@ -165,7 +165,8 @@ public final class ChunkHolderManager {
|
|
return this.chunkHolders.size();
|
|
}
|
|
|
|
- public void close(final boolean save, final boolean halt) {
|
|
+ public void close(boolean save, final boolean halt) { // ASWM
|
|
+ if (this.world instanceof com.infernalsuite.aswm.level.SlimeLevelInstance) save = false; // ASWM
|
|
TickThread.ensureTickThread("Closing world off-main");
|
|
if (halt) {
|
|
LOGGER.info("Waiting 60s for chunk system to halt for world '" + this.world.getWorld().getName() + "'");
|
|
@@ -1316,7 +1317,7 @@ public final class ChunkHolderManager {
|
|
|
|
public boolean processTicketUpdates() {
|
|
co.aikar.timings.MinecraftTimings.distanceManagerTick.startTiming(); try { // Paper - add timings for distance manager
|
|
- return this.processTicketUpdates(true, true, null);
|
|
+ return this.processTicketUpdates(true, true, null);
|
|
} finally { co.aikar.timings.MinecraftTimings.distanceManagerTick.stopTiming(); } // Paper - add timings for distance manager
|
|
}
|
|
|
|
diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLoadTask.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLoadTask.java
|
|
index e7fb084ddb88ab62f1d493a999cc82b9258d275e..943cdd570c9e5c87ee9f3984404e08b4cdffeca0 100644
|
|
--- a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLoadTask.java
|
|
+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLoadTask.java
|
|
@@ -6,6 +6,7 @@ import ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock;
|
|
import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
|
|
import ca.spottedleaf.dataconverter.minecraft.MCDataConverter;
|
|
import ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry;
|
|
+import com.infernalsuite.aswm.level.CommonLoadTask;
|
|
import com.mojang.logging.LogUtils;
|
|
import io.papermc.paper.chunk.system.io.RegionFileIOThread;
|
|
import io.papermc.paper.chunk.system.poi.PoiChunk;
|
|
@@ -32,8 +33,8 @@ public final class ChunkLoadTask extends ChunkProgressionTask {
|
|
|
|
private static final Logger LOGGER = LogUtils.getClassLogger();
|
|
|
|
- private final NewChunkHolder chunkHolder;
|
|
- private final ChunkDataLoadTask loadTask;
|
|
+ public final NewChunkHolder chunkHolder; // ASWM
|
|
+ private final CommonLoadTask loadTask;
|
|
|
|
private volatile boolean cancelled;
|
|
private NewChunkHolder.GenericDataLoadTaskCallback entityLoadTask;
|
|
@@ -45,11 +46,20 @@ public final class ChunkLoadTask extends ChunkProgressionTask {
|
|
final NewChunkHolder chunkHolder, final PrioritisedExecutor.Priority priority) {
|
|
super(scheduler, world, chunkX, chunkZ);
|
|
this.chunkHolder = chunkHolder;
|
|
- this.loadTask = new ChunkDataLoadTask(scheduler, world, chunkX, chunkZ, priority);
|
|
- this.loadTask.addCallback((final GenericDataLoadTask.TaskResult<ChunkAccess, Throwable> result) -> {
|
|
- ChunkLoadTask.this.loadResult = result; // must be before getAndDecrement
|
|
- ChunkLoadTask.this.tryCompleteLoad();
|
|
- });
|
|
+ // ASWM start
|
|
+ if (world instanceof com.infernalsuite.aswm.level.SlimeLevelInstance levelInstance) {
|
|
+
|
|
+ this.loadTask = levelInstance.getLoadTask(this, scheduler, world, chunkX, chunkZ, priority, result -> {
|
|
+ ChunkLoadTask.this.complete(result == null ? null : result.left(), result == null ? null : result.right());
|
|
+ });
|
|
+ } else {
|
|
+ ChunkDataLoadTask task = new ChunkDataLoadTask(scheduler, world, chunkX, chunkZ, priority);
|
|
+ task.addCallback((final GenericDataLoadTask.TaskResult<ChunkAccess, Throwable> result) -> {
|
|
+ ChunkLoadTask.this.complete(result == null ? null : result.left(), result == null ? null : result.right());
|
|
+ });
|
|
+ this.loadTask = task;
|
|
+ }
|
|
+ // ASWM end
|
|
}
|
|
|
|
private void tryCompleteLoad() {
|
|
@@ -274,7 +284,7 @@ public final class ChunkLoadTask extends ChunkProgressionTask {
|
|
}
|
|
}
|
|
|
|
- public static final class ChunkDataLoadTask extends CallbackDataLoadTask<ChunkAccess, ChunkAccess> {
|
|
+ public static final class ChunkDataLoadTask extends CallbackDataLoadTask<ChunkAccess, ChunkAccess> implements CommonLoadTask { // ASWM
|
|
protected ChunkDataLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX,
|
|
final int chunkZ, final PrioritisedExecutor.Priority priority) {
|
|
super(scheduler, world, chunkX, chunkZ, RegionFileIOThread.RegionFileType.CHUNK_DATA, priority);
|
|
diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java
|
|
index b66a7d4aab887309579154815a0d4abf9de506b0..2fe8a17cdc2a823548fa180f72cd70e4d5e5fdf3 100644
|
|
--- a/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java
|
|
+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java
|
|
@@ -1775,7 +1775,7 @@ public final class NewChunkHolder {
|
|
}
|
|
|
|
boolean canSaveChunk = !(chunk instanceof LevelChunk levelChunk && levelChunk.mustNotSave) &&
|
|
- (chunk != null && ((shutdown || chunk instanceof LevelChunk) && chunk.isUnsaved()));
|
|
+ (chunk != null && ((shutdown || chunk instanceof LevelChunk) && chunk.isUnsaved()));
|
|
boolean canSavePOI = !(chunk instanceof LevelChunk levelChunk && levelChunk.mustNotSave) && (poi != null && poi.isDirty());
|
|
boolean canSaveEntities = entities != null;
|
|
|
|
diff --git a/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java
|
|
index 7e8dc9e8f381abfdcce2746edc93122d623622d1..12aadf4c981cdb1a6405de99016f4b40e83a04b8 100644
|
|
--- a/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java
|
|
+++ b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java
|
|
@@ -36,7 +36,7 @@ public final class ChunkEntitySlices {
|
|
protected final EntityCollectionBySection allEntities;
|
|
protected final EntityCollectionBySection hardCollidingEntities;
|
|
protected final Reference2ObjectOpenHashMap<Class<? extends Entity>, EntityCollectionBySection> entitiesByClass;
|
|
- protected final EntityList entities = new EntityList();
|
|
+ public final EntityList entities = new EntityList(); // ASWM
|
|
|
|
public FullChunkStatus status;
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
index 97745f0bab8d82d397c6c2a5775aed92bca0a034..7a9373df55bdbd01eb30ac9c623561087bd6b43e 100644
|
|
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
@@ -268,7 +268,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
private final PackRepository packRepository;
|
|
private final ServerScoreboard scoreboard;
|
|
@Nullable
|
|
- private CommandStorage commandStorage;
|
|
+ public CommandStorage commandStorage;
|
|
private final CustomBossEvents customBossEvents;
|
|
private final ServerFunctionManager functionManager;
|
|
private boolean enforceWhitelist;
|
|
@@ -444,60 +444,75 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
LevelStorageSource.LevelStorageAccess worldSession = this.storageSource;
|
|
|
|
Registry<LevelStem> dimensions = this.registries.compositeAccess().registryOrThrow(Registries.LEVEL_STEM);
|
|
- for (LevelStem worldDimension : dimensions) {
|
|
+ for(LevelStem worldDimension : dimensions) {
|
|
ResourceKey<LevelStem> dimensionKey = dimensions.getResourceKey(worldDimension).get();
|
|
|
|
ServerLevel world;
|
|
int dimension = 0;
|
|
|
|
- if (dimensionKey == LevelStem.NETHER) {
|
|
- if (this.isNetherEnabled()) {
|
|
+ if(dimensionKey == LevelStem.NETHER) {
|
|
+ if(this.isNetherEnabled()) {
|
|
dimension = -1;
|
|
- } else {
|
|
+ // ASWM START
|
|
+ if(com.infernalsuite.aswm.SlimeNMSBridgeImpl.instance().loadNetherOverride()) {
|
|
+ continue;
|
|
+ }
|
|
+ // ASWM END
|
|
+ }else{
|
|
continue;
|
|
}
|
|
- } else if (dimensionKey == LevelStem.END) {
|
|
- if (this.server.getAllowEnd()) {
|
|
+ }else if(dimensionKey == LevelStem.END) {
|
|
+ if(this.server.getAllowEnd()) {
|
|
dimension = 1;
|
|
- } else {
|
|
+ // ASWM START
|
|
+ if(com.infernalsuite.aswm.SlimeNMSBridgeImpl.instance().loadEndOverride()) {
|
|
+ continue;
|
|
+ }
|
|
+ // ASWM END
|
|
+ }else{
|
|
continue;
|
|
}
|
|
- } else if (dimensionKey != LevelStem.OVERWORLD) {
|
|
+ }else if(dimensionKey != LevelStem.OVERWORLD) {
|
|
dimension = -999;
|
|
}
|
|
+ // ASWM START
|
|
+ else if(dimensionKey == LevelStem.OVERWORLD && com.infernalsuite.aswm.SlimeNMSBridgeImpl.instance().loadOverworldOverride()) {
|
|
+ continue;
|
|
+ }
|
|
+ // ASWM END
|
|
|
|
String worldType = (dimension == -999) ? dimensionKey.location().getNamespace() + "_" + dimensionKey.location().getPath() : org.bukkit.World.Environment.getEnvironment(dimension).toString().toLowerCase();
|
|
String name = (dimensionKey == LevelStem.OVERWORLD) ? s : s + "_" + worldType;
|
|
- if (dimension != 0) {
|
|
+ if(dimension != 0) {
|
|
File newWorld = LevelStorageSource.getStorageFolder(new File(name).toPath(), dimensionKey).toFile();
|
|
File oldWorld = LevelStorageSource.getStorageFolder(new File(s).toPath(), dimensionKey).toFile();
|
|
File oldLevelDat = new File(new File(s), "level.dat"); // The data folders exist on first run as they are created in the PersistentCollection constructor above, but the level.dat won't
|
|
|
|
- if (!newWorld.isDirectory() && oldWorld.isDirectory() && oldLevelDat.isFile()) {
|
|
+ if(!newWorld.isDirectory() && oldWorld.isDirectory() && oldLevelDat.isFile()) {
|
|
MinecraftServer.LOGGER.info("---- Migration of old " + worldType + " folder required ----");
|
|
MinecraftServer.LOGGER.info("Unfortunately due to the way that Minecraft implemented multiworld support in 1.6, Bukkit requires that you move your " + worldType + " folder to a new location in order to operate correctly.");
|
|
MinecraftServer.LOGGER.info("We will move this folder for you, but it will mean that you need to move it back should you wish to stop using Bukkit in the future.");
|
|
MinecraftServer.LOGGER.info("Attempting to move " + oldWorld + " to " + newWorld + "...");
|
|
|
|
- if (newWorld.exists()) {
|
|
+ if(newWorld.exists()) {
|
|
MinecraftServer.LOGGER.warn("A file or folder already exists at " + newWorld + "!");
|
|
MinecraftServer.LOGGER.info("---- Migration of old " + worldType + " folder failed ----");
|
|
- } else if (newWorld.getParentFile().mkdirs()) {
|
|
- if (oldWorld.renameTo(newWorld)) {
|
|
+ }else if(newWorld.getParentFile().mkdirs()) {
|
|
+ if(oldWorld.renameTo(newWorld)) {
|
|
MinecraftServer.LOGGER.info("Success! To restore " + worldType + " in the future, simply move " + newWorld + " to " + oldWorld);
|
|
// Migrate world data too.
|
|
try {
|
|
com.google.common.io.Files.copy(oldLevelDat, new File(new File(name), "level.dat"));
|
|
org.apache.commons.io.FileUtils.copyDirectory(new File(new File(s), "data"), new File(new File(name), "data"));
|
|
- } catch (IOException exception) {
|
|
+ } catch(IOException exception) {
|
|
MinecraftServer.LOGGER.warn("Unable to migrate world data.");
|
|
}
|
|
MinecraftServer.LOGGER.info("---- Migration of old " + worldType + " folder complete ----");
|
|
- } else {
|
|
+ }else{
|
|
MinecraftServer.LOGGER.warn("Could not move folder " + oldWorld + " to " + newWorld + "!");
|
|
MinecraftServer.LOGGER.info("---- Migration of old " + worldType + " folder failed ----");
|
|
}
|
|
- } else {
|
|
+ }else{
|
|
MinecraftServer.LOGGER.warn("Could not create path for " + newWorld + "!");
|
|
MinecraftServer.LOGGER.info("---- Migration of old " + worldType + " folder failed ----");
|
|
}
|
|
@@ -505,7 +520,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
|
|
try {
|
|
worldSession = LevelStorageSource.createDefault(this.server.getWorldContainer().toPath()).validateAndCreateAccess(name, dimensionKey);
|
|
- } catch (IOException | ContentValidationException ex) {
|
|
+ } catch(IOException | ContentValidationException ex) {
|
|
throw new RuntimeException(ex);
|
|
}
|
|
}
|
|
@@ -519,18 +534,18 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
DynamicOps<Tag> dynamicops = RegistryOps.create(NbtOps.INSTANCE, (HolderLookup.Provider) worldloader_a.datapackWorldgen());
|
|
Pair<WorldData, WorldDimensions.Complete> pair = worldSession.getDataTag(dynamicops, worldloader_a.dataConfiguration(), iregistry, worldloader_a.datapackWorldgen().allRegistriesLifecycle());
|
|
|
|
- if (pair != null) {
|
|
+ if(pair != null) {
|
|
worlddata = (PrimaryLevelData) pair.getFirst();
|
|
- } else {
|
|
+ }else{
|
|
LevelSettings worldsettings;
|
|
WorldOptions worldoptions;
|
|
WorldDimensions worlddimensions;
|
|
|
|
- if (this.isDemo()) {
|
|
+ if(this.isDemo()) {
|
|
worldsettings = MinecraftServer.DEMO_SETTINGS;
|
|
worldoptions = WorldOptions.DEMO_OPTIONS;
|
|
worlddimensions = WorldPresets.createNormalWorldDimensions(worldloader_a.datapackWorldgen());
|
|
- } else {
|
|
+ }else{
|
|
DedicatedServerProperties dedicatedserverproperties = ((DedicatedServer) this).getProperties();
|
|
|
|
worldsettings = new LevelSettings(dedicatedserverproperties.levelName, dedicatedserverproperties.gamemode, dedicatedserverproperties.hardcore, dedicatedserverproperties.difficulty, false, new GameRules(), worldloader_a.dataConfiguration());
|
|
@@ -555,12 +570,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
LevelStem worlddimension = (LevelStem) dimensions.get(dimensionKey);
|
|
|
|
org.bukkit.generator.WorldInfo worldInfo = new org.bukkit.craftbukkit.generator.CraftWorldInfo(iworlddataserver, worldSession, org.bukkit.World.Environment.getEnvironment(dimension), worlddimension.type().value(), worlddimension.generator(), this.registryAccess()); // Paper
|
|
- if (biomeProvider == null && gen != null) {
|
|
+ if(biomeProvider == null && gen != null) {
|
|
biomeProvider = gen.getDefaultBiomeProvider(worldInfo);
|
|
}
|
|
|
|
// Paper start - fix and optimise world upgrading
|
|
- if (options.has("forceUpgrade")) {
|
|
+ if(options.has("forceUpgrade")) {
|
|
net.minecraft.server.Main.convertWorldButItWorks(
|
|
dimensionKey, worldSession, DataFixers.getDataFixer(), worlddimension.generator().getTypeNameForDataFixer(), options.has("eraseCache")
|
|
);
|
|
@@ -568,7 +583,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
// Paper end - fix and optimise world upgrading
|
|
ResourceKey<Level> worldKey = ResourceKey.create(Registries.DIMENSION, dimensionKey.location());
|
|
|
|
- if (dimensionKey == LevelStem.OVERWORLD) {
|
|
+ if(dimensionKey == LevelStem.OVERWORLD) {
|
|
this.worldData = worlddata;
|
|
this.worldData.setGameType(((DedicatedServer) this).getProperties().gamemode); // From DedicatedServer.init
|
|
|
|
@@ -579,13 +594,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
this.readScoreboard(worldpersistentdata);
|
|
this.server.scoreboardManager = new org.bukkit.craftbukkit.scoreboard.CraftScoreboardManager(this, world.getScoreboard());
|
|
this.commandStorage = new CommandStorage(worldpersistentdata);
|
|
- } else {
|
|
+ }else{
|
|
ChunkProgressListener worldloadlistener = this.progressListenerFactory.create(11);
|
|
// Paper start - option to use the dimension_type to check if spawners should be added. I imagine mojang will add some datapack-y way of managing this in the future.
|
|
final List<CustomSpawner> spawners;
|
|
- if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.useDimensionTypeForCustomSpawners && this.registryAccess().registryOrThrow(Registries.DIMENSION_TYPE).getResourceKey(worlddimension.type().value()).orElseThrow() == net.minecraft.world.level.dimension.BuiltinDimensionTypes.OVERWORLD) {
|
|
+ if(io.papermc.paper.configuration.GlobalConfiguration.get().misc.useDimensionTypeForCustomSpawners && this.registryAccess().registryOrThrow(Registries.DIMENSION_TYPE).getResourceKey(worlddimension.type().value()).orElseThrow() == net.minecraft.world.level.dimension.BuiltinDimensionTypes.OVERWORLD) {
|
|
spawners = list;
|
|
- } else {
|
|
+ }else{
|
|
spawners = Collections.emptyList();
|
|
}
|
|
world = new ServerLevel(this, this.executor, worldSession, iworlddataserver, worldKey, worlddimension, worldloadlistener, flag, j, spawners, true, this.overworld().getRandomSequences(), org.bukkit.World.Environment.getEnvironment(dimension), gen, biomeProvider);
|
|
@@ -599,12 +614,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
// Paper - move up
|
|
this.getPlayerList().addWorldborderListener(world);
|
|
|
|
- if (worlddata.getCustomBossEvents() != null) {
|
|
+ if(worlddata.getCustomBossEvents() != null) {
|
|
this.getCustomBossEvents().load(worlddata.getCustomBossEvents());
|
|
}
|
|
}
|
|
this.forceDifficulty();
|
|
- for (ServerLevel worldserver : this.getAllLevels()) {
|
|
+ for(ServerLevel worldserver : this.getAllLevels()) {
|
|
this.prepareLevels(worldserver.getChunkSource().chunkMap.progressListener, worldserver);
|
|
//worldserver.entityManager.tick(); // SPIGOT-6526: Load pending entities so they are available to the API // Paper - rewrite chunk system, not required to "tick" anything
|
|
this.server.getPluginManager().callEvent(new org.bukkit.event.world.WorldLoadEvent(worldserver.getWorld()));
|
|
@@ -613,11 +628,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
// Paper start - Handle collideRule team for player collision toggle
|
|
final ServerScoreboard scoreboard = this.getScoreboard();
|
|
final java.util.Collection<String> toRemove = scoreboard.getPlayerTeams().stream().filter(team -> team.getName().startsWith("collideRule_")).map(net.minecraft.world.scores.PlayerTeam::getName).collect(java.util.stream.Collectors.toList());
|
|
- for (String teamName : toRemove) {
|
|
+ for(String teamName : toRemove) {
|
|
scoreboard.removePlayerTeam(scoreboard.getPlayerTeam(teamName)); // Clean up after ourselves
|
|
}
|
|
|
|
- if (!io.papermc.paper.configuration.GlobalConfiguration.get().collisions.enablePlayerCollisions) {
|
|
+ if(!io.papermc.paper.configuration.GlobalConfiguration.get().collisions.enablePlayerCollisions) {
|
|
this.getPlayerList().collideRuleTeamName = org.apache.commons.lang3.StringUtils.left("collideRule_" + java.util.concurrent.ThreadLocalRandom.current().nextInt(), 16);
|
|
net.minecraft.world.scores.PlayerTeam collideTeam = scoreboard.addPlayerTeam(this.getPlayerList().collideRuleTeamName);
|
|
collideTeam.setSeeFriendlyInvisibles(false); // Because we want to mimic them not being on a team at all
|
|
@@ -752,26 +767,26 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
// CraftBukkit end
|
|
if (worldserver.getWorld().getKeepSpawnInMemory()) { // Paper
|
|
|
|
- MinecraftServer.LOGGER.info("Preparing start region for dimension {}", worldserver.dimension().location());
|
|
- BlockPos blockposition = worldserver.getSharedSpawnPos();
|
|
+ MinecraftServer.LOGGER.info("Preparing start region for dimension {}", worldserver.dimension().location());
|
|
+ BlockPos blockposition = worldserver.getSharedSpawnPos();
|
|
|
|
- worldloadlistener.updateSpawnPos(new ChunkPos(blockposition));
|
|
- //ChunkProviderServer chunkproviderserver = worldserver.getChunkProvider(); // Paper - move up
|
|
+ worldloadlistener.updateSpawnPos(new ChunkPos(blockposition));
|
|
+ //ChunkProviderServer chunkproviderserver = worldserver.getChunkProvider(); // Paper - move up
|
|
|
|
- this.nextTickTime = Util.getMillis();
|
|
- // Paper start - configurable spawn reason
|
|
- int radiusBlocks = worldserver.paperConfig().spawn.keepSpawnLoadedRange * 16;
|
|
- int radiusChunks = radiusBlocks / 16 + ((radiusBlocks & 15) != 0 ? 1 : 0);
|
|
- int totalChunks = ((radiusChunks) * 2 + 1);
|
|
- totalChunks *= totalChunks;
|
|
- worldloadlistener.setChunkRadius(radiusBlocks / 16);
|
|
-
|
|
- worldserver.addTicketsForSpawn(radiusBlocks, blockposition);
|
|
- // Paper end
|
|
+ this.nextTickTime = Util.getMillis();
|
|
+ // Paper start - configurable spawn reason
|
|
+ int radiusBlocks = worldserver.paperConfig().spawn.keepSpawnLoadedRange * 16;
|
|
+ int radiusChunks = radiusBlocks / 16 + ((radiusBlocks & 15) != 0 ? 1 : 0);
|
|
+ int totalChunks = ((radiusChunks) * 2 + 1);
|
|
+ totalChunks *= totalChunks;
|
|
+ worldloadlistener.setChunkRadius(radiusBlocks / 16);
|
|
+
|
|
+ worldserver.addTicketsForSpawn(radiusBlocks, blockposition);
|
|
+ // Paper end
|
|
|
|
- // this.nextTickTime = SystemUtils.getMillis() + 10L;
|
|
- this.executeModerately();
|
|
- // Iterator iterator = this.levels.values().iterator();
|
|
+ // this.nextTickTime = SystemUtils.getMillis() + 10L;
|
|
+ this.executeModerately();
|
|
+ // Iterator iterator = this.levels.values().iterator();
|
|
}
|
|
|
|
if (true) {
|
|
@@ -1121,7 +1136,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
long j = i / 50L;
|
|
|
|
if (this.server.getWarnOnOverload()) // CraftBukkit
|
|
- MinecraftServer.LOGGER.warn("Can't keep up! Is the server overloaded? Running {}ms or {} ticks behind", i, j);
|
|
+ MinecraftServer.LOGGER.warn("Can't keep up! Is the server overloaded? Running {}ms or {} ticks behind", i, j);
|
|
this.nextTickTime += j * 50L;
|
|
this.lastOverloadWarning = this.nextTickTime;
|
|
}
|
|
@@ -2639,7 +2654,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
}
|
|
|
|
public final java.util.concurrent.ExecutorService chatExecutor = java.util.concurrent.Executors.newCachedThreadPool(
|
|
- new com.google.common.util.concurrent.ThreadFactoryBuilder().setDaemon(true).setNameFormat("Async Chat Thread - #%d").setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER)).build()); // Paper
|
|
+ new com.google.common.util.concurrent.ThreadFactoryBuilder().setDaemon(true).setNameFormat("Async Chat Thread - #%d").setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER)).build()); // Paper
|
|
|
|
public ChatDecorator getChatDecorator() {
|
|
// Paper start - moved to ChatPreviewProcessor
|
|
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
|
index 17b6925b46f8386dcfc561483693de516465ec12..ebba74b64206722b3514797ef512ee591b4a3d60 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
|
@@ -167,7 +167,11 @@ public class ServerChunkCache extends ChunkSource {
|
|
public final io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<LevelChunk> entityTickingChunks = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>(4096, 0.75f, 4096, 0.15, true);
|
|
// Paper end
|
|
|
|
- public ServerChunkCache(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor workerExecutor, ChunkGenerator chunkGenerator, int viewDistance, int simulationDistance, boolean dsync, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier<DimensionDataStorage> persistentStateManagerFactory) {
|
|
+ // ASWM START
|
|
+ public final com.infernalsuite.aswm.level.SlimeBootstrap bootstrap;
|
|
+ public ServerChunkCache(com.infernalsuite.aswm.level.SlimeBootstrap bootstrap, ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor workerExecutor, ChunkGenerator chunkGenerator, int viewDistance, int simulationDistance, boolean dsync, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier<DimensionDataStorage> persistentStateManagerFactory) { // ASWM
|
|
+ this.bootstrap = bootstrap;
|
|
+ // ASWM end
|
|
this.level = world;
|
|
this.mainThreadProcessor = new ServerChunkCache.MainThreadExecutor(world);
|
|
this.mainThread = Thread.currentThread();
|
|
@@ -296,7 +300,7 @@ public class ServerChunkCache extends ChunkSource {
|
|
// Paper end
|
|
com.destroystokyo.paper.io.SyncLoadFinder.logSyncLoad(this.level, x1, z1); // Paper - sync load info
|
|
this.level.timings.syncChunkLoad.startTiming(); // Paper
|
|
- chunkproviderserver_b.managedBlock(completablefuture::isDone);
|
|
+ chunkproviderserver_b.managedBlock(completablefuture::isDone);
|
|
io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.popChunkWait(); // Paper - async chunk debug // Paper - rewrite chunk system
|
|
this.level.timings.syncChunkLoad.stopTiming(); // Paper
|
|
} // Paper
|
|
@@ -448,7 +452,7 @@ public class ServerChunkCache extends ChunkSource {
|
|
public void save(boolean flush) {
|
|
this.runDistanceManagerUpdates();
|
|
try (co.aikar.timings.Timing timed = level.timings.chunkSaveData.startTiming()) { // Paper - Timings
|
|
- this.chunkMap.saveAllChunks(flush);
|
|
+ this.chunkMap.saveAllChunks(flush);
|
|
} // Paper - Timings
|
|
}
|
|
|
|
@@ -614,47 +618,47 @@ public class ServerChunkCache extends ChunkSource {
|
|
iterator1 = shuffled.iterator();
|
|
}
|
|
try {
|
|
- // Paper end - optimise chunk tick iteration
|
|
- while (iterator1.hasNext()) {
|
|
- LevelChunk chunk1 = iterator1.next(); // Paper - optimise chunk tick iteration
|
|
- ChunkPos chunkcoordintpair = chunk1.getPos();
|
|
+ // Paper end - optimise chunk tick iteration
|
|
+ while (iterator1.hasNext()) {
|
|
+ LevelChunk chunk1 = iterator1.next(); // Paper - optimise chunk tick iteration
|
|
+ ChunkPos chunkcoordintpair = chunk1.getPos();
|
|
|
|
- // Paper start - optimise chunk tick iteration
|
|
- com.destroystokyo.paper.util.maplist.ReferenceList<ServerPlayer> playersNearby
|
|
- = nearbyPlayers.getPlayers(chunkcoordintpair, io.papermc.paper.util.player.NearbyPlayers.NearbyMapType.SPAWN_RANGE);
|
|
- if (playersNearby == null) {
|
|
- continue;
|
|
- }
|
|
- Object[] rawData = playersNearby.getRawData();
|
|
- boolean spawn = false;
|
|
- boolean tick = false;
|
|
- for (int itr = 0, len = playersNearby.size(); itr < len; ++itr) {
|
|
- ServerPlayer player = (ServerPlayer)rawData[itr];
|
|
- if (player.isSpectator()) {
|
|
+ // Paper start - optimise chunk tick iteration
|
|
+ com.destroystokyo.paper.util.maplist.ReferenceList<ServerPlayer> playersNearby
|
|
+ = nearbyPlayers.getPlayers(chunkcoordintpair, io.papermc.paper.util.player.NearbyPlayers.NearbyMapType.SPAWN_RANGE);
|
|
+ if (playersNearby == null) {
|
|
continue;
|
|
}
|
|
+ Object[] rawData = playersNearby.getRawData();
|
|
+ boolean spawn = false;
|
|
+ boolean tick = false;
|
|
+ for (int itr = 0, len = playersNearby.size(); itr < len; ++itr) {
|
|
+ ServerPlayer player = (ServerPlayer)rawData[itr];
|
|
+ if (player.isSpectator()) {
|
|
+ continue;
|
|
+ }
|
|
|
|
- double distance = ChunkMap.euclideanDistanceSquared(chunkcoordintpair, player);
|
|
- spawn |= player.lastEntitySpawnRadiusSquared >= distance;
|
|
- tick |= ((double)io.papermc.paper.util.player.NearbyPlayers.SPAWN_RANGE_VIEW_DISTANCE_BLOCKS) * ((double)io.papermc.paper.util.player.NearbyPlayers.SPAWN_RANGE_VIEW_DISTANCE_BLOCKS) >= distance;
|
|
- if (spawn & tick) {
|
|
- break;
|
|
- }
|
|
- }
|
|
- // Paper end - optimise chunk tick iteration
|
|
- if (tick && chunk1.chunkStatus.isOrAfter(net.minecraft.server.level.FullChunkStatus.ENTITY_TICKING)) { // Paper - optimise chunk tick iteration
|
|
- chunk1.incrementInhabitedTime(j);
|
|
- if (spawn && flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair)) { // Spigot // Paper - optimise chunk tick iteration
|
|
- NaturalSpawner.spawnForChunk(this.level, chunk1, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1);
|
|
+ double distance = ChunkMap.euclideanDistanceSquared(chunkcoordintpair, player);
|
|
+ spawn |= player.lastEntitySpawnRadiusSquared >= distance;
|
|
+ tick |= ((double)io.papermc.paper.util.player.NearbyPlayers.SPAWN_RANGE_VIEW_DISTANCE_BLOCKS) * ((double)io.papermc.paper.util.player.NearbyPlayers.SPAWN_RANGE_VIEW_DISTANCE_BLOCKS) >= distance;
|
|
+ if (spawn & tick) {
|
|
+ break;
|
|
+ }
|
|
}
|
|
+ // Paper end - optimise chunk tick iteration
|
|
+ if (tick && chunk1.chunkStatus.isOrAfter(net.minecraft.server.level.FullChunkStatus.ENTITY_TICKING)) { // Paper - optimise chunk tick iteration
|
|
+ chunk1.incrementInhabitedTime(j);
|
|
+ if (spawn && flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair)) { // Spigot // Paper - optimise chunk tick iteration
|
|
+ NaturalSpawner.spawnForChunk(this.level, chunk1, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1);
|
|
+ }
|
|
|
|
- if (true || this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) { // Paper - optimise chunk tick iteration
|
|
- this.level.tickChunk(chunk1, k);
|
|
- if ((chunksTicked++ & 1) == 0) net.minecraft.server.MinecraftServer.getServer().executeMidTickTasks(); // Paper
|
|
+ if (true || this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) { // Paper - optimise chunk tick iteration
|
|
+ this.level.tickChunk(chunk1, k);
|
|
+ if ((chunksTicked++ & 1) == 0) net.minecraft.server.MinecraftServer.getServer().executeMidTickTasks(); // Paper
|
|
+ }
|
|
}
|
|
}
|
|
- }
|
|
- // Paper start - optimise chunk tick iteration
|
|
+ // Paper start - optimise chunk tick iteration
|
|
} finally {
|
|
if (iterator1 instanceof io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.Iterator safeIterator) {
|
|
safeIterator.finishedIterating();
|
|
@@ -665,13 +669,13 @@ public class ServerChunkCache extends ChunkSource {
|
|
gameprofilerfiller.popPush("customSpawners");
|
|
if (flag2) {
|
|
try (co.aikar.timings.Timing ignored = this.level.timings.miscMobSpawning.startTiming()) { // Paper - timings
|
|
- this.level.tickCustomSpawners(this.spawnEnemies, this.spawnFriendlies);
|
|
+ this.level.tickCustomSpawners(this.spawnEnemies, this.spawnFriendlies);
|
|
} // Paper - timings
|
|
}
|
|
|
|
gameprofilerfiller.popPush("broadcast");
|
|
// Paper - optimise chunk tick iteration
|
|
- this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing
|
|
+ this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing
|
|
// Paper start - optimise chunk tick iteration
|
|
if (!this.chunkMap.needsChangeBroadcasting.isEmpty()) {
|
|
it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<ChunkHolder> copy = this.chunkMap.needsChangeBroadcasting.clone();
|
|
@@ -685,7 +689,7 @@ public class ServerChunkCache extends ChunkSource {
|
|
}
|
|
}
|
|
// Paper end - optimise chunk tick iteration
|
|
- this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing
|
|
+ this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing
|
|
// Paper - optimise chunk tick iteration
|
|
gameprofilerfiller.pop();
|
|
gameprofilerfiller.pop();
|
|
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
index 17610196db7a1c6feb2cf74a02479a8691aa323f..3f26fdcc5cbf0a684ad724ab8c042d0053cc187e 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
@@ -317,7 +317,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
|
java.util.function.Consumer<net.minecraft.world.level.chunk.ChunkAccess> consumer = (net.minecraft.world.level.chunk.ChunkAccess chunk) -> {
|
|
if (chunk != null) {
|
|
synchronized (ret) { // Folia - region threading - make callback thread-safe TODO rebase
|
|
- ret.add(chunk);
|
|
+ ret.add(chunk);
|
|
} // Folia - region threading - make callback thread-safe TODO rebase
|
|
chunkProvider.addTicketAtLevel(TicketType.FUTURE_AWAIT, chunk.getPos(), ticketLevel, holderIdentifier);
|
|
}
|
|
@@ -687,6 +687,14 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
|
|
|
// Add env and gen to constructor, IWorldDataServer -> WorldDataServer
|
|
public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey<Level> resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List<CustomSpawner> list, boolean flag1, @Nullable RandomSequences randomsequences, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) {
|
|
+ // ASWM START
|
|
+ this(null, minecraftserver, executor, convertable_conversionsession, iworlddataserver, resourcekey, worlddimension, worldloadlistener, flag, randomsequences, i, list, flag1, env, gen, biomeProvider);
|
|
+ }
|
|
+
|
|
+ public com.infernalsuite.aswm.level.SlimeInMemoryWorld slimeInstance;
|
|
+
|
|
+ public ServerLevel(com.infernalsuite.aswm.level.SlimeBootstrap bootstrap, MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey<Level> resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, @Nullable RandomSequences randomsequences, long i, List<CustomSpawner> list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) {
|
|
+ // ASWM END
|
|
// IRegistryCustom.Dimension iregistrycustom_dimension = minecraftserver.registryAccess(); // CraftBukkit - decompile error
|
|
// Holder holder = worlddimension.type(); // CraftBukkit - decompile error
|
|
|
|
@@ -725,6 +733,12 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
|
chunkgenerator = new org.bukkit.craftbukkit.generator.CustomChunkGenerator(this, chunkgenerator, gen);
|
|
}
|
|
// CraftBukkit end
|
|
+ // ASWM START
|
|
+ ChunkGenerator result = this.getGenerator(bootstrap);
|
|
+ if (result != null) {
|
|
+ chunkgenerator = result;
|
|
+ }
|
|
+ // ASWM END
|
|
boolean flag2 = minecraftserver.forceSynchronousWrites();
|
|
DataFixer datafixer = minecraftserver.getFixerUpper();
|
|
this.entityStorage = new EntityRegionFileStorage(convertable_conversionsession.getDimensionPath(resourcekey).resolve("entities"), flag2); // Paper - rewrite chunk system //EntityPersistentStorage<Entity> entitypersistentstorage = new EntityStorage(this, convertable_conversionsession.getDimensionPath(resourcekey).resolve("entities"), datafixer, flag2, minecraftserver);
|
|
@@ -736,7 +750,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
|
//PersistentEntitySectionManager persistententitysectionmanager = this.entityManager; // Paper - rewrite chunk system
|
|
|
|
//Objects.requireNonNull(this.entityManager); // Paper - rewrite chunk system
|
|
- this.chunkSource = new ServerChunkCache(this, convertable_conversionsession, datafixer, structuretemplatemanager, executor, chunkgenerator, j, k, flag2, worldloadlistener, null, () -> { // Paper - rewrite chunk system
|
|
+ this.chunkSource = new ServerChunkCache(bootstrap, this, convertable_conversionsession, datafixer, structuretemplatemanager, executor, chunkgenerator, j, k, flag2, worldloadlistener, null, () -> { // Paper - rewrite chunk system // ASWM
|
|
return minecraftserver.overworld().getDataStorage();
|
|
});
|
|
this.chunkSource.getGeneratorState().ensureStructuresGenerated();
|
|
@@ -784,6 +798,12 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
|
this.dragonFight = enderDragonFight;
|
|
}
|
|
|
|
+ // ASWM START
|
|
+ public ChunkGenerator getGenerator(com.infernalsuite.aswm.level.SlimeBootstrap bootstrap) {
|
|
+ return null;
|
|
+ }
|
|
+ // ASWM END
|
|
+
|
|
public void setWeatherParameters(int clearDuration, int rainDuration, boolean raining, boolean thundering) {
|
|
this.serverLevelData.setClearWeatherTime(clearDuration);
|
|
this.serverLevelData.setRainTime(rainDuration);
|
|
@@ -1006,12 +1026,12 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
|
gameprofilerfiller.popPush("iceandsnow");
|
|
|
|
if (!this.paperConfig().environment.disableIceAndSnow) { // Paper
|
|
- for (int l = 0; l < randomTickSpeed; ++l) {
|
|
- if (this.random.nextInt(48) == 0) {
|
|
- this.getRandomBlockPosition(j, 0, k, 15, blockposition);
|
|
- this.tickIceAndSnow(flag, blockposition, chunk);
|
|
+ for (int l = 0; l < randomTickSpeed; ++l) {
|
|
+ if (this.random.nextInt(48) == 0) {
|
|
+ this.getRandomBlockPosition(j, 0, k, 15, blockposition);
|
|
+ this.tickIceAndSnow(flag, blockposition, chunk);
|
|
+ }
|
|
}
|
|
- }
|
|
} // Paper
|
|
|
|
// Paper start - optimise random block ticking
|
|
@@ -1357,9 +1377,9 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
|
currentlyTickingEntity.lazySet(entity);
|
|
}
|
|
// Paper end - log detailed entity tick information
|
|
- ++TimingHistory.entityTicks; // Paper - timings
|
|
- // Spigot start
|
|
- co.aikar.timings.Timing timer; // Paper
|
|
+ ++TimingHistory.entityTicks; // Paper - timings
|
|
+ // Spigot start
|
|
+ co.aikar.timings.Timing timer; // Paper
|
|
/*if (!org.spigotmc.ActivationRange.checkIfActive(entity)) { // Paper - comment out - EAR 2, reimplement below
|
|
entity.tickCount++;
|
|
timer = entity.getType().inactiveTickTimer.startTiming(); try { // Paper - timings
|
|
@@ -1367,36 +1387,36 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
|
} finally { timer.stopTiming(); } // Paper
|
|
return;
|
|
}*/ // Paper - comment out EAR 2
|
|
- // Spigot end
|
|
- // Paper start- timings
|
|
- final boolean isActive = org.spigotmc.ActivationRange.checkIfActive(entity);
|
|
- timer = isActive ? entity.getType().tickTimer.startTiming() : entity.getType().inactiveTickTimer.startTiming(); // Paper
|
|
- try {
|
|
- // Paper end - timings
|
|
- entity.setOldPosAndRot();
|
|
- ProfilerFiller gameprofilerfiller = this.getProfiler();
|
|
+ // Spigot end
|
|
+ // Paper start- timings
|
|
+ final boolean isActive = org.spigotmc.ActivationRange.checkIfActive(entity);
|
|
+ timer = isActive ? entity.getType().tickTimer.startTiming() : entity.getType().inactiveTickTimer.startTiming(); // Paper
|
|
+ try {
|
|
+ // Paper end - timings
|
|
+ entity.setOldPosAndRot();
|
|
+ ProfilerFiller gameprofilerfiller = this.getProfiler();
|
|
|
|
- ++entity.tickCount;
|
|
- this.getProfiler().push(() -> {
|
|
- return BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString();
|
|
- });
|
|
- gameprofilerfiller.incrementCounter("tickNonPassenger");
|
|
- if (isActive) { // Paper - EAR 2
|
|
- TimingHistory.activatedEntityTicks++;
|
|
- entity.tick();
|
|
- entity.postTick(); // CraftBukkit
|
|
- } else { entity.inactiveTick(); } // Paper - EAR 2
|
|
- this.getProfiler().pop();
|
|
- } finally { timer.stopTiming(); } // Paper - timings
|
|
- Iterator iterator = entity.getPassengers().iterator();
|
|
+ ++entity.tickCount;
|
|
+ this.getProfiler().push(() -> {
|
|
+ return BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString();
|
|
+ });
|
|
+ gameprofilerfiller.incrementCounter("tickNonPassenger");
|
|
+ if (isActive) { // Paper - EAR 2
|
|
+ TimingHistory.activatedEntityTicks++;
|
|
+ entity.tick();
|
|
+ entity.postTick(); // CraftBukkit
|
|
+ } else { entity.inactiveTick(); } // Paper - EAR 2
|
|
+ this.getProfiler().pop();
|
|
+ } finally { timer.stopTiming(); } // Paper - timings
|
|
+ Iterator iterator = entity.getPassengers().iterator();
|
|
|
|
- while (iterator.hasNext()) {
|
|
- Entity entity1 = (Entity) iterator.next();
|
|
+ while (iterator.hasNext()) {
|
|
+ Entity entity1 = (Entity) iterator.next();
|
|
|
|
- this.tickPassenger(entity, entity1);
|
|
- }
|
|
- // } finally { timer.stopTiming(); } // Paper - timings - move up
|
|
- // Paper start - log detailed entity tick information
|
|
+ this.tickPassenger(entity, entity1);
|
|
+ }
|
|
+ // } finally { timer.stopTiming(); } // Paper - timings - move up
|
|
+ // Paper start - log detailed entity tick information
|
|
} finally {
|
|
if (currentlyTickingEntity.get() == entity) {
|
|
currentlyTickingEntity.lazySet(null);
|
|
@@ -1412,36 +1432,36 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
|
final boolean isActive = org.spigotmc.ActivationRange.checkIfActive(passenger);
|
|
co.aikar.timings.Timing timer = isActive ? passenger.getType().passengerTickTimer.startTiming() : passenger.getType().passengerInactiveTickTimer.startTiming(); // Paper
|
|
try {
|
|
- // Paper end
|
|
- passenger.setOldPosAndRot();
|
|
- ++passenger.tickCount;
|
|
- ProfilerFiller gameprofilerfiller = this.getProfiler();
|
|
-
|
|
- gameprofilerfiller.push(() -> {
|
|
- return BuiltInRegistries.ENTITY_TYPE.getKey(passenger.getType()).toString();
|
|
- });
|
|
- gameprofilerfiller.incrementCounter("tickPassenger");
|
|
- // Paper start - EAR 2
|
|
- if (isActive) {
|
|
- passenger.rideTick();
|
|
- passenger.postTick(); // CraftBukkit
|
|
- } else {
|
|
- passenger.setDeltaMovement(Vec3.ZERO);
|
|
- passenger.inactiveTick();
|
|
- // copied from inside of if (isPassenger()) of passengerTick, but that ifPassenger is unnecessary
|
|
- vehicle.positionRider(passenger);
|
|
- }
|
|
- // Paper end - EAR 2
|
|
- gameprofilerfiller.pop();
|
|
- Iterator iterator = passenger.getPassengers().iterator();
|
|
+ // Paper end
|
|
+ passenger.setOldPosAndRot();
|
|
+ ++passenger.tickCount;
|
|
+ ProfilerFiller gameprofilerfiller = this.getProfiler();
|
|
+
|
|
+ gameprofilerfiller.push(() -> {
|
|
+ return BuiltInRegistries.ENTITY_TYPE.getKey(passenger.getType()).toString();
|
|
+ });
|
|
+ gameprofilerfiller.incrementCounter("tickPassenger");
|
|
+ // Paper start - EAR 2
|
|
+ if (isActive) {
|
|
+ passenger.rideTick();
|
|
+ passenger.postTick(); // CraftBukkit
|
|
+ } else {
|
|
+ passenger.setDeltaMovement(Vec3.ZERO);
|
|
+ passenger.inactiveTick();
|
|
+ // copied from inside of if (isPassenger()) of passengerTick, but that ifPassenger is unnecessary
|
|
+ vehicle.positionRider(passenger);
|
|
+ }
|
|
+ // Paper end - EAR 2
|
|
+ gameprofilerfiller.pop();
|
|
+ Iterator iterator = passenger.getPassengers().iterator();
|
|
|
|
- while (iterator.hasNext()) {
|
|
- Entity entity2 = (Entity) iterator.next();
|
|
+ while (iterator.hasNext()) {
|
|
+ Entity entity2 = (Entity) iterator.next();
|
|
|
|
- this.tickPassenger(passenger, entity2);
|
|
- }
|
|
+ this.tickPassenger(passenger, entity2);
|
|
+ }
|
|
|
|
- } finally { timer.stopTiming(); }// Paper - EAR2 timings
|
|
+ } finally { timer.stopTiming(); }// Paper - EAR2 timings
|
|
}
|
|
} else {
|
|
passenger.stopRiding();
|
|
@@ -1495,18 +1515,18 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
|
if (!savingDisabled) {
|
|
org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(getWorld())); // CraftBukkit
|
|
try (co.aikar.timings.Timing ignored = timings.worldSave.startTiming()) { // Paper
|
|
- if (progressListener != null) {
|
|
- progressListener.progressStartNoAbort(Component.translatable("menu.savingLevel"));
|
|
- }
|
|
+ if (progressListener != null) {
|
|
+ progressListener.progressStartNoAbort(Component.translatable("menu.savingLevel"));
|
|
+ }
|
|
|
|
- this.saveLevelData();
|
|
- if (progressListener != null) {
|
|
- progressListener.progressStage(Component.translatable("menu.savingChunks"));
|
|
- }
|
|
+ this.saveLevelData();
|
|
+ if (progressListener != null) {
|
|
+ progressListener.progressStage(Component.translatable("menu.savingChunks"));
|
|
+ }
|
|
|
|
timings.worldSaveChunks.startTiming(); // Paper
|
|
- if (!close) chunkproviderserver.save(flush); // Paper - rewrite chunk system
|
|
- if (close) chunkproviderserver.close(true); // Paper - rewrite chunk system
|
|
+ if (!close) chunkproviderserver.save(flush); // Paper - rewrite chunk system
|
|
+ if (close) chunkproviderserver.close(true); // Paper - rewrite chunk system
|
|
timings.worldSaveChunks.stopTiming(); // Paper
|
|
}// Paper
|
|
// Paper - rewrite chunk system - entity saving moved into ChunkHolder
|
|
@@ -1834,47 +1854,47 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
|
|
|
this.getChunkSource().blockChanged(pos);
|
|
if(this.paperConfig().misc.updatePathfindingOnBlockUpdate) { // Paper - option to disable pathfinding updates
|
|
- VoxelShape voxelshape = oldState.getCollisionShape(this, pos);
|
|
- VoxelShape voxelshape1 = newState.getCollisionShape(this, pos);
|
|
+ VoxelShape voxelshape = oldState.getCollisionShape(this, pos);
|
|
+ VoxelShape voxelshape1 = newState.getCollisionShape(this, pos);
|
|
|
|
- if (Shapes.joinIsNotEmpty(voxelshape, voxelshape1, BooleanOp.NOT_SAME)) {
|
|
- List<PathNavigation> list = new ObjectArrayList();
|
|
- Iterator iterator = this.navigatingMobs.iterator();
|
|
+ if (Shapes.joinIsNotEmpty(voxelshape, voxelshape1, BooleanOp.NOT_SAME)) {
|
|
+ List<PathNavigation> list = new ObjectArrayList();
|
|
+ Iterator iterator = this.navigatingMobs.iterator();
|
|
|
|
- while (iterator.hasNext()) {
|
|
- // CraftBukkit start - fix SPIGOT-6362
|
|
- Mob entityinsentient;
|
|
- try {
|
|
- entityinsentient = (Mob) iterator.next();
|
|
- } catch (java.util.ConcurrentModificationException ex) {
|
|
- // This can happen because the pathfinder update below may trigger a chunk load, which in turn may cause more navigators to register
|
|
- // In this case we just run the update again across all the iterators as the chunk will then be loaded
|
|
- // As this is a relative edge case it is much faster than copying navigators (on either read or write)
|
|
- this.sendBlockUpdated(pos, oldState, newState, flags);
|
|
- return;
|
|
- }
|
|
- // CraftBukkit end
|
|
- PathNavigation navigationabstract = entityinsentient.getNavigation();
|
|
+ while (iterator.hasNext()) {
|
|
+ // CraftBukkit start - fix SPIGOT-6362
|
|
+ Mob entityinsentient;
|
|
+ try {
|
|
+ entityinsentient = (Mob) iterator.next();
|
|
+ } catch (java.util.ConcurrentModificationException ex) {
|
|
+ // This can happen because the pathfinder update below may trigger a chunk load, which in turn may cause more navigators to register
|
|
+ // In this case we just run the update again across all the iterators as the chunk will then be loaded
|
|
+ // As this is a relative edge case it is much faster than copying navigators (on either read or write)
|
|
+ this.sendBlockUpdated(pos, oldState, newState, flags);
|
|
+ return;
|
|
+ }
|
|
+ // CraftBukkit end
|
|
+ PathNavigation navigationabstract = entityinsentient.getNavigation();
|
|
|
|
- if (navigationabstract.shouldRecomputePath(pos)) {
|
|
- list.add(navigationabstract);
|
|
+ if (navigationabstract.shouldRecomputePath(pos)) {
|
|
+ list.add(navigationabstract);
|
|
+ }
|
|
}
|
|
- }
|
|
|
|
- try {
|
|
- this.isUpdatingNavigations = true;
|
|
- iterator = list.iterator();
|
|
+ try {
|
|
+ this.isUpdatingNavigations = true;
|
|
+ iterator = list.iterator();
|
|
|
|
- while (iterator.hasNext()) {
|
|
- PathNavigation navigationabstract1 = (PathNavigation) iterator.next();
|
|
+ while (iterator.hasNext()) {
|
|
+ PathNavigation navigationabstract1 = (PathNavigation) iterator.next();
|
|
|
|
- navigationabstract1.recomputePath();
|
|
+ navigationabstract1.recomputePath();
|
|
+ }
|
|
+ } finally {
|
|
+ this.isUpdatingNavigations = false;
|
|
}
|
|
- } finally {
|
|
- this.isUpdatingNavigations = false;
|
|
- }
|
|
|
|
- }
|
|
+ }
|
|
} // Paper
|
|
}
|
|
|
|
diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
|
|
index 16fbc633de3a1d9e5e8c65ae107397a6f0e50811..d04c0147a87d4b31e6b5b090abeec316d235f1bd 100644
|
|
--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
|
|
+++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
|
|
@@ -31,7 +31,8 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
|
|
};
|
|
public final IdMap<T> registry;
|
|
private final T @org.jetbrains.annotations.Nullable [] presetValues; // Paper - Anti-Xray - Add preset values
|
|
- private volatile PalettedContainer.Data<T> data;
|
|
+
|
|
+ public volatile PalettedContainer.Data<T> data; // ASWM
|
|
private final PalettedContainer.Strategy strategy;
|
|
// private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); // Paper - unused
|
|
|
|
@@ -399,7 +400,7 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
|
|
void accept(T object, int count);
|
|
}
|
|
|
|
- static record Data<T>(PalettedContainer.Configuration<T> configuration, BitStorage storage, Palette<T> palette) {
|
|
+ public static record Data<T>(PalettedContainer.Configuration<T> configuration, BitStorage storage, Palette<T> palette) { // ASWM
|
|
public void copyFrom(Palette<T> palette, BitStorage storage) {
|
|
for(int i = 0; i < storage.getSize(); ++i) {
|
|
T object = palette.valueFor(storage.get(i));
|
|
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
|
|
index 1379084a80ce25644f13736b4a5ee5fabbd9ec1f..464e1c7970af5aa06ef563b823d7fd8b2776f8f5 100644
|
|
--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
|
|
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
|
|
@@ -1,6 +1,9 @@
|
|
package net.minecraft.world.level.chunk.storage;
|
|
|
|
import com.google.common.collect.Maps;
|
|
+import com.infernalsuite.aswm.api.world.SlimeWorld;
|
|
+import com.infernalsuite.aswm.level.NMSSlimeWorld;
|
|
+import com.infernalsuite.aswm.level.SlimeLevelInstance;
|
|
import com.mojang.logging.LogUtils;
|
|
import com.mojang.serialization.Codec;
|
|
import com.mojang.serialization.DataResult;
|
|
@@ -134,6 +137,7 @@ public class ChunkSerializer {
|
|
public static ProtoChunk read(ServerLevel world, PoiManager poiStorage, ChunkPos chunkPos, CompoundTag nbt) {
|
|
// Paper start - add variant for async calls
|
|
InProgressChunkHolder holder = loadChunk(world, poiStorage, chunkPos, nbt, true);
|
|
+
|
|
return holder.protoChunk;
|
|
}
|
|
// Paper start
|
|
@@ -229,36 +233,40 @@ public class ChunkSerializer {
|
|
// Paper start - rewrite the light engine
|
|
if (flag) {
|
|
try {
|
|
- int y = sectionData.getByte("Y");
|
|
- // Paper end - rewrite the light engine
|
|
- if (flag3) {
|
|
- // Paper start - rewrite the light engine
|
|
- // this is where our diff is
|
|
- blockNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(sectionData.getByteArray("BlockLight").clone(), sectionData.getInt(BLOCKLIGHT_STATE_TAG)); // clone for data safety
|
|
- } else {
|
|
- blockNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(null, sectionData.getInt(BLOCKLIGHT_STATE_TAG));
|
|
+ int y = sectionData.getByte("Y");
|
|
// Paper end - rewrite the light engine
|
|
- }
|
|
+ if (flag3) {
|
|
+ // Paper start - rewrite the light engine
|
|
+ // this is where our diff is
|
|
+ blockNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(sectionData.getByteArray("BlockLight").clone(), sectionData.getInt(BLOCKLIGHT_STATE_TAG)); // clone for data safety
|
|
+ } else {
|
|
+ blockNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(null, sectionData.getInt(BLOCKLIGHT_STATE_TAG));
|
|
+ // Paper end - rewrite the light engine
|
|
+ }
|
|
|
|
- if (flag4) {
|
|
- // Paper start - rewrite the light engine
|
|
- // we store under the same key so mod programs editing nbt
|
|
- // can still read the data, hopefully.
|
|
- // however, for compatibility we store chunks as unlit so vanilla
|
|
- // is forced to re-light them if it encounters our data. It's too much of a burden
|
|
- // to try and maintain compatibility with a broken and inferior skylight management system.
|
|
- skyNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(sectionData.getByteArray("SkyLight").clone(), sectionData.getInt(SKYLIGHT_STATE_TAG)); // clone for data safety
|
|
- } else if (flag1) {
|
|
- skyNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(null, sectionData.getInt(SKYLIGHT_STATE_TAG));
|
|
- // Paper end - rewrite the light engine
|
|
- }
|
|
+ if (flag4) {
|
|
+ // Paper start - rewrite the light engine
|
|
+ // we store under the same key so mod programs editing nbt
|
|
+ // can still read the data, hopefully.
|
|
+ // however, for compatibility we store chunks as unlit so vanilla
|
|
+ // is forced to re-light them if it encounters our data. It's too much of a burden
|
|
+ // to try and maintain compatibility with a broken and inferior skylight management system.
|
|
+ skyNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(sectionData.getByteArray("SkyLight").clone(), sectionData.getInt(SKYLIGHT_STATE_TAG)); // clone for data safety
|
|
+ } else if (flag1) {
|
|
+ skyNibbles[y - minSection] = new ca.spottedleaf.starlight.common.light.SWMRNibbleArray(null, sectionData.getInt(SKYLIGHT_STATE_TAG));
|
|
+ // Paper end - rewrite the light engine
|
|
+ }
|
|
|
|
- // Paper start - rewrite the light engine
|
|
+ // Paper start - rewrite the light engine
|
|
} catch (Exception ex) {
|
|
LOGGER.warn("Failed to load light data for chunk " + chunkPos + " in world '" + world.getWorld().getName() + "', light will be regenerated", ex);
|
|
flag = false;
|
|
}
|
|
// Paper end - rewrite light engine
|
|
+
|
|
+ if(world instanceof SlimeLevelInstance) {
|
|
+ poiStorage.checkConsistencyWithBlocks(SectionPos.of(chunkPos.getWorldPosition()), achunksection[j]);
|
|
+ }
|
|
}
|
|
}
|
|
|
|
@@ -441,7 +449,7 @@ public class ChunkSerializer {
|
|
ChunkSerializer.LOGGER.error("Recoverable errors when loading section [" + chunkPos.x + ", " + y + ", " + chunkPos.z + "]: " + message);
|
|
}
|
|
|
|
- private static Codec<PalettedContainerRO<Holder<Biome>>> makeBiomeCodec(Registry<Biome> biomeRegistry) {
|
|
+ public static Codec<PalettedContainerRO<Holder<Biome>>> makeBiomeCodec(Registry<Biome> biomeRegistry) { // ASWM
|
|
return PalettedContainer.codecRO(biomeRegistry.asHolderIdMap(), biomeRegistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomeRegistry.getHolderOrThrow(Biomes.PLAINS));
|
|
}
|
|
|
|
@@ -620,7 +628,7 @@ public class ChunkSerializer {
|
|
nbttagcompound.put(BLOCK_TICKS_TAG, asyncsavedata.blockTickList);
|
|
nbttagcompound.put(FLUID_TICKS_TAG, asyncsavedata.fluidTickList);
|
|
} else {
|
|
- ChunkSerializer.saveTicks(world, nbttagcompound, chunk.getTicksForSerialization());
|
|
+ ChunkSerializer.saveTicks(world, nbttagcompound, chunk.getTicksForSerialization());
|
|
}
|
|
// Paper end
|
|
nbttagcompound.put("PostProcessing", ChunkSerializer.packOffsets(chunk.getPostProcessing()));
|