diff --git a/api/src/main/java/com/infernalsuite/asp/api/SlimeDataConverter.java b/api/src/main/java/com/infernalsuite/asp/api/SlimeDataConverter.java index 5820073d9..2a62e9c5c 100644 --- a/api/src/main/java/com/infernalsuite/asp/api/SlimeDataConverter.java +++ b/api/src/main/java/com/infernalsuite/asp/api/SlimeDataConverter.java @@ -15,8 +15,11 @@ public interface SlimeDataConverter { CompoundBinaryTag convertChunkTo1_13(CompoundBinaryTag globalTag); + CompoundBinaryTag convertChunk(CompoundBinaryTag globalTag, int to); + List convertEntities(List input, int from, int to); List convertTileEntities(List input, int from, int to); ListBinaryTag convertBlockPalette(ListBinaryTag input, int from, int to); + int getServerVersion(); } diff --git a/aspaper-api/build.gradle.kts.patch b/aspaper-api/build.gradle.kts.patch index 238e02fc8..81953aa43 100644 --- a/aspaper-api/build.gradle.kts.patch +++ b/aspaper-api/build.gradle.kts.patch @@ -6,10 +6,10 @@ dependencies { + api(project(":api")) //ASP // api dependencies are listed transitively to API consumers - api("com.google.guava:guava:33.3.1-jre") - api("com.google.code.gson:gson:2.11.0") + api("com.google.guava:guava:33.5.0-jre") + api("com.google.code.gson:gson:2.13.2") @@ -91,7 +_,7 @@ - testRuntimeOnly("org.junit.platform:junit-platform-launcher") + testRuntimeOnly("org.junit.platform:junit-platform-launcher:6.0.3") } -val generatedDir: java.nio.file.Path = layout.projectDirectory.dir("src/generated/java").asFile.toPath() diff --git a/aspaper-server/build.gradle.kts.patch b/aspaper-server/build.gradle.kts.patch index 0f7bbb593..e6ee92dcf 100644 --- a/aspaper-server/build.gradle.kts.patch +++ b/aspaper-server/build.gradle.kts.patch @@ -1,9 +1,11 @@ --- a/paper-server/build.gradle.kts +++ b/paper-server/build.gradle.kts -@@ -22,6 +_,17 @@ +@@ -22,9 +_,20 @@ minecraftVersion = providers.gradleProperty("mcVersion") gitFilePatches = false +- updatingMinecraft { +- // oldPaperCommit = "711c5de2b05df39b72c44ff54e9f9381f8d153cb" + val aspaper = forks.register("aspaper") { + upstream.patchDir("paperServer") { + upstreamPath = "paper-server" @@ -11,14 +13,17 @@ + patchesDir = rootDirectory.dir("aspaper-server/paper-patches") + outputDir = rootDirectory.dir("paper-server") + } -+ } + } + + activeFork = aspaper + - spigot { - enabled = true - buildDataRef = "17f77cee7117ab9d6175f088ae8962bfd04e61a9" -@@ -104,7 +_,19 @@ ++// updatingMinecraft { ++// oldPaperCommit = "7e80cef5198561d0db53406127e5b8bc7af51577" ++// } + } + + tasks.generateDevelopmentBundle { +@@ -86,7 +_,19 @@ } } @@ -39,17 +44,17 @@ configurations.named(log4jPlugins.compileClasspathConfigurationName) { extendsFrom(configurations.compileClasspath.get()) } -@@ -127,7 +_,8 @@ +@@ -109,7 +_,8 @@ } dependencies { - implementation(project(":paper-api")) + implementation(project(":aspaper-api")) //ASP + implementation(project(":core")) //ASP - implementation("ca.spottedleaf:concurrentutil:0.0.8") + implementation("ca.spottedleaf:concurrentutil:0.0.10") implementation("org.jline:jline-terminal-ffm:3.27.1") // use ffm on java 22+ implementation("org.jline:jline-terminal-jni:3.27.1") // fall back to jni on java 21 -@@ -198,14 +_,14 @@ +@@ -171,14 +_,14 @@ val gitBranch = git.exec(providers, "rev-parse", "--abbrev-ref", "HEAD").get().trim() attributes( "Main-Class" to "org.bukkit.craftbukkit.Main", @@ -69,7 +74,7 @@ "Build-Number" to (build ?: ""), "Build-Time" to buildTime.toString(), "Git-Branch" to gitBranch, -@@ -264,7 +_,7 @@ +@@ -237,7 +_,7 @@ jvmArgumentProviders.add(provider) } diff --git a/aspaper-server/minecraft-patches/features/0001-Disable-dragon-battle.patch b/aspaper-server/minecraft-patches/features/0001-Disable-dragon-battle.patch index 43da52134..94879af42 100644 --- a/aspaper-server/minecraft-patches/features/0001-Disable-dragon-battle.patch +++ b/aspaper-server/minecraft-patches/features/0001-Disable-dragon-battle.patch @@ -5,21 +5,27 @@ Subject: [PATCH] Disable dragon battle diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index 24d3b1161cf45a915b37f510645500c5a45350eb..cd7aca9f76aef0824d94c73373a8f4536d05c4e8 100644 +index 70f12c31ad5f9b8bcb32b4be041ea16684d430f3..b59f29850d3ef1840b740cfc3b2f254686620cb9 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java -@@ -715,7 +715,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -2,6 +2,7 @@ package net.minecraft.server.level; + + import com.google.common.annotations.VisibleForTesting; + import com.google.common.collect.Lists; ++import com.infernalsuite.asp.api.world.properties.SlimeProperty; + import com.mojang.datafixers.DataFixer; + import com.mojang.datafixers.util.Pair; + import com.mojang.logging.LogUtils; +@@ -748,7 +749,11 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet ); - this.structureManager = new StructureManager(this, this.serverLevelData.worldGenOptions(), this.structureCheck); // CraftBukkit - if (this.dimension() == Level.END && this.dimensionTypeRegistration().is(BuiltinDimensionTypes.END) || env == org.bukkit.World.Environment.THE_END) { // CraftBukkit - Allow to create EnderDragonBattle in default and custom END -- this.dragonFight = new EndDragonFight(this, this.serverLevelData.worldGenOptions().seed(), this.serverLevelData.endDragonFightData()); // CraftBukkit -+ // ASP START -+ if (bootstrap == null || bootstrap.initial().getPropertyMap().getValue(com.infernalsuite.asp.api.world.properties.SlimeProperties.DRAGON_BATTLE)) { -+ this.dragonFight = new EndDragonFight(this, this.serverLevelData.worldGenOptions().seed(), this.serverLevelData.endDragonFightData()); // CraftBukkit + this.structureManager = new StructureManager(this, options, this.structureCheck); + if (this.dimensionType().hasEnderDragonFight()) { +- this.dragonFight = this.getDataStorage().computeIfAbsent(EnderDragonFight.TYPE); ++ if(bootstrap == null || bootstrap.initial().getPropertyMap().getValue(com.infernalsuite.asp.api.world.properties.SlimeProperties.DRAGON_BATTLE)) { // ASP - only create dragon fight if property is enabled) ++ this.dragonFight = this.getDataStorage().computeIfAbsent(EnderDragonFight.TYPE); + } else { -+ this.dragonFight = new EndDragonFight(this, this.serverLevelData.worldGenOptions().seed(), new EndDragonFight.Data(false, true, true, false,Optional.empty(),Optional.empty(),Optional.empty())); // ASP - disable dragon ++ this.dragonFight = new EnderDragonFight(false, true, true, Optional.empty(), 0,Optional.empty(),Optional.empty(), List.of(), List.of()); + } -+ // ASP END - } else { - this.dragonFight = null; + this.dragonFight.init(this, seed, BlockPos.ZERO); } + diff --git a/aspaper-server/minecraft-patches/features/0002-Avoid-IO-call-for-UUID.patch b/aspaper-server/minecraft-patches/features/0002-Avoid-IO-call-for-UUID.patch deleted file mode 100644 index 53c38eb97..000000000 --- a/aspaper-server/minecraft-patches/features/0002-Avoid-IO-call-for-UUID.patch +++ /dev/null @@ -1,19 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: David Mayr -Date: Mon, 10 Mar 2025 11:41:14 +0100 -Subject: [PATCH] Avoid IO call for UUID - - -diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index cd7aca9f76aef0824d94c73373a8f4536d05c4e8..7893b71f81ae055512ec88415ebe388b7152cc5a 100644 ---- a/net/minecraft/server/level/ServerLevel.java -+++ b/net/minecraft/server/level/ServerLevel.java -@@ -642,7 +642,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - // CraftBukkit start - super(serverLevelData, dimension, server.registryAccess(), levelStem.type(), false, isDebug, biomeZoomSeed, server.getMaxChainedNeighborUpdates(), gen, biomeProvider, env, spigotConfig -> server.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(levelStorageAccess.levelDirectory.path(), serverLevelData.getLevelName(), dimension.identifier(), spigotConfig, server.registryAccess(), serverLevelData.getGameRules())), dispatcher); // Paper - create paper world configs & Async-Anti-Xray: Pass executor - this.levelStorageAccess = levelStorageAccess; -- this.uuid = org.bukkit.craftbukkit.util.WorldUUID.getOrCreate(levelStorageAccess.levelDirectory.path().toFile()); -+ this.uuid = bootstrap == null ? org.bukkit.craftbukkit.util.WorldUUID.getOrCreate(levelStorageAccess.levelDirectory.path().toFile()) : java.util.UUID.randomUUID(); //ASP - avoid IO calls - this.levelLoadListener = new net.minecraft.server.level.progress.LoggingLevelLoadListener(false, this); - // CraftBukkit end - this.tickTime = tickTime; diff --git a/aspaper-server/minecraft-patches/features/0002-Prevent-config-disk-io-on-world-load.patch b/aspaper-server/minecraft-patches/features/0002-Prevent-config-disk-io-on-world-load.patch new file mode 100644 index 000000000..35bc4b7fc --- /dev/null +++ b/aspaper-server/minecraft-patches/features/0002-Prevent-config-disk-io-on-world-load.patch @@ -0,0 +1,32 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: David Mayr +Date: Wed, 12 Mar 2025 21:14:56 +0100 +Subject: [PATCH] Prevent config disk io on world load + + +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java +index b59f29850d3ef1840b740cfc3b2f254686620cb9..54cdc9c357c446b530c2ae757f1243d1912d02a0 100644 +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -660,7 +660,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet + savedDataStorage.set(io.papermc.paper.world.saveddata.PaperWorldPDC.TYPE, loadedWorldData.pdc() == null ? io.papermc.paper.world.saveddata.PaperWorldPDC.TYPE.constructor().get() : loadedWorldData.pdc()); + final GameRules gameRules = new GameRules(server.getWorldData().enabledFeatures(), savedDataStorage.computeIfAbsent(net.minecraft.world.level.gamerules.GameRuleMap.TYPE)); + this.gameRules = gameRules; +- super(levelData, dimension, server.registryAccess(), levelStem.type(), false, isDebug, biomeZoomSeed, server.getMaxChainedNeighborUpdates(), loadedWorldData.bukkitName(), gen, biomeProvider, env, spigotConfig -> server.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(server.storageSource.getDimensionPath(dimension), dimension.identifier(), spigotConfig, server.registryAccess(), gameRules)), executor); // Paper - create paper world configs // Paper - Anti-Xray - Pass executor ++ super(levelData, dimension, server.registryAccess(), levelStem.type(), false, isDebug, biomeZoomSeed, server.getMaxChainedNeighborUpdates(), loadedWorldData.bukkitName(), gen, biomeProvider, env, spigotConfig -> bootstrap != null ? com.infernalsuite.asp.config.SlimePaperWorldConfig.initializeOrGet() : server.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(server.storageSource.getDimensionPath(dimension), dimension.identifier(), spigotConfig, server.registryAccess(), gameRules)), executor); // Paper - create paper world configs // Paper - Anti-Xray - Pass executor //ASP - Optimize world config + this.weatherData = savedDataStorage.computeIfAbsent(WeatherData.TYPE); + this.weatherData.setLevel(this); + this.typeKey = typeKey; +diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java +index 56659181dc817ea028428eabf5937308c04b8cc5..90600ee28f28b7a863dba2c750b4a2195fb91c7a 100644 +--- a/net/minecraft/world/level/Level.java ++++ b/net/minecraft/world/level/Level.java +@@ -850,7 +850,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + this.sectionsCount = this.maxSectionY - this.minSectionY + 1; + // Paper end - getblock optimisations - cache world height/sections + final org.bukkit.NamespacedKey worldKey = CraftNamespacedKey.fromMinecraft(dimension.identifier()); // Paper +- this.spigotConfig = new org.spigotmc.SpigotWorldConfig(bukkitName, worldKey); // Spigot ++ this.spigotConfig = new org.spigotmc.SpigotWorldConfig(bukkitName, CraftNamespacedKey.fromMinecraft(dimension.identifier()), !(this instanceof com.infernalsuite.asp.level.SlimeLevelInstance)); // Spigot //ASP - Improve Slime IO + this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config + this.generator = generator; + this.world = new CraftWorld((ServerLevel) this, worldKey, biomeProvider, environment); diff --git a/aspaper-server/minecraft-patches/features/0003-Prevent-config-disk-io-on-world-load.patch b/aspaper-server/minecraft-patches/features/0003-Prevent-config-disk-io-on-world-load.patch deleted file mode 100644 index 43196636b..000000000 --- a/aspaper-server/minecraft-patches/features/0003-Prevent-config-disk-io-on-world-load.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: David Mayr -Date: Wed, 12 Mar 2025 21:14:56 +0100 -Subject: [PATCH] Prevent config disk io on world load - - -diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index ab0c9f02e446476053d586567ea1a61ae2675e0c..3241c3ae29b25c3a44a30677ae86d03ec3eca88d 100644 ---- a/net/minecraft/server/level/ServerLevel.java -+++ b/net/minecraft/server/level/ServerLevel.java -@@ -640,7 +640,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - ) { - //ASP end - // CraftBukkit start -- super(serverLevelData, dimension, server.registryAccess(), levelStem.type(), false, isDebug, biomeZoomSeed, server.getMaxChainedNeighborUpdates(), gen, biomeProvider, env, spigotConfig -> server.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(levelStorageAccess.levelDirectory.path(), serverLevelData.getLevelName(), dimension.identifier(), spigotConfig, server.registryAccess(), serverLevelData.getGameRules())), dispatcher); // Paper - create paper world configs & Async-Anti-Xray: Pass executor -+ super(serverLevelData, dimension, server.registryAccess(), levelStem.type(), false, isDebug, biomeZoomSeed, server.getMaxChainedNeighborUpdates(), gen, biomeProvider, env, spigotConfig -> bootstrap != null ? com.infernalsuite.asp.config.SlimePaperWorldConfig.initializeOrGet() : server.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(levelStorageAccess.levelDirectory.path(), serverLevelData.getLevelName(), dimension.identifier(), spigotConfig, server.registryAccess(), serverLevelData.getGameRules())), dispatcher); // Paper - create paper world configs & Async-Anti-Xray: Pass executor //ASP - Optimize world config - this.levelStorageAccess = levelStorageAccess; - this.uuid = bootstrap == null ? org.bukkit.craftbukkit.util.WorldUUID.getOrCreate(levelStorageAccess.levelDirectory.path().toFile()) : java.util.UUID.randomUUID(); //ASP - avoid IO calls - this.levelLoadListener = new net.minecraft.server.level.progress.LoggingLevelLoadListener(false, this); -diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java -index 579bbba4e823d4d0318e58759ca732b7c8e4d865..f7b2cd6a02a371de162159ef9d8999f737be7435 100644 ---- a/net/minecraft/world/level/Level.java -+++ b/net/minecraft/world/level/Level.java -@@ -843,7 +843,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl - this.maxSectionY = this.maxY >> 4; - this.sectionsCount = this.maxSectionY - this.minSectionY + 1; - // Paper end - getblock optimisations - cache world height/sections -- this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) levelData).getLevelName()); // Spigot -+ this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) levelData).getLevelName(), !(this instanceof com.infernalsuite.asp.level.SlimeLevelInstance)); // Spigot //ASP - Improve Slime IO - this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config - this.generator = generator; - this.world = new CraftWorld((ServerLevel) this, generator, biomeProvider, environment); diff --git a/aspaper-server/minecraft-patches/features/0004-Read-only-dimension-data-store.patch b/aspaper-server/minecraft-patches/features/0004-Read-only-dimension-data-store.patch deleted file mode 100644 index d3d2261b9..000000000 --- a/aspaper-server/minecraft-patches/features/0004-Read-only-dimension-data-store.patch +++ /dev/null @@ -1,25 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: David Mayr -Date: Thu, 13 Mar 2025 00:09:20 +0100 -Subject: [PATCH] Read only dimension data store - - -diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java -index af67b07722bb0125acd081dab767d7e7b360623b..fc4f631f7d55ce67f348e3b6090095e6ef8445e8 100644 ---- a/net/minecraft/server/level/ServerChunkCache.java -+++ b/net/minecraft/server/level/ServerChunkCache.java -@@ -208,7 +208,13 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon - LOGGER.error("Failed to create dimension data storage directory", (Throwable)var14); - } - -- this.dataStorage = new DimensionDataStorage(path, fixerUpper, level.registryAccess()); -+ //ASP start - No dimension data storage -+ if(level instanceof com.infernalsuite.asp.level.SlimeLevelInstance) { -+ this.dataStorage = new com.infernalsuite.asp.level.ReadOnlyDimensionDataStorage(path, fixerUpper, level.registryAccess()); -+ } else { -+ this.dataStorage = new DimensionDataStorage(path, fixerUpper, level.registryAccess()); -+ } -+ //ASP end - No dimension data storage - this.ticketStorage = this.dataStorage.computeIfAbsent(TicketStorage.TYPE); - this.chunkMap = new ChunkMap( - level, diff --git a/aspaper-server/minecraft-patches/sources/ca/spottedleaf/moonrise/paper/PaperHooks.java.patch b/aspaper-server/minecraft-patches/sources/ca/spottedleaf/moonrise/paper/PaperHooks.java.patch index 120dff2fb..e02b8b1ef 100644 --- a/aspaper-server/minecraft-patches/sources/ca/spottedleaf/moonrise/paper/PaperHooks.java.patch +++ b/aspaper-server/minecraft-patches/sources/ca/spottedleaf/moonrise/paper/PaperHooks.java.patch @@ -1,6 +1,6 @@ --- a/ca/spottedleaf/moonrise/paper/PaperHooks.java +++ b/ca/spottedleaf/moonrise/paper/PaperHooks.java -@@ -214,6 +_,7 @@ +@@ -224,6 +_,7 @@ @Override public boolean forceNoSave(final ChunkAccess chunk) { diff --git a/aspaper-server/minecraft-patches/sources/net/minecraft/server/level/ServerLevel.java.patch b/aspaper-server/minecraft-patches/sources/net/minecraft/server/level/ServerLevel.java.patch index c8af94f8e..ee6f952a7 100644 --- a/aspaper-server/minecraft-patches/sources/net/minecraft/server/level/ServerLevel.java.patch +++ b/aspaper-server/minecraft-patches/sources/net/minecraft/server/level/ServerLevel.java.patch @@ -1,70 +1,75 @@ --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java -@@ -598,7 +_,31 @@ +@@ -603,8 +_,35 @@ + this.playerTickingChunks.remove((LevelChunk)chunkHolder.getCurrentChunk()); } // Paper end - chunk tick iteration - +- - public ServerLevel( + public com.infernalsuite.asp.level.SlimeInMemoryWorld slimeInstance; // ASP + + public ServerLevel( -+ MinecraftServer server, -+ Executor dispatcher, -+ LevelStorageSource.LevelStorageAccess levelStorageAccess, -+ net.minecraft.world.level.storage.PrimaryLevelData serverLevelData, // CraftBukkit -+ ResourceKey dimension, -+ LevelStem levelStem, -+ boolean isDebug, -+ long biomeZoomSeed, -+ List customSpawners, -+ boolean tickTime, -+ @Nullable RandomSequences randomSequences, -+ org.bukkit.World.Environment env, // CraftBukkit -+ org.bukkit.generator.ChunkGenerator gen, // CraftBukkit -+ org.bukkit.generator.BiomeProvider biomeProvider // CraftBukkit ++ final MinecraftServer server, ++ final Executor executor, ++ final LevelStorageSource.LevelStorageAccess levelStorage, ++ final WorldGenSettings worldGenSettings, // CraftBukkit ++ final ResourceKey dimension, ++ final LevelStem levelStem, ++ final boolean isDebug, ++ final long biomeZoomSeed, ++ final List customSpawners, ++ final boolean tickTime ++ // Paper start - add parameters ++ , ResourceKey typeKey, ++ org.bukkit.World.Environment env, ++ org.bukkit.generator.ChunkGenerator gen, ++ org.bukkit.generator.BiomeProvider biomeProvider, ++ SavedDataStorage savedDataStorage, ++ io.papermc.paper.world.PaperWorldLoader.LoadedWorldData loadedWorldData ++ // Paper end - add parameters + ) { + //ASP start -+ this(null, server, dispatcher, levelStorageAccess, serverLevelData, dimension, levelStem, -+ isDebug, biomeZoomSeed, customSpawners, tickTime, randomSequences, env, gen, biomeProvider); ++ this(null, server, executor, levelStorage, worldGenSettings, dimension, levelStem, ++ isDebug, biomeZoomSeed, customSpawners, tickTime, typeKey, env, gen, biomeProvider, savedDataStorage, loadedWorldData); + } + + public ServerLevel( + com.infernalsuite.asp.level.@Nullable SlimeBootstrap bootstrap, - MinecraftServer server, - Executor dispatcher, - LevelStorageSource.LevelStorageAccess levelStorageAccess, -@@ -614,6 +_,7 @@ - org.bukkit.generator.ChunkGenerator gen, // CraftBukkit - org.bukkit.generator.BiomeProvider biomeProvider // CraftBukkit + final MinecraftServer server, + final Executor executor, + final LevelStorageSource.LevelStorageAccess levelStorage, +@@ -624,6 +_,7 @@ + io.papermc.paper.world.PaperWorldLoader.LoadedWorldData loadedWorldData + // Paper end - add parameters ) { + //ASP end // CraftBukkit start - super(serverLevelData, dimension, server.registryAccess(), levelStem.type(), false, isDebug, biomeZoomSeed, server.getMaxChainedNeighborUpdates(), gen, biomeProvider, env, spigotConfig -> server.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(levelStorageAccess.levelDirectory.path(), serverLevelData.getLevelName(), dimension.identifier(), spigotConfig, server.registryAccess(), serverLevelData.getGameRules())), dispatcher); // Paper - create paper world configs & Async-Anti-Xray: Pass executor - this.levelStorageAccess = levelStorageAccess; -@@ -641,6 +_,13 @@ - chunkGenerator = new org.bukkit.craftbukkit.generator.CustomChunkGenerator(this, chunkGenerator, gen); - } - // CraftBukkit end + final io.papermc.paper.world.saveddata.PaperLevelOverrides levelData = loadedWorldData.levelOverrides(); + savedDataStorage.set(io.papermc.paper.world.saveddata.PaperLevelOverrides.TYPE, levelData); +@@ -645,7 +_,13 @@ + this.server = server; + this.customSpawners = customSpawners; + this.serverLevelData = levelData; + // ASP START + ChunkGenerator generator = levelStem.generator(); + ChunkGenerator result = this.getGenerator(bootstrap); + if (result != null) { -+ chunkGenerator = result; ++ generator = result; + } + // ASP END -+ - boolean flag = server.forceSynchronousWrites(); - DataFixer fixerUpper = server.getFixerUpper(); - // Paper - rewrite chunk system -@@ -723,6 +_,12 @@ - public void setDragonFight(@Nullable EndDragonFight dragonFight) { - this.dragonFight = dragonFight; + // CraftBukkit start + // Paper start - per-world time + if (!io.papermc.paper.configuration.GlobalConfiguration.get().time.affectsAllWorlds) { +@@ -739,6 +_,12 @@ + // Paper end - rewrite chunk system + this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit } + + // ASP START -+ public ChunkGenerator getGenerator(com.infernalsuite.asp.level.SlimeBootstrap bootstrap) { ++ public @Nullable ChunkGenerator getGenerator(com.infernalsuite.asp.level.@Nullable SlimeBootstrap bootstrap) { + return null; + } + // ASP END - public void setWeatherParameters(int clearTime, int weatherTime, boolean isRaining, boolean isThundering) { - this.serverLevelData.setClearWeatherTime(clearTime); + // Paper start + @Override diff --git a/aspaper-server/paper-patches/features/0001-Warning-if-people-use-old-swm-api.patch b/aspaper-server/paper-patches/features/0001-Warning-if-people-use-old-swm-api.patch index 0f685c0ec..9a56a7795 100644 --- a/aspaper-server/paper-patches/features/0001-Warning-if-people-use-old-swm-api.patch +++ b/aspaper-server/paper-patches/features/0001-Warning-if-people-use-old-swm-api.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Warning if people use old swm api diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java -index 3e82ea07ca4194844c5528446e2c4a46ff4acee5..1f8bff31ce60f9a1b143e749916fa51cf115f5d7 100644 +index 4dd7c6c0d7f54f5cc5879b9c60c60c455f00c9f8..45831c8e3f0803530b0edb511caf9835c9513963 100644 --- a/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java +++ b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java -@@ -64,6 +64,15 @@ class PaperPluginInstanceManager { +@@ -69,6 +69,15 @@ class PaperPluginInstanceManager { } public @Nullable Plugin getPlugin(@NotNull String name) { diff --git a/aspaper-server/paper-patches/features/0004-Delete-temp-folder-after-world-is-unloaded.patch b/aspaper-server/paper-patches/features/0004-Delete-temp-folder-after-world-is-unloaded.patch index 3f53a5191..002ffb48d 100644 --- a/aspaper-server/paper-patches/features/0004-Delete-temp-folder-after-world-is-unloaded.patch +++ b/aspaper-server/paper-patches/features/0004-Delete-temp-folder-after-world-is-unloaded.patch @@ -5,13 +5,13 @@ Subject: [PATCH] Delete temp folder after world is unloaded diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 7a8399c5f5ea404e31b2a907c7324cab5f9b4699..c56d700bd879bcaf785cacad07c7562f391f9e18 100644 +index d6418084621338579615d1b486cc2fd0d8c00fb5..56a126bb70f53a13047e72cf2e70cbd45f9ae8bd 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1357,6 +1357,12 @@ public final class CraftServer implements Server { +@@ -1352,6 +1352,12 @@ public final class CraftServer implements Server { + handle.getChunkSource().close(save); io.papermc.paper.FeatureHooks.closeEntityManager(handle, save); // SPIGOT-6722: close entityManager // Paper - chunk system - handle.levelStorageAccess.close(); + + //ASP start - avoid temp storage leak during runtime + if(handle instanceof com.infernalsuite.asp.level.SlimeLevelInstance asp) { diff --git a/aspaper-server/paper-patches/features/0005-World-overrides.patch b/aspaper-server/paper-patches/features/0005-World-overrides.patch new file mode 100644 index 000000000..9bcc53e1c --- /dev/null +++ b/aspaper-server/paper-patches/features/0005-World-overrides.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: David +Date: Thu, 18 Sep 2025 20:28:39 +0200 +Subject: [PATCH] World overrides + + +diff --git a/src/main/java/io/papermc/paper/world/PaperWorldLoader.java b/src/main/java/io/papermc/paper/world/PaperWorldLoader.java +index cd3e7cc8cf083915de46e03d9b2aaea3003f48fb..64fcc1333b9e72ff02176daaf85290935cb21082 100644 +--- a/src/main/java/io/papermc/paper/world/PaperWorldLoader.java ++++ b/src/main/java/io/papermc/paper/world/PaperWorldLoader.java +@@ -131,11 +131,24 @@ public record PaperWorldLoader(MinecraftServer server, String levelId) { + final var levelStemRegistry = this.server.registryAccess().lookupOrThrow(Registries.LEVEL_STEM); + final boolean hasWorldData = this.server.storageSource.hasWorldData(); + final LevelStem overworldStem = requireNonNull(levelStemRegistry.getValue(LevelStem.OVERWORLD), "Overworld stem missing"); +- this.loadInitialWorld(overworldStem, hasWorldData); ++ //ASP start - overwrite vanilla worlds if needed ++ if(!com.infernalsuite.asp.SlimeNMSBridgeImpl.instance().loadOverworldOverride()) { ++ this.loadInitialWorld(overworldStem, hasWorldData); ++ } ++ //ASP end - overwrite vanilla worlds if needed + for (final LevelStem stem : levelStemRegistry) { + if (stem == overworldStem) { + continue; + } ++ //ASP start - overwrite vanilla worlds if needed ++ ResourceKey levelStemKey = levelStemRegistry.getResourceKey(stem).get(); ++ if(levelStemKey == LevelStem.NETHER && com.infernalsuite.asp.SlimeNMSBridgeImpl.instance().loadNetherOverride()) { ++ continue; ++ } ++ if(levelStemKey == LevelStem.END && com.infernalsuite.asp.SlimeNMSBridgeImpl.instance().loadEndOverride()) { ++ continue; ++ } ++ //ASP end - overwrite vanilla worlds if needed + this.loadInitialWorld(stem, hasWorldData); + } + diff --git a/aspaper-server/paper-patches/features/0005-Prevent-config-disk-io-on-world-load.patch b/aspaper-server/paper-patches/features/0006-Prevent-config-disk-io-on-world-load.patch similarity index 75% rename from aspaper-server/paper-patches/features/0005-Prevent-config-disk-io-on-world-load.patch rename to aspaper-server/paper-patches/features/0006-Prevent-config-disk-io-on-world-load.patch index 43df3051a..169953933 100644 --- a/aspaper-server/paper-patches/features/0005-Prevent-config-disk-io-on-world-load.patch +++ b/aspaper-server/paper-patches/features/0006-Prevent-config-disk-io-on-world-load.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Prevent config disk io on world load diff --git a/src/main/java/org/spigotmc/SpigotConfig.java b/src/main/java/org/spigotmc/SpigotConfig.java -index c935d75b83338ce41507e9be11153dd1c4052cb6..3feaba566c22b1e93b2b4edc7786723a2f9e28c6 100644 +index 025ec3f3e84a3367b4d709c8c3ecd98f6dfecab4..182de15ca133fc42bf703ee9d116302bf5207c0d 100644 --- a/src/main/java/org/spigotmc/SpigotConfig.java +++ b/src/main/java/org/spigotmc/SpigotConfig.java @@ -79,7 +79,12 @@ public class SpigotConfig { @@ -35,21 +35,22 @@ index c935d75b83338ce41507e9be11153dd1c4052cb6..3feaba566c22b1e93b2b4edc7786723a Bukkit.getLogger().log(Level.SEVERE, "Could not save " + SpigotConfig.CONFIG_FILE, ex); } diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java -index 43c6240ec2855c0f668ce04de29d22a223d2612f..c6f398977149c8b35454fb79f099322c36d557d5 100644 +index 339832e312578ee385bbf3c9d5c131435abe7d7a..f723674434dc2b33870a2205f3ba1a3715e26aca 100644 --- a/src/main/java/org/spigotmc/SpigotWorldConfig.java +++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java -@@ -10,17 +10,28 @@ public class SpigotWorldConfig { +@@ -14,14 +14,25 @@ public class SpigotWorldConfig { private final YamlConfiguration config; private boolean verbose; + // ASP start - Improve Slime IO - public SpigotWorldConfig(String worldName) { -+ this(worldName, true); + public SpigotWorldConfig(String legacyWorldName, Key worldKey) { ++ this(legacyWorldName, worldKey, true); + } + // ASP end - Improve Slime IO + -+ public SpigotWorldConfig(String worldName, boolean saveOnLoad) { // ASP - Improve Slime IO - this.worldName = worldName; ++ public SpigotWorldConfig(String legacyWorldName, Key worldKey, boolean saveOnLoad) { // ASP - Improve Slime IO + this.legacyWorldName = legacyWorldName; + this.worldName = worldKey.asString(); this.config = SpigotConfig.config; - this.init(); + this.init(saveOnLoad); // ASP - Improve Slime IO @@ -62,6 +63,10 @@ index 43c6240ec2855c0f668ce04de29d22a223d2612f..c6f398977149c8b35454fb79f099322c + public void init(boolean saveOnLoad) { + // ASP end - Improve Slime IO this.verbose = this.getBoolean("verbose", false); // Paper + if (SpigotConfig.version <= 12) { + ConfigurationSection section = this.config.getConfigurationSection("world-settings." + this.legacyWorldName); +@@ -33,7 +44,7 @@ public class SpigotWorldConfig { + } this.log("-------- World Settings For [" + this.worldName + "] --------"); - SpigotConfig.readConfig(SpigotWorldConfig.class, this); diff --git a/aspaper-server/paper-patches/features/0006-World-overrides.patch b/aspaper-server/paper-patches/features/0006-World-overrides.patch deleted file mode 100644 index 8e3cb747c..000000000 --- a/aspaper-server/paper-patches/features/0006-World-overrides.patch +++ /dev/null @@ -1,41 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: David -Date: Thu, 18 Sep 2025 20:28:39 +0200 -Subject: [PATCH] World overrides - - -diff --git a/src/main/java/io/papermc/paper/world/PaperWorldLoader.java b/src/main/java/io/papermc/paper/world/PaperWorldLoader.java -index f5e9dd5880681cf4c9b442fbd2e0e9aad1cfb804..6de0f58a8c1a08393276ecaba5b8e7e9640bdbf6 100644 ---- a/src/main/java/io/papermc/paper/world/PaperWorldLoader.java -+++ b/src/main/java/io/papermc/paper/world/PaperWorldLoader.java -@@ -3,6 +3,7 @@ package io.papermc.paper.world; - import com.google.common.io.Files; - import com.mojang.logging.LogUtils; - import com.mojang.serialization.Dynamic; -+import net.minecraft.core.Registry; - import net.minecraft.core.registries.Registries; - import net.minecraft.nbt.NbtException; - import net.minecraft.nbt.ReportedNbtException; -@@ -108,7 +109,21 @@ public record PaperWorldLoader(MinecraftServer server, String levelId) { - - // Loosely modeled on code in net.minecraft.server.Main - public void loadInitialWorlds() { -- for (final LevelStem stem : this.server.registryAccess().lookupOrThrow(Registries.LEVEL_STEM)) { -+ //ASP start - overwrite vanilla worlds if needed -+ Registry registry = this.server.registryAccess().lookupOrThrow(Registries.LEVEL_STEM); -+ for (final LevelStem stem : registry) { -+ ResourceKey levelStemKey = registry.getResourceKey(stem).get(); -+ if(levelStemKey == LevelStem.NETHER && com.infernalsuite.asp.SlimeNMSBridgeImpl.instance().loadNetherOverride()) { -+ continue; -+ } -+ if(levelStemKey == LevelStem.END && com.infernalsuite.asp.SlimeNMSBridgeImpl.instance().loadEndOverride()) { -+ continue; -+ } -+ if(levelStemKey == LevelStem.OVERWORLD && com.infernalsuite.asp.SlimeNMSBridgeImpl.instance().loadOverworldOverride()) { -+ continue; -+ } -+ //ASP end - overwrite vanilla worlds if needed -+ - final WorldLoadingInfo info = this.getWorldInfo(this.levelId, stem); - this.migrateWorldFolder(info); - if (!info.enabled()) { diff --git a/aspaper-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftServer.java.patch b/aspaper-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftServer.java.patch index 7e591c25d..46411ebe0 100644 --- a/aspaper-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftServer.java.patch +++ b/aspaper-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/CraftServer.java.patch @@ -1,6 +1,6 @@ --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1347,6 +_,8 @@ +@@ -1343,6 +_,8 @@ return false; } diff --git a/aspaper-server/src/main/java/com/infernalsuite/asp/AdvancedSlimePaper.java b/aspaper-server/src/main/java/com/infernalsuite/asp/AdvancedSlimePaper.java index 188e13b78..2966b59d7 100644 --- a/aspaper-server/src/main/java/com/infernalsuite/asp/AdvancedSlimePaper.java +++ b/aspaper-server/src/main/java/com/infernalsuite/asp/AdvancedSlimePaper.java @@ -48,6 +48,7 @@ public class AdvancedSlimePaper implements AdvancedSlimePaperAPI { } private final SlimeSerializationAdapter serializationAdapter = new SlimeSerializationAdapterImpl(); + private final AnvilWorldReader reader = new AnvilWorldReader(BRIDGE_INSTANCE.getSlimeDataConverter()); public static AdvancedSlimePaper instance() { return (AdvancedSlimePaper) AdvancedSlimePaperAPI.instance(); @@ -206,7 +207,7 @@ public SlimeWorld readVanillaWorld(File worldDir, String worldName, SlimeLoader SlimeWorld world; try { - world = AnvilWorldReader.INSTANCE.readFromData(AnvilImportData.legacy(worldDir, worldName, loader)); + world = this.reader.readFromData(AnvilImportData.legacy(worldDir, worldName, loader)); } catch (RuntimeException e) { if (e.getCause() == null) { throw e; diff --git a/aspaper-server/src/main/java/com/infernalsuite/asp/Converter.java b/aspaper-server/src/main/java/com/infernalsuite/asp/Converter.java index 7bf597c80..0c5e6efc8 100644 --- a/aspaper-server/src/main/java/com/infernalsuite/asp/Converter.java +++ b/aspaper-server/src/main/java/com/infernalsuite/asp/Converter.java @@ -55,13 +55,21 @@ public static Tag convertTag(T tag) { case Tag.TAG_BYTE_ARRAY -> new ByteArrayTag(((ByteArrayBinaryTag) tag).value()); case Tag.TAG_STRING -> StringTag.valueOf(((StringBinaryTag) tag).value()); case Tag.TAG_LIST -> { - ListTag list = new ListTag(); - for (BinaryTag entry : ((ListBinaryTag) tag)) list.addAndUnwrap(convertTag(entry)); + ListBinaryTag listTag = (ListBinaryTag) tag; + int size = listTag.size(); + ListTag list = new ListTag(new ArrayList<>(size)); + for (int i = 0; i < size; i++) { + list.addAndUnwrap(convertTag(listTag.get(i))); + } yield list; } case Tag.TAG_COMPOUND -> { - CompoundTag compound = new CompoundTag(); - ((CompoundBinaryTag) tag).forEach(entry -> compound.put(entry.getKey(), convertTag(entry.getValue()))); + CompoundBinaryTag compoundTag = (CompoundBinaryTag) tag; + if(compoundTag.isEmpty()) yield new CompoundTag(); + CompoundTag compound = new CompoundTag(new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(compoundTag.size(), 0.8f)); + for (Map.Entry entry : compoundTag) { + compound.put(entry.getKey(), convertTag(entry.getValue())); + } yield compound; } case Tag.TAG_INT_ARRAY -> new IntArrayTag(((IntArrayBinaryTag) tag).value()); @@ -99,26 +107,20 @@ public static T convertTag(Tag base) { if(originalList.isEmpty()) { yield (T) ListBinaryTag.empty(); } - List list = new ArrayList<>(originalList.size()); - BinaryTagType tagType = null; + ListBinaryTag.Builder listBuilder = ListBinaryTag.heterogeneousListBinaryTag(originalList.size()); for (Tag entry : originalList) { BinaryTag converted = convertTag(entry); - - if(tagType != null && !converted.type().equals(tagType)) { - tagType = BinaryTagTypes.LIST_WILDCARD; - } else if(tagType == null) { - tagType = converted.type(); - } - - list.add(converted); + listBuilder.add(converted); } - yield (T) ListBinaryTag.listBinaryTag(tagType, list); + yield (T) listBuilder.build(); } case Tag.TAG_COMPOUND -> { - CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(); - CompoundTag originalCompound = ((CompoundTag) base); - for (String key : originalCompound.keySet()) builder.put(key, convertTag(Objects.requireNonNull(originalCompound.get(key)))); + CompoundTag tag = (CompoundTag) base; + CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(tag.size()); + for (Map.Entry entry : tag.entrySet()) { + builder.put(entry.getKey(), convertTag(entry.getValue())); + } yield (T) builder.build(); } case Tag.TAG_INT_ARRAY -> (T) IntArrayBinaryTag.intArrayBinaryTag(((IntArrayTag) base).getAsIntArray()); diff --git a/aspaper-server/src/main/java/com/infernalsuite/asp/SimpleDataFixerConverter.java b/aspaper-server/src/main/java/com/infernalsuite/asp/SimpleDataFixerConverter.java index 1fa4802dd..c7eb01d74 100644 --- a/aspaper-server/src/main/java/com/infernalsuite/asp/SimpleDataFixerConverter.java +++ b/aspaper-server/src/main/java/com/infernalsuite/asp/SimpleDataFixerConverter.java @@ -15,6 +15,7 @@ import com.infernalsuite.asp.api.world.SlimeChunk; import com.infernalsuite.asp.api.world.SlimeChunkSection; import com.infernalsuite.asp.api.world.SlimeWorld; +import com.infernalsuite.asp.util.Util; import net.kyori.adventure.nbt.CompoundBinaryTag; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; @@ -129,11 +130,16 @@ private static CompoundBinaryTag convertAndBack(CompoundBinaryTag value, Consume @Override public CompoundBinaryTag convertChunkTo1_13(CompoundBinaryTag tag) { - CompoundTag nmsTag = (CompoundTag) Converter.convertTag(tag); + return convertChunk(tag, 1631); + } + + @Override + public CompoundBinaryTag convertChunk(CompoundBinaryTag globalTag, int to) { + CompoundTag nmsTag = (CompoundTag) Converter.convertTag(globalTag); int version = nmsTag.getInt("DataVersion").orElseThrow(); - long encodedNewVersion = DataConverter.encodeVersions(1631, Integer.MAX_VALUE); + long encodedNewVersion = DataConverter.encodeVersions(to, Integer.MAX_VALUE); long encodedCurrentVersion = DataConverter.encodeVersions(version, Integer.MAX_VALUE); MCTypeRegistry.CHUNK.convert(new NBTMapType(nmsTag), encodedCurrentVersion, encodedNewVersion); @@ -189,4 +195,9 @@ public ListBinaryTag convertBlockPalette(ListBinaryTag input, int from, int to) return Converter.convertTag(listType.getTag()); } + + @Override + public int getServerVersion() { + return 0; + } } diff --git a/aspaper-server/src/main/java/com/infernalsuite/asp/SlimeNMSBridgeImpl.java b/aspaper-server/src/main/java/com/infernalsuite/asp/SlimeNMSBridgeImpl.java index c0433c4b2..f55838f31 100644 --- a/aspaper-server/src/main/java/com/infernalsuite/asp/SlimeNMSBridgeImpl.java +++ b/aspaper-server/src/main/java/com/infernalsuite/asp/SlimeNMSBridgeImpl.java @@ -20,6 +20,7 @@ import net.minecraft.server.WorldLoader; import net.minecraft.server.dedicated.DedicatedServer; import net.minecraft.server.dedicated.DedicatedServerProperties; +import net.minecraft.world.Difficulty; import net.minecraft.world.level.gamerules.GameRules; import net.minecraft.world.level.gamerules.GameRuleMap; import net.minecraft.world.level.Level; @@ -27,8 +28,8 @@ 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 net.minecraft.world.level.storage.SavedDataStorage; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.bukkit.Bukkit; @@ -79,7 +80,7 @@ public boolean loadOverworldOverride() { // 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(); + SavedDataStorage worldpersistentdata = instance.getDataStorage(); instance.getCraftServer().scoreboardManager = new org.bukkit.craftbukkit.scoreboard.CraftScoreboardManager(instance.getServer(), instance.getScoreboard()); instance.getServer().commandStorage = new CommandStorage(worldpersistentdata); @@ -170,7 +171,7 @@ public int getCurrentVersion() { public void registerWorld(SlimeLevelInstance server) { MinecraftServer mcServer = MinecraftServer.getServer(); - mcServer.initWorld(server, server.serverLevelData, server.serverLevelData.worldGenOptions()); + mcServer.initWorld(server, null); mcServer.addLevel(server); } @@ -210,14 +211,12 @@ private PrimaryLevelData createWorldData(SlimeWorld world) { MinecraftServer mcServer = MinecraftServer.getServer(); DedicatedServerProperties serverProps = ((DedicatedServer) mcServer).getProperties(); String worldName = world.getName(); - WorldLoader.DataLoadContext context = mcServer.worldLoaderContext; - LevelSettings worldsettings = new LevelSettings(worldName, serverProps.gameMode.get(), false, serverProps.difficulty.get(), - true, new GameRules(context.dataConfiguration().enabledFeatures(), GameRuleMap.of()), mcServer.worldLoaderContext.dataConfiguration()); + LevelSettings worldsettings = new LevelSettings(worldName, serverProps.gameMode.get(), new LevelSettings.DifficultySettings( + serverProps.difficulty.get(), serverProps.hardcore, false + ), true, mcServer.worldLoaderContext.dataConfiguration()); - WorldOptions worldoptions = new WorldOptions(0, false, false); - - PrimaryLevelData data = new PrimaryLevelData(worldsettings, worldoptions, PrimaryLevelData.SpecialWorldProperty.FLAT, Lifecycle.stable()); + PrimaryLevelData data = new PrimaryLevelData(worldsettings, PrimaryLevelData.SpecialWorldProperty.FLAT, Lifecycle.stable()); data.checkName(worldName); data.setModdedInfo(mcServer.getServerModName(), mcServer.getModdedStatus().shouldReportAsModified()); data.setInitialized(true); diff --git a/aspaper-server/src/main/java/com/infernalsuite/asp/config/SlimePaperWorldConfig.java b/aspaper-server/src/main/java/com/infernalsuite/asp/config/SlimePaperWorldConfig.java index f2f3fcac8..771162adb 100644 --- a/aspaper-server/src/main/java/com/infernalsuite/asp/config/SlimePaperWorldConfig.java +++ b/aspaper-server/src/main/java/com/infernalsuite/asp/config/SlimePaperWorldConfig.java @@ -3,6 +3,7 @@ import io.papermc.paper.configuration.Configurations; import io.papermc.paper.configuration.PaperConfigurations; import io.papermc.paper.configuration.WorldConfiguration; +import net.kyori.adventure.key.Key; import net.minecraft.resources.Identifier; import net.minecraft.server.MinecraftServer; import net.minecraft.world.level.gamerules.GameRules; @@ -30,7 +31,7 @@ private static void initialize( PaperConfigurations paperConfigurations, MinecraftServer server ) { - SpigotWorldConfig spigotWorldConfig = new SpigotWorldConfig("asp-slimeworld"); + SpigotWorldConfig spigotWorldConfig = new SpigotWorldConfig("asp-slimeworld", Key.key(FAKE_WORLD_KEY.getNamespace(), FAKE_WORLD_KEY.getPath()), false); GameRules gameRules = new GameRules(server.worldLoaderContext.dataConfiguration().enabledFeatures()); @@ -43,7 +44,6 @@ private static void initialize( */ Path.of("config", "advancedslimepaper"), - "asp-slimeworld", FAKE_WORLD_KEY, spigotWorldConfig, server.registryAccess(), diff --git a/aspaper-server/src/main/java/com/infernalsuite/asp/level/ReadOnlyDimensionDataStorage.java b/aspaper-server/src/main/java/com/infernalsuite/asp/level/ReadOnlyDimensionDataStorage.java index 4397426de..f17727f8e 100644 --- a/aspaper-server/src/main/java/com/infernalsuite/asp/level/ReadOnlyDimensionDataStorage.java +++ b/aspaper-server/src/main/java/com/infernalsuite/asp/level/ReadOnlyDimensionDataStorage.java @@ -4,7 +4,7 @@ import net.minecraft.core.HolderLookup; import net.minecraft.world.level.saveddata.SavedData; import net.minecraft.world.level.saveddata.SavedDataType; -import net.minecraft.world.level.storage.DimensionDataStorage; +import net.minecraft.world.level.storage.SavedDataStorage; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -15,7 +15,7 @@ /* * This dimension data storage does not serialize and/or load from disk. */ -public class ReadOnlyDimensionDataStorage extends DimensionDataStorage { +public class ReadOnlyDimensionDataStorage extends SavedDataStorage { public ReadOnlyDimensionDataStorage(Path dataFolder, DataFixer fixerUpper, HolderLookup.Provider registries) { super(dataFolder, fixerUpper, registries); diff --git a/aspaper-server/src/main/java/com/infernalsuite/asp/level/SlimeInMemoryWorld.java b/aspaper-server/src/main/java/com/infernalsuite/asp/level/SlimeInMemoryWorld.java index 7fb69fc6c..ce8814728 100644 --- a/aspaper-server/src/main/java/com/infernalsuite/asp/level/SlimeInMemoryWorld.java +++ b/aspaper-server/src/main/java/com/infernalsuite/asp/level/SlimeInMemoryWorld.java @@ -3,7 +3,7 @@ import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.ChunkEntitySlices; import ca.spottedleaf.moonrise.patches.chunk_system.level.poi.PoiChunk; import com.infernalsuite.asp.Converter; -import com.infernalsuite.asp.Util; +import com.infernalsuite.asp.util.Util; import com.infernalsuite.asp.api.exceptions.WorldAlreadyExistsException; import com.infernalsuite.asp.api.loaders.SlimeLoader; import com.infernalsuite.asp.api.world.properties.SlimeProperties; @@ -181,13 +181,7 @@ public SlimeWorld getSerializableCopy() { // NMS "live" chunks need to be converted { NMSSlimeChunk chunk = null; - if (clonedChunk instanceof SafeNmsChunkWrapper safeNmsChunkWrapper) { - if (safeNmsChunkWrapper.shouldDefaultBackToSlimeChunk()) { - clonedChunk = safeNmsChunkWrapper.getSafety(); - } else { - chunk = safeNmsChunkWrapper.getWrapper(); - } - } else if (clonedChunk instanceof NMSSlimeChunk nmsSlimeChunk) { + if (clonedChunk instanceof NMSSlimeChunk nmsSlimeChunk) { chunk = nmsSlimeChunk; } @@ -295,6 +289,6 @@ public SlimeLevelInstance getInstance() { } public void promoteInChunkStorage(SlimeChunkLevel chunk) { - chunkStorage.put(Util.chunkPosition(chunk.locX, chunk.locZ), chunk.getSafeSlimeReference()); + chunkStorage.put(Util.chunkPosition(chunk.locX, chunk.locZ), chunk.getNmsSlimeChunk()); } } diff --git a/aspaper-server/src/main/java/com/infernalsuite/asp/level/SlimeLevelInstance.java b/aspaper-server/src/main/java/com/infernalsuite/asp/level/SlimeLevelInstance.java index 51a1c1b81..fe51b4336 100644 --- a/aspaper-server/src/main/java/com/infernalsuite/asp/level/SlimeLevelInstance.java +++ b/aspaper-server/src/main/java/com/infernalsuite/asp/level/SlimeLevelInstance.java @@ -17,6 +17,9 @@ import com.infernalsuite.asp.api.world.properties.SlimeProperties; import com.infernalsuite.asp.api.world.properties.SlimePropertyMap; import com.mojang.logging.LogUtils; +import io.papermc.paper.world.PaperWorldLoader; +import io.papermc.paper.world.saveddata.PaperLevelOverrides; +import io.papermc.paper.world.saveddata.PaperWorldPDC; import net.kyori.adventure.nbt.BinaryTag; import net.kyori.adventure.nbt.CompoundBinaryTag; import net.kyori.adventure.util.TriState; @@ -25,6 +28,7 @@ import net.minecraft.core.Holder; import net.minecraft.core.registries.Registries; import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtOps; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.Identifier; import net.minecraft.server.MinecraftServer; @@ -40,7 +44,10 @@ import net.minecraft.world.level.chunk.storage.RegionStorageInfo; import net.minecraft.world.level.dimension.LevelStem; import net.minecraft.world.level.gamerules.GameRules; +import net.minecraft.world.level.levelgen.WorldGenSettings; +import net.minecraft.world.level.levelgen.WorldOptions; import net.minecraft.world.level.storage.LevelData; +import net.minecraft.world.level.storage.LevelResource; import net.minecraft.world.level.storage.LevelStorageSource; import net.minecraft.world.level.storage.PrimaryLevelData; import net.minecraft.world.level.validation.DirectoryValidator; @@ -69,14 +76,15 @@ public class SlimeLevelInstance extends ServerLevel { - public static LevelStorageSource CUSTOM_LEVEL_STORAGE; + public static LevelStorageSource.LevelStorageAccess CUSTOM_LEVEL_STORAGE_ACCESS; private static final Logger LOGGER = LogUtils.getClassLogger(); 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()); + CUSTOM_LEVEL_STORAGE_ACCESS = new LevelStorageSource(path, path, directoryvalidator, DataFixers.getDataFixer()) + .createAccess("slime"); FileUtils.forceDeleteOnExit(path.toFile()); @@ -89,26 +97,35 @@ public class SlimeLevelInstance extends ServerLevel { .setNameFormat("SWM Pool Thread #%1$d").build()); private final Object saveLock = new Object(); + private final boolean isFlat; public SlimeLevelInstance(SlimeBootstrap slimeBootstrap, PrimaryLevelData primaryLevelData, - ResourceKey worldKey, - ResourceKey dimensionKey, LevelStem worldDimension, + ResourceKey dimensionKey, + ResourceKey levelStemKey, LevelStem levelStem, org.bukkit.World.Environment environment) throws IOException { + WorldGenSettings settings = WorldGenSettings.of( + new WorldOptions(WorldOptions.randomSeed(), false, false), + MinecraftServer.getServer().registryAccess() + ); + ConcurrentMap initialExtraData = slimeBootstrap.initial().getExtraData(); + PaperWorldPDC pdc = null; + if(initialExtraData.containsKey("BukkitValues")) { + pdc = PaperWorldPDC.CODEC.parse(NbtOps.INSTANCE, Converter.convertTag(initialExtraData.get("BukkitValues"))) + .getOrThrow(); + } - super(slimeBootstrap, MinecraftServer.getServer(), MinecraftServer.getServer().executor, - CUSTOM_LEVEL_STORAGE.createAccess(slimeBootstrap.initial().getName() + UUID.randomUUID(), dimensionKey), - primaryLevelData, worldKey, worldDimension, false, 0, - Collections.emptyList(), true, null, environment, null, null); - this.slimeInstance = new SlimeInMemoryWorld(slimeBootstrap, this); - + final PaperWorldLoader.LoadedWorldData data = new PaperWorldLoader.LoadedWorldData( + slimeBootstrap.initial().getName(), + UUID.randomUUID(), + pdc, + PaperLevelOverrides.createFromLiveLevelData(primaryLevelData) + ); SlimePropertyMap propertyMap = slimeBootstrap.initial().getPropertyMap(); - - this.serverLevelData.setDifficulty(Difficulty.valueOf(propertyMap.getValue(SlimeProperties.DIFFICULTY).toUpperCase())); - serverLevelData.setSpawn( + data.levelOverrides().setSpawn( new LevelData.RespawnData( GlobalPos.of( - ResourceKey.create(Registries.DIMENSION, this.dimension().identifier()), + ResourceKey.create(Registries.DIMENSION, dimensionKey.identifier()), new BlockPos( propertyMap.getValue(SlimeProperties.SPAWN_X), propertyMap.getValue(SlimeProperties.SPAWN_Y), @@ -119,22 +136,45 @@ public SlimeLevelInstance(SlimeBootstrap slimeBootstrap, PrimaryLevelData primar Mth.wrapDegrees(0F) ) ); + data.levelOverrides().setDifficulty(Difficulty.valueOf(propertyMap.getValue(SlimeProperties.DIFFICULTY).toUpperCase())); + data.levelOverrides().attach(primaryLevelData, dimensionKey); + data.levelOverrides().setInitialized(true); + + super( + slimeBootstrap, + MinecraftServer.getServer(), + MinecraftServer.getServer().executor, + CUSTOM_LEVEL_STORAGE_ACCESS, + settings, + dimensionKey, + levelStem, + + false, + 0L, + Collections.emptyList(), + true, + levelStemKey, + + environment, + null, + null, + new ReadOnlyDimensionDataStorage(CUSTOM_LEVEL_STORAGE_ACCESS.getDimensionPath(dimensionKey), MinecraftServer.getServer().getFixerUpper(), MinecraftServer.getServer().registryAccess()), + data + ); + this.isFlat = slimeBootstrap.initial().getPropertyMap().getValue(SlimeProperties.WORLD_TYPE).equalsIgnoreCase("flat"); + + this.slimeInstance = new SlimeInMemoryWorld(slimeBootstrap, this); super.chunkSource.setSpawnSettings(propertyMap.getValue(SlimeProperties.ALLOW_MONSTERS), propertyMap.getValue(SlimeProperties.ALLOW_ANIMALS)); - ConcurrentMap extraData = this.slimeInstance.getExtraData(); - //Attempt to read PDC - if (extraData.containsKey("BukkitValues")) { - getWorld().readBukkitValues(Converter.convertTag(extraData.get("BukkitValues"))); - } propertyMap.getOptionalValue(SlimeProperties.PVP) .ifPresent(val -> getGameRules().set(GameRules.PVP, val, this)); this.entityDataController = new SlimeEntityDataLoader( new ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller.EntityDataController.EntityRegionFileStorage( - new RegionStorageInfo(levelStorageAccess.getLevelId(), worldKey, "entities"), - levelStorageAccess.getDimensionPath(worldKey).resolve("entities"), + new RegionStorageInfo(CUSTOM_LEVEL_STORAGE_ACCESS.getLevelId(), dimensionKey, "entities"), + CUSTOM_LEVEL_STORAGE_ACCESS.getDimensionPath(dimensionKey).resolve("entities"), MinecraftServer.getServer().forceSynchronousWrites() ), this.chunkTaskScheduler, @@ -143,6 +183,11 @@ public SlimeLevelInstance(SlimeBootstrap slimeBootstrap, PrimaryLevelData primar this.poiDataController = new SlimePoiDataLoader(this, this.chunkTaskScheduler); } + @Override + public boolean isFlat() { + return isFlat; + } + @Override public @NotNull ChunkGenerator getGenerator(SlimeBootstrap slimeBootstrap) { String biomeStr = slimeBootstrap.initial().getPropertyMap().getValue(SlimeProperties.DEFAULT_BIOME); @@ -175,8 +220,6 @@ public Future save() { Bukkit.getPluginManager().callEvent(new WorldSaveEvent(getWorld())); //this.getChunkSource().save(forceSave); - this.serverLevelData.setCustomBossEvents(MinecraftServer.getServer().getCustomBossEvents().save(MinecraftServer.getServer().registryAccess())); - if (MinecraftServer.getServer().isStopped()) { // Make sure the world gets saved before stopping the server by running it from the main thread saveInternal().get(); // Async wait for it to finish } else { @@ -221,7 +264,7 @@ public ChunkDataLoadTask getLoadTask(ChunkLoadTask task, ChunkTaskScheduler sche public void deleteTempFiles() { WORLD_SAVER_SERVICE.execute(() -> { - Path path = this.levelStorageAccess.levelDirectory.path(); + Path path = CUSTOM_LEVEL_STORAGE_ACCESS.getDimensionPath(this.dimension()); try { // We do this manually and not use the deleteLevel function as it would cause a level deleted message // to appear in the log which might be confusing for our users @@ -240,7 +283,7 @@ public void deleteTempFiles() { if (exception != null) { throw exception; } else { - if (dir.equals(levelStorageAccess.levelDirectory.path())) { + if (dir.equals(CUSTOM_LEVEL_STORAGE_ACCESS.getDimensionPath(dimension()))) { Files.deleteIfExists(path); } diff --git a/aspaper-server/src/main/java/com/infernalsuite/asp/level/chunk/NMSSlimeChunk.java b/aspaper-server/src/main/java/com/infernalsuite/asp/level/chunk/NMSSlimeChunk.java index 8165f80aa..1dd875495 100644 --- a/aspaper-server/src/main/java/com/infernalsuite/asp/level/chunk/NMSSlimeChunk.java +++ b/aspaper-server/src/main/java/com/infernalsuite/asp/level/chunk/NMSSlimeChunk.java @@ -52,12 +52,12 @@ public void updatePersistentDataContainer() { @Override public int getX() { - return chunk.getPos().x; + return chunk.getPos().x(); } @Override public int getZ() { - return chunk.getPos().z; + return chunk.getPos().z(); } @Override diff --git a/aspaper-server/src/main/java/com/infernalsuite/asp/level/chunk/SafeNmsChunkWrapper.java b/aspaper-server/src/main/java/com/infernalsuite/asp/level/chunk/SafeNmsChunkWrapper.java deleted file mode 100644 index c26290e89..000000000 --- a/aspaper-server/src/main/java/com/infernalsuite/asp/level/chunk/SafeNmsChunkWrapper.java +++ /dev/null @@ -1,129 +0,0 @@ -package com.infernalsuite.asp.level.chunk; - -import com.infernalsuite.asp.api.world.SlimeChunk; -import com.infernalsuite.asp.api.world.SlimeChunkSection; -import net.kyori.adventure.nbt.BinaryTag; -import net.kyori.adventure.nbt.CompoundBinaryTag; -import net.kyori.adventure.nbt.ListBinaryTag; -import org.jetbrains.annotations.Nullable; - -import java.util.List; -import java.util.Map; - -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 CompoundBinaryTag getHeightMaps() { - if (shouldDefaultBackToSlimeChunk()) { - return this.safety.getHeightMaps(); - } - - return this.wrapper.getHeightMaps(); - } - - @Override - public List getTileEntities() { - if (shouldDefaultBackToSlimeChunk()) { - return this.safety.getTileEntities(); - } - - return this.wrapper.getTileEntities(); - } - - @Override - public List getEntities() { - if (shouldDefaultBackToSlimeChunk()) { - return this.safety.getEntities(); - } - - return this.wrapper.getEntities(); - } - - @Override - public Map getExtraData() { - if (shouldDefaultBackToSlimeChunk()) { - return this.safety.getExtraData(); - } - - return this.wrapper.getExtraData(); - } - - @Override - public CompoundBinaryTag getUpgradeData() { - if (shouldDefaultBackToSlimeChunk()) { - return this.safety.getUpgradeData(); - } - - return this.wrapper.getUpgradeData(); - } - - @Override - public @Nullable ListBinaryTag getBlockTicks() { - if(shouldDefaultBackToSlimeChunk()) { - return this.safety.getBlockTicks(); - } - return this.wrapper.getBlockTicks(); - } - - @Override - public @Nullable ListBinaryTag getFluidTicks() { - if(shouldDefaultBackToSlimeChunk()) { - return this.safety.getFluidTicks(); - } - return this.wrapper.getFluidTicks(); - } - - @Override - public @Nullable CompoundBinaryTag getPoiChunkSections() { - if(shouldDefaultBackToSlimeChunk()) { - return this.safety.getPoiChunkSections(); - } - return this.wrapper.getPoiChunkSections(); - } - - /* - 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; - } -} diff --git a/aspaper-server/src/main/java/com/infernalsuite/asp/level/chunk/SlimeChunkConverter.java b/aspaper-server/src/main/java/com/infernalsuite/asp/level/chunk/SlimeChunkConverter.java index 277219ba0..7e586183c 100644 --- a/aspaper-server/src/main/java/com/infernalsuite/asp/level/chunk/SlimeChunkConverter.java +++ b/aspaper-server/src/main/java/com/infernalsuite/asp/level/chunk/SlimeChunkConverter.java @@ -11,6 +11,7 @@ import com.infernalsuite.asp.skeleton.SlimeChunkSectionSkeleton; import com.mojang.serialization.Codec; import com.mojang.serialization.DataResult; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; import net.kyori.adventure.nbt.CompoundBinaryTag; import net.kyori.adventure.nbt.ListBinaryTag; import net.minecraft.SharedConstants; @@ -100,12 +101,20 @@ public static SlimeChunkLevel deserializeSlimeChunk(SlimeLevelInstance instance, PalettedContainer blockPalette; if (slimeSection.getBlockStatesTag() != null) { - DataResult> dataresult = instance.palettedContainerFactory().blockStatesContainerCodec().parse(NbtOps.INSTANCE, Converter.convertTag(slimeSection.getBlockStatesTag())).promotePartial((s) -> { + + //If Paper AntiXray is not ready during upgrading, comment these three lines and uncomment the one below. + final BlockState[] presetBlockStates = instance.chunkPacketBlockController.getPresetBlockStates(instance, pos, sectionId); // Paper - Anti-Xray - Add preset block states + final Codec> antiXrayBlockStateCodec = presetBlockStates == null ? instance.palettedContainerFactory().blockStatesContainerCodec() + : PalettedContainer.codecRW(BlockState.CODEC, instance.palettedContainerFactory().blockStatesStrategy(), net.minecraft.world.level.block.Blocks.AIR.defaultBlockState(), presetBlockStates); // Paper - Anti-Xray + //For upgrading purposes only + //final Codec> antiXrayBlockStateCodec = instance.palettedContainerFactory().blockStatesContainerCodec(); + + DataResult> dataresult = antiXrayBlockStateCodec.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(); // todo proper logging } else { - blockPalette = new PalettedContainer<>(Blocks.AIR.defaultBlockState(), instance.palettedContainerFactory().blockStatesStrategy(), null); + blockPalette = instance.palettedContainerFactory().createForBlockStates(instance, pos, instance.getSectionYFromSectionIndex(sectionId)); } PalettedContainer> biomePalette; @@ -116,7 +125,7 @@ public static SlimeChunkLevel deserializeSlimeChunk(SlimeLevelInstance instance, }); biomePalette = dataresult.getOrThrow(); // todo proper logging } else { - biomePalette = new PalettedContainer<>(biomeRegistry.get(Biomes.PLAINS).orElseThrow(), instance.palettedContainerFactory().biomeStrategy(), null); + biomePalette = instance.palettedContainerFactory().createForBiomes(); } if (sectionId < sections.length) { @@ -154,10 +163,15 @@ public static SlimeChunkLevel deserializeSlimeChunk(SlimeLevelInstance instance, upgradeData = UpgradeData.EMPTY; } + List tileEntities = new ObjectArrayList<>(chunk.getTileEntities().size()); + for (CompoundBinaryTag tileEntity : chunk.getTileEntities()) { + tileEntities.add((CompoundTag) Converter.convertTag(tileEntity)); + } + LevelChunk.PostLoadProcessor processor = SerializableChunkData.postLoadChunk( instance, new ArrayList<>(), //Entities are loaded by moonrise - chunk.getTileEntities().stream().map(tag -> (net.minecraft.nbt.CompoundTag) Converter.convertTag(tag)).toList() + tileEntities ); SlimeChunkLevel nmsChunk = new SlimeChunkLevel(instance, chunk, pos, upgradeData, blockLevelChunkTicks, diff --git a/aspaper-server/src/main/java/com/infernalsuite/asp/level/chunk/SlimeChunkLevel.java b/aspaper-server/src/main/java/com/infernalsuite/asp/level/chunk/SlimeChunkLevel.java index 6bee94368..16d66c2cd 100644 --- a/aspaper-server/src/main/java/com/infernalsuite/asp/level/chunk/SlimeChunkLevel.java +++ b/aspaper-server/src/main/java/com/infernalsuite/asp/level/chunk/SlimeChunkLevel.java @@ -17,7 +17,6 @@ public class SlimeChunkLevel extends LevelChunk { private final SlimeInMemoryWorld inMemoryWorld; private final NMSSlimeChunk nmsSlimeChunk; - private final @Nullable SlimeChunk slimeReference; public SlimeChunkLevel( SlimeLevelInstance world, @@ -34,7 +33,6 @@ public SlimeChunkLevel( super(world, pos, upgradeData, blockTickScheduler, fluidTickScheduler, inhabitedTime, sectionArrayInitializer, entityLoader, blendingData); this.inMemoryWorld = world.slimeInstance; this.nmsSlimeChunk = new NMSSlimeChunk(this, reference); - this.slimeReference = reference; } @Override @@ -46,11 +44,6 @@ public void loadCallback() { super.loadCallback(); } - public SlimeChunk getSafeSlimeReference() { - if(this.slimeReference == null) return this.nmsSlimeChunk; - return new SafeNmsChunkWrapper(this.nmsSlimeChunk, this.slimeReference); - } - public NMSSlimeChunk getNmsSlimeChunk() { return nmsSlimeChunk; } diff --git a/aspaper-server/src/main/java/com/infernalsuite/asp/util/NmsUtil.java b/aspaper-server/src/main/java/com/infernalsuite/asp/util/NmsUtil.java index c62d4c1d1..7485f9f85 100644 --- a/aspaper-server/src/main/java/com/infernalsuite/asp/util/NmsUtil.java +++ b/aspaper-server/src/main/java/com/infernalsuite/asp/util/NmsUtil.java @@ -47,6 +47,6 @@ public static void runSyncAndWait(Runnable runnable) { } public static NewChunkHolder getChunkHolder(LevelChunk chunk) { - return chunk.level.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(chunk.getPos().x, chunk.getPos().z); + return chunk.level.moonrise$getChunkTaskScheduler().chunkHolderManager.getChunkHolder(chunk.getPos().x(), chunk.getPos().z()); } } diff --git a/build-data/aspaper.at b/build-data/aspaper.at index 7a9085f55..52cf4d47e 100644 --- a/build-data/aspaper.at +++ b/build-data/aspaper.at @@ -5,6 +5,7 @@ protected-f net.minecraft.server.level.ServerLevel entityDataController protected-f net.minecraft.server.level.ServerLevel poiDataController public ca.spottedleaf.moonrise.patches.chunk_system.level.entity.ChunkEntitySlices entities public ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task.ChunkLoadTask chunkHolder +public net.minecraft.nbt.CompoundTag (Ljava/util/Map;)V public net.minecraft.server.MinecraftServer commandStorage public net.minecraft.world.level.chunk.storage.SerializableChunkData makeBiomeCodec(Lnet/minecraft/core/Registry;)Lcom/mojang/serialization/Codec; public net.minecraft.world.level.chunk.storage.SerializableChunkData postLoadChunk(Lnet/minecraft/server/level/ServerLevel;Ljava/util/List;Ljava/util/List;)Lnet/minecraft/world/level/chunk/LevelChunk$PostLoadProcessor; diff --git a/build.gradle.kts b/build.gradle.kts index 6249f1aa3..6865b4ad2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,6 @@ import org.gradle.api.tasks.testing.logging.TestExceptionFormat import org.gradle.api.tasks.testing.logging.TestLogEvent +import org.gradle.kotlin.dsl.register plugins { java @@ -69,3 +70,8 @@ subprojects { } } } + +tasks.register("createMojmapPaperclipJar") { + description = "Fallback to the old mojmap naming for our CI... Yes this was the easiest fix I could come up with." + dependsOn(":aspaper-server:createPaperclipJar") +} \ No newline at end of file diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 622214173..363e9de87 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -5,6 +5,7 @@ plugins { group = "com.infernalsuite" repositories { + maven("https://repo.papermc.io/repository/maven-public/") mavenLocal() mavenCentral() gradlePluginPortal() diff --git a/buildSrc/src/main/kotlin/constants.kt b/buildSrc/src/main/kotlin/constants.kt index 6f6fe73c9..2abb70b36 100644 --- a/buildSrc/src/main/kotlin/constants.kt +++ b/buildSrc/src/main/kotlin/constants.kt @@ -1,2 +1,2 @@ -const val JAVA_VERSION = 21 +const val JAVA_VERSION = 25 const val PAPER_MAVEN_PUBLIC_URL = "https://repo.papermc.io/repository/maven-public/" \ No newline at end of file diff --git a/core/src/main/java/com/infernalsuite/asp/serialization/anvil/AnvilWorldReader.java b/core/src/main/java/com/infernalsuite/asp/serialization/anvil/AnvilWorldReader.java index dfd6c0729..d62cd66ba 100644 --- a/core/src/main/java/com/infernalsuite/asp/serialization/anvil/AnvilWorldReader.java +++ b/core/src/main/java/com/infernalsuite/asp/serialization/anvil/AnvilWorldReader.java @@ -1,6 +1,7 @@ package com.infernalsuite.asp.serialization.anvil; -import com.infernalsuite.asp.Util; +import com.infernalsuite.asp.util.Util; +import com.infernalsuite.asp.api.SlimeDataConverter; import com.infernalsuite.asp.api.exceptions.InvalidWorldException; import com.infernalsuite.asp.api.utils.NibbleArray; import com.infernalsuite.asp.api.world.SlimeChunk; @@ -8,6 +9,7 @@ import com.infernalsuite.asp.api.world.SlimeWorld; import com.infernalsuite.asp.api.world.properties.SlimeProperties; import com.infernalsuite.asp.api.world.properties.SlimePropertyMap; +import com.infernalsuite.asp.serialization.slime.ChunkPruner; import com.infernalsuite.asp.skeleton.SlimeChunkSectionSkeleton; import com.infernalsuite.asp.skeleton.SlimeChunkSkeleton; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; @@ -39,21 +41,39 @@ public class AnvilWorldReader implements com.infernalsuite.asp.serialization.Sli private static final Logger LOGGER = LoggerFactory.getLogger(AnvilWorldReader.class); - public static final AnvilWorldReader INSTANCE = new AnvilWorldReader(); + public static final AnvilWorldReader INSTANCE_NO_CONVERSION = new AnvilWorldReader(null); + + private final SlimeDataConverter slimeDataConverter; + + public AnvilWorldReader(SlimeDataConverter slimeDataConverter) { + this.slimeDataConverter = slimeDataConverter; + } @Override public SlimeWorld readFromData(AnvilImportData importData) { Path worldDir = importData.worldDir(); try { - Path levelFile = worldDir.resolve("level.dat"); - if (!Files.exists(levelFile) || !Files.isRegularFile(levelFile)) { - throw new RuntimeException(new InvalidWorldException(worldDir)); - } - LevelData data = readLevelData(levelFile); + int worldVersion = 0; + int spawnX = 0; + int spawnY = 0; + int spawnZ = 0; - int worldVersion = data.version; + Path levelFile = worldDir.resolve("level.dat"); + if (Files.exists(levelFile) && Files.isRegularFile(levelFile)) { + LevelData levelData = readLevelData(levelFile); + worldVersion = levelData.version; + spawnX = levelData.x; + spawnY = levelData.y; + spawnZ = levelData.z; + } else { + if(!Files.exists(worldDir.resolve("region"))) + throw new RuntimeException(new InvalidWorldException(worldDir)); + + worldVersion = -1; //Figure that out later + //TODO: Spawn pos + } SlimePropertyMap propertyMap = new SlimePropertyMap(); @@ -67,6 +87,7 @@ public SlimeWorld readFromData(AnvilImportData importData) { * * Vanilla users would need to delete the main region folder in order to import other dimensions. */ + //TODO: Figure this out on 26.1.2. I think we cant so we need to make users provide this info Path environmentDir = worldDir; propertyMap.setValue(SlimeProperties.ENVIRONMENT, "normal"); if (!doesWorldContainRegion(worldDir)) { @@ -88,11 +109,45 @@ public SlimeWorld readFromData(AnvilImportData importData) { Long2ObjectMap chunks = new Long2ObjectOpenHashMap<>(); + int worldHeight; + int minY; + switch (propertyMap.getValue(SlimeProperties.ENVIRONMENT)) { + case "normal" -> { + worldHeight = 384; + minY = -64; + } + case "nether", "the_end" -> { + worldHeight = 256; + minY = 0; + } + case null, default -> throw new IllegalStateException("Unsupported environment, cant obtain world height data"); + } + + int minSectionY = minY >> 4; + int maxSectionY = (minY + worldHeight - 1) >> 4; + try (var stream = Files.newDirectoryStream(regionDir, path -> path.toString().endsWith(".mca"))) { for (final Path path : stream) { LOGGER.info("Loading region file {}...", path.getFileName()); - chunks.putAll(loadChunks(path, worldVersion, propertyMap).stream() - .collect(Collectors.toMap(chunk -> Util.chunkPosition(chunk.getX(), chunk.getZ()), Function.identity()))); + + List chunkNBT = loadChunks(path, propertyMap); + if(worldVersion == -1) { + for (CompoundBinaryTag entries : chunkNBT) { + int dataVersion = entries.getInt("DataVersion", -1); + if (dataVersion != -1) { + worldVersion = dataVersion; + break; + } + } + } + + for (CompoundBinaryTag entries : chunkNBT) { + SlimeChunk slimeChunk = convertChunk(entries, worldVersion, minSectionY, maxSectionY); + if(slimeChunk == null) continue; + if(ChunkPruner.canBePruned(slimeChunk)) continue; + + chunks.put(Util.chunkPosition(slimeChunk.getX(), slimeChunk.getZ()), slimeChunk); + } } } @@ -114,9 +169,9 @@ public SlimeWorld readFromData(AnvilImportData importData) { throw new InvalidWorldException(environmentDir); } - propertyMap.setValue(SlimeProperties.SPAWN_X, data.x); - propertyMap.setValue(SlimeProperties.SPAWN_Y, data.y); - propertyMap.setValue(SlimeProperties.SPAWN_Z, data.z); + propertyMap.setValue(SlimeProperties.SPAWN_X, spawnX); + propertyMap.setValue(SlimeProperties.SPAWN_Y, spawnY); + propertyMap.setValue(SlimeProperties.SPAWN_Z, spawnZ); return new com.infernalsuite.asp.skeleton.SkeletonSlimeWorld(importData.newName(), importData.loader(), importData.loader() == null, chunks, new ConcurrentHashMap<>(), propertyMap, worldVersion); @@ -148,7 +203,7 @@ private static LevelData readLevelData(Path file) throws IOException, InvalidWor throw new InvalidWorldException(file.getParent()); } - private static void loadEntities(Path path, int version, Long2ObjectMap chunkMap) throws IOException { + private void loadEntities(Path path, int version, Long2ObjectMap chunkMap) throws IOException { byte[] regionByteArray = Files.readAllBytes(path); //Is that in mca spec? Well, at least one world had empty MCA files, so lets just keep that here. if(regionByteArray.length == 0) return; @@ -187,7 +242,7 @@ private static void loadEntities(Path path, int version, Long2ObjectMap loadChunks(Path path, int worldVersion, SlimePropertyMap propertyMap) throws IOException { + private List loadChunks(Path path, SlimePropertyMap propertyMap) throws IOException { byte[] regionByteArray = Files.readAllBytes(path); DataInputStream inputStream = new DataInputStream(new ByteArrayInputStream(regionByteArray)); @@ -204,23 +259,6 @@ private static List loadChunks(Path path, int worldVersion, SlimePro } } - int worldHeight; - int minY; - switch (propertyMap.getValue(SlimeProperties.ENVIRONMENT)) { - case "normal" -> { - worldHeight = 384; - minY = -64; - } - case "nether", "the_end" -> { - worldHeight = 256; - minY = 0; - } - case null, default -> throw new IllegalStateException("Unsupported environment, cant obtain world height data"); - } - - int minSectionY = minY >> 4; - int maxSectionY = (minY + worldHeight - 1) >> 4; - return chunks.stream().map((entry) -> { try { @@ -232,7 +270,12 @@ private static List loadChunks(Path path, int worldVersion, SlimePro DataInputStream chunkStream = new DataInputStream(new ByteArrayInputStream(regionByteArray, entry.offset() + 5, chunkSize)); InputStream decompressorStream = compressionScheme == 1 ? new GZIPInputStream(chunkStream) : new InflaterInputStream(chunkStream); CompoundBinaryTag tag = BinaryTagIO.unlimitedReader().read(decompressorStream); - return readChunk(tag, worldVersion, minSectionY, maxSectionY); + + if(slimeDataConverter != null) { + tag = slimeDataConverter.convertChunk(tag, slimeDataConverter.getServerVersion()); + } + + return tag; } catch (IOException ex) { throw new RuntimeException(ex); } @@ -240,14 +283,14 @@ private static List loadChunks(Path path, int worldVersion, SlimePro }).filter(Objects::nonNull).collect(Collectors.toList()); } - private static void readEntityChunk(CompoundBinaryTag compound, int worldVersion, Long2ObjectMap slimeChunkMap) { + private void readEntityChunk(CompoundBinaryTag compound, int worldVersion, Long2ObjectMap slimeChunkMap) { int[] position = compound.getIntArray("Position"); if (position.length == 0) throw new IllegalStateException("Entity chunk is missing position data"); int chunkX = position[0]; int chunkZ = position[1]; int dataVersion = compound.getInt("DataVersion", -1); - if (dataVersion != worldVersion) { + if (dataVersion != worldVersion && (worldVersion != -1 || slimeDataConverter == null)) { LOGGER.error("Cannot load entity chunk at {},{}: data version {} does not match world version {}", chunkX, chunkZ, dataVersion, worldVersion); return; } @@ -260,6 +303,9 @@ private static void readEntityChunk(CompoundBinaryTag compound, int worldVersion for (BinaryTag binaryTag : compound.getList("Entities", BinaryTagTypes.COMPOUND)) { entities.add((CompoundBinaryTag) binaryTag); } + if(slimeDataConverter != null) { + entities = slimeDataConverter.convertEntities(entities, dataVersion, slimeDataConverter.getServerVersion()); + } slimeChunkMap.put(Util.chunkPosition(chunkX, chunkZ), new SlimeChunkSkeleton( chunk.getX(), @@ -277,12 +323,12 @@ private static void readEntityChunk(CompoundBinaryTag compound, int worldVersion } } - private static SlimeChunk readChunk(CompoundBinaryTag compound, int worldVersion, int minSectionY, int maxSectionY) { + private SlimeChunk convertChunk(CompoundBinaryTag compound, int worldVersion, int minSectionY, int maxSectionY) { int chunkX = compound.getInt("xPos"); int chunkZ = compound.getInt("zPos"); int dataVersion = compound.getInt("DataVersion", -1); - if (dataVersion != worldVersion) { + if (dataVersion != worldVersion && (worldVersion != -1 || slimeDataConverter == null)) { LOGGER.error("Cannot load chunk at {},{}: data version {} does not match world version {}", chunkX, chunkZ, dataVersion, worldVersion); return null; } diff --git a/core/src/main/java/com/infernalsuite/asp/serialization/slime/ChunkPruner.java b/core/src/main/java/com/infernalsuite/asp/serialization/slime/ChunkPruner.java index 892a327bd..58d21e813 100644 --- a/core/src/main/java/com/infernalsuite/asp/serialization/slime/ChunkPruner.java +++ b/core/src/main/java/com/infernalsuite/asp/serialization/slime/ChunkPruner.java @@ -42,6 +42,10 @@ public static boolean canBePruned(SlimeWorld world, SlimeChunk chunk) { return false; } + public static boolean canBePruned(SlimeChunk chunk) { + return chunk.getTileEntities().isEmpty() && chunk.getEntities().isEmpty() && areSectionsEmpty(chunk.getSections()); + } + // TAG_List("palette"): 1 entries of type TAG_Compound //[13:15:06 INFO]: { //[13:15:06 INFO]: TAG_Compound: 1 entries diff --git a/core/src/main/java/com/infernalsuite/asp/serialization/slime/SlimeSerializer.java b/core/src/main/java/com/infernalsuite/asp/serialization/slime/SlimeSerializer.java index a11791c7f..9c36dd069 100644 --- a/core/src/main/java/com/infernalsuite/asp/serialization/slime/SlimeSerializer.java +++ b/core/src/main/java/com/infernalsuite/asp/serialization/slime/SlimeSerializer.java @@ -1,6 +1,6 @@ package com.infernalsuite.asp.serialization.slime; -import com.github.luben.zstd.Zstd; +import com.github.luben.zstd.ZstdOutputStream; import com.infernalsuite.asp.api.utils.SlimeFormat; import com.infernalsuite.asp.api.world.SlimeChunk; import com.infernalsuite.asp.api.world.SlimeChunkSection; @@ -8,6 +8,7 @@ import com.infernalsuite.asp.api.world.properties.SlimeProperties; import com.infernalsuite.asp.api.world.properties.SlimePropertyMap; import com.infernalsuite.asp.serialization.slime.reader.impl.v13.v13AdditionalWorldData; +import com.infernalsuite.asp.util.ThrowingConsumer; import net.kyori.adventure.nbt.BinaryTag; import net.kyori.adventure.nbt.BinaryTagIO; import net.kyori.adventure.nbt.BinaryTagTypes; @@ -16,9 +17,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.IOException; +import java.io.*; import java.util.*; public class SlimeSerializer { @@ -60,20 +59,13 @@ public static byte[] serialize(SlimeWorld world) { outStream.writeByte(v13AdditionalWorldData.fromSet(additionalWorldData)); // Chunks - byte[] chunkData = serializeChunks(world, world.getChunkStorage(), additionalWorldData); - byte[] compressedChunkData = Zstd.compress(chunkData); - outStream.writeInt(compressedChunkData.length); - outStream.writeInt(chunkData.length); - outStream.write(compressedChunkData); - - // Extra Tag - byte[] extra = serializeCompoundTag(CompoundBinaryTag.builder().put(extraData).build()); - byte[] compressedExtra = Zstd.compress(extra); + writeCompressed(outStream, value -> serializeChunks(value, world, world.getChunkStorage(), additionalWorldData)); - outStream.writeInt(compressedExtra.length); - outStream.writeInt(extra.length); - outStream.write(compressedExtra); + writeCompressed(outStream, value -> { + //Avoid a buffered output stream by casting to DataOutput. Buffered Output Streams make the memory usage explode + BinaryTagIO.writer().write(CompoundBinaryTag.builder().put(extraData).build(), (DataOutput) new DataOutputStream(value)); + }); } catch (Exception e) { throw new RuntimeException(e); @@ -82,9 +74,7 @@ public static byte[] serialize(SlimeWorld world) { return outByteStream.toByteArray(); } - static byte[] serializeChunks(SlimeWorld world, Collection chunks, EnumSet data) throws IOException { - ByteArrayOutputStream outByteStream = new ByteArrayOutputStream(16384); - DataOutputStream outStream = new DataOutputStream(outByteStream); + static void serializeChunks(DataOutputStream outStream, SlimeWorld world, Collection chunks, EnumSet data) throws IOException { // Prune chunks List chunksToSave = chunks.stream() @@ -175,15 +165,31 @@ static byte[] serializeChunks(SlimeWorld world, Collection chunks, E // Extra Tag if (chunk.getExtraData() == null) { - LOGGER.warn("Chunk at " + chunk.getX() + ", " + chunk.getZ() + " from world " + world.getName() + " has no extra data! When deserialized, this chunk will have an empty extra data tag!"); + LOGGER.warn("Chunk at {}, {} from world {} has no extra data! When deserialized, this chunk will have an empty extra data tag!", chunk.getX(), chunk.getZ(), world.getName()); } byte[] extra = serializeCompoundTag(CompoundBinaryTag.from(chunk.getExtraData())); outStream.writeInt(extra.length); outStream.write(extra); } + } - return outByteStream.toByteArray(); + private static void writeCompressed(DataOutputStream out, ThrowingConsumer writer) throws Exception { + ByteArrayOutputStream compressedOut = new ByteArrayOutputStream(); + ZstdOutputStream zstd = new ZstdOutputStream(compressedOut); + DataOutputStream dataOut = new DataOutputStream(zstd); + + // write uncompressed data into zstd stream + writer.accept(dataOut); + + dataOut.flush(); + zstd.close(); + + byte[] compressed = compressedOut.toByteArray(); + + out.writeInt(compressed.length); + out.writeInt(dataOut.size()); + out.write(compressed); } private static CompoundBinaryTag wrap(String key, ListBinaryTag list) { @@ -197,7 +203,8 @@ protected static byte[] serializeCompoundTag(CompoundBinaryTag tag) throws IOExc if (tag == null || tag.isEmpty()) return new byte[0]; ByteArrayOutputStream outByteStream = new ByteArrayOutputStream(); - BinaryTagIO.writer().write(tag, outByteStream); + //Avoid a buffered output stream by casting to DataOutput. Buffered Output Streams make the memory usage explode + BinaryTagIO.writer().write(tag, (DataOutput) new DataOutputStream(outByteStream)); return outByteStream.toByteArray(); } diff --git a/core/src/main/java/com/infernalsuite/asp/serialization/slime/reader/impl/SlimeWorldDeserializerHelper.java b/core/src/main/java/com/infernalsuite/asp/serialization/slime/reader/impl/SlimeWorldDeserializerHelper.java new file mode 100644 index 000000000..6337e98d9 --- /dev/null +++ b/core/src/main/java/com/infernalsuite/asp/serialization/slime/reader/impl/SlimeWorldDeserializerHelper.java @@ -0,0 +1,63 @@ +package com.infernalsuite.asp.serialization.slime.reader.impl; + +import com.github.luben.zstd.ZstdInputStream; +import com.infernalsuite.asp.util.LimitedInputStream; +import net.kyori.adventure.nbt.BinaryTagIO; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import org.jetbrains.annotations.NotNull; + +import java.io.BufferedInputStream; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.IOException; + +public class SlimeWorldDeserializerHelper { + + public static DataInputStream openCompressedStream(DataInputStream stream) throws IOException { + int compressedLength = stream.readInt(); + stream.readInt(); //Decompressed length, legacy + + LimitedInputStream limitedInputStream = new LimitedInputStream(stream, compressedLength); + ZstdInputStream inputStream = new ZstdInputStream(limitedInputStream); + return new DataInputStream(new BufferedInputStream(inputStream)); + } + + public static @NotNull CompoundBinaryTag readLimitedCompound(DataInputStream stream) throws IOException { + int length = stream.readInt(); + if(length == 0) return CompoundBinaryTag.empty(); + + LimitedInputStream limitedInputStream = new LimitedInputStream(stream, length); + + //Avoid a buffered input stream by casting to DataInput. Buffered Input Streams make the memory + //usage explode (e.g. with buffered streams here 1,3gb; with a data input directly: 300mb) + CompoundBinaryTag tag = BinaryTagIO.unlimitedReader().read((DataInput) new DataInputStream(limitedInputStream)); + + //binary tag reading does not guarantee that the buffer is fully read. If we don't do this, + //we might error out later + limitedInputStream.drainRemaining(); + return tag; + } + + public static @NotNull CompoundBinaryTag readCompressedCompound(DataInputStream stream) throws IOException { + int compressedLength = stream.readInt(); + int decompressedLength = stream.readInt(); + + if(decompressedLength == 0) return CompoundBinaryTag.empty(); + + LimitedInputStream limitedInputStream = new LimitedInputStream(stream, compressedLength); + try(ZstdInputStream zstd = new ZstdInputStream(limitedInputStream)) { + + //Avoid a buffered input stream by casting to DataInput. Buffered Input Streams make the memory + //usage explode (e.g. with buffered streams here 1,3gb; with a data input directly: 300mb) + CompoundBinaryTag tag = BinaryTagIO.unlimitedReader().read((DataInput) new DataInputStream(zstd)); + + //binary tag reading does not guarantee that the buffer is fully read. If we don't do this, + //we might error out later + byte[] buffer = new byte[512]; + while (zstd.read(buffer) != -1) {} + + return tag; + } + } + +} diff --git a/core/src/main/java/com/infernalsuite/asp/serialization/slime/reader/impl/v10/v10SlimeWorldDeSerializer.java b/core/src/main/java/com/infernalsuite/asp/serialization/slime/reader/impl/v10/v10SlimeWorldDeSerializer.java index c8d366512..a8afb31c0 100644 --- a/core/src/main/java/com/infernalsuite/asp/serialization/slime/reader/impl/v10/v10SlimeWorldDeSerializer.java +++ b/core/src/main/java/com/infernalsuite/asp/serialization/slime/reader/impl/v10/v10SlimeWorldDeSerializer.java @@ -1,7 +1,7 @@ package com.infernalsuite.asp.serialization.slime.reader.impl.v10; import com.github.luben.zstd.Zstd; -import com.infernalsuite.asp.Util; +import com.infernalsuite.asp.util.Util; import com.infernalsuite.asp.api.exceptions.CorruptedWorldException; import com.infernalsuite.asp.api.loaders.SlimeLoader; import com.infernalsuite.asp.serialization.slime.reader.VersionedByteSlimeWorldReader; @@ -9,7 +9,6 @@ import com.infernalsuite.asp.api.world.SlimeChunk; import com.infernalsuite.asp.api.world.SlimeChunkSection; import com.infernalsuite.asp.api.world.SlimeWorld; -import com.infernalsuite.asp.api.world.properties.SlimeProperties; import com.infernalsuite.asp.api.world.properties.SlimePropertyMap; import com.infernalsuite.asp.skeleton.SlimeChunkSectionSkeleton; diff --git a/core/src/main/java/com/infernalsuite/asp/serialization/slime/reader/impl/v11/v11SlimeWorldDeSerializer.java b/core/src/main/java/com/infernalsuite/asp/serialization/slime/reader/impl/v11/v11SlimeWorldDeSerializer.java index fcb25b70d..3bb63cb70 100644 --- a/core/src/main/java/com/infernalsuite/asp/serialization/slime/reader/impl/v11/v11SlimeWorldDeSerializer.java +++ b/core/src/main/java/com/infernalsuite/asp/serialization/slime/reader/impl/v11/v11SlimeWorldDeSerializer.java @@ -1,7 +1,8 @@ package com.infernalsuite.asp.serialization.slime.reader.impl.v11; -import com.github.luben.zstd.Zstd; -import com.infernalsuite.asp.Util; +import com.github.luben.zstd.ZstdInputStream; +import com.infernalsuite.asp.serialization.slime.reader.impl.SlimeWorldDeserializerHelper; +import com.infernalsuite.asp.util.Util; import com.infernalsuite.asp.api.exceptions.CorruptedWorldException; import com.infernalsuite.asp.api.exceptions.NewerFormatException; import com.infernalsuite.asp.api.loaders.SlimeLoader; @@ -9,15 +10,18 @@ import com.infernalsuite.asp.api.world.SlimeChunk; import com.infernalsuite.asp.api.world.SlimeChunkSection; import com.infernalsuite.asp.api.world.SlimeWorld; -import com.infernalsuite.asp.api.world.properties.SlimeProperties; import com.infernalsuite.asp.api.world.properties.SlimePropertyMap; +import com.infernalsuite.asp.util.LimitedInputStream; +import com.infernalsuite.asp.skeleton.SlimeChunkSectionSkeleton; import com.infernalsuite.asp.skeleton.SlimeChunkSkeleton; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import net.kyori.adventure.nbt.*; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.io.ByteArrayInputStream; +import java.io.BufferedInputStream; +import java.io.DataInput; import java.io.DataInputStream; import java.io.IOException; import java.util.ArrayList; @@ -35,11 +39,11 @@ public class v11SlimeWorldDeSerializer implements com.infernalsuite.asp.serializ public SlimeWorld deserializeWorld(byte version, @Nullable SlimeLoader loader, String worldName, DataInputStream dataStream, SlimePropertyMap propertyMap, boolean readOnly) throws IOException, CorruptedWorldException, NewerFormatException { int worldVersion = dataStream.readInt(); - byte[] chunkBytes = readCompressed(dataStream); + DataInputStream chunkBytes = SlimeWorldDeserializerHelper.openCompressedStream(dataStream); Long2ObjectMap chunks = readChunks(propertyMap, chunkBytes); + chunkBytes.close(); - byte[] extraTagBytes = readCompressed(dataStream); - CompoundBinaryTag extraTag = readCompound(extraTagBytes); + CompoundBinaryTag extraTag = SlimeWorldDeserializerHelper.readCompressedCompound(dataStream); SlimePropertyMap worldPropertyMap = propertyMap; CompoundBinaryTag propertiesMap = extraTag.get("properties") != null @@ -57,12 +61,12 @@ public SlimeWorld deserializeWorld(byte version, @Nullable SlimeLoader loader, S ConcurrentMap extraData = new ConcurrentHashMap<>(); extraTag.forEach(entry -> extraData.put(entry.getKey(), entry.getValue())); + dataStream.close(); return new com.infernalsuite.asp.skeleton.SkeletonSlimeWorld(worldName, loader, readOnly, chunks, extraData, worldPropertyMap, worldVersion); } - private static Long2ObjectMap readChunks(SlimePropertyMap slimePropertyMap, byte[] chunkBytes) throws IOException { + private static Long2ObjectMap readChunks(SlimePropertyMap slimePropertyMap, DataInputStream chunkData) throws IOException { Long2ObjectMap chunkMap = new Long2ObjectOpenHashMap<>(); - DataInputStream chunkData = new DataInputStream(new ByteArrayInputStream(chunkBytes)); int chunks = chunkData.readInt(); for (int i = 0; i < chunks; i++) { @@ -97,33 +101,20 @@ private static Long2ObjectMap readChunks(SlimePropertyMap slimePrope } // Block Data - byte[] blockStateData = new byte[chunkData.readInt()]; - chunkData.read(blockStateData); - CompoundBinaryTag blockStateTag = readCompound(blockStateData); + CompoundBinaryTag blockStateTag = SlimeWorldDeserializerHelper.readLimitedCompound(chunkData); // Biome Data - byte[] biomeData = new byte[chunkData.readInt()]; - chunkData.read(biomeData); - CompoundBinaryTag biomeTag = readCompound(biomeData); + CompoundBinaryTag biomeTag = SlimeWorldDeserializerHelper.readLimitedCompound(chunkData); - chunkSections[sectionId] = new com.infernalsuite.asp.skeleton.SlimeChunkSectionSkeleton(blockStateTag, biomeTag, blockLightArray, skyLightArray); + chunkSections[sectionId] = new SlimeChunkSectionSkeleton(blockStateTag, biomeTag, blockLightArray, skyLightArray); } // HeightMaps - byte[] heightMapData = new byte[chunkData.readInt()]; - chunkData.read(heightMapData); - CompoundBinaryTag heightMaps = readCompound(heightMapData); + CompoundBinaryTag heightMaps = SlimeWorldDeserializerHelper.readLimitedCompound(chunkData); // Tile Entities - int compressedTileEntitiesLength = chunkData.readInt(); - int decompressedTileEntitiesLength = chunkData.readInt(); - byte[] compressedTileEntitiesData = new byte[compressedTileEntitiesLength]; - byte[] decompressedTileEntitiesData = new byte[decompressedTileEntitiesLength]; - chunkData.read(compressedTileEntitiesData); - Zstd.decompress(decompressedTileEntitiesData, compressedTileEntitiesData); - - CompoundBinaryTag tileEntitiesCompound = readCompound(decompressedTileEntitiesData); + CompoundBinaryTag tileEntitiesCompound = SlimeWorldDeserializerHelper.readCompressedCompound(chunkData); ListBinaryTag tileEntitiesTag = tileEntitiesCompound.getList("tileEntities", BinaryTagTypes.COMPOUND); List serializedTileEntities = new ArrayList<>(tileEntitiesTag.size()); @@ -133,14 +124,7 @@ private static Long2ObjectMap readChunks(SlimePropertyMap slimePrope // Entities - int compressedEntitiesLength = chunkData.readInt(); - int decompressedEntitiesLength = chunkData.readInt(); - byte[] compressedEntitiesData = new byte[compressedEntitiesLength]; - byte[] decompressedEntitiesData = new byte[decompressedEntitiesLength]; - chunkData.read(compressedEntitiesData); - Zstd.decompress(decompressedEntitiesData, compressedEntitiesData); - - CompoundBinaryTag entitiesCompound = readCompound(decompressedEntitiesData); + CompoundBinaryTag entitiesCompound = SlimeWorldDeserializerHelper.readCompressedCompound(chunkData); ListBinaryTag entitiesTag = entitiesCompound.getList("entities", BinaryTagTypes.COMPOUND); List serializedEntities = new ArrayList<>(entitiesTag.size()); for (BinaryTag binaryTag : entitiesTag) { @@ -153,19 +137,4 @@ private static Long2ObjectMap readChunks(SlimePropertyMap slimePrope return chunkMap; } - private static byte[] readCompressed(DataInputStream stream) throws IOException { - int compressedLength = stream.readInt(); - int decompressedLength = stream.readInt(); - byte[] compressedData = new byte[compressedLength]; - byte[] decompressedData = new byte[decompressedLength]; - stream.read(compressedData); - Zstd.decompress(decompressedData, compressedData); - return decompressedData; - } - - private static CompoundBinaryTag readCompound(byte[] tagBytes) throws IOException { - if (tagBytes.length == 0) return CompoundBinaryTag.empty(); - - return BinaryTagIO.unlimitedReader().read(new ByteArrayInputStream(tagBytes)); - } } diff --git a/core/src/main/java/com/infernalsuite/asp/serialization/slime/reader/impl/v12/v12SlimeWorldDeSerializer.java b/core/src/main/java/com/infernalsuite/asp/serialization/slime/reader/impl/v12/v12SlimeWorldDeSerializer.java index 71516aa23..1163e4d1f 100644 --- a/core/src/main/java/com/infernalsuite/asp/serialization/slime/reader/impl/v12/v12SlimeWorldDeSerializer.java +++ b/core/src/main/java/com/infernalsuite/asp/serialization/slime/reader/impl/v12/v12SlimeWorldDeSerializer.java @@ -1,7 +1,8 @@ package com.infernalsuite.asp.serialization.slime.reader.impl.v12; -import com.github.luben.zstd.Zstd; -import com.infernalsuite.asp.Util; +import com.github.luben.zstd.ZstdInputStream; +import com.infernalsuite.asp.serialization.slime.reader.impl.SlimeWorldDeserializerHelper; +import com.infernalsuite.asp.util.Util; import com.infernalsuite.asp.api.exceptions.CorruptedWorldException; import com.infernalsuite.asp.api.exceptions.NewerFormatException; import com.infernalsuite.asp.api.loaders.SlimeLoader; @@ -9,8 +10,8 @@ import com.infernalsuite.asp.api.world.SlimeChunk; import com.infernalsuite.asp.api.world.SlimeChunkSection; import com.infernalsuite.asp.api.world.SlimeWorld; -import com.infernalsuite.asp.api.world.properties.SlimeProperties; import com.infernalsuite.asp.api.world.properties.SlimePropertyMap; +import com.infernalsuite.asp.util.LimitedInputStream; import com.infernalsuite.asp.skeleton.SlimeChunkSectionSkeleton; import com.infernalsuite.asp.skeleton.SlimeChunkSkeleton; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; @@ -22,7 +23,8 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.io.ByteArrayInputStream; +import java.io.BufferedInputStream; +import java.io.DataInput; import java.io.DataInputStream; import java.io.IOException; import java.util.Collections; @@ -40,11 +42,11 @@ public class v12SlimeWorldDeSerializer implements com.infernalsuite.asp.serializ public SlimeWorld deserializeWorld(byte version, @Nullable SlimeLoader loader, String worldName, DataInputStream dataStream, SlimePropertyMap propertyMap, boolean readOnly) throws IOException, CorruptedWorldException, NewerFormatException { int worldVersion = dataStream.readInt(); - byte[] chunkBytes = readCompressed(dataStream); + DataInputStream chunkBytes = SlimeWorldDeserializerHelper.openCompressedStream(dataStream); Long2ObjectMap chunks = readChunks(propertyMap, chunkBytes); + chunkBytes.close(); - byte[] extraTagBytes = readCompressed(dataStream); - CompoundBinaryTag extraTag = readCompound(extraTagBytes); + CompoundBinaryTag extraTag = SlimeWorldDeserializerHelper.readCompressedCompound(dataStream); ConcurrentMap extraData = new ConcurrentHashMap<>(); extraTag.forEach(entry -> extraData.put(entry.getKey(), entry.getValue())); @@ -56,12 +58,12 @@ public SlimeWorld deserializeWorld(byte version, @Nullable SlimeLoader loader, S worldPropertyMap.merge(propertyMap); } + dataStream.close(); return new com.infernalsuite.asp.skeleton.SkeletonSlimeWorld(worldName, loader, readOnly, chunks, extraData, worldPropertyMap, worldVersion); } - private static Long2ObjectMap readChunks(SlimePropertyMap slimePropertyMap, byte[] chunkBytes) throws IOException { + private static Long2ObjectMap readChunks(SlimePropertyMap slimePropertyMap, DataInputStream chunkData) throws IOException { Long2ObjectMap chunkMap = new Long2ObjectOpenHashMap<>(); - DataInputStream chunkData = new DataInputStream(new ByteArrayInputStream(chunkBytes)); int chunks = chunkData.readInt(); for (int i = 0; i < chunks; i++) { @@ -79,7 +81,7 @@ private static Long2ObjectMap readChunks(SlimePropertyMap slimePrope NibbleArray blockLightArray; if (chunkData.readBoolean()) { byte[] blockLightByteArray = new byte[ARRAY_SIZE]; - chunkData.read(blockLightByteArray); + chunkData.readFully(blockLightByteArray); blockLightArray = new NibbleArray(blockLightByteArray); } else { blockLightArray = null; @@ -89,35 +91,28 @@ private static Long2ObjectMap readChunks(SlimePropertyMap slimePrope NibbleArray skyLightArray; if (chunkData.readBoolean()) { byte[] skyLightByteArray = new byte[ARRAY_SIZE]; - chunkData.read(skyLightByteArray); + chunkData.readFully(skyLightByteArray); skyLightArray = new NibbleArray(skyLightByteArray); } else { skyLightArray = null; } // Block Data - byte[] blockStateData = new byte[chunkData.readInt()]; - chunkData.read(blockStateData); - CompoundBinaryTag blockStateTag = readCompound(blockStateData); + CompoundBinaryTag blockStateTag = SlimeWorldDeserializerHelper.readLimitedCompound(chunkData); // Biome Data - byte[] biomeData = new byte[chunkData.readInt()]; - chunkData.read(biomeData); - CompoundBinaryTag biomeTag = readCompound(biomeData); + CompoundBinaryTag biomeTag = SlimeWorldDeserializerHelper.readLimitedCompound(chunkData); chunkSections[sectionId] = new SlimeChunkSectionSkeleton(blockStateTag, biomeTag, blockLightArray, skyLightArray); } // HeightMaps - byte[] heightMapData = new byte[chunkData.readInt()]; - chunkData.read(heightMapData); - CompoundBinaryTag heightMaps = readCompound(heightMapData); + CompoundBinaryTag heightMaps = SlimeWorldDeserializerHelper.readLimitedCompound(chunkData); // Tile Entities - byte[] tileEntitiesRaw = read(chunkData); List tileEntities; - CompoundBinaryTag tileEntitiesCompound = readCompound(tileEntitiesRaw); + CompoundBinaryTag tileEntitiesCompound = SlimeWorldDeserializerHelper.readLimitedCompound(chunkData); if (tileEntitiesCompound.isEmpty()) { tileEntities = Collections.emptyList(); } else { @@ -128,9 +123,8 @@ private static Long2ObjectMap readChunks(SlimePropertyMap slimePrope // Entities - byte[] entitiesRaw = read(chunkData); List entities; - CompoundBinaryTag entitiesCompound = readCompound(entitiesRaw); + CompoundBinaryTag entitiesCompound = SlimeWorldDeserializerHelper.readLimitedCompound(chunkData); if (entitiesCompound.isEmpty()) { entities = Collections.emptyList(); } else { @@ -140,8 +134,7 @@ private static Long2ObjectMap readChunks(SlimePropertyMap slimePrope } // Extra Tag - byte[] rawExtra = read(chunkData); - CompoundBinaryTag extra = readCompound(rawExtra); + CompoundBinaryTag extra = SlimeWorldDeserializerHelper.readLimitedCompound(chunkData); Map extraData = new HashMap<>(); extra.forEach(entry -> extraData.put(entry.getKey(), entry.getValue())); @@ -150,27 +143,4 @@ private static Long2ObjectMap readChunks(SlimePropertyMap slimePrope } return chunkMap; } - - private static byte[] readCompressed(DataInputStream stream) throws IOException { - int compressedLength = stream.readInt(); - int decompressedLength = stream.readInt(); - byte[] compressedData = new byte[compressedLength]; - byte[] decompressedData = new byte[decompressedLength]; - stream.read(compressedData); - Zstd.decompress(decompressedData, compressedData); - return decompressedData; - } - - private static byte[] read(DataInputStream stream) throws IOException { - int length = stream.readInt(); - byte[] data = new byte[length]; - stream.read(data); - return data; - } - - private static @NotNull CompoundBinaryTag readCompound(byte[] tagBytes) throws IOException { - if (tagBytes.length == 0) return CompoundBinaryTag.empty(); - - return BinaryTagIO.unlimitedReader().read(new ByteArrayInputStream(tagBytes)); - } } diff --git a/core/src/main/java/com/infernalsuite/asp/serialization/slime/reader/impl/v13/v13SlimeWorldDeSerializer.java b/core/src/main/java/com/infernalsuite/asp/serialization/slime/reader/impl/v13/v13SlimeWorldDeSerializer.java index 38cbac883..0ce34b844 100644 --- a/core/src/main/java/com/infernalsuite/asp/serialization/slime/reader/impl/v13/v13SlimeWorldDeSerializer.java +++ b/core/src/main/java/com/infernalsuite/asp/serialization/slime/reader/impl/v13/v13SlimeWorldDeSerializer.java @@ -1,8 +1,9 @@ package com.infernalsuite.asp.serialization.slime.reader.impl.v13; -import com.github.luben.zstd.Zstd; +import com.github.luben.zstd.ZstdInputStream; import com.infernalsuite.asp.SlimeLogger; -import com.infernalsuite.asp.Util; +import com.infernalsuite.asp.serialization.slime.reader.impl.SlimeWorldDeserializerHelper; +import com.infernalsuite.asp.util.Util; import com.infernalsuite.asp.api.exceptions.CorruptedWorldException; import com.infernalsuite.asp.api.exceptions.NewerFormatException; import com.infernalsuite.asp.api.loaders.SlimeLoader; @@ -10,8 +11,10 @@ import com.infernalsuite.asp.api.world.SlimeChunk; import com.infernalsuite.asp.api.world.SlimeChunkSection; import com.infernalsuite.asp.api.world.SlimeWorld; -import com.infernalsuite.asp.api.world.properties.SlimeProperties; import com.infernalsuite.asp.api.world.properties.SlimePropertyMap; +import com.infernalsuite.asp.util.LimitedInputStream; +import com.infernalsuite.asp.skeleton.SkeletonSlimeWorld; +import com.infernalsuite.asp.skeleton.SlimeChunkSectionSkeleton; import com.infernalsuite.asp.skeleton.SlimeChunkSkeleton; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; @@ -19,9 +22,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.io.ByteArrayInputStream; -import java.io.DataInputStream; -import java.io.IOException; +import java.io.*; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -38,11 +39,11 @@ public SlimeWorld deserializeWorld(byte version, @Nullable SlimeLoader loader, S int worldVersion = dataStream.readInt(); byte additionalWorldData = dataStream.readByte(); - byte[] chunkBytes = readCompressed(dataStream); + DataInputStream chunkBytes = SlimeWorldDeserializerHelper.openCompressedStream(dataStream); Long2ObjectMap chunks = readChunks(propertyMap, additionalWorldData, chunkBytes); + chunkBytes.close(); - byte[] extraTagBytes = readCompressed(dataStream); - CompoundBinaryTag extraTag = readCompound(extraTagBytes); + CompoundBinaryTag extraTag = SlimeWorldDeserializerHelper.readCompressedCompound(dataStream); ConcurrentMap extraData = new ConcurrentHashMap<>(); extraTag.forEach(entry -> extraData.put(entry.getKey(), entry.getValue())); @@ -54,12 +55,13 @@ public SlimeWorld deserializeWorld(byte version, @Nullable SlimeLoader loader, S worldPropertyMap.merge(propertyMap); } - return new com.infernalsuite.asp.skeleton.SkeletonSlimeWorld(worldName, loader, readOnly, chunks, extraData, worldPropertyMap, worldVersion); + + dataStream.close(); + return new SkeletonSlimeWorld(worldName, loader, readOnly, chunks, extraData, worldPropertyMap, worldVersion); } - private static Long2ObjectMap readChunks(SlimePropertyMap slimePropertyMap, byte additionalWorldData, byte[] chunkBytes) throws IOException { + private static Long2ObjectMap readChunks(SlimePropertyMap slimePropertyMap, byte additionalWorldData, DataInputStream chunkData) throws IOException { Long2ObjectMap chunkMap = new Long2ObjectOpenHashMap<>(); - DataInputStream chunkData = new DataInputStream(new ByteArrayInputStream(chunkBytes)); int chunks = chunkData.readInt(); for (int i = 0; i < chunks; i++) { @@ -77,7 +79,7 @@ private static Long2ObjectMap readChunks(SlimePropertyMap slimePrope NibbleArray blockLightArray; if ((sectionFlags & 1) == 1) { byte[] blockLightByteArray = new byte[ARRAY_SIZE]; - chunkData.read(blockLightByteArray); + chunkData.readFully(blockLightByteArray); blockLightArray = new NibbleArray(blockLightByteArray); } else { blockLightArray = null; @@ -87,49 +89,37 @@ private static Long2ObjectMap readChunks(SlimePropertyMap slimePrope NibbleArray skyLightArray; if (((sectionFlags >> 1) & 1) == 1) { byte[] skyLightByteArray = new byte[ARRAY_SIZE]; - chunkData.read(skyLightByteArray); + chunkData.readFully(skyLightByteArray); skyLightArray = new NibbleArray(skyLightByteArray); } else { skyLightArray = null; } // Block Data - byte[] blockStateData = new byte[chunkData.readInt()]; - chunkData.read(blockStateData); - CompoundBinaryTag blockStateTag = readCompound(blockStateData); + CompoundBinaryTag blockStateTag = SlimeWorldDeserializerHelper.readLimitedCompound(chunkData); // Biome Data - byte[] biomeData = new byte[chunkData.readInt()]; - chunkData.read(biomeData); - CompoundBinaryTag biomeTag = readCompound(biomeData); + CompoundBinaryTag biomeTag = SlimeWorldDeserializerHelper.readLimitedCompound(chunkData); - chunkSections[sectionId] = new com.infernalsuite.asp.skeleton.SlimeChunkSectionSkeleton(blockStateTag, biomeTag, blockLightArray, skyLightArray); + chunkSections[sectionId] = new SlimeChunkSectionSkeleton(blockStateTag, biomeTag, blockLightArray, skyLightArray); } // HeightMaps - byte[] heightMapData = new byte[chunkData.readInt()]; - chunkData.read(heightMapData); - CompoundBinaryTag heightMaps = readCompound(heightMapData); + CompoundBinaryTag heightMaps = SlimeWorldDeserializerHelper.readLimitedCompound(chunkData); CompoundBinaryTag poiChunk = null; if(v13AdditionalWorldData.POI_CHUNKS.isSet(additionalWorldData)) { - byte[] poiData = new byte[chunkData.readInt()]; - chunkData.read(poiData); - poiChunk = readCompound(poiData); + poiChunk = SlimeWorldDeserializerHelper.readLimitedCompound(chunkData); } ListBinaryTag blockTicks = null; if(v13AdditionalWorldData.BLOCK_TICKS.isSet(additionalWorldData)) { - byte[] blockTickData = new byte[chunkData.readInt()]; - chunkData.read(blockTickData); - CompoundBinaryTag tag = readCompound(blockTickData); + CompoundBinaryTag tag = SlimeWorldDeserializerHelper.readLimitedCompound(chunkData); blockTicks = tag.getList("block_ticks", BinaryTagTypes.COMPOUND); } ListBinaryTag fluidTicks = null; if(v13AdditionalWorldData.FLUID_TICKS.isSet(additionalWorldData)) { - byte[] fluidTickData = new byte[chunkData.readInt()]; - chunkData.read(fluidTickData); - CompoundBinaryTag tag = readCompound(fluidTickData); + CompoundBinaryTag tag = SlimeWorldDeserializerHelper.readLimitedCompound(chunkData); fluidTicks = tag.getList("fluid_ticks", BinaryTagTypes.COMPOUND); } @@ -144,9 +134,8 @@ private static Long2ObjectMap readChunks(SlimePropertyMap slimePrope // Tile Entities - byte[] tileEntitiesRaw = read(chunkData); List tileEntities; - CompoundBinaryTag tileEntitiesCompound = readCompound(tileEntitiesRaw); + CompoundBinaryTag tileEntitiesCompound = SlimeWorldDeserializerHelper.readLimitedCompound(chunkData); if (tileEntitiesCompound.isEmpty()) { tileEntities = Collections.emptyList(); } else { @@ -157,9 +146,8 @@ private static Long2ObjectMap readChunks(SlimePropertyMap slimePrope // Entities - byte[] entitiesRaw = read(chunkData); List entities; - CompoundBinaryTag entitiesCompound = readCompound(entitiesRaw); + CompoundBinaryTag entitiesCompound = SlimeWorldDeserializerHelper.readLimitedCompound(chunkData); if (entitiesCompound.isEmpty()) { entities = Collections.emptyList(); } else { @@ -169,8 +157,7 @@ private static Long2ObjectMap readChunks(SlimePropertyMap slimePrope } // Extra Tag - byte[] rawExtra = read(chunkData); - CompoundBinaryTag extra = readCompound(rawExtra); + CompoundBinaryTag extra = SlimeWorldDeserializerHelper.readLimitedCompound(chunkData); Map extraData = new HashMap<>(); extra.forEach(entry -> extraData.put(entry.getKey(), entry.getValue())); @@ -180,26 +167,5 @@ private static Long2ObjectMap readChunks(SlimePropertyMap slimePrope return chunkMap; } - private static byte[] readCompressed(DataInputStream stream) throws IOException { - int compressedLength = stream.readInt(); - int decompressedLength = stream.readInt(); - byte[] compressedData = new byte[compressedLength]; - byte[] decompressedData = new byte[decompressedLength]; - stream.read(compressedData); - Zstd.decompress(decompressedData, compressedData); - return decompressedData; - } - - private static byte[] read(DataInputStream stream) throws IOException { - int length = stream.readInt(); - byte[] data = new byte[length]; - stream.read(data); - return data; - } - - private static @NotNull CompoundBinaryTag readCompound(byte[] tagBytes) throws IOException { - if (tagBytes.length == 0) return CompoundBinaryTag.empty(); - return BinaryTagIO.unlimitedReader().read(new ByteArrayInputStream(tagBytes)); - } } diff --git a/core/src/main/java/com/infernalsuite/asp/serialization/slime/reader/impl/v1_9/v1_9SlimeWorldDeserializer.java b/core/src/main/java/com/infernalsuite/asp/serialization/slime/reader/impl/v1_9/v1_9SlimeWorldDeserializer.java index 1086e2fd0..8c5196d1b 100644 --- a/core/src/main/java/com/infernalsuite/asp/serialization/slime/reader/impl/v1_9/v1_9SlimeWorldDeserializer.java +++ b/core/src/main/java/com/infernalsuite/asp/serialization/slime/reader/impl/v1_9/v1_9SlimeWorldDeserializer.java @@ -2,7 +2,7 @@ import com.github.luben.zstd.Zstd; import com.infernalsuite.asp.SlimeLogger; -import com.infernalsuite.asp.Util; +import com.infernalsuite.asp.util.Util; import com.infernalsuite.asp.api.exceptions.CorruptedWorldException; import com.infernalsuite.asp.api.loaders.SlimeLoader; import com.infernalsuite.asp.api.utils.NibbleArray; diff --git a/core/src/main/java/com/infernalsuite/asp/skeleton/SkeletonCloning.java b/core/src/main/java/com/infernalsuite/asp/skeleton/SkeletonCloning.java index aa2a32fd9..08263ad2e 100644 --- a/core/src/main/java/com/infernalsuite/asp/skeleton/SkeletonCloning.java +++ b/core/src/main/java/com/infernalsuite/asp/skeleton/SkeletonCloning.java @@ -1,6 +1,6 @@ package com.infernalsuite.asp.skeleton; -import com.infernalsuite.asp.Util; +import com.infernalsuite.asp.util.Util; import com.infernalsuite.asp.api.loaders.SlimeLoader; import com.infernalsuite.asp.api.utils.NibbleArray; import com.infernalsuite.asp.api.world.SlimeChunk; diff --git a/core/src/main/java/com/infernalsuite/asp/skeleton/SkeletonSlimeWorld.java b/core/src/main/java/com/infernalsuite/asp/skeleton/SkeletonSlimeWorld.java index fdb7b85f9..edca3892f 100644 --- a/core/src/main/java/com/infernalsuite/asp/skeleton/SkeletonSlimeWorld.java +++ b/core/src/main/java/com/infernalsuite/asp/skeleton/SkeletonSlimeWorld.java @@ -1,6 +1,6 @@ package com.infernalsuite.asp.skeleton; -import com.infernalsuite.asp.Util; +import com.infernalsuite.asp.util.Util; import com.infernalsuite.asp.api.exceptions.WorldAlreadyExistsException; import com.infernalsuite.asp.api.loaders.SlimeLoader; import com.infernalsuite.asp.api.world.SlimeChunk; diff --git a/core/src/main/java/com/infernalsuite/asp/util/LimitedInputStream.java b/core/src/main/java/com/infernalsuite/asp/util/LimitedInputStream.java new file mode 100644 index 000000000..daa81b8ea --- /dev/null +++ b/core/src/main/java/com/infernalsuite/asp/util/LimitedInputStream.java @@ -0,0 +1,70 @@ +package com.infernalsuite.asp.util; + +import com.google.common.base.Preconditions; + +import java.io.IOException; +import java.io.InputStream; + +/** + * An {@link InputStream} that reads at most {@code limit} bytes from an underlying stream, + * then signals end-of-stream — leaving the underlying stream open and positioned immediately + * after the last byte of the limit. + * + *

Close behaviour

+ *

{@link #close()} intentionally does not close the underlying stream. Instead, it + * calls {@link #drainRemaining()}, which reads and discards any bytes that the caller did not + * consume. This ensures two invariants: + *

    + *
  1. The underlying stream stays open. Closing it would destroy the underlying stream, making further reads impossible.
  2. + *
  3. The underlying stream is left at a predictable position. If {@code close()} were + * called without draining, the unconsumed bytes would be seen by the next reader as the + * start of the following data block, silently corrupting the data.
  4. + *
+ * + *

Note: The caller is responsible for closing the underlying stream once all messages + * have been processed. + */ +public class LimitedInputStream extends InputStream { + private final InputStream in; + private int remaining; + + public LimitedInputStream(InputStream in, int limit) { + Preconditions.checkNotNull(in, "Input stream cannot be null"); + Preconditions.checkArgument(limit >= 0, "Limit must be non-negative"); + this.in = in; + this.remaining = limit; + } + + @Override + public int read() throws IOException { + if (remaining <= 0) return -1; + int b = in.read(); + if (b != -1) remaining--; + return b; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (remaining <= 0) return -1; + len = Math.min(len, remaining); + int read = in.read(b, off, len); + if (read > 0) remaining -= read; + return read; + } + + public void drainRemaining() { + byte[] buffer = new byte[512]; + try { + while (remaining > 0) { + int read = read(buffer, 0, Math.min(buffer.length, remaining)); + if (read == -1) break; + } + } catch (IOException ignored) { + } + } + + @Override + public void close() throws IOException { + drainRemaining(); + } +} diff --git a/core/src/main/java/com/infernalsuite/asp/util/ThrowingConsumer.java b/core/src/main/java/com/infernalsuite/asp/util/ThrowingConsumer.java new file mode 100644 index 000000000..0516ba4f8 --- /dev/null +++ b/core/src/main/java/com/infernalsuite/asp/util/ThrowingConsumer.java @@ -0,0 +1,7 @@ +package com.infernalsuite.asp.util; + +public interface ThrowingConsumer { + + public void accept(T value) throws Exception; + +} diff --git a/core/src/main/java/com/infernalsuite/asp/Util.java b/core/src/main/java/com/infernalsuite/asp/util/Util.java similarity index 85% rename from core/src/main/java/com/infernalsuite/asp/Util.java rename to core/src/main/java/com/infernalsuite/asp/util/Util.java index 1a3652ffe..0e1ed0dee 100644 --- a/core/src/main/java/com/infernalsuite/asp/Util.java +++ b/core/src/main/java/com/infernalsuite/asp/util/Util.java @@ -1,4 +1,4 @@ -package com.infernalsuite.asp; +package com.infernalsuite.asp.util; public final class Util { diff --git a/gradle.properties b/gradle.properties index 5da7a80bf..925065d60 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,10 +1,10 @@ group=com.infernalsuite.asp aspApiVersion=4.2.0-SNAPSHOT -apiVersion=1.21.11 -version=1.21.11-R0.1-SNAPSHOT -mcVersion=1.21.11 +apiVersion=26.1.2 +version=26.1.2.build.69-stable +mcVersion=26.1.2 -paperRef=3f5728e24942a470a428c60839eda5340f85f9f8 +paperRef=76d2ac758cb3abe75aceefa88207443768f585c6 org.gradle.caching=true org.gradle.parallel=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6ed94a33e..45a2ce363 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,11 +1,11 @@ [versions] -adventure = "4.24.0" -annotations = "26.0.1" +adventure = "4.26.1" +annotations = "1.0.0" autoservice = "1.1.1" blossom = "2.1.0" bstats = "3.1.0" cloud-core = "2.0.0" -cloud-minecraft = "2.0.0-beta.14" +cloud-minecraft = "2.0.0-SNAPSHOT" configurate = "4.1.2" indra-git = "3.1.3" gradle-profiles = "0.54.0" @@ -15,7 +15,7 @@ lettuce = "6.5.1.RELEASE" lombok = "1.18.36" lombok-plugin = "8.11" mongo = "5.2.1" -paperweight = "2.0.0-beta.19" +paperweight = "2.0.0-SNAPSHOT" plugin-yml-paper = "0.6.0" shadow = "9.2.2" slf4j = "2.0.16" @@ -33,7 +33,7 @@ shadow = { id = "com.gradleup.shadow", version.ref = "shadow" } [libraries] adventure-nbt = { module = "net.kyori:adventure-nbt", version.ref = "adventure" } -annotations = { module = "org.jetbrains:annotations", version.ref = "annotations" } +annotations = { module = "org.jspecify:jspecify", version.ref = "annotations" } auto-service = { module = "com.google.auto.service:auto-service", version.ref = "autoservice" } auto-service-annotations = { module = "com.google.auto.service:auto-service-annotations", version.ref = "autoservice" } bstats = { module = "org.bstats:bstats-bukkit", version.ref = "bstats" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index bad7c2462..c61a118f7 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/importer/src/main/java/com/infernalsuite/asp/importer/SWMImporter.java b/importer/src/main/java/com/infernalsuite/asp/importer/SWMImporter.java index 561e57876..1e82384e0 100644 --- a/importer/src/main/java/com/infernalsuite/asp/importer/SWMImporter.java +++ b/importer/src/main/java/com/infernalsuite/asp/importer/SWMImporter.java @@ -49,7 +49,7 @@ public static void main(String[] args) { public static void importWorld(File worldDir, File outputFile, boolean shouldPrintDebug) { try { outputFile.createNewFile(); - Files.write(outputFile.toPath(), SlimeSerializer.serialize(AnvilWorldReader.INSTANCE.readFromData(AnvilImportData.legacy(worldDir, outputFile.getName(), null)))); + Files.write(outputFile.toPath(), SlimeSerializer.serialize(AnvilWorldReader.INSTANCE_NO_CONVERSION.readFromData(AnvilImportData.legacy(worldDir, outputFile.getName(), null)))); } catch (IndexOutOfBoundsException ex) { System.err.println("Oops, it looks like the world provided is too big to be imported. " + "Please trim it by using the MCEdit tool and try again."); diff --git a/plugin/src/main/java/com/infernalsuite/asp/plugin/commands/CommandManager.java b/plugin/src/main/java/com/infernalsuite/asp/plugin/commands/CommandManager.java index b72366494..484e4a81b 100644 --- a/plugin/src/main/java/com/infernalsuite/asp/plugin/commands/CommandManager.java +++ b/plugin/src/main/java/com/infernalsuite/asp/plugin/commands/CommandManager.java @@ -10,22 +10,7 @@ import com.infernalsuite.asp.plugin.commands.parser.NamedWorldDataParser; import com.infernalsuite.asp.plugin.commands.parser.SlimeWorldParser; import com.infernalsuite.asp.plugin.commands.parser.suggestion.KnownSlimeWorldSuggestionProvider; -import com.infernalsuite.asp.plugin.commands.sub.CloneWorldCmd; -import com.infernalsuite.asp.plugin.commands.sub.CreateWorldCmd; -import com.infernalsuite.asp.plugin.commands.sub.DSListCmd; -import com.infernalsuite.asp.plugin.commands.sub.DeleteWorldCmd; -import com.infernalsuite.asp.plugin.commands.sub.GotoCmd; -import com.infernalsuite.asp.plugin.commands.sub.HelpCmd; -import com.infernalsuite.asp.plugin.commands.sub.ImportWorldCmd; -import com.infernalsuite.asp.plugin.commands.sub.LoadTemplateWorldCmd; -import com.infernalsuite.asp.plugin.commands.sub.LoadWorldCmd; -import com.infernalsuite.asp.plugin.commands.sub.MigrateWorldCmd; -import com.infernalsuite.asp.plugin.commands.sub.ReloadConfigCmd; -import com.infernalsuite.asp.plugin.commands.sub.SaveWorldCmd; -import com.infernalsuite.asp.plugin.commands.sub.SetSpawnCmd; -import com.infernalsuite.asp.plugin.commands.sub.UnloadWorldCmd; -import com.infernalsuite.asp.plugin.commands.sub.VersionCmd; -import com.infernalsuite.asp.plugin.commands.sub.WorldListCmd; +import com.infernalsuite.asp.plugin.commands.sub.*; import io.leangen.geantyref.TypeToken; import io.papermc.paper.command.brigadier.CommandSourceStack; import net.kyori.adventure.text.Component; @@ -136,7 +121,8 @@ public CommandManager(SWPlugin plugin) { new UnloadWorldCmd(this), new VersionCmd(this), new WorldListCmd(this), - new HelpCmd(this, commandManager) + new HelpCmd(this, commandManager), + new AddWorldCmd(this) ); } diff --git a/plugin/src/main/java/com/infernalsuite/asp/plugin/commands/sub/AddWorldCmd.java b/plugin/src/main/java/com/infernalsuite/asp/plugin/commands/sub/AddWorldCmd.java new file mode 100644 index 000000000..7cb8c1c44 --- /dev/null +++ b/plugin/src/main/java/com/infernalsuite/asp/plugin/commands/sub/AddWorldCmd.java @@ -0,0 +1,117 @@ +package com.infernalsuite.asp.plugin.commands.sub; + + +import com.infernalsuite.asp.api.AdvancedSlimePaperAPI; +import com.infernalsuite.asp.api.exceptions.CorruptedWorldException; +import com.infernalsuite.asp.api.exceptions.NewerFormatException; +import com.infernalsuite.asp.api.exceptions.UnknownWorldException; +import com.infernalsuite.asp.api.exceptions.WorldAlreadyExistsException; +import com.infernalsuite.asp.api.world.SlimeWorld; +import com.infernalsuite.asp.api.world.properties.SlimeProperties; +import com.infernalsuite.asp.api.world.properties.SlimePropertyMap; +import com.infernalsuite.asp.plugin.commands.CommandManager; +import com.infernalsuite.asp.plugin.commands.SlimeCommand; +import com.infernalsuite.asp.plugin.commands.exception.MessageCommandException; +import com.infernalsuite.asp.plugin.commands.parser.NamedSlimeLoader; +import com.infernalsuite.asp.plugin.config.ConfigManager; +import com.infernalsuite.asp.plugin.config.WorldData; +import com.infernalsuite.asp.plugin.config.WorldsConfig; +import com.infernalsuite.asp.plugin.util.ExecutorUtil; +import io.papermc.paper.registry.RegistryAccess; +import io.papermc.paper.registry.RegistryKey; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.*; +import org.bukkit.block.Biome; +import org.incendo.cloud.annotations.*; +import org.incendo.cloud.paper.util.sender.Source; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; + +public class AddWorldCmd extends SlimeCommand { + private static final Logger LOGGER = LoggerFactory.getLogger(AddWorldCmd.class); + + public AddWorldCmd(CommandManager commandManager) { + super(commandManager); + } + + @Command("swp|aswm|swm add ") + @CommandDescription("Adds an already existing world from a datasource to the plugin") + @Permission("swm.addworld") + public CompletableFuture addWorld( + Source sender, + @Argument(value = "world") String worldName, + @Argument(value = "data-source") NamedSlimeLoader loader + ) { + if (commandManager.getWorldsInUse().contains(worldName)) { + throw new MessageCommandException(COMMAND_PREFIX.append( + Component.text("World " + worldName + " is already being used on another command! Wait some time and try again.")).color(NamedTextColor.RED) + ); + } + + World world = Bukkit.getWorld(worldName); + + if (world != null) { + throw new MessageCommandException(COMMAND_PREFIX.append( + Component.text("World " + worldName + " already exists!")).color(NamedTextColor.RED) + ); + } + + WorldsConfig config = ConfigManager.getWorldConfig(); + + if (config.getWorlds().containsKey(worldName)) { + throw new MessageCommandException(COMMAND_PREFIX.append( + Component.text("There is already a world called " + worldName + " inside the worlds config file.")).color(NamedTextColor.RED) + ); + } + + return CompletableFuture.runAsync(() -> { + commandManager.getWorldsInUse().add(worldName); + + try { + SlimeWorld loadedWorld = AdvancedSlimePaperAPI.instance().readWorld(loader.slimeLoader(), worldName, false, new SlimePropertyMap()); + + long start = System.currentTimeMillis(); + + WorldData worldData = new WorldData(); + worldData.setSpawn("0, 64, 0"); + worldData.setDataSource(loader.name()); + worldData.setDefaultBiome(loadedWorld.getPropertyMap().getValue(SlimeProperties.DEFAULT_BIOME)); + worldData.setEnvironment(loadedWorld.getPropertyMap().getValue(SlimeProperties.ENVIRONMENT)); + + ExecutorUtil.runSyncAndWait(plugin, () -> { + try { + asp.loadWorld(loadedWorld, true); + + // Config + config.getWorlds().put(worldName, worldData); + } catch (IllegalArgumentException ex) { + throw new MessageCommandException(COMMAND_PREFIX.append( + Component.text("Failed to add world " + worldName + ": " + ex.getMessage() + ".").color(NamedTextColor.RED) + )); + } + }); + config.save(); + + sender.source().sendMessage(COMMAND_PREFIX.append( + Component.text("World ").color(NamedTextColor.GREEN) + .append(Component.text(worldName).color(NamedTextColor.YELLOW)) + .append(Component.text(" added in " + (System.currentTimeMillis() - start) + "ms!").color(NamedTextColor.GREEN)) + )); + } catch (IOException | UnknownWorldException | CorruptedWorldException | NewerFormatException ex) { + LOGGER.error("Failed to add world {}:", worldName, ex); + throw new MessageCommandException(COMMAND_PREFIX.append( + Component.text("Failed to add world " + worldName + ". Take a look at the server console for more information.").color(NamedTextColor.RED) + )); + } finally { + commandManager.getWorldsInUse().remove(worldName); + } + }); + } +} + diff --git a/plugin/src/main/java/com/infernalsuite/asp/plugin/commands/sub/ImportWorldCmd.java b/plugin/src/main/java/com/infernalsuite/asp/plugin/commands/sub/ImportWorldCmd.java index 41b88b2b8..2869deb6c 100644 --- a/plugin/src/main/java/com/infernalsuite/asp/plugin/commands/sub/ImportWorldCmd.java +++ b/plugin/src/main/java/com/infernalsuite/asp/plugin/commands/sub/ImportWorldCmd.java @@ -8,8 +8,10 @@ import com.infernalsuite.asp.api.exceptions.WorldTooBigException; import com.infernalsuite.asp.api.world.SlimeWorld; import com.infernalsuite.asp.api.world.properties.SlimeProperties; +import net.kyori.adventure.key.Key; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; import org.incendo.cloud.annotations.Argument; import org.incendo.cloud.annotations.Command; @@ -17,6 +19,7 @@ import org.incendo.cloud.annotations.Permission; import org.incendo.cloud.annotations.injection.RawArgs; import org.incendo.cloud.paper.util.sender.Source; +import org.jspecify.annotations.NonNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,13 +48,7 @@ public CompletableFuture importWorld(Source source, String[] args, @Argument(value = "data-source") com.infernalsuite.asp.plugin.commands.parser.NamedSlimeLoader loader, @Argument(value = "new-world-name") String newWorldName) { CommandSender sender = source.source(); - File worldDir = new File(pathToWorld); - - if (!worldDir.exists() || !worldDir.isDirectory()) { - throw new com.infernalsuite.asp.plugin.commands.exception.MessageCommandException(COMMAND_PREFIX.append( - Component.text("Path " + worldDir.getPath() + " does not point out to a valid world directory.")).color(NamedTextColor.RED) - ); - } + File worldDir = getWorldFolder(pathToWorld); String[] oldArgs = importCache.getIfPresent(sender.getName()); @@ -160,5 +157,29 @@ public CompletableFuture importWorld(Source source, String[] args, } } + private static @NonNull File getWorldFolder(String pathToWorld) { + File worldDir = Bukkit.getServer().getLevelDirectory().resolve("dimensions") + .resolve("minecraft") + .resolve(pathToWorld).toFile(); + + if(!worldDir.exists() || !worldDir.isDirectory() && Key.parseable(pathToWorld)) { + Key key = Key.key(pathToWorld); + worldDir = Bukkit.getServer().getLevelDirectory().resolve("dimensions") + .resolve(key.namespace()) + .resolve(key.value()).toFile(); + } + + if(!worldDir.exists() || !worldDir.isDirectory()) { + worldDir = new File(pathToWorld); + } + + if (!worldDir.exists() || !worldDir.isDirectory()) { + throw new com.infernalsuite.asp.plugin.commands.exception.MessageCommandException(COMMAND_PREFIX.append( + Component.text("Path " + worldDir.getPath() + " does not point out to a valid world directory.")).color(NamedTextColor.RED) + ); + } + return worldDir; + } + } diff --git a/settings.gradle.kts b/settings.gradle.kts index d38dbeaf4..bd4bfc38e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,14 +1,14 @@ pluginManagement { repositories { + maven("https://repo.papermc.io/repository/maven-public/") mavenLocal() mavenCentral() gradlePluginPortal() - maven("https://repo.papermc.io/repository/maven-public/") } } plugins { - id("org.gradle.toolchains.foojay-resolver-convention") version "0.9.0" + id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" } rootProject.name = "ASPaper"