diff --git a/patches/server/0001-Rebrand-to-Lumina-and-build-changes.patch b/patches/server/0001-Rebrand-to-Lumina-and-build-changes.patch index 97d01d2..6915653 100644 --- a/patches/server/0001-Rebrand-to-Lumina-and-build-changes.patch +++ b/patches/server/0001-Rebrand-to-Lumina-and-build-changes.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Rebrand to Lumina and build changes diff --git a/build.gradle.kts b/build.gradle.kts -index 8d2b5fec6fe27dca3ce01ba1ce50506179fc3b4d..619becf5c3f4f25661d9db090df21bd83dd5e0f3 100644 +index 8d2b5fec6fe27dca3ce01ba1ce50506179fc3b4d..2c26932456401bebe45780527e3408cc939955a9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -25,7 +25,7 @@ abstract class MockitoAgentProvider : CommandLineArgumentProvider { @@ -17,26 +17,19 @@ index 8d2b5fec6fe27dca3ce01ba1ce50506179fc3b4d..619becf5c3f4f25661d9db090df21bd8 implementation("ca.spottedleaf:concurrentutil:0.0.2") // Paper - Add ConcurrentUtil dependency // Paper start implementation("org.jline:jline-terminal-ffm:3.27.1") // use ffm on java 22+ -@@ -44,9 +44,18 @@ dependencies { - alsoShade(log4jPlugins.output) - implementation("io.netty:netty-codec-haproxy:4.1.97.Final") // Paper - Add support for proxy protocol - // Paper end -+ // LinearPaper start - Linear format -+ implementation("com.github.luben:zstd-jni:1.5.6-3") -+ implementation("org.lz4:lz4-java:1.8.0") -+ // LinearPaper end +@@ -47,6 +47,11 @@ dependencies { implementation("org.apache.logging.log4j:log4j-iostreams:2.24.1") // Paper - remove exclusion implementation("org.ow2.asm:asm-commons:9.7.1") implementation("org.spongepowered:configurate-yaml:4.2.0-SNAPSHOT") // Paper - config files + implementation("org.spongepowered:configurate-hocon:4.2.0-SNAPSHOT") // Lumina - config files + // Lumina start - Command API -+ compileOnly("dev.jorel:commandapi-preprocessor:9.5.3") -+ implementation("dev.jorel:commandapi-bukkit-core:9.5.3") ++ compileOnly("dev.jorel:commandapi-preprocessor:9.7.0") ++ implementation("dev.jorel:commandapi-bukkit-core:9.7.0") + // Lumina end - Command API implementation("commons-lang:commons-lang:2.6") runtimeOnly("org.xerial:sqlite-jdbc:3.47.0.0") runtimeOnly("com.mysql:mysql-connector-j:9.1.0") -@@ -100,14 +109,14 @@ tasks.jar { +@@ -100,14 +105,14 @@ tasks.jar { val gitBranch = git("rev-parse", "--abbrev-ref", "HEAD").getText().trim() // Paper attributes( "Main-Class" to "org.bukkit.craftbukkit.Main", @@ -55,7 +48,7 @@ index 8d2b5fec6fe27dca3ce01ba1ce50506179fc3b4d..619becf5c3f4f25661d9db090df21bd8 "Build-Number" to (build ?: ""), "Build-Time" to Instant.now().toString(), "Git-Branch" to gitBranch, // Paper -@@ -237,18 +246,27 @@ tasks.registerRunTask("runBundler") { +@@ -237,18 +242,27 @@ tasks.registerRunTask("runBundler") { classpath(rootProject.tasks.named("createMojmapBundlerJar").flatMap { it.outputZip }) mainClass.set(null as String?) } diff --git a/patches/server/0004-Command-API-support.patch b/patches/server/0004-Command-API-support.patch index f89913b..1bd41f8 100644 --- a/patches/server/0004-Command-API-support.patch +++ b/patches/server/0004-Command-API-support.patch @@ -240,12 +240,12 @@ index 0000000000000000000000000000000000000000..003a869e730000c498e0546e9ce692af + } + +} -diff --git a/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R4.java b/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R4.java +diff --git a/src/main/java/dev/jorel/commandapi/nms/NMS_1_21_R3.java b/src/main/java/dev/jorel/commandapi/nms/NMS_1_21_R3.java new file mode 100644 -index 0000000000000000000000000000000000000000..2e625f0345d79a8c8fe709b083f43460badc82bc +index 0000000000000000000000000000000000000000..9c824a1937cbbd787d690c8483a622c47d52775e --- /dev/null -+++ b/src/main/java/dev/jorel/commandapi/nms/NMS_1_20_R4.java -@@ -0,0 +1,1081 @@ ++++ b/src/main/java/dev/jorel/commandapi/nms/NMS_1_21_R3.java +@@ -0,0 +1,1055 @@ +/******************************************************************************* + * Copyright 2024 Jorel Ali (Skepter) - MIT License + * @@ -266,69 +266,8 @@ index 0000000000000000000000000000000000000000..2e625f0345d79a8c8fe709b083f43460 + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + *******************************************************************************/ -+// Modified from https://github.com/JorelAli/CommandAPI +package dev.jorel.commandapi.nms; + -+import java.io.File; -+import java.io.IOException; -+import java.io.PrintWriter; -+import java.io.StringWriter; -+import java.lang.reflect.Field; -+import java.lang.reflect.Modifier; -+import java.lang.reflect.ParameterizedType; -+import java.lang.reflect.Type; -+import java.nio.charset.StandardCharsets; -+import java.util.ArrayList; -+import java.util.Collection; -+import java.util.HashSet; -+import java.util.Iterator; -+import java.util.List; -+import java.util.Map; -+import java.util.Optional; -+import java.util.Set; -+import java.util.concurrent.CompletableFuture; -+import java.util.concurrent.atomic.AtomicInteger; -+import java.util.function.Predicate; -+import java.util.function.ToIntFunction; -+ -+import dev.jorel.commandapi.*; -+import io.papermc.paper.command.brigadier.bukkit.BukkitCommandNode; -+import net.minecraft.commands.*; -+import org.bukkit.Bukkit; -+import org.bukkit.Color; -+import org.bukkit.Location; -+import org.bukkit.NamespacedKey; -+import org.bukkit.Particle; -+import org.bukkit.Particle.DustOptions; -+import org.bukkit.Particle.DustTransition; -+import org.bukkit.Registry; -+import org.bukkit.Vibration; -+import org.bukkit.Vibration.Destination; -+import org.bukkit.Vibration.Destination.BlockDestination; -+import org.bukkit.World; -+import org.bukkit.advancement.Advancement; -+import org.bukkit.block.Biome; -+import org.bukkit.block.Block; -+import org.bukkit.block.data.BlockData; -+import org.bukkit.command.CommandSender; -+import org.bukkit.command.SimpleCommandMap; -+import org.bukkit.craftbukkit.CraftLootTable; -+import org.bukkit.craftbukkit.CraftParticle; -+import org.bukkit.craftbukkit.CraftServer; -+import org.bukkit.craftbukkit.CraftSound; -+import org.bukkit.craftbukkit.block.data.CraftBlockData; -+import org.bukkit.craftbukkit.command.VanillaCommandWrapper; -+import org.bukkit.craftbukkit.entity.CraftEntity; -+import org.bukkit.craftbukkit.help.CustomHelpTopic; -+import org.bukkit.craftbukkit.help.SimpleHelpMap; -+import org.bukkit.craftbukkit.inventory.CraftItemStack; -+import org.bukkit.craftbukkit.potion.CraftPotionEffectType; -+import org.bukkit.enchantments.Enchantment; -+import org.bukkit.entity.EntityType; -+import org.bukkit.entity.Player; -+import org.bukkit.help.HelpTopic; -+import org.bukkit.inventory.Recipe; -+ +import com.google.common.collect.ImmutableList; +import com.google.common.io.Files; +import com.google.gson.GsonBuilder; @@ -342,22 +281,16 @@ index 0000000000000000000000000000000000000000..2e625f0345d79a8c8fe709b083f43460 +import com.mojang.logging.LogUtils; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; -+ ++import dev.jorel.commandapi.*; +import dev.jorel.commandapi.arguments.ArgumentSubType; +import dev.jorel.commandapi.arguments.SuggestionProviders; +import dev.jorel.commandapi.commandsenders.AbstractCommandSender; +import dev.jorel.commandapi.commandsenders.BukkitCommandSender; +import dev.jorel.commandapi.commandsenders.BukkitNativeProxyCommandSender; +import dev.jorel.commandapi.preprocessor.Differs; -+import dev.jorel.commandapi.wrappers.ComplexRecipeImpl; -+import dev.jorel.commandapi.wrappers.FloatRange; -+import dev.jorel.commandapi.wrappers.FunctionWrapper; -+import dev.jorel.commandapi.wrappers.IntegerRange; -+import dev.jorel.commandapi.wrappers.Location2D; -+import dev.jorel.commandapi.wrappers.NativeProxyCommandSender; -+import dev.jorel.commandapi.wrappers.ParticleData; -+import dev.jorel.commandapi.wrappers.ScoreboardSlot; -+import dev.jorel.commandapi.wrappers.SimpleFunctionWrapper; ++import dev.jorel.commandapi.preprocessor.NMSMeta; ++import dev.jorel.commandapi.preprocessor.RequireField; ++import dev.jorel.commandapi.wrappers.*; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; @@ -365,17 +298,8 @@ index 0000000000000000000000000000000000000000..2e625f0345d79a8c8fe709b083f43460 +import net.md_5.bungee.chat.ComponentSerializer; +import net.minecraft.advancements.AdvancementHolder; +import net.minecraft.advancements.critereon.MinMaxBounds; -+import net.minecraft.commands.arguments.ColorArgument; -+import net.minecraft.commands.arguments.ComponentArgument; -+import net.minecraft.commands.arguments.DimensionArgument; -+import net.minecraft.commands.arguments.EntityArgument; -+import net.minecraft.commands.arguments.MessageArgument; -+import net.minecraft.commands.arguments.ParticleArgument; -+import net.minecraft.commands.arguments.RangeArgument; -+import net.minecraft.commands.arguments.ResourceArgument; -+import net.minecraft.commands.arguments.ResourceLocationArgument; -+import net.minecraft.commands.arguments.ScoreHolderArgument; -+import net.minecraft.commands.arguments.ScoreboardSlotArgument; ++import net.minecraft.commands.*; ++import net.minecraft.commands.arguments.*; +import net.minecraft.commands.arguments.blocks.BlockPredicateArgument; +import net.minecraft.commands.arguments.blocks.BlockStateArgument; +import net.minecraft.commands.arguments.coordinates.BlockPosArgument; @@ -392,18 +316,8 @@ index 0000000000000000000000000000000000000000..2e625f0345d79a8c8fe709b083f43460 +import net.minecraft.commands.functions.InstantiatedFunction; +import net.minecraft.commands.synchronization.ArgumentUtils; +import net.minecraft.core.BlockPos; -+import net.minecraft.core.component.DataComponentMap; -+import net.minecraft.core.component.PatchedDataComponentMap; -+import net.minecraft.core.particles.BlockParticleOption; -+import net.minecraft.core.particles.ColorParticleOption; -+import net.minecraft.core.particles.DustColorTransitionOptions; -+import net.minecraft.core.particles.DustParticleOptions; -+import net.minecraft.core.particles.ItemParticleOption; -+import net.minecraft.core.particles.ParticleOptions; -+import net.minecraft.core.particles.SculkChargeParticleOptions; -+import net.minecraft.core.particles.ShriekParticleOption; -+import net.minecraft.core.particles.SimpleParticleType; -+import net.minecraft.core.particles.VibrationParticleOption; ++import net.minecraft.core.Holder; ++import net.minecraft.core.particles.*; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.nbt.CompoundTag; @@ -424,35 +338,82 @@ index 0000000000000000000000000000000000000000..2e625f0345d79a8c8fe709b083f43460 +import net.minecraft.server.packs.resources.SimpleReloadInstance; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.util.Unit; ++import net.minecraft.util.profiling.Profiler; +import net.minecraft.util.profiling.ProfilerFiller; +import net.minecraft.world.entity.Entity; ++import net.minecraft.world.flag.FeatureFlagSet; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.RecipeHolder; +import net.minecraft.world.level.DataPackConfig; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.WorldDataConfiguration; ++import net.minecraft.world.level.block.entity.FuelValues; +import net.minecraft.world.level.block.state.pattern.BlockInWorld; +import net.minecraft.world.level.gameevent.BlockPositionSource; +import net.minecraft.world.phys.Vec2; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.scores.ScoreHolder; -+import org.bukkit.loot.LootTable; -+import org.jetbrains.annotations.NotNull; ++import org.bukkit.*; ++import org.bukkit.Particle.DustOptions; ++import org.bukkit.Particle.DustTransition; ++import org.bukkit.Particle.Trail; ++import org.bukkit.Vibration.Destination; ++import org.bukkit.Vibration.Destination.BlockDestination; ++import org.bukkit.advancement.Advancement; ++import org.bukkit.block.Biome; ++import org.bukkit.block.Block; ++import org.bukkit.block.data.BlockData; ++import org.bukkit.command.CommandSender; ++import org.bukkit.command.SimpleCommandMap; ++import org.bukkit.craftbukkit.CraftLootTable; ++import org.bukkit.craftbukkit.CraftParticle; ++import org.bukkit.craftbukkit.CraftServer; ++import org.bukkit.craftbukkit.CraftSound; ++import org.bukkit.craftbukkit.block.data.CraftBlockData; ++import org.bukkit.craftbukkit.command.VanillaCommandWrapper; ++import org.bukkit.craftbukkit.entity.CraftEntity; ++import org.bukkit.craftbukkit.help.CustomHelpTopic; ++import org.bukkit.craftbukkit.help.SimpleHelpMap; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.craftbukkit.potion.CraftPotionEffectType; ++import org.bukkit.enchantments.Enchantment; ++import org.bukkit.entity.EntityType; ++import org.bukkit.entity.Player; ++import org.bukkit.help.HelpTopic; ++import org.bukkit.inventory.Recipe; ++ ++import java.io.File; ++import java.io.IOException; ++import java.io.PrintWriter; ++import java.io.StringWriter; ++import java.lang.reflect.Field; ++import java.nio.charset.StandardCharsets; ++import java.util.*; ++import java.util.concurrent.CompletableFuture; ++import java.util.concurrent.atomic.AtomicInteger; ++import java.util.function.Predicate; ++import java.util.function.ToIntFunction; + +// Mojang-Mapped reflection + +/** -+ * NMS implementation for Minecraft 1.20.5, 1.20.6 ++ * NMS implementation for Minecraft 1.21.4 + */ -+public class NMS_1_20_R4 extends NMS_Common { ++@NMSMeta(compatibleWith = {"1.21.4"}) ++@RequireField(in = SimpleHelpMap.class, name = "helpTopics", ofType = Map.class) ++@RequireField(in = EntitySelector.class, name = "usesSelector", ofType = boolean.class) ++// @RequireField(in = ItemInput.class, name = "tag", ofType = CompoundTag.class) ++@RequireField(in = ServerFunctionLibrary.class, name = "dispatcher", ofType = CommandDispatcher.class) ++@RequireField(in = MinecraftServer.class, name = "fuelValues", ofType = FuelValues.class) ++public class NMS_1_21_R3 extends NMS_Common { + + private static final SafeVarHandle> helpMapTopics; + private static final Field entitySelectorUsesSelector; + // private static final SafeVarHandle itemInput; + private static final Field serverFunctionLibraryDispatcher; ++ private static final SafeVarHandle minecraftServerFuelValues; + + // Derived from net.minecraft.commands.Commands; -+ @NotNull + private static final CommandBuildContext COMMAND_BUILD_CONTEXT; + + // Compute all var handles all in one go so we don't do this during main server @@ -462,40 +423,29 @@ index 0000000000000000000000000000000000000000..2e625f0345d79a8c8fe709b083f43460 + COMMAND_BUILD_CONTEXT = CommandBuildContext.simple(server.getServer().registryAccess(), + server.getServer().getWorldData().getDataConfiguration().enabledFeatures()); + } else { -+ throw new IllegalStateException("Bukkit server is not a CraftServer! This should never happen!"); ++ COMMAND_BUILD_CONTEXT = null; + } + + helpMapTopics = SafeVarHandle.ofOrNull(SimpleHelpMap.class, "helpTopics", "helpTopics", Map.class); + // For some reason, MethodHandles fails for this field, but Field works okay -+ // entitySelectorUsesSelector = EntitySelector::usesSelector; -+ Field usesSelector = null; -+ for (Field field : EntitySelector.class.getDeclaredFields()) { -+ if (field.getType().equals(boolean.class) && Modifier.isPublic(field.getModifiers())) { -+ usesSelector = CommandAPIHandler.getField(EntitySelector.class, field.getName(), ""); -+ } -+ } -+ entitySelectorUsesSelector = usesSelector; ++ entitySelectorUsesSelector = CommandAPIHandler.getField(EntitySelector.class, "p", "usesSelector"); + // itemInput = SafeVarHandle.ofOrNull(ItemInput.class, "c", "tag", CompoundTag.class); + // For some reason, MethodHandles fails for this field, but Field works okay -+ Field dispatcher = null; -+ for (Field field : ServerFunctionLibrary.class.getDeclaredFields()) { -+ if (CommandDispatcher.class.isAssignableFrom(field.getType())) { -+ Type genericType = field.getGenericType(); -+ if (genericType instanceof ParameterizedType) { -+ Type[] actualTypeArguments = ((ParameterizedType) genericType).getActualTypeArguments(); -+ if (actualTypeArguments.length == 1 && actualTypeArguments[0].equals(CommandSourceStack.class)) { -+ dispatcher = CommandAPIHandler.getField(ServerFunctionLibrary.class, field.getName(), ""); -+ } -+ } -+ } -+ } -+ serverFunctionLibraryDispatcher = dispatcher; ++ serverFunctionLibraryDispatcher = CommandAPIHandler.getField(ServerFunctionLibrary.class, "h", "dispatcher"); ++ ++ minecraftServerFuelValues = SafeVarHandle.ofOrNull(MinecraftServer.class, "aE", "fuelValues", FuelValues.class); + } + + private static NamespacedKey fromResourceLocation(ResourceLocation key) { + return NamespacedKey.fromString(key.getNamespace() + ":" + key.getPath()); + } + ++ @Differs(from = "1.21.1", by = "New advancement argument implementation") ++ @Override ++ public ArgumentType _ArgumentAdvancement() { ++ return ResourceKeyArgument.key(Registries.ADVANCEMENT); ++ } ++ + @Override + public final ArgumentType _ArgumentBlockPredicate() { + return BlockPredicateArgument.blockPredicate(COMMAND_BUILD_CONTEXT); @@ -506,7 +456,6 @@ index 0000000000000000000000000000000000000000..2e625f0345d79a8c8fe709b083f43460 + return BlockStateArgument.block(COMMAND_BUILD_CONTEXT); + } + -+ @Differs(from = "1.20.4", by = "Now needs a command build context") + @Override + public ArgumentType _ArgumentChatComponent() { + return ComponentArgument.textComponent(COMMAND_BUILD_CONTEXT); @@ -543,6 +492,12 @@ index 0000000000000000000000000000000000000000..2e625f0345d79a8c8fe709b083f43460 + return ParticleArgument.particle(COMMAND_BUILD_CONTEXT); + } + ++ @Differs(from = "1.21.1", by = "New recipe argument implementation") ++ @Override ++ public ArgumentType _ArgumentRecipe() { ++ return ResourceKeyArgument.key(Registries.RECIPE); ++ } ++ + @Override + public final ArgumentType _ArgumentSyntheticBiome() { + return ResourceArgument.resource(COMMAND_BUILD_CONTEXT, Registries.BIOME); @@ -555,22 +510,22 @@ index 0000000000000000000000000000000000000000..2e625f0345d79a8c8fe709b083f43460 + + @Override + public String[] compatibleVersions() { -+ return new String[]{"1.20.5", "1.20.6"}; ++ return new String[]{"1.21.4"}; + } + ++ ; ++ ++ @Differs(from = "1.20.6", by = "ItemInput constructor uses a data components patch, instead of a data components map") + private static String serializeNMSItemStack(ItemStack is) { -+ final DataComponentMap patchedMap = PatchedDataComponentMap.fromPatch(PatchedDataComponentMap.EMPTY, is.getComponentsPatch()); -+ return new ItemInput(is.getItemHolder(), patchedMap).serialize(COMMAND_BUILD_CONTEXT); ++ return new ItemInput(is.getItemHolder(), is.getComponentsPatch()).serialize(COMMAND_BUILD_CONTEXT); + } + -+ @Differs(from = "1.20.4", by = "Everything") + @Override + public final String convert(org.bukkit.inventory.ItemStack is) { + return serializeNMSItemStack(CraftItemStack.asNMSCopy(is)); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) -+ @Differs(from = "1.20.4", by = "Everything") + @Override + public final String convert(ParticleData particle) { + final ParticleOptions particleOptions = CraftParticle.createParticleParam(particle.particle(), particle.data()); @@ -594,10 +549,10 @@ index 0000000000000000000000000000000000000000..2e625f0345d79a8c8fe709b083f43460 + * @param css the command source stack to execute this command + * @return the result of our function. This is either 0 is the command failed, or greater than 0 if the command succeeded + */ -+ @SuppressWarnings("JavadocReference") ++ @Differs(from = "1.21.1", by = "Use Profiler.get() instead of MinecraftServer.getProfiler()") + private final int runCommandFunction(CommandFunction commandFunction, CommandSourceStack css) { + // Profile the function. We want to simulate the execution sequence exactly -+ ProfilerFiller profiler = this.getMinecraftServer().getProfiler(); ++ ProfilerFiller profiler = Profiler.get(); + profiler.push(() -> "function " + commandFunction.id()); + + // Store our function result @@ -606,7 +561,7 @@ index 0000000000000000000000000000000000000000..2e625f0345d79a8c8fe709b083f43460 + + try { + final InstantiatedFunction instantiatedFunction = commandFunction.instantiate((CompoundTag) null, this.getBrigadierDispatcher()); -+ Commands.executeCommandInContext(css, (executioncontext) -> { ++ net.minecraft.commands.Commands.executeCommandInContext(css, (executioncontext) -> { + ExecutionContext.queueInitialFunctionCall(executioncontext, instantiatedFunction, css, onCommandResult); + }); + } catch (FunctionInstantiationException functionInstantiationException) { @@ -655,13 +610,13 @@ index 0000000000000000000000000000000000000000..2e625f0345d79a8c8fe709b083f43460 + return new CustomHelpTopic(commandName, shortDescription, fullDescription, permission); + } + ++ @Differs(from = "1.21.1", by = "Uses ResourceKeyArgument instead of ResourceLocationArgument") + @Override + public Advancement getAdvancement(CommandContext cmdCtx, String key) + throws CommandSyntaxException { -+ return ResourceLocationArgument.getAdvancement(cmdCtx, key).toBukkit(); ++ return ResourceKeyArgument.getAdvancement(cmdCtx, key).toBukkit(); + } + -+ @Differs(from = "1.20.4", by = "Serializer.toJson now needs a Provider") + @Override + public Component getAdventureChat(CommandContext cmdCtx, String key) throws CommandSyntaxException { + return GsonComponentSerializer.gson().deserialize(Serializer.toJson(MessageArgument.getMessage(cmdCtx, key), COMMAND_BUILD_CONTEXT)); @@ -675,7 +630,6 @@ index 0000000000000000000000000000000000000000..2e625f0345d79a8c8fe709b083f43460 + + @Override + public final Component getAdventureChatComponent(CommandContext cmdCtx, String key) { -+ // TODO: Figure out if an empty provider is suitable for this context + return GsonComponentSerializer.gson() + .deserialize(Serializer.toJson(ComponentArgument.getComponent(cmdCtx, key), COMMAND_BUILD_CONTEXT)); + } @@ -687,7 +641,7 @@ index 0000000000000000000000000000000000000000..2e625f0345d79a8c8fe709b083f43460 + .location(); + return switch (subType) { + case BIOME_BIOME -> { -+ Biome biome; ++ Biome biome = null; + try { + biome = Biome.valueOf(resourceLocation.getPath().toUpperCase()); + } catch (IllegalArgumentException biomeNotFound) { @@ -719,7 +673,6 @@ index 0000000000000000000000000000000000000000..2e625f0345d79a8c8fe709b083f43460 + return VanillaCommandWrapper.getListener(sender.getSource()); + } + -+ @Differs(from = "1.20.4", by = "Serializer.toJson now needs a Provider") + @Override + public final BaseComponent[] getChat(CommandContext cmdCtx, String key) throws CommandSyntaxException { + return ComponentSerializer.parse(Serializer.toJson(MessageArgument.getMessage(cmdCtx, key), COMMAND_BUILD_CONTEXT)); @@ -734,8 +687,9 @@ index 0000000000000000000000000000000000000000..2e625f0345d79a8c8fe709b083f43460 + @Override + public final Enchantment getEnchantment(CommandContext cmdCtx, String key) + throws CommandSyntaxException { -+ return Registry.ENCHANTMENT.get(fromResourceLocation( -+ BuiltInRegistries.ENCHANTMENT.getKey(ResourceArgument.getEnchantment(cmdCtx, key).value()))); ++ final net.minecraft.world.item.enchantment.Enchantment enchantment = ResourceArgument.getEnchantment(cmdCtx, key).value(); ++ final ResourceLocation resource = this.getMinecraftServer().registryAccess().lookupOrThrow(Registries.ENCHANTMENT).getKey(enchantment); ++ return Registry.ENCHANTMENT.get(fromResourceLocation(resource)); + } + + @Override @@ -830,9 +784,10 @@ index 0000000000000000000000000000000000000000..2e625f0345d79a8c8fe709b083f43460 + return result.toArray(new FunctionWrapper[0]); + } + ++ @Differs(from = "1.20.6", by = "ResourceLocation constructor change to static ResourceLocation#fromNamespaceAndPath") + @Override + public SimpleFunctionWrapper getFunction(NamespacedKey key) { -+ final ResourceLocation resourceLocation = new ResourceLocation(key.getNamespace(), key.getKey()); ++ final ResourceLocation resourceLocation = ResourceLocation.fromNamespaceAndPath(key.getNamespace(), key.getKey()); + Optional> commandFunctionOptional = this + .getMinecraftServer().getFunctions().get(resourceLocation); + if (commandFunctionOptional.isPresent()) { @@ -870,7 +825,7 @@ index 0000000000000000000000000000000000000000..2e625f0345d79a8c8fe709b083f43460 + ItemInput input = ItemArgument.getItem(cmdCtx, key); + + // Create the basic ItemStack with an amount of 1 -+ ItemStack item = input.createItemStack(1, false); ++ net.minecraft.world.item.ItemStack item = input.createItemStack(1, false); + return CraftItemStack.asBukkitCopy(item); + } + @@ -911,7 +866,7 @@ index 0000000000000000000000000000000000000000..2e625f0345d79a8c8fe709b083f43460 + } + + @Override -+ public final LootTable getLootTable(CommandContext cmdCtx, String key) { ++ public final org.bukkit.loot.LootTable getLootTable(CommandContext cmdCtx, String key) { + return CraftLootTable.minecraftToBukkit(ResourceLocationArgument.getId(cmdCtx, key)); + } + @@ -920,8 +875,6 @@ index 0000000000000000000000000000000000000000..2e625f0345d79a8c8fe709b083f43460 + return fromResourceLocation(ResourceLocationArgument.getId(cmdCtx, key)); + } + -+ @SuppressWarnings("UnnecessaryBoxing") -+ @Differs(from = "1.20.4", by = "New particle option ColorParticleOption") + @Override + public final ParticleData getParticle(CommandContext cmdCtx, String key) { + final ParticleOptions particleOptions = ParticleArgument.getParticle(cmdCtx, key); @@ -941,47 +894,46 @@ index 0000000000000000000000000000000000000000..2e625f0345d79a8c8fe709b083f43460 + particle = null; + } + -+ switch (particleOptions) { -+ case SimpleParticleType simpleParticleType -> { -+ return new ParticleData(particle, null); -+ } -+ case BlockParticleOption options -> { -+ return new ParticleData(particle, CraftBlockData.fromData(options.getState())); -+ } -+ case DustColorTransitionOptions options -> { -+ return getParticleDataAsDustColorTransitionOption(particle, options); -+ } -+ case DustParticleOptions options -> { -+ final Color color = Color.fromRGB((int) (options.getColor().x() * 255.0F), -+ (int) (options.getColor().y() * 255.0F), (int) (options.getColor().z() * 255.0F)); -+ return new ParticleData(particle, new DustOptions(color, options.getScale())); -+ } -+ case ItemParticleOption options -> { -+ return new ParticleData(particle, -+ CraftItemStack.asBukkitCopy(options.getItem())); -+ } -+ case VibrationParticleOption options -> { -+ return getParticleDataAsVibrationParticleOption(cmdCtx, particle, options); -+ } -+ case ShriekParticleOption options -> { -+ // CraftBukkit implements shriek particles as a (boxed) Integer object -+ return new ParticleData(particle, Integer.valueOf(options.getDelay())); -+ } -+ case SculkChargeParticleOptions options -> { -+ // CraftBukkit implements sculk charge particles as a (boxed) Float object -+ return new ParticleData(particle, Float.valueOf(options.roll())); -+ } -+ case ColorParticleOption options -> { -+ return getParticleDataAsColorParticleOption(particle, options); -+ } -+ default -> { -+ CommandAPI.getLogger().warning("Invalid particle data type for " + particle.getDataType().toString()); -+ return new ParticleData(particle, null); -+ } ++ if (particleOptions instanceof SimpleParticleType) { ++ return new ParticleData(particle, null); ++ } else if (particleOptions instanceof BlockParticleOption options) { ++ return new ParticleData(particle, CraftBlockData.fromData(options.getState())); ++ } else if (particleOptions instanceof DustColorTransitionOptions options) { ++ return getParticleDataAsDustColorTransitionOption(particle, options); ++ } else if (particleOptions instanceof DustParticleOptions options) { ++ final Color color = Color.fromRGB((int) (options.getColor().x() * 255.0F), ++ (int) (options.getColor().y() * 255.0F), (int) (options.getColor().z() * 255.0F)); ++ return new ParticleData(particle, new DustOptions(color, options.getScale())); ++ } else if (particleOptions instanceof ItemParticleOption options) { ++ return new ParticleData(particle, ++ CraftItemStack.asBukkitCopy(options.getItem())); ++ } else if (particleOptions instanceof VibrationParticleOption options) { ++ return getParticleDataAsVibrationParticleOption(cmdCtx, particle, options); ++ } else if (particleOptions instanceof ShriekParticleOption options) { ++ // CraftBukkit implements shriek particles as a (boxed) Integer object ++ return new ParticleData(particle, Integer.valueOf(options.getDelay())); ++ } else if (particleOptions instanceof SculkChargeParticleOptions options) { ++ // CraftBukkit implements sculk charge particles as a (boxed) Float object ++ return new ParticleData(particle, Float.valueOf(options.roll())); ++ } else if (particleOptions instanceof ColorParticleOption options) { ++ return getParticleDataAsColorParticleOption(particle, options); ++ } else if (particleOptions instanceof TrailParticleOption options) { ++ return getParticleDataAsTrailParticleOption(cmdCtx, particle, options); ++ } else { ++ CommandAPI.getLogger().warning("Invalid particle data type for " + particle.getDataType().toString()); ++ return new ParticleData(particle, null); + } + } + -+ @Differs(from = "1.20.4", by = "This now exists") ++ private ParticleData getParticleDataAsTrailParticleOption(CommandContext cmdCtx, ++ Particle particle, TrailParticleOption options) { ++ final Level level = cmdCtx.getSource().getLevel(); ++ final Vec3 target = options.target(); ++ final Location targetLocation = new Location(level.getWorld(), target.x, target.y, target.z); ++ final Color color = Color.fromARGB(options.color()); ++ return new ParticleData(particle, new Trail(targetLocation, color, options.duration())); ++ } ++ + private ParticleData getParticleDataAsColorParticleOption(Particle particle, + ColorParticleOption options) { + final Color color = Color.fromARGB( @@ -993,7 +945,6 @@ index 0000000000000000000000000000000000000000..2e625f0345d79a8c8fe709b083f43460 + return new ParticleData(particle, color); + } + -+ @Differs(from = "1.20.4", by = "Now uses options#getFromColor instead of options.getColor") + private ParticleData getParticleDataAsDustColorTransitionOption(Particle particle, + DustColorTransitionOptions options) { + final Color color = Color.fromRGB((int) (options.getFromColor().x() * 255.0F), @@ -1003,7 +954,6 @@ index 0000000000000000000000000000000000000000..2e625f0345d79a8c8fe709b083f43460 + return new ParticleData(particle, new DustTransition(color, toColor, options.getScale())); + } + -+ @SuppressWarnings("removal") + private ParticleData getParticleDataAsVibrationParticleOption(CommandContext cmdCtx, + Particle particle, VibrationParticleOption options) { + // The "from" part of the Vibration object in Bukkit is completely ignored now, @@ -1026,16 +976,18 @@ index 0000000000000000000000000000000000000000..2e625f0345d79a8c8fe709b083f43460 + @Override + public Object getPotionEffect(CommandContext cmdCtx, String key, ArgumentSubType subType) throws CommandSyntaxException { + return switch (subType) { -+ case POTION_EFFECT_POTION_EFFECT -> CraftPotionEffectType.minecraftToBukkit(ResourceArgument.getMobEffect(cmdCtx, key).value()); ++ case POTION_EFFECT_POTION_EFFECT -> ++ CraftPotionEffectType.minecraftToBukkit(ResourceArgument.getMobEffect(cmdCtx, key).value()); + case POTION_EFFECT_NAMESPACEDKEY -> fromResourceLocation(ResourceLocationArgument.getId(cmdCtx, key)); + default -> throw new IllegalArgumentException("Unexpected value: " + subType); + }; + } + ++ @Differs(from = "1.21.1", by = "Uses ResourceKeyArgument instead of ResourceLocationArgument") + @Override + public final Recipe getRecipe(CommandContext cmdCtx, String key) throws CommandSyntaxException { -+ RecipeHolder recipe = ResourceLocationArgument.getRecipe(cmdCtx, key); -+ return new ComplexRecipeImpl(fromResourceLocation(recipe.id()), recipe.toBukkitRecipe()); ++ RecipeHolder recipe = ResourceKeyArgument.getRecipe(cmdCtx, key); ++ return new ComplexRecipeImpl(fromResourceLocation(recipe.id().registry()), recipe.toBukkitRecipe()); + } + + @Override @@ -1100,11 +1052,11 @@ index 0000000000000000000000000000000000000000..2e625f0345d79a8c8fe709b083f43460 + final ResourceLocation soundResource = ResourceLocationArgument.getId(cmdCtx, key); + return switch (subType) { + case SOUND_SOUND -> { -+ final SoundEvent soundEvent = BuiltInRegistries.SOUND_EVENT.get(soundResource); -+ if (soundEvent == null) { ++ final Optional> soundEvent = BuiltInRegistries.SOUND_EVENT.get(soundResource); ++ if (soundEvent.isEmpty()) { + yield null; + } else { -+ yield CraftSound.minecraftToBukkit(soundEvent); ++ yield CraftSound.minecraftToBukkit(soundEvent.get().value()); + } + } + case SOUND_NAMESPACEDKEY -> { @@ -1122,7 +1074,10 @@ index 0000000000000000000000000000000000000000..2e625f0345d79a8c8fe709b083f43460 + SharedSuggestionProvider.suggestResource(functionData.getTagNames(), builder, "#"); + return SharedSuggestionProvider.suggestResource(functionData.getFunctionNames(), builder); + }; -+ case RECIPES -> net.minecraft.commands.synchronization.SuggestionProviders.ALL_RECIPES; ++ case RECIPES -> (cmdCtx, builder) -> { ++ return SharedSuggestionProvider.suggestResource(this.getMinecraftServer().getRecipeManager() ++ .getRecipes().stream().map(holder -> holder.id().location()), builder); ++ }; + case SOUNDS -> net.minecraft.commands.synchronization.SuggestionProviders.AVAILABLE_SOUNDS; + case ADVANCEMENTS -> (cmdCtx, builder) -> { + return SharedSuggestionProvider.suggestResource(this.getMinecraftServer().getAdvancements() @@ -1138,9 +1093,10 @@ index 0000000000000000000000000000000000000000..2e625f0345d79a8c8fe709b083f43460 + }; + } + ++ @Differs(from = "1.20.6", by = "ResourceLocation constructor change to static ResourceLocation#fromNamespaceAndPath") + @Override + public final SimpleFunctionWrapper[] getTag(NamespacedKey key) { -+ Collection> customFunctions = this.getMinecraftServer().getFunctions().getTag(new ResourceLocation(key.getNamespace(), key.getKey())); ++ Collection> customFunctions = this.getMinecraftServer().getFunctions().getTag(ResourceLocation.fromNamespaceAndPath(key.getNamespace(), key.getKey())); + SimpleFunctionWrapper[] convertedCustomFunctions = new SimpleFunctionWrapper[customFunctions.size()]; + int index = 0; + for (CommandFunction customFunction : customFunctions) { @@ -1223,6 +1179,10 @@ index 0000000000000000000000000000000000000000..2e625f0345d79a8c8fe709b083f43460 + MultiPackResourceManager resourceManager = new MultiPackResourceManager(PackType.SERVER_DATA, + packResources); + ++ // TODO: I'm not sure if this is sufficient anymore - Do we not want to load tags for existing ++ // registries here as well? ++ // List> TagList = TagLoader.loadTagsForExistingRegistries(resourceManager, this.getMinecraftServer().registries().compositeAccess()); ++ + // Not using packResources, because we really really want this to work + CompletableFuture simpleReloadInstance = SimpleReloadInstance.create(resourceManager, + serverResources.managers().listeners(), this.getMinecraftServer().executor, @@ -1240,6 +1200,8 @@ index 0000000000000000000000000000000000000000..2e625f0345d79a8c8fe709b083f43460 + this.getMinecraftServer().server.syncCommands(); + this.getMinecraftServer().getPackRepository().setSelected(collection); + ++ final FeatureFlagSet enabledFeatures = this.getMinecraftServer().getWorldData().getDataConfiguration().enabledFeatures(); ++ + // this.getMinecraftServer().getSelectedPacks + Collection selectedIDs = this.getMinecraftServer().getPackRepository() + .getSelectedIds(); @@ -1250,11 +1212,11 @@ index 0000000000000000000000000000000000000000..2e625f0345d79a8c8fe709b083f43460 + disabledIDs.removeIf(enabledIDs::contains); + + this.getMinecraftServer().getWorldData() -+ .setDataConfiguration(new WorldDataConfiguration(new DataPackConfig(enabledIDs, disabledIDs), -+ this.getMinecraftServer().getWorldData().getDataConfiguration() -+ .enabledFeatures())); ++ .setDataConfiguration(new WorldDataConfiguration(new DataPackConfig(enabledIDs, disabledIDs), enabledFeatures)); + // this.getMinecraftServer().resources.managers().updateRegistryTags(registryAccess); -+ this.getMinecraftServer().resources.managers().updateRegistryTags(); ++ this.getMinecraftServer().resources.managers().updateStaticRegistryTags(); // TODO: Review this ++ this.getMinecraftServer().resources.managers().getRecipeManager().finalizeRecipeLoading(enabledFeatures); ++ + // May need to be commented out, may not. Comment it out just in case. + // For some reason, calling getPlayerList().saveAll() may just hang + // the server indefinitely. Not sure why! @@ -1263,6 +1225,13 @@ index 0000000000000000000000000000000000000000..2e625f0345d79a8c8fe709b083f43460 + // this.getMinecraftServer().getFunctions().replaceLibrary(this.getMinecraftServer().resources.managers().getFunctionLibrary()); + this.getMinecraftServer().getStructureManager() + .onResourceManagerReload(this.getMinecraftServer().resources.resourceManager()); ++ ++ // Set fuel values with the new loaded fuel values from the list of enabled features ++ minecraftServerFuelValues.set(this.getMinecraftServer(), ++ FuelValues.vanillaBurnTimes(this.getMinecraftServer().registries().compositeAccess(), ++ enabledFeatures ++ ) ++ ); + }); + + // Step 4: Block the thread until everything's done @@ -1317,23 +1286,28 @@ index 0000000000000000000000000000000000000000..2e625f0345d79a8c8fe709b083f43460 + + @Override + public CommandRegistrationStrategy createCommandRegistrationStrategy() { ++ // This class is Paper-server specific, so we need to use paper's userdev plugin to ++ // access it directly. That might need gradle, but there might also be a maven version? ++ // https://discord.com/channels/289587909051416579/1121227200277004398/1246910745761812480 ++ Class bukkitCommandNode_bukkitBrigCommand; ++ try { ++ bukkitCommandNode_bukkitBrigCommand = Class.forName("io.papermc.paper.command.brigadier.bukkit.BukkitCommandNode$BukkitBrigCommand"); ++ } catch (ClassNotFoundException e) { ++ throw new IllegalStateException("Expected to find class", e); ++ } + return new PaperCommandRegistration<>( + () -> this.getMinecraftServer().getCommands().getDispatcher(), -+ node -> isBukkitCommand(node.getCommand()) ++ node -> bukkitCommandNode_bukkitBrigCommand.isInstance(node.getCommand()) + ); + } -+ -+ private boolean isBukkitCommand(Object o) { -+ return o instanceof BukkitCommandNode.BukkitBrigCommand; -+ } +} \ No newline at end of file diff --git a/src/main/java/dev/jorel/commandapi/nms/NMS_Common.java b/src/main/java/dev/jorel/commandapi/nms/NMS_Common.java new file mode 100644 -index 0000000000000000000000000000000000000000..d9d2f5a111f1fac23de7f87e224c75112416d80d +index 0000000000000000000000000000000000000000..216ef38a930b762fb88c3d76a3315d0382fb8b36 --- /dev/null +++ b/src/main/java/dev/jorel/commandapi/nms/NMS_Common.java -@@ -0,0 +1,595 @@ +@@ -0,0 +1,604 @@ +/******************************************************************************* + * Copyright 2018, 2021 Jorel Ali (Skepter) - MIT License + * @@ -1375,11 +1349,14 @@ index 0000000000000000000000000000000000000000..d9d2f5a111f1fac23de7f87e224c7511 +import dev.jorel.commandapi.wrappers.*; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; ++import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.md_5.bungee.api.chat.BaseComponent; ++import net.md_5.bungee.chat.ComponentSerializer; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.arguments.*; +import net.minecraft.commands.arguments.coordinates.*; +import net.minecraft.commands.arguments.item.FunctionArgument; ++import net.minecraft.network.chat.Component.Serializer; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.phys.Vec2; +import org.bukkit.*; @@ -1427,6 +1404,11 @@ index 0000000000000000000000000000000000000000..d9d2f5a111f1fac23de7f87e224c7511 + } + + @Override ++ public ArgumentType _ArgumentAdvancement() { ++ return ResourceLocationArgument.id(); ++ } ++ ++ @Override + public final ArgumentType _ArgumentAngle() { + return AngleArgument.angle(); + } @@ -1524,6 +1506,11 @@ index 0000000000000000000000000000000000000000..d9d2f5a111f1fac23de7f87e224c7511 + public abstract ArgumentType _ArgumentParticle(); + + @Override ++ public ArgumentType _ArgumentRecipe() { ++ return ResourceLocationArgument.id(); ++ } ++ ++ @Override + public final ArgumentType _ArgumentPosition() { + return BlockPosArgument.blockPos(); + } @@ -1617,7 +1604,6 @@ index 0000000000000000000000000000000000000000..d9d2f5a111f1fac23de7f87e224c7511 + @Unimplemented(because = VERSION_SPECIFIC_IMPLEMENTATION) + public abstract String convert(ParticleData particle); + -+ @SuppressWarnings("deprecation") + @Override + public final String convert(PotionEffectType potion) { + return potion.getName().toLowerCase(Locale.ENGLISH); @@ -1681,7 +1667,6 @@ index 0000000000000000000000000000000000000000..d9d2f5a111f1fac23de7f87e224c7511 + @Unimplemented(because = REQUIRES_CRAFTBUKKIT, classNamed = "CraftBlockData") + public abstract BlockData getBlockState(CommandContext cmdCtx, String key); + -+ @SuppressWarnings("deprecation") + @Override + @Overridden(in = "1.20.5", because = "Serializer.toJson now needs a Provider") + public BaseComponent[] getChat(CommandContext cmdCtx, String key) throws CommandSyntaxException { @@ -1689,13 +1674,11 @@ index 0000000000000000000000000000000000000000..d9d2f5a111f1fac23de7f87e224c7511 + return null; + } + -+ @SuppressWarnings("deprecation") + @Override + public final ChatColor getChatColor(CommandContext cmdCtx, String key) { + return ChatColor.getByChar(ColorArgument.getColor(cmdCtx, key).getChar()); + } + -+ @SuppressWarnings("deprecation") + @Override + public final BaseComponent[] getChatComponent(CommandContext cmdCtx, String key) { + // return ComponentSerializer.parse(Serializer.toJson(ComponentArgument.getComponent(cmdCtx, key))); @@ -1853,7 +1836,7 @@ index 0000000000000000000000000000000000000000..d9d2f5a111f1fac23de7f87e224c7511 + public abstract ScoreboardSlot getScoreboardSlot(CommandContext cmdCtx, String key); + + @Override -+ // Overridden in 1.20.3 because this now returns a Collection ++ // TODO: Overridden in 1.20.3 because this now returns a Collection + public Collection getScoreHolderMultiple(CommandContext cmdCtx, String key) + throws CommandSyntaxException { + // return ScoreHolderArgument.getNames(cmdCtx, key); @@ -1861,7 +1844,7 @@ index 0000000000000000000000000000000000000000..d9d2f5a111f1fac23de7f87e224c7511 + } + + @Override -+ // Overridden in 1.20.3 because this now returns a ScoreHolder ++ // TODO: Overridden in 1.20.3 because this now returns a ScoreHolder + public String getScoreHolderSingle(CommandContext cmdCtx, String key) throws CommandSyntaxException { + // return ScoreHolderArgument.getName(cmdCtx, key); + return null; diff --git a/patches/server/0005-Lumina-server-config-and-command.patch b/patches/server/0005-Lumina-server-config-and-command.patch index 1e85daa..373f953 100644 --- a/patches/server/0005-Lumina-server-config-and-command.patch +++ b/patches/server/0005-Lumina-server-config-and-command.patch @@ -3285,20 +3285,41 @@ index 0000000000000000000000000000000000000000..b043bf36de0604028cb7bdd166d000d2 +} diff --git a/src/main/java/org/leavesmc/lumina/config/modules/misc/RegionFormatConfig.java b/src/main/java/org/leavesmc/lumina/config/modules/misc/RegionFormatConfig.java new file mode 100644 -index 0000000000000000000000000000000000000000..35aed045ceb1f7647de4b0efbf7c609f62c402d8 +index 0000000000000000000000000000000000000000..a77ee98609c33e47a36ceb28628bd3ffd49f7614 --- /dev/null +++ b/src/main/java/org/leavesmc/lumina/config/modules/misc/RegionFormatConfig.java -@@ -0,0 +1,11 @@ +@@ -0,0 +1,32 @@ +package org.leavesmc.lumina.config.modules.misc; + ++import abomination.LinearRegionFile; ++import me.earthme.luminol.utils.EnumRegionFormat; ++import net.minecraft.server.MinecraftServer; +import org.spongepowered.configurate.objectmapping.ConfigSerializable; -+import org.stupidcraft.linearpaper.region.EnumRegionFileExtension; ++import org.spongepowered.configurate.objectmapping.meta.PostProcess; + +@ConfigSerializable +public class RegionFormatConfig { -+ public EnumRegionFileExtension format = EnumRegionFileExtension.MCA; ++ public EnumRegionFormat regionFormat; + public int linearCompressionLevel = 1; -+ public int linearFlushFrequency = 10; ++ public int linearIoThreadCount = 6; ++ public int linearIoFlushDelayMs = 100; ++ public boolean linearUseVirtualThread = true; ++ ++ ++ @PostProcess ++ public void postProcess() { ++ if (regionFormat == EnumRegionFormat.LINEAR_V2) { ++ if (linearCompressionLevel > 23 || linearCompressionLevel < 1) { ++ MinecraftServer.LOGGER.error("Linear region compression level should be between 1 and 22 in config: {}", linearCompressionLevel); ++ MinecraftServer.LOGGER.error("Falling back to compression level 1."); ++ linearCompressionLevel = 1; ++ } ++ ++ LinearRegionFile.SAVE_DELAY_MS = linearIoFlushDelayMs; ++ LinearRegionFile.SAVE_THREAD_MAX_COUNT = linearIoThreadCount; ++ LinearRegionFile.USE_VIRTUAL_THREAD = linearUseVirtualThread; ++ } ++ } +} diff --git a/src/main/java/org/leavesmc/lumina/config/modules/misc/WatchdogConfig.java b/src/main/java/org/leavesmc/lumina/config/modules/misc/WatchdogConfig.java new file mode 100644 diff --git a/patches/server/0006-Luminol-Linear-region-format.patch b/patches/server/0006-Luminol-Linear-region-format.patch deleted file mode 100644 index 5d9675e..0000000 --- a/patches/server/0006-Luminol-Linear-region-format.patch +++ /dev/null @@ -1,495 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: MrHua269 -Date: Thu, 27 Jun 2024 18:25:12 +0800 -Subject: [PATCH] Luminol Linear region format - -This patch is Powered by Luminol(https://github.com/LuminolMC/Luminol) -License: -MIT License - -Copyright (c) 2024 LuminolMC - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -diff --git a/src/main/java/org/stupidcraft/linearpaper/region/AbstractRegionFile.java b/src/main/java/org/stupidcraft/linearpaper/region/AbstractRegionFile.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1325ac1d603bc65224860170b5ffad3f8cca2e9c ---- /dev/null -+++ b/src/main/java/org/stupidcraft/linearpaper/region/AbstractRegionFile.java -@@ -0,0 +1,30 @@ -+package org.stupidcraft.linearpaper.region; -+ -+import java.io.DataInputStream; -+import java.io.DataOutputStream; -+import java.io.IOException; -+import java.nio.file.Path; -+import java.util.concurrent.locks.ReentrantLock; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.chunk.status.ChunkStatus; -+ -+public interface AbstractRegionFile { -+ void flush() throws IOException; -+ void clear(ChunkPos pos) throws IOException; -+ void close() throws IOException; -+ void setStatus(int x, int z, ChunkStatus status); -+ void setOversized(int x, int z, boolean b) throws IOException; -+ -+ boolean hasChunk(ChunkPos pos); -+ boolean doesChunkExist(ChunkPos pos) throws Exception; -+ boolean isOversized(int x, int z); -+ boolean recalculateHeader() throws IOException; -+ -+ DataOutputStream getChunkDataOutputStream(ChunkPos pos) throws IOException; -+ DataInputStream getChunkDataInputStream(ChunkPos pos) throws IOException; -+ CompoundTag getOversizedData(int x, int z) throws IOException; -+ ChunkStatus getStatusIfCached(int x, int z); -+ ReentrantLock getFileLock(); -+ Path getRegionFile(); -+} -diff --git a/src/main/java/org/stupidcraft/linearpaper/region/AbstractRegionFileFactory.java b/src/main/java/org/stupidcraft/linearpaper/region/AbstractRegionFileFactory.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ddc8391108cff4fd90b046087d519409c00f8bf8 ---- /dev/null -+++ b/src/main/java/org/stupidcraft/linearpaper/region/AbstractRegionFileFactory.java -@@ -0,0 +1,40 @@ -+package org.stupidcraft.linearpaper.region; -+ -+import java.io.IOException; -+import java.nio.file.Path; -+import java.util.Objects; -+ -+import net.minecraft.world.level.chunk.storage.RegionFile; -+import net.minecraft.world.level.chunk.storage.RegionFileVersion; -+import net.minecraft.world.level.chunk.storage.RegionStorageInfo; -+import org.jetbrains.annotations.Contract; -+import org.jetbrains.annotations.NotNull; -+import org.leavesmc.lumina.config.LuminaConfig; -+ -+public class AbstractRegionFileFactory { -+ @Contract("_, _, _, _ -> new") -+ public static @NotNull AbstractRegionFile getAbstractRegionFile(RegionStorageInfo storageKey, Path directory, Path path, boolean dsync) throws IOException { -+ return getAbstractRegionFile(storageKey, directory, path, RegionFileVersion.getCompressionFormat(), dsync); -+ } -+ -+ @Contract("_, _, _, _, _ -> new") -+ public static @NotNull AbstractRegionFile getAbstractRegionFile(RegionStorageInfo storageKey, Path directory, Path path, boolean dsync, boolean canRecalcHeader) throws IOException { -+ return getAbstractRegionFile(storageKey, directory, path, RegionFileVersion.getCompressionFormat(), dsync, canRecalcHeader); -+ } -+ -+ @Contract("_, _, _, _, _ -> new") -+ public static @NotNull AbstractRegionFile getAbstractRegionFile(RegionStorageInfo storageKey, Path path, Path directory, RegionFileVersion compressionFormat, boolean dsync) throws IOException { -+ return getAbstractRegionFile(storageKey, path, directory, compressionFormat, dsync, true); -+ } -+ -+ @Contract("_, _, _, _, _, _ -> new") -+ public static @NotNull AbstractRegionFile getAbstractRegionFile(RegionStorageInfo storageKey, @NotNull Path path, Path directory, RegionFileVersion compressionFormat, boolean dsync, boolean canRecalcHeader) throws IOException { -+ final String fullFileName = path.getFileName().toString(); -+ final String[] fullNameSplit = fullFileName.split("\\."); -+ final String extensionName = fullNameSplit[fullNameSplit.length - 1]; -+ if (Objects.requireNonNull(EnumRegionFileExtension.fromExtension(extensionName)) == EnumRegionFileExtension.LINEAR) { -+ return new LinearRegionFile(path, LuminaConfig.configModule().misc.regionFormat.linearCompressionLevel); -+ } -+ return new RegionFile(storageKey, path, directory, compressionFormat, dsync, canRecalcHeader); -+ } -+} -diff --git a/src/main/java/org/stupidcraft/linearpaper/region/EnumRegionFileExtension.java b/src/main/java/org/stupidcraft/linearpaper/region/EnumRegionFileExtension.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d51ec3faeb6a78992d440a7996739f4a5f0a5387 ---- /dev/null -+++ b/src/main/java/org/stupidcraft/linearpaper/region/EnumRegionFileExtension.java -@@ -0,0 +1,55 @@ -+package org.stupidcraft.linearpaper.region; -+ -+ -+import org.jetbrains.annotations.Contract; -+import org.jetbrains.annotations.NotNull; -+ -+public enum EnumRegionFileExtension { -+ LINEAR(".linear"), -+ MCA(".mca"), -+ UNKNOWN(null); -+ -+ private final String extensionName; -+ -+ EnumRegionFileExtension(String extensionName) { -+ this.extensionName = extensionName; -+ } -+ -+ public String getExtensionName() { -+ return this.extensionName; -+ } -+ -+ @Contract(pure = true) -+ public static EnumRegionFileExtension fromName(@NotNull String name){ -+ switch (name){ -+ default -> { -+ return UNKNOWN; -+ } -+ -+ case "MCA" -> { -+ return MCA; -+ } -+ -+ case "LINEAR" -> { -+ return LINEAR; -+ } -+ } -+ } -+ -+ @Contract(pure = true) -+ public static EnumRegionFileExtension fromExtension(@NotNull String name){ -+ switch (name){ -+ default -> { -+ return UNKNOWN; -+ } -+ -+ case ".mca" -> { -+ return MCA; -+ } -+ -+ case ".linear" -> { -+ return LINEAR; -+ } -+ } -+ } -+} -diff --git a/src/main/java/org/stupidcraft/linearpaper/region/LinearRegionFile.java b/src/main/java/org/stupidcraft/linearpaper/region/LinearRegionFile.java -new file mode 100644 -index 0000000000000000000000000000000000000000..db954f52f8dd39119669742b2c5dd23af846e920 ---- /dev/null -+++ b/src/main/java/org/stupidcraft/linearpaper/region/LinearRegionFile.java -@@ -0,0 +1,317 @@ -+package org.stupidcraft.linearpaper.region; -+ -+import com.github.luben.zstd.ZstdInputStream; -+import com.github.luben.zstd.ZstdOutputStream; -+import com.mojang.logging.LogUtils; -+import java.io.BufferedOutputStream; -+import java.io.ByteArrayInputStream; -+import java.io.ByteArrayOutputStream; -+import java.io.DataInputStream; -+import java.io.DataOutputStream; -+import java.io.File; -+import java.io.FileInputStream; -+import java.io.FileOutputStream; -+import java.io.IOException; -+import java.io.InputStream; -+import java.nio.ByteBuffer; -+import java.nio.file.Files; -+import java.nio.file.Path; -+import java.nio.file.StandardCopyOption; -+import java.util.ArrayList; -+import java.util.Arrays; -+import java.util.List; -+import java.util.concurrent.TimeUnit; -+import java.util.concurrent.atomic.AtomicBoolean; -+import java.util.concurrent.locks.ReentrantLock; -+import javax.annotation.Nullable; -+ -+import org.leavesmc.lumina.config.LuminaConfig; -+import org.leavesmc.lumina.config.modules.misc.RegionFormatConfig; -+import net.jpountz.lz4.LZ4Compressor; -+import net.jpountz.lz4.LZ4Factory; -+import net.jpountz.lz4.LZ4FastDecompressor; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.chunk.status.ChunkStatus; -+import org.slf4j.Logger; -+ -+public class LinearRegionFile implements AbstractRegionFile, AutoCloseable { -+ private static final long SUPERBLOCK = -4323716122432332390L; -+ private static final byte VERSION = 2; -+ private static final int HEADER_SIZE = 32; -+ private static final int FOOTER_SIZE = 8; -+ private static final Logger LOGGER = LogUtils.getLogger(); -+ private static final List SUPPORTED_VERSIONS = Arrays.asList((byte) 1, (byte) 2); -+ public final ReentrantLock fileLock = new ReentrantLock(true); -+ private final byte[][] buffer = new byte[1024][]; -+ private final int[] bufferUncompressedSize = new int[1024]; -+ private final int[] chunkTimestamps = new int[1024]; -+ private final ChunkStatus[] statuses = new ChunkStatus[1024]; -+ private final LZ4Compressor compressor; -+ private final LZ4FastDecompressor decompressor; -+ private final int compressionLevel; -+ public boolean closed = false; -+ public Path path; -+ private volatile long lastFlushed = System.nanoTime(); -+ -+ -+ public LinearRegionFile(Path file, int compression) throws IOException { -+ this.path = file; -+ this.compressionLevel = compression; -+ this.compressor = LZ4Factory.fastestInstance().fastCompressor(); -+ this.decompressor = LZ4Factory.fastestInstance().fastDecompressor(); -+ -+ File regionFile = new File(this.path.toString()); -+ -+ Arrays.fill(this.bufferUncompressedSize, 0); -+ -+ if (!regionFile.canRead()) return; -+ -+ try (FileInputStream fileStream = new FileInputStream(regionFile); -+ DataInputStream rawDataStream = new DataInputStream(fileStream)) { -+ -+ long superBlock = rawDataStream.readLong(); -+ if (superBlock != SUPERBLOCK) -+ throw new RuntimeException("Invalid superblock: " + superBlock + " in " + file); -+ -+ byte version = rawDataStream.readByte(); -+ if (!SUPPORTED_VERSIONS.contains(version)) -+ throw new RuntimeException("Invalid version: " + version + " in " + file); -+ -+ // Skip newestTimestamp (Long) + Compression level (Byte) + Chunk count (Short): Unused. -+ rawDataStream.skipBytes(11); -+ -+ int dataCount = rawDataStream.readInt(); -+ long fileLength = file.toFile().length(); -+ if (fileLength != HEADER_SIZE + dataCount + FOOTER_SIZE) -+ throw new IOException("Invalid file length: " + this.path + " " + fileLength + " " + (HEADER_SIZE + dataCount + FOOTER_SIZE)); -+ -+ rawDataStream.skipBytes(8); // Skip data hash (Long): Unused. -+ -+ byte[] rawCompressed = new byte[dataCount]; -+ rawDataStream.readFully(rawCompressed, 0, dataCount); -+ -+ superBlock = rawDataStream.readLong(); -+ if (superBlock != SUPERBLOCK) -+ throw new IOException("Footer superblock invalid " + this.path); -+ -+ try (DataInputStream dataStream = new DataInputStream(new ZstdInputStream(new ByteArrayInputStream(rawCompressed)))) { -+ -+ int[] starts = new int[1024]; -+ for (int i = 0; i < 1024; i++) { -+ starts[i] = dataStream.readInt(); -+ dataStream.skipBytes(4); // Skip timestamps (Int): Unused. -+ } -+ -+ for (int i = 0; i < 1024; i++) { -+ if (starts[i] > 0) { -+ int size = starts[i]; -+ byte[] b = new byte[size]; -+ dataStream.readFully(b, 0, size); -+ -+ int maxCompressedLength = this.compressor.maxCompressedLength(size); -+ byte[] compressed = new byte[maxCompressedLength]; -+ int compressedLength = this.compressor.compress(b, 0, size, compressed, 0, maxCompressedLength); -+ b = new byte[compressedLength]; -+ System.arraycopy(compressed, 0, b, 0, compressedLength); -+ -+ this.buffer[i] = b; -+ this.bufferUncompressedSize[i] = size; -+ } -+ } -+ } -+ } -+ } -+ -+ private static int getChunkIndex(int x, int z) { -+ return (x & 31) + ((z & 31) << 5); -+ } -+ -+ private static int getTimestamp() { -+ return (int) (System.currentTimeMillis() / 1000L); -+ } -+ -+ public Path getRegionFile() { -+ return this.path; -+ } -+ -+ public ReentrantLock getFileLock() { -+ return this.fileLock; -+ } -+ -+ public void flush() throws IOException { -+ flushWrapper(); // sync -+ } -+ -+ public void flushWrapper() { -+ try { -+ save(); -+ } catch (IOException e) { -+ LOGGER.error("Failed to flush region file " + path.toAbsolutePath(), e); -+ } -+ } -+ -+ public boolean doesChunkExist(ChunkPos pos) throws Exception { -+ throw new Exception("doesChunkExist is a stub"); -+ } -+ -+ private synchronized void save() throws IOException { -+ long timestamp = getTimestamp(); -+ short chunkCount = 0; -+ -+ File tempFile = new File(path.toString() + ".tmp"); -+ -+ try (FileOutputStream fileStream = new FileOutputStream(tempFile); -+ ByteArrayOutputStream zstdByteArray = new ByteArrayOutputStream(); -+ ZstdOutputStream zstdStream = new ZstdOutputStream(zstdByteArray, this.compressionLevel); -+ DataOutputStream zstdDataStream = new DataOutputStream(zstdStream); -+ DataOutputStream dataStream = new DataOutputStream(fileStream)) { -+ -+ dataStream.writeLong(SUPERBLOCK); -+ dataStream.writeByte(VERSION); -+ dataStream.writeLong(timestamp); -+ dataStream.writeByte(this.compressionLevel); -+ -+ ArrayList byteBuffers = new ArrayList<>(); -+ for (int i = 0; i < 1024; i++) { -+ if (this.bufferUncompressedSize[i] != 0) { -+ chunkCount += 1; -+ byte[] content = new byte[bufferUncompressedSize[i]]; -+ this.decompressor.decompress(buffer[i], 0, content, 0, bufferUncompressedSize[i]); -+ -+ byteBuffers.add(content); -+ } else byteBuffers.add(null); -+ } -+ for (int i = 0; i < 1024; i++) { -+ zstdDataStream.writeInt(this.bufferUncompressedSize[i]); // Write uncompressed size -+ zstdDataStream.writeInt(this.chunkTimestamps[i]); // Write timestamp -+ } -+ for (int i = 0; i < 1024; i++) { -+ if (byteBuffers.get(i) != null) -+ zstdDataStream.write(byteBuffers.get(i), 0, byteBuffers.get(i).length); -+ } -+ zstdDataStream.close(); -+ -+ dataStream.writeShort(chunkCount); -+ -+ byte[] compressed = zstdByteArray.toByteArray(); -+ -+ dataStream.writeInt(compressed.length); -+ dataStream.writeLong(0); -+ -+ dataStream.write(compressed, 0, compressed.length); -+ dataStream.writeLong(SUPERBLOCK); -+ -+ dataStream.flush(); -+ fileStream.getFD().sync(); -+ fileStream.getChannel().force(true); // Ensure atomicity on Btrfs -+ } -+ Files.move(tempFile.toPath(), this.path, StandardCopyOption.REPLACE_EXISTING); -+ this.lastFlushed = System.nanoTime(); -+ } -+ -+ public void setStatus(int x, int z, ChunkStatus status) { -+ this.statuses[getChunkIndex(x, z)] = status; -+ } -+ -+ public synchronized void write(ChunkPos pos, ByteBuffer buffer) { -+ try { -+ byte[] b = toByteArray(new ByteArrayInputStream(buffer.array())); -+ int uncompressedSize = b.length; -+ -+ int maxCompressedLength = this.compressor.maxCompressedLength(b.length); -+ byte[] compressed = new byte[maxCompressedLength]; -+ int compressedLength = this.compressor.compress(b, 0, b.length, compressed, 0, maxCompressedLength); -+ b = new byte[compressedLength]; -+ System.arraycopy(compressed, 0, b, 0, compressedLength); -+ -+ int index = getChunkIndex(pos.x, pos.z); -+ this.buffer[index] = b; -+ this.chunkTimestamps[index] = getTimestamp(); -+ this.bufferUncompressedSize[getChunkIndex(pos.x, pos.z)] = uncompressedSize; -+ } catch (IOException e) { -+ LOGGER.error("Chunk write IOException " + e + " " + this.path); -+ } -+ -+ if ((System.nanoTime() - this.lastFlushed) >= TimeUnit.NANOSECONDS.toSeconds(LuminaConfig.configModule().misc.regionFormat.linearFlushFrequency)){ -+ this.flushWrapper(); -+ } -+ } -+ -+ public DataOutputStream getChunkDataOutputStream(ChunkPos pos) { -+ return new DataOutputStream(new BufferedOutputStream(new ChunkBuffer(pos))); -+ } -+ -+ private byte[] toByteArray(InputStream in) throws IOException { -+ ByteArrayOutputStream out = new ByteArrayOutputStream(); -+ byte[] tempBuffer = new byte[4096]; -+ -+ int length; -+ while ((length = in.read(tempBuffer)) >= 0) { -+ out.write(tempBuffer, 0, length); -+ } -+ -+ return out.toByteArray(); -+ } -+ -+ @Nullable -+ public synchronized DataInputStream getChunkDataInputStream(ChunkPos pos) { -+ if (this.bufferUncompressedSize[getChunkIndex(pos.x, pos.z)] != 0) { -+ byte[] content = new byte[bufferUncompressedSize[getChunkIndex(pos.x, pos.z)]]; -+ this.decompressor.decompress(this.buffer[getChunkIndex(pos.x, pos.z)], 0, content, 0, bufferUncompressedSize[getChunkIndex(pos.x, pos.z)]); -+ return new DataInputStream(new ByteArrayInputStream(content)); -+ } -+ return null; -+ } -+ -+ public ChunkStatus getStatusIfCached(int x, int z) { -+ return this.statuses[getChunkIndex(x, z)]; -+ } -+ -+ public void clear(ChunkPos pos) { -+ int i = getChunkIndex(pos.x, pos.z); -+ this.buffer[i] = null; -+ this.bufferUncompressedSize[i] = 0; -+ this.chunkTimestamps[i] = getTimestamp(); -+ this.flushWrapper(); -+ } -+ -+ public boolean hasChunk(ChunkPos pos) { -+ return this.bufferUncompressedSize[getChunkIndex(pos.x, pos.z)] > 0; -+ } -+ -+ public void close() throws IOException { -+ if (closed) return; -+ closed = true; -+ flush(); // sync -+ } -+ -+ public boolean recalculateHeader() { -+ return false; -+ } -+ -+ public void setOversized(int x, int z, boolean something) { -+ } -+ -+ public CompoundTag getOversizedData(int x, int z) throws IOException { -+ throw new IOException("getOversizedData is a stub " + this.path); -+ } -+ -+ public boolean isOversized(int x, int z) { -+ return false; -+ } -+ -+ private class ChunkBuffer extends ByteArrayOutputStream { -+ private final ChunkPos pos; -+ -+ public ChunkBuffer(ChunkPos chunkcoordintpair) { -+ super(); -+ this.pos = chunkcoordintpair; -+ } -+ -+ public void close() throws IOException { -+ ByteBuffer bytebuffer = ByteBuffer.wrap(this.buf, 0, this.count); -+ LinearRegionFile.this.write(this.pos, bytebuffer); -+ } -+ } -+} diff --git a/patches/server/0006-Luminol-Linear-v2-region-format-support.patch b/patches/server/0006-Luminol-Linear-v2-region-format-support.patch new file mode 100644 index 0000000..2ab3c91 --- /dev/null +++ b/patches/server/0006-Luminol-Linear-v2-region-format-support.patch @@ -0,0 +1,1153 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MrHua269 +Date: Thu, 27 Jun 2024 18:25:12 +0800 +Subject: [PATCH] Luminol Linear v2 region format support + +This patch is Powered by Luminol(https://github.com/LuminolMC/Luminol) +License: +MIT License + +Copyright (c) 2024 LuminolMC + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +diff --git a/build.gradle.kts b/build.gradle.kts +index 2c26932456401bebe45780527e3408cc939955a9..a04c71d96c74d724a792e95fa86b876ef4b3883c 100644 +--- a/build.gradle.kts ++++ b/build.gradle.kts +@@ -86,6 +86,11 @@ dependencies { + implementation("me.lucko:spark-api:0.1-20240720.200737-2") + implementation("me.lucko:spark-paper:1.10.119-SNAPSHOT") + // Paper end - spark ++ // Abomination start ++ implementation("com.github.luben:zstd-jni:1.5.4-1") ++ implementation("org.lz4:lz4-java:1.8.0") ++ implementation("net.openhft:zero-allocation-hashing:0.16") ++ // Abomination end + } + + paperweight { +diff --git a/src/main/java/abomination/IRegionFile.java b/src/main/java/abomination/IRegionFile.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d92f1d549c7e01daa6b5bba7d405e462a9d57e27 +--- /dev/null ++++ b/src/main/java/abomination/IRegionFile.java +@@ -0,0 +1,39 @@ ++package abomination; ++ ++import ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemRegionFile; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.world.level.ChunkPos; ++ ++import java.io.DataInputStream; ++import java.io.DataOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++import java.nio.file.Path; ++ ++public interface IRegionFile extends ChunkSystemRegionFile { ++ Path getPath(); ++ ++ DataInputStream getChunkDataInputStream(ChunkPos pos) throws IOException; ++ ++ boolean doesChunkExist(ChunkPos pos) throws Exception; ++ ++ DataOutputStream getChunkDataOutputStream(ChunkPos pos) throws IOException; ++ ++ void flush() throws IOException; ++ ++ void clear(ChunkPos pos) throws IOException; ++ ++ boolean hasChunk(ChunkPos pos); ++ ++ void close() throws IOException; ++ ++ void write(ChunkPos pos, ByteBuffer buf) throws IOException; ++ ++ CompoundTag getOversizedData(int x, int z) throws IOException; ++ ++ boolean isOversized(int x, int z); ++ ++ boolean recalculateHeader() throws IOException; ++ ++ void setOversized(int x, int z, boolean oversized) throws IOException; ++} +diff --git a/src/main/java/abomination/LinearRegionFile.java b/src/main/java/abomination/LinearRegionFile.java +new file mode 100644 +index 0000000000000000000000000000000000000000..bb0fcf5f47b5ae3d86e1d0572f951236afdcd017 +--- /dev/null ++++ b/src/main/java/abomination/LinearRegionFile.java +@@ -0,0 +1,622 @@ ++package abomination; ++ ++import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO; ++import com.github.luben.zstd.ZstdInputStream; ++import com.github.luben.zstd.ZstdOutputStream; ++import com.mojang.logging.LogUtils; ++import net.jpountz.lz4.LZ4Compressor; ++import net.jpountz.lz4.LZ4Factory; ++import net.jpountz.lz4.LZ4FastDecompressor; ++import net.openhft.hashing.LongHashFunction; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.world.level.chunk.storage.RegionStorageInfo; ++import net.minecraft.world.level.chunk.storage.RegionFileVersion; ++import net.minecraft.world.level.ChunkPos; ++import org.slf4j.Logger; ++ ++import javax.annotation.Nullable; ++import java.io.*; ++import java.nio.ByteBuffer; ++import java.nio.file.Files; ++import java.nio.file.Path; ++import java.nio.file.StandardCopyOption; ++import java.util.ArrayList; ++import java.util.Arrays; ++import java.util.List; ++import java.util.concurrent.TimeUnit; ++import java.util.concurrent.locks.LockSupport; ++import java.util.concurrent.locks.ReentrantLock; ++ ++// LinearRegionFile_implementation_version_0_5byXymb ++// Just gonna use this string to inform other forks about updates ;-) ++public class LinearRegionFile implements IRegionFile{ ++ private static final long SUPERBLOCK = 0xc3ff13183cca9d9aL; ++ private static final byte VERSION = 3; ++ private static final int HEADER_SIZE = 27; ++ private static final int FOOTER_SIZE = 8; ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ ++ private byte[][] bucketBuffers; ++ private final byte[][] buffer = new byte[1024][]; ++ private final int[] bufferUncompressedSize = new int[1024]; ++ ++ private final long[] chunkTimestamps = new long[1024]; ++ private final Object markedToSaveLock = new Object(); ++ ++ private final LZ4Compressor compressor; ++ private final LZ4FastDecompressor decompressor; ++ ++ private boolean markedToSave = false; ++ private boolean close = false; ++ ++ public final ReentrantLock fileLock = new ReentrantLock(true); ++ public Path regionFile; ++ ++ private final int compressionLevel; ++ private int gridSize = 8; ++ private int bucketSize = 4; ++ private final Thread bindThread; ++ ++ public Path getRegionFile() { ++ return this.regionFile; ++ } ++ ++ public ReentrantLock getFileLock() { ++ return this.fileLock; ++ } ++ ++ private int chunkToBucketIdx(int chunkX, int chunkZ) { ++ int bx = chunkX / bucketSize, bz = chunkZ / bucketSize; ++ return bx * gridSize + bz; ++ } ++ ++ private void openBucket(int chunkX, int chunkZ) { ++ chunkX = Math.floorMod(chunkX, 32); ++ chunkZ = Math.floorMod(chunkZ, 32); ++ int idx = chunkToBucketIdx(chunkX, chunkZ); ++ ++ if (bucketBuffers == null) return; ++ if (bucketBuffers[idx] != null) { ++ try { ++ ByteArrayInputStream bucketByteStream = new ByteArrayInputStream(bucketBuffers[idx]); ++ ZstdInputStream zstdStream = new ZstdInputStream(bucketByteStream); ++ ByteBuffer bucketBuffer = ByteBuffer.wrap(zstdStream.readAllBytes()); ++ ++ int bx = chunkX / bucketSize, bz = chunkZ / bucketSize; ++ ++ for (int cx = 0; cx < 32 / gridSize; cx++) { ++ for (int cz = 0; cz < 32 / gridSize; cz++) { ++ int chunkIndex = (bx * (32 / gridSize) + cx) + (bz * (32 / gridSize) + cz) * 32; ++ ++ int chunkSize = bucketBuffer.getInt(); ++ long timestamp = bucketBuffer.getLong(); ++ this.chunkTimestamps[chunkIndex] = timestamp; ++ ++ if (chunkSize > 0) { ++ byte[] chunkData = new byte[chunkSize - 8]; ++ bucketBuffer.get(chunkData); ++ ++ int maxCompressedLength = this.compressor.maxCompressedLength(chunkData.length); ++ byte[] compressed = new byte[maxCompressedLength]; ++ int compressedLength = this.compressor.compress(chunkData, 0, chunkData.length, compressed, 0, maxCompressedLength); ++ byte[] finalCompressed = new byte[compressedLength]; ++ System.arraycopy(compressed, 0, finalCompressed, 0, compressedLength); ++ ++ // TODO: Optimization - return the requested chunk immediately to save on one LZ4 decompression ++ this.buffer[chunkIndex] = finalCompressed; ++ this.bufferUncompressedSize[chunkIndex] = chunkData.length; ++ } ++ } ++ } ++ } catch (IOException ex) { ++ throw new RuntimeException("Region file corrupted: " + regionFile + " bucket: " + idx); ++ // TODO: Make sure the server crashes instead of corrupting the world ++ } ++ bucketBuffers[idx] = null; ++ } ++ } ++ ++ public boolean regionFileOpen = false; ++ ++ private synchronized void openRegionFile() { ++ if (regionFileOpen) return; ++ regionFileOpen = true; ++ ++ File regionFile = new File(this.regionFile.toString()); ++ ++ if(!regionFile.canRead()) { ++ this.bindThread.start(); ++ return; ++ } ++ ++ try { ++ byte[] fileContent = Files.readAllBytes(this.regionFile); ++ ByteBuffer buffer = ByteBuffer.wrap(fileContent); ++ ++ long superBlock = buffer.getLong(); ++ if (superBlock != SUPERBLOCK) ++ throw new RuntimeException("Invalid superblock: " + superBlock + " file " + this.regionFile); ++ ++ byte version = buffer.get(); ++ if (version == 1 || version == 2) { ++ parseLinearV1(buffer); ++ } else if (version == 3) { ++ parseLinearV2(buffer); ++ } else { ++ throw new RuntimeException("Invalid version: " + version + " file " + this.regionFile); ++ } ++ ++ this.bindThread.start(); ++ } catch (IOException e) { ++ throw new RuntimeException("Failed to open region file " + this.regionFile, e); ++ } ++ } ++ ++ private void parseLinearV1(ByteBuffer buffer) throws IOException { ++ final int HEADER_SIZE = 32; ++ final int FOOTER_SIZE = 8; ++ ++ // Skip newestTimestamp (Long) + Compression level (Byte) + Chunk count (Short): Unused. ++ buffer.position(buffer.position() + 11); ++ ++ int dataCount = buffer.getInt(); ++ long fileLength = this.regionFile.toFile().length(); ++ if (fileLength != HEADER_SIZE + dataCount + FOOTER_SIZE) { ++ throw new IOException("Invalid file length: " + this.regionFile + " " + fileLength + " " + (HEADER_SIZE + dataCount + FOOTER_SIZE)); ++ } ++ ++ buffer.position(buffer.position() + 8); // Skip data hash (Long): Unused. ++ ++ byte[] rawCompressed = new byte[dataCount]; ++ buffer.get(rawCompressed); ++ ++ ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(rawCompressed); ++ ZstdInputStream zstdInputStream = new ZstdInputStream(byteArrayInputStream); ++ ByteBuffer decompressedBuffer = ByteBuffer.wrap(zstdInputStream.readAllBytes()); ++ ++ int[] starts = new int[1024]; ++ for (int i = 0; i < 1024; i++) { ++ starts[i] = decompressedBuffer.getInt(); ++ decompressedBuffer.getInt(); // Skip timestamps (Int): Unused. ++ } ++ ++ for (int i = 0; i < 1024; i++) { ++ if (starts[i] > 0) { ++ int size = starts[i]; ++ byte[] chunkData = new byte[size]; ++ decompressedBuffer.get(chunkData); ++ ++ int maxCompressedLength = this.compressor.maxCompressedLength(size); ++ byte[] compressed = new byte[maxCompressedLength]; ++ int compressedLength = this.compressor.compress(chunkData, 0, size, compressed, 0, maxCompressedLength); ++ byte[] finalCompressed = new byte[compressedLength]; ++ System.arraycopy(compressed, 0, finalCompressed, 0, compressedLength); ++ ++ this.buffer[i] = finalCompressed; ++ this.bufferUncompressedSize[i] = size; ++ this.chunkTimestamps[i] = getTimestamp(); // Use current timestamp as we don't have the original ++ } ++ } ++ } ++ ++ private void parseLinearV2(ByteBuffer buffer) throws IOException { ++ buffer.getLong(); // Skip newestTimestamp (Long) ++ gridSize = buffer.get(); ++ if (gridSize != 1 && gridSize != 2 && gridSize != 4 && gridSize != 8 && gridSize != 16 && gridSize != 32) ++ throw new RuntimeException("Invalid grid size: " + gridSize + " file " + this.regionFile); ++ bucketSize = 32 / gridSize; ++ ++ buffer.getInt(); // Skip region_x (Int) ++ buffer.getInt(); // Skip region_z (Int) ++ ++ boolean[] chunkExistenceBitmap = deserializeExistenceBitmap(buffer); ++ ++ while (true) { ++ byte featureNameLength = buffer.get(); ++ if (featureNameLength == 0) break; ++ byte[] featureNameBytes = new byte[featureNameLength]; ++ buffer.get(featureNameBytes); ++ String featureName = new String(featureNameBytes); ++ int featureValue = buffer.getInt(); ++ // System.out.println("NBT Feature: " + featureName + " = " + featureValue); ++ } ++ ++ int[] bucketSizes = new int[gridSize * gridSize]; ++ byte[] bucketCompressionLevels = new byte[gridSize * gridSize]; ++ long[] bucketHashes = new long[gridSize * gridSize]; ++ for (int i = 0; i < gridSize * gridSize; i++) { ++ bucketSizes[i] = buffer.getInt(); ++ bucketCompressionLevels[i] = buffer.get(); ++ bucketHashes[i] = buffer.getLong(); ++ } ++ ++ bucketBuffers = new byte[gridSize * gridSize][]; ++ for (int i = 0; i < gridSize * gridSize; i++) { ++ if (bucketSizes[i] > 0) { ++ bucketBuffers[i] = new byte[bucketSizes[i]]; ++ buffer.get(bucketBuffers[i]); ++ long rawHash = LongHashFunction.xx().hashBytes(bucketBuffers[i]); ++ if (rawHash != bucketHashes[i]) throw new IOException("Region file hash incorrect " + this.regionFile); ++ } ++ } ++ ++ long footerSuperBlock = buffer.getLong(); ++ if (footerSuperBlock != SUPERBLOCK) ++ throw new IOException("Footer superblock invalid " + this.regionFile); ++ } ++ ++ public LinearRegionFile(RegionStorageInfo storageKey, Path directory, Path path, boolean dsync, int compressionLevel) throws IOException { ++ this(storageKey, directory, path, RegionFileVersion.getCompressionFormat(), dsync, compressionLevel); ++ } ++ ++ public LinearRegionFile(RegionStorageInfo storageKey, Path path, Path directory, RegionFileVersion compressionFormat, boolean dsync, int compressionLevel) throws IOException { ++ Runnable flushCheck = () -> { ++ while (!close) { ++ synchronized (saveLock) { ++ if (markedToSave && activeSaveThreads < SAVE_THREAD_MAX_COUNT) { ++ activeSaveThreads++; ++ Runnable flushOperation = () -> { ++ try { ++ flush(); ++ } catch (IOException ex) { ++ LOGGER.error("Region file {} flush failed", this.regionFile.toAbsolutePath(), ex); ++ } finally { ++ synchronized (saveLock) { ++ activeSaveThreads--; ++ } ++ } ++ }; ++ ++ Thread saveThread = USE_VIRTUAL_THREAD ? ++ Thread.ofVirtual().name("Linear IO - " + LinearRegionFile.this.hashCode()).unstarted(flushOperation) : ++ Thread.ofPlatform().name("Linear IO - " + LinearRegionFile.this.hashCode()).unstarted(flushOperation); ++ saveThread.setPriority(Thread.NORM_PRIORITY - 3); ++ saveThread.start(); ++ } ++ } ++ LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(SAVE_DELAY_MS)); ++ } ++ }; ++ this.bindThread = USE_VIRTUAL_THREAD ? Thread.ofVirtual().unstarted(flushCheck) : Thread.ofPlatform().unstarted(flushCheck); ++ this.bindThread.setName("Linear IO Schedule - " + this.hashCode()); ++ this.regionFile = path; ++ this.compressionLevel = compressionLevel; ++ ++ this.compressor = LZ4Factory.fastestInstance().fastCompressor(); ++ this.decompressor = LZ4Factory.fastestInstance().fastDecompressor(); ++ } ++ ++ private synchronized void markToSave() { ++ synchronized(markedToSaveLock) { ++ markedToSave = true; ++ } ++ } ++ ++ private synchronized boolean isMarkedToSave() { ++ synchronized(markedToSaveLock) { ++ if(markedToSave) { ++ markedToSave = false; ++ return true; ++ } ++ return false; ++ } ++ } ++ ++ public static int SAVE_THREAD_MAX_COUNT = 6; ++ public static int SAVE_DELAY_MS = 100; ++ public static boolean USE_VIRTUAL_THREAD = true; ++ private static final Object saveLock = new Object(); ++ private static int activeSaveThreads = 0; ++ ++ /*public void run() { ++ while (!close) { ++ synchronized (saveLock) { ++ if (markedToSave && activeSaveThreads < SAVE_THREAD_MAX_COUNT) { ++ activeSaveThreads++; ++ Thread saveThread = new Thread(() -> { ++ try { ++ flush(); ++ } catch (IOException ex) { ++ LOGGER.error("Region file " + this.regionFile.toAbsolutePath() + " flush failed", ex); ++ } finally { ++ synchronized (saveLock) { ++ activeSaveThreads--; ++ } ++ } ++ }, "RegionFileFlush"); ++ saveThread.setPriority(Thread.NORM_PRIORITY - 3); ++ saveThread.start(); ++ } ++ } ++ LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(SAVE_DELAY_MS)); ++ } ++ }*/ ++ ++ public synchronized boolean doesChunkExist(ChunkPos pos) throws Exception { ++ openRegionFile(); ++ throw new Exception("doesChunkExist is a stub"); ++ } ++ ++ public synchronized void flush() throws IOException { ++ if(!isMarkedToSave()) return; ++ ++ openRegionFile(); ++ ++ long timestamp = getTimestamp(); ++ ++long writeStart = System.nanoTime(); ++ File tempFile = new File(regionFile.toString() + ".tmp"); ++ FileOutputStream fileStream = new FileOutputStream(tempFile); ++ DataOutputStream dataStream = new DataOutputStream(fileStream); ++ ++ dataStream.writeLong(SUPERBLOCK); ++ dataStream.writeByte(VERSION); ++ dataStream.writeLong(timestamp); ++ dataStream.writeByte(gridSize); ++ ++ String fileName = regionFile.getFileName().toString(); ++ String[] parts = fileName.split("\\."); ++ int regionX = 0; ++ int regionZ = 0; ++ try { ++ if (parts.length >= 4) { ++ regionX = Integer.parseInt(parts[1]); ++ regionZ = Integer.parseInt(parts[2]); ++ } else { ++ LOGGER.warn("Unexpected file name format: " + fileName); ++ } ++ } catch (NumberFormatException e) { ++ LOGGER.error("Failed to parse region coordinates from file name: " + fileName, e); ++ } ++ ++ dataStream.writeInt(regionX); ++ dataStream.writeInt(regionZ); ++ ++ boolean[] chunkExistenceBitmap = new boolean[1024]; ++ for (int i = 0; i < 1024; i++) { ++ chunkExistenceBitmap[i] = (this.bufferUncompressedSize[i] > 0); ++ } ++ writeSerializedExistenceBitmap(dataStream, chunkExistenceBitmap); ++ ++ writeNBTFeatures(dataStream); ++ ++ int bucketMisses = 0; ++ byte[][] buckets = new byte[gridSize * gridSize][]; ++ for (int bx = 0; bx < gridSize; bx++) { ++ for (int bz = 0; bz < gridSize; bz++) { ++ if (bucketBuffers != null && bucketBuffers[bx * gridSize + bz] != null) { ++ buckets[bx * gridSize + bz] = bucketBuffers[bx * gridSize + bz]; ++ continue; ++ } ++ bucketMisses++; ++ ++ ByteArrayOutputStream bucketStream = new ByteArrayOutputStream(); ++ ZstdOutputStream zstdStream = new ZstdOutputStream(bucketStream, this.compressionLevel); ++ DataOutputStream bucketDataStream = new DataOutputStream(zstdStream); ++ ++ boolean hasData = false; ++ for (int cx = 0; cx < 32 / gridSize; cx++) { ++ for (int cz = 0; cz < 32 / gridSize; cz++) { ++ int chunkIndex = (bx * 32 / gridSize + cx) + (bz * 32 / gridSize + cz) * 32; ++ if (this.bufferUncompressedSize[chunkIndex] > 0) { ++ hasData = true; ++ byte[] chunkData = new byte[this.bufferUncompressedSize[chunkIndex]]; ++ this.decompressor.decompress(this.buffer[chunkIndex], 0, chunkData, 0, this.bufferUncompressedSize[chunkIndex]); ++ bucketDataStream.writeInt(chunkData.length + 8); ++ bucketDataStream.writeLong(this.chunkTimestamps[chunkIndex]); ++ bucketDataStream.write(chunkData); ++ } else { ++ bucketDataStream.writeInt(0); ++ bucketDataStream.writeLong(this.chunkTimestamps[chunkIndex]); ++ } ++ } ++ } ++ bucketDataStream.close(); ++ ++ if (hasData) { ++ buckets[bx * gridSize + bz] = bucketStream.toByteArray(); ++ } ++ } ++ } ++ ++ for (int i = 0; i < gridSize * gridSize; i++) { ++ dataStream.writeInt(buckets[i] != null ? buckets[i].length : 0); ++ dataStream.writeByte(this.compressionLevel); ++ long rawHash = 0; ++ if (buckets[i] != null) { ++ rawHash = LongHashFunction.xx().hashBytes(buckets[i]); ++ } ++ dataStream.writeLong(rawHash); ++ } ++ ++ for (int i = 0; i < gridSize * gridSize; i++) { ++ if (buckets[i] != null) { ++ dataStream.write(buckets[i]); ++ } ++ } ++ ++ dataStream.writeLong(SUPERBLOCK); ++ ++ dataStream.flush(); ++ fileStream.getFD().sync(); ++ fileStream.getChannel().force(true); // Ensure atomicity on Btrfs ++ dataStream.close(); ++ ++ fileStream.close(); ++ Files.move(tempFile.toPath(), this.regionFile, StandardCopyOption.REPLACE_EXISTING); ++//System.out.println("writeStart REGION FILE FLUSH " + (System.nanoTime() - writeStart) + " misses: " + bucketMisses); ++ } ++ ++ private void writeNBTFeatures(DataOutputStream dataStream) throws IOException { ++ // writeNBTFeature(dataStream, "example", 1); ++ dataStream.writeByte(0); // End of NBT features ++ } ++ ++ private void writeNBTFeature(DataOutputStream dataStream, String featureName, int featureValue) throws IOException { ++ byte[] featureNameBytes = featureName.getBytes(); ++ dataStream.writeByte(featureNameBytes.length); ++ dataStream.write(featureNameBytes); ++ dataStream.writeInt(featureValue); ++ } ++ ++ public static final int MAX_CHUNK_SIZE = 500 * 1024 * 1024; // Abomination - prevent chunk dupe ++ ++ public synchronized void write(ChunkPos pos, ByteBuffer buffer) { ++ openRegionFile(); ++ openBucket(pos.x, pos.z); ++ try { ++ byte[] b = toByteArray(new ByteArrayInputStream(buffer.array())); ++ int uncompressedSize = b.length; ++ ++ if (uncompressedSize > MAX_CHUNK_SIZE) { ++ LOGGER.error("Chunk dupe attempt " + this.regionFile); ++ clear(pos); ++ } else { ++ int maxCompressedLength = this.compressor.maxCompressedLength(b.length); ++ byte[] compressed = new byte[maxCompressedLength]; ++ int compressedLength = this.compressor.compress(b, 0, b.length, compressed, 0, maxCompressedLength); ++ b = new byte[compressedLength]; ++ System.arraycopy(compressed, 0, b, 0, compressedLength); ++ ++ int index = getChunkIndex(pos.x, pos.z); ++ this.buffer[index] = b; ++ this.chunkTimestamps[index] = getTimestamp(); ++ this.bufferUncompressedSize[getChunkIndex(pos.x, pos.z)] = uncompressedSize; ++ } ++ } catch (IOException e) { ++ LOGGER.error("Chunk write IOException " + e + " " + this.regionFile); ++ } ++ markToSave(); ++ } ++ ++ public DataOutputStream getChunkDataOutputStream(ChunkPos pos) { ++ openRegionFile(); ++ openBucket(pos.x, pos.z); ++ return new DataOutputStream(new BufferedOutputStream(new LinearRegionFile.ChunkBuffer(pos))); ++ } ++ ++ @Override ++ public MoonriseRegionFileIO.RegionDataController.WriteData moonrise$startWrite(CompoundTag data, ChunkPos pos) throws IOException { ++ final DataOutputStream out = this.getChunkDataOutputStream(pos); ++ ++ return new ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData( ++ data, ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData.WriteResult.WRITE, ++ out, regionFile -> out.close() ++ ); ++ } ++ ++ private class ChunkBuffer extends ByteArrayOutputStream { ++ ++ private final ChunkPos pos; ++ ++ public ChunkBuffer(ChunkPos chunkcoordintpair) { ++ super(); ++ this.pos = chunkcoordintpair; ++ } ++ ++ public void close() throws IOException { ++ ByteBuffer bytebuffer = ByteBuffer.wrap(this.buf, 0, this.count); ++ LinearRegionFile.this.write(this.pos, bytebuffer); ++ } ++ } ++ ++ private byte[] toByteArray(InputStream in) throws IOException { ++ ByteArrayOutputStream out = new ByteArrayOutputStream(); ++ byte[] tempBuffer = new byte[4096]; ++ ++ int length; ++ while ((length = in.read(tempBuffer)) >= 0) { ++ out.write(tempBuffer, 0, length); ++ } ++ ++ return out.toByteArray(); ++ } ++ ++ @Nullable ++ public synchronized DataInputStream getChunkDataInputStream(ChunkPos pos) { ++ openRegionFile(); ++ openBucket(pos.x, pos.z); ++ ++ if(this.bufferUncompressedSize[getChunkIndex(pos.x, pos.z)] != 0) { ++ byte[] content = new byte[bufferUncompressedSize[getChunkIndex(pos.x, pos.z)]]; ++ this.decompressor.decompress(this.buffer[getChunkIndex(pos.x, pos.z)], 0, content, 0, bufferUncompressedSize[getChunkIndex(pos.x, pos.z)]); ++ return new DataInputStream(new ByteArrayInputStream(content)); ++ } ++ return null; ++ } ++ ++ public synchronized void clear(ChunkPos pos) { ++ openRegionFile(); ++ openBucket(pos.x, pos.z); ++ int i = getChunkIndex(pos.x, pos.z); ++ this.buffer[i] = null; ++ this.bufferUncompressedSize[i] = 0; ++ this.chunkTimestamps[i] = 0; ++ markToSave(); ++ } ++ ++ public synchronized boolean hasChunk(ChunkPos pos) { ++ openRegionFile(); ++ openBucket(pos.x, pos.z); ++ return this.bufferUncompressedSize[getChunkIndex(pos.x, pos.z)] > 0; ++ } ++ ++ public synchronized void close() throws IOException { ++ openRegionFile(); ++ close = true; ++ try { ++ flush(); ++ } catch(IOException e) { ++ throw new IOException("Region flush IOException " + e + " " + this.regionFile); ++ } ++ } ++ ++ private static int getChunkIndex(int x, int z) { ++ return (x & 31) + ((z & 31) << 5); ++ } ++ ++ private static int getTimestamp() { ++ return (int) (System.currentTimeMillis() / 1000L); ++ } ++ ++ public boolean recalculateHeader() { ++ return false; ++ } ++ ++ public void setOversized(int x, int z, boolean something) {} ++ ++ public CompoundTag getOversizedData(int x, int z) throws IOException { ++ throw new IOException("getOversizedData is a stub " + this.regionFile); ++ } ++ ++ public boolean isOversized(int x, int z) { ++ return false; ++ } ++ ++ public Path getPath() { ++ return this.regionFile; ++ } ++ ++ private boolean[] deserializeExistenceBitmap(ByteBuffer buffer) { ++ boolean[] result = new boolean[1024]; ++ for (int i = 0; i < 128; i++) { ++ byte b = buffer.get(); ++ for (int j = 0; j < 8; j++) { ++ result[i * 8 + j] = ((b >> (7 - j)) & 1) == 1; ++ } ++ } ++ return result; ++ } ++ ++ private void writeSerializedExistenceBitmap(DataOutputStream out, boolean[] bitmap) throws IOException { ++ for (int i = 0; i < 128; i++) { ++ byte b = 0; ++ for (int j = 0; j < 8; j++) { ++ if (bitmap[i * 8 + j]) { ++ b |= (1 << (7 - j)); ++ } ++ } ++ out.writeByte(b); ++ } ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java +index a814512fcfb85312474ae2c2c21443843bf57831..2e084a5b28cbe4737f48c25e10af589213525362 100644 +--- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java ++++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java +@@ -8,9 +8,9 @@ public interface ChunkSystemRegionFileStorage { + + public boolean moonrise$doesRegionFileNotExistNoIO(final int chunkX, final int chunkZ); + +- public RegionFile moonrise$getRegionFileIfLoaded(final int chunkX, final int chunkZ); ++ public abomination.IRegionFile moonrise$getRegionFileIfLoaded(final int chunkX, final int chunkZ); // Luminol - Configurable region file format + +- public RegionFile moonrise$getRegionFileIfExists(final int chunkX, final int chunkZ) throws IOException; ++ public abomination.IRegionFile moonrise$getRegionFileIfExists(final int chunkX, final int chunkZ) throws IOException; // Luminol - Configurable region file format + + public MoonriseRegionFileIO.RegionDataController.WriteData moonrise$startWrite( + final int chunkX, final int chunkZ, final CompoundTag compound +diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java +index 1acea58838f057ab87efd103cbecb6f5aeaef393..f9b89684208b9fe2c93f0368f7df5a400061f6c7 100644 +--- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java ++++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java +@@ -1462,7 +1462,7 @@ public final class MoonriseRegionFileIO { + + public static interface IORunnable { + +- public void run(final RegionFile regionFile) throws IOException; ++ public void run(final abomination.IRegionFile regionFile) throws IOException; + + } + } +diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java +index 51c126735ace8fdde89ad97b5cab62f244212db0..c7d4d944eb198ac53a3eeae717a25c7d5815c8c1 100644 +--- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java ++++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java +@@ -8,5 +8,5 @@ public interface ChunkSystemChunkBuffer { + + public void moonrise$setWriteOnClose(final boolean value); + +- public void moonrise$write(final RegionFile regionFile) throws IOException; ++ public void moonrise$write(final abomination.IRegionFile regionFile) throws IOException; // Luminol - Configurable region file format + } +diff --git a/src/main/java/me/earthme/luminol/utils/EnumRegionFormat.java b/src/main/java/me/earthme/luminol/utils/EnumRegionFormat.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5f6087a96b5bfa035922e7a245f35b180d7d23f0 +--- /dev/null ++++ b/src/main/java/me/earthme/luminol/utils/EnumRegionFormat.java +@@ -0,0 +1,40 @@ ++package me.earthme.luminol.utils; ++ ++import abomination.LinearRegionFile; ++import org.leavesmc.lumina.config.LuminaConfig; ++import net.minecraft.world.level.chunk.storage.RegionFile; ++import org.jetbrains.annotations.Nullable; ++ ++public enum EnumRegionFormat { ++ MCA("mca", "mca" , (info) -> new RegionFile(info.info(), info.filePath(), info.folder(), info.sync())), ++ LINEAR_V2("linear_v2", "linear" ,(info) -> new LinearRegionFile(info.info(), info.filePath(), info.folder(), info.sync(), LuminaConfig.configModule().misc.regionFormat.linearCompressionLevel)); ++ ++ private final String name; ++ private final String argument; ++ private final IRegionCreateFunction creator; ++ ++ EnumRegionFormat(String name, String argument, IRegionCreateFunction creator) { ++ this.name = name; ++ this.argument = argument; ++ this.creator = creator; ++ } ++ ++ @Nullable ++ public static EnumRegionFormat fromString(String string) { ++ for (EnumRegionFormat format : values()) { ++ if (format.name.equalsIgnoreCase(string)) { ++ return format; ++ } ++ } ++ ++ return null; ++ } ++ ++ public IRegionCreateFunction getCreator() { ++ return this.creator; ++ } ++ ++ public String getArgument() { ++ return this.argument; ++ } ++} +diff --git a/src/main/java/me/earthme/luminol/utils/IRegionCreateFunction.java b/src/main/java/me/earthme/luminol/utils/IRegionCreateFunction.java +new file mode 100644 +index 0000000000000000000000000000000000000000..fb87ef13803122aa5a2e7f0c578de359140d4f31 +--- /dev/null ++++ b/src/main/java/me/earthme/luminol/utils/IRegionCreateFunction.java +@@ -0,0 +1,9 @@ ++package me.earthme.luminol.utils; ++ ++import abomination.IRegionFile; ++ ++import java.io.IOException; ++ ++public interface IRegionCreateFunction { ++ IRegionFile create(RegionCreatorInfo info) throws IOException; ++} +diff --git a/src/main/java/me/earthme/luminol/utils/RegionCreatorInfo.java b/src/main/java/me/earthme/luminol/utils/RegionCreatorInfo.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5af068489646ed70330d8c6242ec88f536c4c289 +--- /dev/null ++++ b/src/main/java/me/earthme/luminol/utils/RegionCreatorInfo.java +@@ -0,0 +1,7 @@ ++package me.earthme.luminol.utils; ++ ++import net.minecraft.world.level.chunk.storage.RegionStorageInfo; ++ ++import java.nio.file.Path; ++ ++public record RegionCreatorInfo (RegionStorageInfo info, Path filePath, Path folder, boolean sync) {} +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 1676c4abb3f6f71bb7b25351aa58b4e127786fca..84e13f30a65fc3963703be0746a4ebd15cc990d4 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1036,10 +1036,10 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop> progressMap = Reference2FloatMaps.synchronize(new Reference2FloatOpenHashMap()); + volatile Component status = Component.translatable("optimizeWorld.stage.counting"); +- static final Pattern REGEX = Pattern.compile("^r\\.(-?[0-9]+)\\.(-?[0-9]+)\\.mca$"); ++ static final Pattern REGEX = Pattern.compile("^r\\.(-?[0-9]+)\\.(-?[0-9]+)\\\\"+ net.minecraft.world.level.chunk.storage.RegionFileStorage.getExtensionName() +"$"); // Luminol - Configurable region file format + final DimensionDataStorage overworldDataStorage; + + public WorldUpgrader(LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, RegistryAccess dynamicRegistryManager, boolean eraseCache, boolean recreateRegionFiles) { +@@ -399,7 +399,7 @@ public class WorldUpgrader implements AutoCloseable { + + private static List getAllChunkPositions(RegionStorageInfo key, Path regionDirectory) { + File[] afile = regionDirectory.toFile().listFiles((file, s) -> { +- return s.endsWith(".mca"); ++ return s.endsWith(net.minecraft.world.level.chunk.storage.RegionFileStorage.getExtensionName()); // Luminol - Configurable region file format + }); + + if (afile == null) { +@@ -419,7 +419,7 @@ public class WorldUpgrader implements AutoCloseable { + List list1 = Lists.newArrayList(); + + try { +- RegionFile regionfile = new RegionFile(key, file.toPath(), regionDirectory, true); ++ abomination.IRegionFile regionfile = net.minecraft.world.level.chunk.storage.RegionFileStorage.createNew(key, file.toPath(), regionDirectory, true); + + try { + for (int i1 = 0; i1 < 32; ++i1) { +@@ -482,7 +482,7 @@ public class WorldUpgrader implements AutoCloseable { + + protected abstract boolean tryProcessOnePosition(T storage, ChunkPos chunkPos, ResourceKey worldKey); + +- private void onFileFinished(RegionFile regionFile) { ++ private void onFileFinished(abomination.IRegionFile regionFile) { + if (WorldUpgrader.this.recreateRegionFiles) { + if (this.previousWriteFuture != null) { + this.previousWriteFuture.join(); +@@ -507,7 +507,7 @@ public class WorldUpgrader implements AutoCloseable { + } + } + +- static record FileToUpgrade(RegionFile file, List chunksToUpgrade) { ++ static record FileToUpgrade(abomination.IRegionFile file, List chunksToUpgrade) { + + } + +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java +index 16f07007a0f73ec0c6f421c9b082518e87e8cc7b..fc69834e18e0860750d878e1361722fc38b513f8 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java +@@ -28,7 +28,7 @@ import net.minecraft.nbt.NbtIo; // Paper + import net.minecraft.world.level.ChunkPos; + import org.slf4j.Logger; + +-public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemRegionFile { // Paper - rewrite chunk system ++public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemRegionFile , abomination.IRegionFile{ // Paper - rewrite chunk system // Luminol - Configurable region file format + + private static final Logger LOGGER = LogUtils.getLogger(); + private static final int SECTOR_BYTES = 4096; +@@ -129,7 +129,7 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche + } + + // note: only call for CHUNK regionfiles +- boolean recalculateHeader() throws IOException { ++ public boolean recalculateHeader() throws IOException { // Luminol - Configurable region file format // Luminol - Configurable region file format + if (!this.canRecalcHeader) { + return false; + } +@@ -810,7 +810,7 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche + } + } + +- protected synchronized void write(ChunkPos pos, ByteBuffer buf) throws IOException { ++ public synchronized void write(ChunkPos pos, ByteBuffer buf) throws IOException { // Luminol - Configurable region file format + int i = RegionFile.getOffsetIndex(pos); + int j = this.offsets.get(i); + int k = RegionFile.getSectorNumber(j); +@@ -952,10 +952,10 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche + private static int getChunkIndex(int x, int z) { + return (x & 31) + (z & 31) * 32; + } +- synchronized boolean isOversized(int x, int z) { ++ public synchronized boolean isOversized(int x, int z) { // Luminol - Configurable region file format + return this.oversized[getChunkIndex(x, z)] == 1; + } +- synchronized void setOversized(int x, int z, boolean oversized) throws IOException { ++ public synchronized void setOversized(int x, int z, boolean oversized) throws IOException { // Luminol - Configurable region file format + final int offset = getChunkIndex(x, z); + boolean previous = this.oversized[offset] == 1; + this.oversized[offset] = (byte) (oversized ? 1 : 0); +@@ -994,7 +994,7 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche + return this.path.getParent().resolve(this.path.getFileName().toString().replaceAll("\\.mca$", "") + "_oversized_" + x + "_" + z + ".nbt"); + } + +- synchronized CompoundTag getOversizedData(int x, int z) throws IOException { ++ public synchronized CompoundTag getOversizedData(int x, int z) throws IOException { // Luminol - Configurable region file format + Path file = getOversizedFile(x, z); + try (DataInputStream out = new DataInputStream(new java.io.BufferedInputStream(new InflaterInputStream(Files.newInputStream(file))))) { + return NbtIo.read((java.io.DataInput) out); +@@ -1021,7 +1021,7 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche + } + + @Override +- public final void moonrise$write(final RegionFile regionFile) throws IOException { ++ public final void moonrise$write(final abomination.IRegionFile regionFile) throws IOException { // Luminol - Configurable region file format + regionFile.write(this.pos, ByteBuffer.wrap(this.buf, 0, this.count)); + } + // Paper end - rewrite chunk system +diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +index e40665cead218502b44dd49051a53326ed94f061..912e99d71c5221e853a120ec3ab4bdc68002a9db 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java ++++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +@@ -23,7 +23,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + + public static final String ANVIL_EXTENSION = ".mca"; + private static final int MAX_CACHE_SIZE = 256; +- public final Long2ObjectLinkedOpenHashMap regionCache = new Long2ObjectLinkedOpenHashMap(); ++ public final Long2ObjectLinkedOpenHashMap regionCache = new Long2ObjectLinkedOpenHashMap(); // Luminol - Configurable region file format + private final RegionStorageInfo info; + private final Path folder; + private final boolean sync; +@@ -33,8 +33,27 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + private static final int MAX_NON_EXISTING_CACHE = 1024 * 4; + private final it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet nonExistingRegionFiles = new it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet(); + private static String getRegionFileName(final int chunkX, final int chunkZ) { +- return "r." + (chunkX >> REGION_SHIFT) + "." + (chunkZ >> REGION_SHIFT) + ".mca"; ++ return "r." + (chunkX >> REGION_SHIFT) + "." + (chunkZ >> REGION_SHIFT) + getExtensionName(); // Luminol - Configurable region file format + } ++ // Luminol start - Configurable region file format ++ public static abomination.IRegionFile createNew(RegionStorageInfo info, Path filePath, Path folder, boolean sync) throws IOException{ ++ final me.earthme.luminol.utils.EnumRegionFormat regionFormat = org.leavesmc.lumina.config.LuminaConfig.configModule().misc.regionFormat.regionFormat; ++ final String fullFileName = filePath.getFileName().toString(); ++ final String[] fullNameSplit = fullFileName.split("\\."); ++ final String extensionName = fullNameSplit[fullNameSplit.length - 1]; ++ ++ if (!regionFormat.getArgument().equalsIgnoreCase(extensionName)) { ++ net.minecraft.server.MinecraftServer.setFatalException(new RuntimeException("Invalid region file format: " + extensionName + " expected " + regionFormat.getArgument())); ++ throw new IOException("Invalid region file format: " + extensionName + " expected " + regionFormat.getArgument()); ++ } ++ ++ return regionFormat.getCreator().create(new me.earthme.luminol.utils.RegionCreatorInfo(info, filePath, folder, sync)); ++ } ++ ++ public static String getExtensionName() { ++ return "." + org.leavesmc.lumina.config.LuminaConfig.configModule().misc.regionFormat.regionFormat.getArgument(); ++ } ++ // Luminol end + + private boolean doesRegionFilePossiblyExist(final long position) { + synchronized (this.nonExistingRegionFiles) { +@@ -68,15 +87,15 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + } + + @Override +- public synchronized final RegionFile moonrise$getRegionFileIfLoaded(final int chunkX, final int chunkZ) { ++ public synchronized final abomination.IRegionFile moonrise$getRegionFileIfLoaded(final int chunkX, final int chunkZ) { // Luminol - Configurable region file format + return this.regionCache.getAndMoveToFirst(ChunkPos.asLong(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT)); + } + + @Override +- public synchronized final RegionFile moonrise$getRegionFileIfExists(final int chunkX, final int chunkZ) throws IOException { ++ public synchronized final abomination.IRegionFile moonrise$getRegionFileIfExists(final int chunkX, final int chunkZ) throws IOException { // Luminol - Configurable region file format + final long key = ChunkPos.asLong(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT); + +- RegionFile ret = this.regionCache.getAndMoveToFirst(key); ++ abomination.IRegionFile ret = this.regionCache.getAndMoveToFirst(key); // Luminol - Configurable region file format + if (ret != null) { + return ret; + } +@@ -100,7 +119,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + + FileUtil.createDirectoriesSafe(this.folder); + +- ret = new RegionFile(this.info, regionPath, this.folder, this.sync); ++ ret = this.createNew(this.info, regionPath, this.folder, this.sync); // Luminol - Configurable region file format + + this.regionCache.putAndMoveToFirst(key, ret); + +@@ -119,7 +138,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + } + + final ChunkPos pos = new ChunkPos(chunkX, chunkZ); +- final RegionFile regionFile = this.getRegionFile(pos); ++ final abomination.IRegionFile regionFile = this.getRegionFile(pos); // Luminol - Configurable region file format + + // note: not required to keep regionfile loaded after this call, as the write param takes a regionfile as input + // (and, the regionfile parameter is unused for writing until the write call) +@@ -153,7 +172,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + ) throws IOException { + final ChunkPos pos = new ChunkPos(chunkX, chunkZ); + if (writeData.result() == ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData.WriteResult.DELETE) { +- final RegionFile regionFile = this.moonrise$getRegionFileIfExists(chunkX, chunkZ); ++ final abomination.IRegionFile regionFile = this.moonrise$getRegionFileIfExists(chunkX, chunkZ); // Luminol - Configurable region file format + if (regionFile != null) { + regionFile.clear(pos); + } // else: didn't exist +@@ -168,7 +187,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + public final ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData moonrise$readData( + final int chunkX, final int chunkZ + ) throws IOException { +- final RegionFile regionFile = this.moonrise$getRegionFileIfExists(chunkX, chunkZ); ++ final abomination.IRegionFile regionFile = this.moonrise$getRegionFileIfExists(chunkX, chunkZ); // Luminol - Configurable region file format + + final DataInputStream input = regionFile == null ? null : regionFile.getChunkDataInputStream(new ChunkPos(chunkX, chunkZ)); + +@@ -221,7 +240,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + @Nullable + public static ChunkPos getRegionFileCoordinates(Path file) { + String fileName = file.getFileName().toString(); +- if (!fileName.startsWith("r.") || !fileName.endsWith(".mca")) { ++ if (!fileName.startsWith("r.") || !fileName.endsWith(getExtensionName())) { // Luminol - Configurable region file format + return null; + } + +@@ -250,12 +269,12 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + } + + // Paper start - rewrite chunk system +- public RegionFile getRegionFile(ChunkPos chunkcoordintpair) throws IOException { ++ public abomination.IRegionFile getRegionFile(ChunkPos chunkcoordintpair) throws IOException { // Luminol - Configurable region file format + return this.getRegionFile(chunkcoordintpair, false); + } + // Paper end - rewrite chunk system + +- public RegionFile getRegionFile(ChunkPos chunkcoordintpair, boolean existingOnly) throws IOException { // CraftBukkit // Paper - public ++ public abomination.IRegionFile getRegionFile(ChunkPos chunkcoordintpair, boolean existingOnly) throws IOException { // CraftBukkit // Paper - public // Luminol - Configurable region file format + // Paper start - rewrite chunk system + if (existingOnly) { + return this.moonrise$getRegionFileIfExists(chunkcoordintpair.x, chunkcoordintpair.z); +@@ -263,7 +282,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + synchronized (this) { + final long key = ChunkPos.asLong(chunkcoordintpair.x >> REGION_SHIFT, chunkcoordintpair.z >> REGION_SHIFT); + +- RegionFile ret = this.regionCache.getAndMoveToFirst(key); ++ abomination.IRegionFile ret = this.regionCache.getAndMoveToFirst(key); // Luminol - Configurable region file format + if (ret != null) { + return ret; + } +@@ -278,7 +297,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + + FileUtil.createDirectoriesSafe(this.folder); + +- ret = new RegionFile(this.info, regionPath, this.folder, this.sync); ++ ret = this.createNew(this.info, regionPath, this.folder, this.sync); + + this.regionCache.putAndMoveToFirst(key, ret); + +@@ -292,7 +311,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + org.apache.logging.log4j.LogManager.getLogger().fatal(msg + " (" + file.toString().replaceAll(".+[\\\\/]", "") + " - " + x + "," + z + ") Go clean it up to remove this message. /minecraft:tp " + (x<<4)+" 128 "+(z<<4) + " - DO NOT REPORT THIS TO PAPER - You may ask for help on Discord, but do not file an issue. These error messages can not be removed."); + } + +- private static CompoundTag readOversizedChunk(RegionFile regionfile, ChunkPos chunkCoordinate) throws IOException { ++ private static CompoundTag readOversizedChunk(abomination.IRegionFile regionfile, ChunkPos chunkCoordinate) throws IOException { // Luminol - Configurable region file format + synchronized (regionfile) { + try (DataInputStream datainputstream = regionfile.getChunkDataInputStream(chunkCoordinate)) { + CompoundTag oversizedData = regionfile.getOversizedData(chunkCoordinate.x, chunkCoordinate.z); +@@ -327,7 +346,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + @Nullable + public CompoundTag read(ChunkPos pos) throws IOException { + // CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing +- RegionFile regionfile = this.getRegionFile(pos, true); ++ abomination.IRegionFile regionfile = this.getRegionFile(pos, true); // Luminol - Configurable region file format + if (regionfile == null) { + return null; + } +@@ -391,7 +410,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + + public void scanChunk(ChunkPos chunkPos, StreamTagVisitor scanner) throws IOException { + // CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing +- RegionFile regionfile = this.getRegionFile(chunkPos, true); ++ abomination.IRegionFile regionfile = this.getRegionFile(chunkPos, true); // Luminol - Configurable region file format + if (regionfile == null) { + return; + } +@@ -421,7 +440,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + } + + public void write(ChunkPos pos, @Nullable CompoundTag nbt) throws IOException { // Paper - rewrite chunk system - public +- RegionFile regionfile = this.getRegionFile(pos, nbt == null); // CraftBukkit // Paper - rewrite chunk system ++ abomination.IRegionFile regionfile = this.getRegionFile(pos, nbt == null); // CraftBukkit // Paper - rewrite chunk system // Luminol - Configurable region file format + // Paper start - rewrite chunk system + if (regionfile == null) { + // if the RegionFile doesn't exist, no point in deleting from it +@@ -465,7 +484,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + // Paper start - rewrite chunk system + synchronized (this) { + final ExceptionCollector exceptionCollector = new ExceptionCollector<>(); +- for (final RegionFile regionFile : this.regionCache.values()) { ++ for (final abomination.IRegionFile regionFile : this.regionCache.values()) { // Luminol - Configurable region file format + try { + regionFile.close(); + } catch (final IOException ex) { +@@ -482,7 +501,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + // Paper start - rewrite chunk system + synchronized (this) { + final ExceptionCollector exceptionCollector = new ExceptionCollector<>(); +- for (final RegionFile regionFile : this.regionCache.values()) { ++ for (final abomination.IRegionFile regionFile : this.regionCache.values()) { // Luminol - Configurable region file format + try { + regionFile.flush(); + } catch (final IOException ex) { diff --git a/patches/server/0007-Luminol-Try-fixing-folia-spector-teleportation.patch b/patches/server/0007-Luminol-Try-fixing-folia-spector-teleportation.patch index 41ff8ec..c8ba451 100644 --- a/patches/server/0007-Luminol-Try-fixing-folia-spector-teleportation.patch +++ b/patches/server/0007-Luminol-Try-fixing-folia-spector-teleportation.patch @@ -28,7 +28,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 23f852ede94bce4d000c8fcaa8fba5d4800b533c..93d5ad158635a67dfc5966d9482a3432f68b36bc 100644 +index 23f852ede94bce4d000c8fcaa8fba5d4800b533c..22165b6fca47d4ac43d7f3c70915d6bfe8b78ba3 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java @@ -1161,6 +1161,11 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple @@ -36,7 +36,7 @@ index 23f852ede94bce4d000c8fcaa8fba5d4800b533c..93d5ad158635a67dfc5966d9482a3432 Entity entity = this.getCamera(); + //Luminol start - Fix folia spector teleportation -+ if (!io.papermc.paper.util.TickThread.isTickThreadFor(entity) && org.leavesmc.lumina.config.LuminaConfig.configModule().fix.fixFoliaSpectorTeleport){ ++ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(entity) && org.leavesmc.lumina.config.LuminaConfig.configModule().fix.fixFoliaSpectorTeleport){ + this.setCamera(this); + } + //Luminol end diff --git a/patches/server/0008-Luminol-Teleport-async-if-entity-was-moving-to-anoth.patch b/patches/server/0008-Luminol-Teleport-async-if-entity-was-moving-to-anoth.patch index 5c9d71c..c9dacfe 100644 --- a/patches/server/0008-Luminol-Teleport-async-if-entity-was-moving-to-anoth.patch +++ b/patches/server/0008-Luminol-Teleport-async-if-entity-was-moving-to-anoth.patch @@ -29,7 +29,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 94f2610e1f2cce41d998bb9c92abbb38d9811f56..f324fc896b3dddb8a473323c82a9e9c639a99e00 100644 +index 94f2610e1f2cce41d998bb9c92abbb38d9811f56..de5c353c918a3e80ec5f5e783a2d9ef60b8d078d 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -1180,6 +1180,9 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess @@ -47,13 +47,13 @@ index 94f2610e1f2cce41d998bb9c92abbb38d9811f56..f324fc896b3dddb8a473323c82a9e9c6 this.moveVector = movement; } + //Luminol start - Fix high position moving -+ if (org.leavesmc.lumina.config.LuminaConfig.configModule().fix.fixFoliaEntityMoving.enabled && io.papermc.paper.util.TickThread.isTickThread()){ //Except the threads because it may be called by the chunk system worker thread ++ if (org.leavesmc.lumina.config.LuminaConfig.configModule().fix.fixFoliaEntityMoving.enabled && ca.spottedleaf.moonrise.common.util.TickThread.isTickThread()){ //Except the threads because it may be called by the chunk system worker thread + var finalPosition = movement.add(this.position); + if (this.preventMoving || Double.isNaN(finalPosition.x) || Double.isNaN(finalPosition.y) || Double.isNaN(finalPosition.z)){ + return; + } + -+ if (!io.papermc.paper.util.TickThread.isTickThreadFor(((ServerLevel) this.level),finalPosition)){ ++ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(((ServerLevel) this.level),finalPosition)){ + this.preventMoving = true; + this.teleportAsync( + (ServerLevel) this.level(), diff --git a/patches/server/0009-Luminol-Try-fixing-folia-off-region-POI-accessing-is.patch b/patches/server/0009-Luminol-Try-fixing-folia-off-region-POI-accessing-is.patch index f068dfd..0da64f4 100644 --- a/patches/server/0009-Luminol-Try-fixing-folia-off-region-POI-accessing-is.patch +++ b/patches/server/0009-Luminol-Try-fixing-folia-off-region-POI-accessing-is.patch @@ -28,7 +28,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/GoToPotentialJobSite.java b/src/main/java/net/minecraft/world/entity/ai/behavior/GoToPotentialJobSite.java -index aac1df9e2c0652bc6348af0404deba7465f82d42..422a30808b5cef371a8de810ce01644b408dfd57 100644 +index aac1df9e2c0652bc6348af0404deba7465f82d42..2eefec35f7e261249773e8d420028c1161a5ec0c 100644 --- a/src/main/java/net/minecraft/world/entity/ai/behavior/GoToPotentialJobSite.java +++ b/src/main/java/net/minecraft/world/entity/ai/behavior/GoToPotentialJobSite.java @@ -49,10 +49,17 @@ public class GoToPotentialJobSite extends Behavior { @@ -42,7 +42,7 @@ index aac1df9e2c0652bc6348af0404deba7465f82d42..422a30808b5cef371a8de810ce01644b } - + }; -+ if (!io.papermc.paper.util.TickThread.isTickThreadFor(serverLevel2, blockPos) && org.leavesmc.lumina.config.LuminaConfig.configModule().fix.fixFoliaPoiAccessOffRegion) ++ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(serverLevel2, blockPos) && org.leavesmc.lumina.config.LuminaConfig.configModule().fix.fixFoliaPoiAccessOffRegion) + io.papermc.paper.threadedregions.RegionizedServer.getInstance().taskQueue.queueTickTaskQueue(serverLevel2, blockPos.getX() >> 4, blockPos.getZ() >> 4, scheduledRelease); + else + scheduledRelease.run(); @@ -51,7 +51,7 @@ index aac1df9e2c0652bc6348af0404deba7465f82d42..422a30808b5cef371a8de810ce01644b } }); diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/YieldJobSite.java b/src/main/java/net/minecraft/world/entity/ai/behavior/YieldJobSite.java -index d1a9b62d3304916275dd6b4c4e783cf1563b5e21..bfa290c1e5ff0109ba96f49c19d8ce09db6a7e23 100644 +index d1a9b62d3304916275dd6b4c4e783cf1563b5e21..3a8f214aad0654cae763a473190defa5bf641b3b 100644 --- a/src/main/java/net/minecraft/world/entity/ai/behavior/YieldJobSite.java +++ b/src/main/java/net/minecraft/world/entity/ai/behavior/YieldJobSite.java @@ -6,6 +6,7 @@ import net.minecraft.core.BlockPos; @@ -70,12 +70,12 @@ index d1a9b62d3304916275dd6b4c4e783cf1563b5e21..bfa290c1e5ff0109ba96f49c19d8ce09 + final GlobalPos globalPos = context.get(potentialJobSite); // Luminol start - Try fixing off main POI accessing + final ServerLevel targetLevel = net.minecraft.server.MinecraftServer.getServer().getLevel(globalPos.dimension()); //Luminol - Try fixing off main POI accessing + BlockPos blockPos = globalPos.pos(); // Luminol end - Try fixing off main POI accessing -+ if (!io.papermc.paper.util.TickThread.isTickThreadFor(targetLevel, blockPos) && org.leavesmc.lumina.config.LuminaConfig.configModule().fix.fixFoliaPoiAccessOffRegion) return true; // Luminol - Try fixing off main POI accessing ++ if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(targetLevel, blockPos) && org.leavesmc.lumina.config.LuminaConfig.configModule().fix.fixFoliaPoiAccessOffRegion) return true; // Luminol - Try fixing off main POI accessing Optional> optional = world.getPoiManager().getType(blockPos); if (optional.isEmpty()) { return true; diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java -index f3f98e6276dda3bc4f290fc2d80569f7e1e7ef66..6ade40d4b4f24b31d16c625944656d48a276a077 100644 +index f3f98e6276dda3bc4f290fc2d80569f7e1e7ef66..96de137af5242a94dc158d14eaa0a235cfd101a4 100644 --- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java +++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java @@ -212,7 +212,7 @@ public class PoiManager extends SectionStorage im @@ -83,7 +83,7 @@ index f3f98e6276dda3bc4f290fc2d80569f7e1e7ef66..6ade40d4b4f24b31d16c625944656d48 public Stream getInSquare(Predicate> typePredicate, BlockPos pos, int radius, PoiManager.Occupancy occupationStatus) { int i = Math.floorDiv(radius, 16) + 1; - return ChunkPos.rangeClosed(new ChunkPos(pos), i).flatMap(chunkPos -> this.getInChunk(typePredicate, chunkPos, occupationStatus)).filter(poi -> { -+ return ChunkPos.rangeClosed(new ChunkPos(pos), i).filter(cpos -> org.leavesmc.lumina.config.LuminaConfig.configModule().fix.fixFoliaPoiAccessOffRegion ? io.papermc.paper.util.TickThread.isTickThreadFor(this.world,cpos) : true).flatMap(chunkPos -> this.getInChunk(typePredicate, chunkPos, occupationStatus)).filter(poi -> { // Luminol - Fix off region POI access ++ return ChunkPos.rangeClosed(new ChunkPos(pos), i).filter(cpos -> org.leavesmc.lumina.config.LuminaConfig.configModule().fix.fixFoliaPoiAccessOffRegion ? ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this.world,cpos) : true).flatMap(chunkPos -> this.getInChunk(typePredicate, chunkPos, occupationStatus)).filter(poi -> { // Luminol - Fix off region POI access BlockPos blockPos2 = poi.getPos(); return Math.abs(blockPos2.getX() - pos.getX()) <= radius && Math.abs(blockPos2.getZ() - pos.getZ()) <= radius; }); diff --git a/patches/server/0010-Kaiiju-Don-t-pathfind-outside-region.patch b/patches/server/0010-Kaiiju-Don-t-pathfind-outside-region.patch index d5a37e7..1239ca5 100644 --- a/patches/server/0010-Kaiiju-Don-t-pathfind-outside-region.patch +++ b/patches/server/0010-Kaiiju-Don-t-pathfind-outside-region.patch @@ -7,14 +7,14 @@ This patch is Powered by Kaiiju(https://github.com/KaiijuMC/Kaiiju) License: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html) (https://github.com/KaiijuMC/Kaiiju?tab=GPL-3.0-1-ov-file) diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java b/src/main/java/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java -index 2a7a26ca447cc78f24e61a2bf557411c31eb16b2..28602455d659dfac4653ab5b7bf4139f65c190ad 100644 +index 2a7a26ca447cc78f24e61a2bf557411c31eb16b2..361e79e13dacc765283b2736324f365400e9860e 100644 --- a/src/main/java/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java +++ b/src/main/java/net/minecraft/world/entity/ai/behavior/MoveToTargetSink.java @@ -120,7 +120,9 @@ public class MoveToTargetSink extends Behavior { private boolean tryComputePath(Mob entity, WalkTarget walkTarget, long time) { BlockPos blockPos = walkTarget.getTarget().currentBlockPosition(); -+ if (io.papermc.paper.util.TickThread.isTickThreadFor((ServerLevel) entity.level(), blockPos)) // Kaiiju - Don't pathfind outside region ++ if (ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor((ServerLevel) entity.level(), blockPos)) // Kaiiju - Don't pathfind outside region this.path = entity.getNavigation().createPath(blockPos, 0); + else this.path = null; // Kaiiju - Don't pathfind outside region this.speedModifier = walkTarget.getSpeedModifier(); diff --git a/patches/server/0011-Kaiiju-Vanilla-end-portal-teleportation.patch b/patches/server/0011-Kaiiju-Vanilla-end-portal-teleportation.patch index 75388d5..8fd49cb 100644 --- a/patches/server/0011-Kaiiju-Vanilla-end-portal-teleportation.patch +++ b/patches/server/0011-Kaiiju-Vanilla-end-portal-teleportation.patch @@ -7,7 +7,7 @@ This patch is Powered by Kaiiju(https://github.com/KaiijuMC/Kaiiju) License: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html) (https://github.com/KaiijuMC/Kaiiju?tab=GPL-3.0-1-ov-file) diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index f324fc896b3dddb8a473323c82a9e9c639a99e00..a4d3557bd654f15469d8d460ff1ae920903ad14b 100644 +index de5c353c918a3e80ec5f5e783a2d9ef60b8d078d..ba812363d1524406f034ac814e167858f128b09f 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -4457,13 +4457,18 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess @@ -36,7 +36,7 @@ index f324fc896b3dddb8a473323c82a9e9c639a99e00..a4d3557bd654f15469d8d460ff1ae920 return false; } + // Kaiiju start - sync end platform spawning & entity teleportation -+ final java.util.function.Consumer tpComplete = type == PortalType.END && destination.getTypeKey() == LevelStem.END ? ++ final java.util.function.Consumer tpComplete = type == PortalType.END && destination.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.END ? + e -> net.minecraft.world.level.levelgen.feature.EndPlatformFeature.createEndPlatform(destination, ServerLevel.END_SPAWN_POINT.below(), true, null) : teleportComplete; + // Kaiiju end diff --git a/patches/server/0017-Pufferfish-Reduce-projectile-chunk-loading.patch b/patches/server/0017-Pufferfish-Reduce-projectile-chunk-loading.patch index d768ae8..3ca7c86 100644 --- a/patches/server/0017-Pufferfish-Reduce-projectile-chunk-loading.patch +++ b/patches/server/0017-Pufferfish-Reduce-projectile-chunk-loading.patch @@ -7,7 +7,7 @@ This patch is Powered by Pufferfish(https://github.com/pufferfish-gg/Pufferfish) License: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html) (https://github.com/pufferfish-gg/Pufferfish?tab=GPL-3.0-1-ov-file) diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -index a6bcf7b57b804af74f75c0b24ff48ee2714c3b73..2b4f2acafac716d410e62024067554e62eb22648 100644 +index a6bcf7b57b804af74f75c0b24ff48ee2714c3b73..7f1bf03487832753d7261a0e7a258925e6d07310 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java +++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java @@ -58,6 +58,36 @@ public abstract class Projectile extends Entity implements TraceableEntity { @@ -29,7 +29,7 @@ index a6bcf7b57b804af74f75c0b24ff48ee2714c3b73..2b4f2acafac716d410e62024067554e6 + int previousX = Mth.floor(this.getX()) >> 4, previousZ = Mth.floor(this.getZ()) >> 4; + int newX = Mth.floor(x) >> 4, newZ = Mth.floor(z) >> 4; + if (previousX != newX || previousZ != newZ) { -+ boolean isLoaded = ((net.minecraft.server.level.ServerChunkCache) this.level().getChunkSource()).getChunkAtIfLoadedMainThread(newX, newZ) != null; ++ boolean isLoaded = ((net.minecraft.server.level.ServerChunkCache) this.level().getChunkSource()).getChunkAtIfCachedImmediately(newX, newZ) != null; + if (!isLoaded) { + if (Projectile.loadedThisTick > org.leavesmc.lumina.config.LuminaConfig.configModule().performance.projectileChunkReduce.maxProjectileLoadsPerTick) { + if (++this.loadedLifetime > org.leavesmc.lumina.config.LuminaConfig.configModule().performance.projectileChunkReduce.maxProjectileLoadsPerProjectile) { diff --git a/patches/server/0038-Leaves-Protocol-core.patch b/patches/server/0038-Leaves-Protocol-core.patch index ed0655f..99d9496 100644 --- a/patches/server/0038-Leaves-Protocol-core.patch +++ b/patches/server/0038-Leaves-Protocol-core.patch @@ -48,6 +48,19 @@ index 7655987d061bdb2839b30f926efb034046feaea3..7ae6b2bb868cc3d391bae87fa5e141cf } }; } +diff --git a/src/main/java/net/minecraft/resources/ResourceLocation.java b/src/main/java/net/minecraft/resources/ResourceLocation.java +index 1967c43ee3a12e63365cc40ee6565307e2fd73cf..823653f3e70d314b42d1da3bebc1aa06381f7ede 100644 +--- a/src/main/java/net/minecraft/resources/ResourceLocation.java ++++ b/src/main/java/net/minecraft/resources/ResourceLocation.java +@@ -36,7 +36,7 @@ public final class ResourceLocation implements Comparable { + private final String namespace; + private final String path; + +- private ResourceLocation(String namespace, String path) { ++ public ResourceLocation(String namespace, String path) { // Lumina - private -> public + assert isValidNamespace(namespace); + + assert isValidPath(path); diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java index b6c0f4bc58cb1cc3ab39ac53e92f2ed6caf2e28a..52a6cb54186a4d66e7eb1afb0aeb7e3536edc4bb 100644 --- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java @@ -168,7 +181,7 @@ index 0000000000000000000000000000000000000000..986d2a6641ff8017dddf3e5f2655adfc +} diff --git a/src/main/java/org/leavesmc/leaves/protocol/core/LeavesProtocolManager.java b/src/main/java/org/leavesmc/leaves/protocol/core/LeavesProtocolManager.java new file mode 100644 -index 0000000000000000000000000000000000000000..4d3031be4e670dd8369c32cc07e72f10d0afb228 +index 0000000000000000000000000000000000000000..61978bd1617f6bef252bffffb03f53e771cf4847 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/core/LeavesProtocolManager.java @@ -0,0 +1,437 @@ @@ -581,7 +594,7 @@ index 0000000000000000000000000000000000000000..4d3031be4e670dd8369c32cc07e72f10 + + public record FabricRegisterPayload(Set channels) implements LeavesCustomPayload { + -+ public static final ResourceLocation CHANNEL = new ResourceLocation("register"); ++ public static final ResourceLocation CHANNEL = ResourceLocation.withDefaultNamespace("register"); + + @New + public FabricRegisterPayload(ResourceLocation location, FriendlyByteBuf buf) { diff --git a/patches/server/0039-Leaves-Jade-protocol.patch b/patches/server/0039-Leaves-Jade-protocol.patch index e491b65..e1f3150 100644 --- a/patches/server/0039-Leaves-Jade-protocol.patch +++ b/patches/server/0039-Leaves-Jade-protocol.patch @@ -127,10 +127,10 @@ index 30d0133a42ce990352f5c492fcf9beb105364848..1ab2eab686b3a89d406f127a6036c0e2 protected CompositeLootItemCondition(List terms, Predicate predicate) { diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/JadeProtocol.java b/src/main/java/org/leavesmc/leaves/protocol/jade/JadeProtocol.java new file mode 100644 -index 0000000000000000000000000000000000000000..1cd839012f381db2984d9aead0ecb4bbb713c507 +index 0000000000000000000000000000000000000000..79180118cfe9a4a3132ad5ca1b5c2c17ddddfe8f --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/JadeProtocol.java -@@ -0,0 +1,397 @@ +@@ -0,0 +1,398 @@ +package org.leavesmc.leaves.protocol.jade; + +import com.mojang.logging.LogUtils; @@ -245,7 +245,7 @@ index 0000000000000000000000000000000000000000..1cd839012f381db2984d9aead0ecb4bb + + @Contract("_ -> new") + public static @NotNull ResourceLocation mc_id(String path) { -+ return new ResourceLocation(path); ++ return ResourceLocation.withDefaultNamespace(path); + } + + private static boolean isPrimaryKey(ResourceLocation key) { @@ -312,8 +312,9 @@ index 0000000000000000000000000000000000000000..1cd839012f381db2984d9aead0ecb4bb + + try { + shearableBlocks = Collections.unmodifiableList(LootTableMineableCollector.execute( -+ MinecraftServer.getServer().reloadableRegistries().get().registryOrThrow(Registries.LOOT_TABLE), -+ Items.SHEARS.getDefaultInstance())); ++ MinecraftServer.getServer().reloadableRegistries().lookup().lookupOrThrow(Registries.LOOT_TABLE), ++ Items.SHEARS.getDefaultInstance() ++ )); + } catch (Throwable ignore) { + shearableBlocks = List.of(); + LOGGER.error("Failed to collect shearable blocks"); @@ -671,7 +672,7 @@ index 0000000000000000000000000000000000000000..14613e35a6785fc599b1520a667e1311 +} diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/ItemStorageExtensionProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/ItemStorageExtensionProvider.java new file mode 100644 -index 0000000000000000000000000000000000000000..2702121daf6b2358202e9a19b4833acf2b00b928 +index 0000000000000000000000000000000000000000..c79750213f1024389fc6a726791434ab3a86deef --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/ItemStorageExtensionProvider.java @@ -0,0 +1,143 @@ @@ -730,7 +731,7 @@ index 0000000000000000000000000000000000000000..2702121daf6b2358202e9a19b4833acf + case RandomizableContainer te when te.getLootTable() != null -> { + return List.of(); + } -+ case ContainerEntity containerEntity when containerEntity.getLootTable() != null -> { ++ case ContainerEntity containerEntity when containerEntity.getContainerLootTable() != null -> { + return List.of(); + } + default -> { @@ -960,7 +961,7 @@ index 0000000000000000000000000000000000000000..fd4112ed1911171b3c6b5840b7184b5f +} diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/CampfireProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/CampfireProvider.java new file mode 100644 -index 0000000000000000000000000000000000000000..d22013480b53017db71697af37c0b3daa19c7ac5 +index 0000000000000000000000000000000000000000..a1a479987f2c0b6ff4cfd511cbcac1ea7b1c247b --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/CampfireProvider.java @@ -0,0 +1,52 @@ @@ -1000,7 +1001,7 @@ index 0000000000000000000000000000000000000000..d22013480b53017db71697af37c0b3da + stack = stack.copy(); + + CustomData customData = stack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY) -+ .update(COOKING_TIME_CODEC, campfire.cookingTime[i] - campfire.cookingProgress[i]) ++ .update(NbtOps.INSTANCE, COOKING_TIME_CODEC, campfire.cookingTime[i] - campfire.cookingProgress[i]) + .getOrThrow(); + stack.set(DataComponents.CUSTOM_DATA, customData); + @@ -1239,13 +1240,14 @@ index 0000000000000000000000000000000000000000..ea6cf2482807b327d8ff10f0aa117b9b +} diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/MobSpawnerCooldownProvider.java b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/MobSpawnerCooldownProvider.java new file mode 100644 -index 0000000000000000000000000000000000000000..63af91ae159cdcdb1195d98530f6e779449fb60b +index 0000000000000000000000000000000000000000..caf550cd07faeb1efdee0cbf3e48cb1f0d4a2c7f --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/provider/block/MobSpawnerCooldownProvider.java -@@ -0,0 +1,32 @@ +@@ -0,0 +1,33 @@ +package org.leavesmc.leaves.protocol.jade.provider.block; + +import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.TrialSpawnerBlockEntity; +import net.minecraft.world.level.block.entity.trialspawner.TrialSpawnerData; @@ -1263,7 +1265,7 @@ index 0000000000000000000000000000000000000000..63af91ae159cdcdb1195d98530f6e779 + public void saveData(DataAccessor data, BlockAccessor request) { + if (request.target() instanceof TrialSpawnerBlockEntity spawner) { + TrialSpawnerData spawnerData = spawner.getTrialSpawner().getData(); -+ Level level = request.world(); ++ ServerLevel level = ((ServerLevel) request.world()); + if (spawner.getTrialSpawner().canSpawnInLevel(level) && level.getGameTime() < spawnerData.cooldownEndsAt) { + data.putInt("Cooldown", (int) (spawnerData.cooldownEndsAt - level.getGameTime())); + } @@ -2245,15 +2247,16 @@ index 0000000000000000000000000000000000000000..4d65e9a8b5224bd268b1bf18bc39a58d +} diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/util/LootTableMineableCollector.java b/src/main/java/org/leavesmc/leaves/protocol/jade/util/LootTableMineableCollector.java new file mode 100644 -index 0000000000000000000000000000000000000000..c811f89295964b1cb86c3eea39cd20f979ebceb9 +index 0000000000000000000000000000000000000000..c73a7c90121e3c6c404f4c0c1d26174445c9738c --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/jade/util/LootTableMineableCollector.java -@@ -0,0 +1,104 @@ +@@ -0,0 +1,109 @@ +package org.leavesmc.leaves.protocol.jade.util; + +import com.google.common.collect.Lists; +import net.minecraft.advancements.critereon.ItemPredicate; -+import net.minecraft.core.Registry; ++import net.minecraft.core.Holder; ++import net.minecraft.core.HolderGetter; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.Block; @@ -2265,22 +2268,24 @@ index 0000000000000000000000000000000000000000..c811f89295964b1cb86c3eea39cd20f9 +import net.minecraft.world.level.storage.loot.predicates.AnyOfCondition; +import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; +import net.minecraft.world.level.storage.loot.predicates.MatchTool; ++import org.jetbrains.annotations.NotNull; +import org.leavesmc.leaves.protocol.jade.tool.ShearsToolHandler; + +import java.util.List; ++import java.util.Optional; +import java.util.function.Function; + +public class LootTableMineableCollector { + -+ private final Registry lootRegistry; ++ private final HolderGetter lootRegistry; + private final ItemStack toolItem; + -+ public LootTableMineableCollector(Registry lootRegistry, ItemStack toolItem) { ++ public LootTableMineableCollector(HolderGetter lootRegistry, ItemStack toolItem) { + this.lootRegistry = lootRegistry; + this.toolItem = toolItem; + } + -+ public static List execute(Registry lootRegistry, ItemStack toolItem) { ++ public static @NotNull List execute(HolderGetter lootRegistry, ItemStack toolItem) { + LootTableMineableCollector collector = new LootTableMineableCollector(lootRegistry, toolItem); + List list = Lists.newArrayList(); + for (Block block : BuiltInRegistries.BLOCK) { @@ -2288,9 +2293,11 @@ index 0000000000000000000000000000000000000000..c811f89295964b1cb86c3eea39cd20f9 + continue; + } + -+ LootTable lootTable = lootRegistry.get(block.getLootTable()); -+ if (collector.doLootTable(lootTable)) { -+ list.add(block); ++ if (block.getLootTable().isPresent()) { ++ LootTable lootTable = lootRegistry.get(block.getLootTable().get()).map(Holder::value).orElse(null); ++ if (collector.doLootTable(lootTable)) { ++ list.add(block); ++ } + } + } + return list; @@ -2309,7 +2316,7 @@ index 0000000000000000000000000000000000000000..c811f89295964b1cb86c3eea39cd20f9 + return false; + } + -+ private boolean doLootPool(LootPool lootPool) { ++ private boolean doLootPool(@NotNull LootPool lootPool) { + for (LootPoolEntryContainer entry : lootPool.entries) { + if (doLootPoolEntry(entry)) { + return true; @@ -2326,7 +2333,7 @@ index 0000000000000000000000000000000000000000..c811f89295964b1cb86c3eea39cd20f9 + } + } + } else if (entry instanceof NestedLootTable nestedLootTable) { -+ LootTable lootTable = nestedLootTable.contents.map(lootRegistry::get, Function.identity()); ++ LootTable lootTable = nestedLootTable.contents.map($ -> lootRegistry.get($).map(Holder::value).orElse(null), Function.identity()); + return doLootTable(lootTable); + } else { + return isCorrectConditions(entry.conditions, toolItem); @@ -2334,14 +2341,14 @@ index 0000000000000000000000000000000000000000..c811f89295964b1cb86c3eea39cd20f9 + return false; + } + -+ public static boolean isCorrectConditions(List conditions, ItemStack toolItem) { ++ public static boolean isCorrectConditions(@NotNull List conditions, ItemStack toolItem) { + if (conditions.size() != 1) { + return false; + } + + LootItemCondition condition = conditions.getFirst(); -+ if (condition instanceof MatchTool matchTool) { -+ ItemPredicate itemPredicate = matchTool.predicate().orElse(null); ++ if (condition instanceof MatchTool(Optional predicate)) { ++ ItemPredicate itemPredicate = predicate.orElse(null); + return itemPredicate != null && itemPredicate.test(toolItem); + } else if (condition instanceof AnyOfCondition anyOfCondition) { + for (LootItemCondition child : anyOfCondition.terms) { @@ -2353,6 +2360,7 @@ index 0000000000000000000000000000000000000000..c811f89295964b1cb86c3eea39cd20f9 + return false; + } +} +\ No newline at end of file diff --git a/src/main/java/org/leavesmc/leaves/protocol/jade/util/PairHierarchyLookup.java b/src/main/java/org/leavesmc/leaves/protocol/jade/util/PairHierarchyLookup.java new file mode 100644 index 0000000000000000000000000000000000000000..ff8a457aed356a2c209c13a6f5bf2c81ee10ca07 diff --git a/patches/server/0040-Leaves-BBOR-Protocol.patch b/patches/server/0040-Leaves-BBOR-Protocol.patch index 14a0eac..79fb3de 100644 --- a/patches/server/0040-Leaves-BBOR-Protocol.patch +++ b/patches/server/0040-Leaves-BBOR-Protocol.patch @@ -36,7 +36,7 @@ index 6003e3495e61073c39928918b9d9f4c2e20f3a49..b82e57fbcaa5aeabb81748ff2c5689a5 public Level getLevel() { diff --git a/src/main/java/org/leavesmc/leaves/protocol/BBORProtocol.java b/src/main/java/org/leavesmc/leaves/protocol/BBORProtocol.java new file mode 100644 -index 0000000000000000000000000000000000000000..a71bd73f23c68ffede651980bbfad8cef71ef9c6 +index 0000000000000000000000000000000000000000..3e14fce2088e3c01f6179440fdd0203ed1a08e71 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/BBORProtocol.java @@ -0,0 +1,227 @@ @@ -146,7 +146,7 @@ index 0000000000000000000000000000000000000000..a71bd73f23c68ffede651980bbfad8ce + public static void onChunkLoaded(@NotNull LevelChunk chunk) { + if (LuminaConfig.configModule().protocol.bborProtocol) { + Map structures = new HashMap<>(); -+ final Registry structureFeatureRegistry = chunk.getLevel().registryAccess().registryOrThrow(Registries.STRUCTURE); ++ final Registry structureFeatureRegistry = chunk.getLevel().registryAccess().lookupOrThrow(Registries.STRUCTURE); + for (var es : chunk.getAllStarts().entrySet()) { + final var optional = structureFeatureRegistry.getResourceKey(es.getKey()); + optional.ifPresent(key -> structures.put(key.location().toString(), es.getValue())); @@ -187,7 +187,7 @@ index 0000000000000000000000000000000000000000..a71bd73f23c68ffede651980bbfad8ce + } + + private static void sendStructureList(ServerPlayer player) { -+ final Registry structureRegistry = player.server.registryAccess().registryOrThrow(Registries.STRUCTURE); ++ final Registry structureRegistry = player.server.registryAccess().lookupOrThrow(Registries.STRUCTURE); + final Set structureIds = structureRegistry.entrySet().stream() + .map(e -> e.getKey().location().toString()).collect(Collectors.toSet()); + ProtocolUtils.sendPayloadPacket(player, STRUCTURE_LIST_SYNC, buf -> { diff --git a/patches/server/0041-PCA-sync-protocol.patch b/patches/server/0041-PCA-sync-protocol.patch index 915f213..554e919 100644 --- a/patches/server/0041-PCA-sync-protocol.patch +++ b/patches/server/0041-PCA-sync-protocol.patch @@ -298,7 +298,7 @@ index 26f0c6ddea76c8a6e17faea2e5603e7c02a7f1b1..c27776bfeef0e2a79ac0a22384c862d4 return ShulkerBoxBlockEntity.SLOTS; diff --git a/src/main/java/org/leavesmc/leaves/protocol/PcaSyncProtocol.java b/src/main/java/org/leavesmc/leaves/protocol/PcaSyncProtocol.java new file mode 100644 -index 0000000000000000000000000000000000000000..25fed027ddb8fa2e11d7f37214868a92dc32c7fb +index 0000000000000000000000000000000000000000..1527a2ac7d607375f6e55df3891e95de5bf3a935 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/protocol/PcaSyncProtocol.java @@ -0,0 +1,390 @@ @@ -416,7 +416,7 @@ index 0000000000000000000000000000000000000000..25fed027ddb8fa2e11d7f37214868a92 + BlockEntity blockEntityAdj = null; + if (blockState.getBlock() instanceof ChestBlock) { + if (blockState.getValue(ChestBlock.TYPE) != ChestType.SINGLE) { -+ BlockPos posAdj = pos.offset(ChestBlock.getConnectedDirection(blockState).getNormal()); ++ BlockPos posAdj = pos.relative(ChestBlock.getConnectedDirection(blockState)); + // The method in World now checks that the caller is from the same thread... + blockEntityAdj = world.getChunk(posAdj).getBlockEntity(posAdj); + } @@ -577,7 +577,7 @@ index 0000000000000000000000000000000000000000..25fed027ddb8fa2e11d7f37214868a92 + + if (blockState.getBlock() instanceof ChestBlock) { + if (blockState.getValue(ChestBlock.TYPE) != ChestType.SINGLE) { -+ BlockPos posAdj = pos.offset(ChestBlock.getConnectedDirection(blockState).getNormal()); ++ BlockPos posAdj = pos.relative(ChestBlock.getConnectedDirection(blockState)); + playerListAdj = getWatchPlayerList(world, posAdj); + } + } diff --git a/patches/server/0042-Carpet-features.patch b/patches/server/0042-Carpet-features.patch index 554a339..0a55c53 100644 --- a/patches/server/0042-Carpet-features.patch +++ b/patches/server/0042-Carpet-features.patch @@ -8,6 +8,19 @@ Licenses: - GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html) (https://github.com/LeavesMC/Leaves?tab=License-1-ov-file) - MIT license (https://github.com/gnembon/fabric-carpet?tab=MIT-1-ov-file#readme) +diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java +index 811479551bad422123ad1b09329e6fc8e12c7c4e..b8211313f823f0b6bdae43ad43819302e9c097ea 100644 +--- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java ++++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java +@@ -80,7 +80,7 @@ public final class ChunkHolderManager { + private static final long PROBE_MARKER = Long.MIN_VALUE + 1; + public final ReentrantAreaLock ticketLockArea; + +- private final ConcurrentLong2ReferenceChainedHashTable>> tickets = new ConcurrentLong2ReferenceChainedHashTable<>(); ++ public final ConcurrentLong2ReferenceChainedHashTable>> tickets = new ConcurrentLong2ReferenceChainedHashTable<>(); // Lumina - private -> public + private final ConcurrentLong2ReferenceChainedHashTable sectionToChunkToExpireCount = new ConcurrentLong2ReferenceChainedHashTable<>(); + final ChunkUnloadQueue unloadQueue; + diff --git a/src/main/java/io/papermc/paper/threadedregions/RegionShutdownThread.java b/src/main/java/io/papermc/paper/threadedregions/RegionShutdownThread.java index 261b3019878c31a9e44e56b6611899de6c00ebee..3b6c3b45425f7325a3b9c0ed96a0c7bc730b566e 100644 --- a/src/main/java/io/papermc/paper/threadedregions/RegionShutdownThread.java @@ -107,7 +120,7 @@ index 30e3d0d7b0ca8cfa30efb60eefa73d94c65c4690..f24ad290d7d1c14e5ff1dbc92ccc40d6 if (this.channel.eventLoop().inEventLoop()) { this.doSendPacket(packet, callbacks, flush); diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 1ef892d54dcb5c355af28b93b249cd73e9d30a9e..3f15e9c90d5ea9eaf009e50383fd9d128327ade5 100644 +index 5ee586507dc07552266902722a757353bfeb3114..1350aad84f37671a58a85b73331fb6b849322b28 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -548,6 +548,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop> set = this.getChunkHolderManager().getTicketsCopy().get(pos); ++ SortedArraySet> set = moonrise$getChunkHolderManager().tickets.get(pos); + Ticket existingTicket = null; + if (set != null) + { @@ -302,7 +315,7 @@ index f6015f2ac77aeddbd900226d183bf24c93112b21..e679feeb56f150c0b3ca61ba0be0f5e3 static final int PLAYER_TICKET_LEVEL = ChunkLevel.byStatus(FullChunkStatus.ENTITY_TICKING); private static final int INITIAL_TICKET_LIST_CAPACITY = 4; diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index ca47a512452ae425160e30dc7c4a79f40aa97a26..0de24cf6e3d526a3eb415225fa3cab7c91c75a0e 100644 +index ca47a512452ae425160e30dc7c4a79f40aa97a26..679dc34019331b83ff03e0aa56645412cebccc1b 100644 --- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java +++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java @@ -525,6 +525,39 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon @@ -353,7 +366,7 @@ index ca47a512452ae425160e30dc7c4a79f40aa97a26..0de24cf6e3d526a3eb415225fa3cab7c + // Lumina start - Porting Carpet's features + int j = distanceManager.getNaturalSpawnChunkCount(); + net.minecraft.resources.ResourceKey dim = this.level.dimension(); -+ org.leavesmc.lumina.carpet.utils.SpawnReporter.chunkCounts.put(dim, k); ++ org.leavesmc.lumina.carpet.utils.SpawnReporter.chunkCounts.put(dim, j); + + if (org.leavesmc.lumina.carpet.utils.SpawnReporter.trackingSpawns()) { + //local spawns now need to be tracked globally cause each calll is just for chunk @@ -369,8 +382,21 @@ index ca47a512452ae425160e30dc7c4a79f40aa97a26..0de24cf6e3d526a3eb415225fa3cab7c // Paper start - Optional per player mob spawns final int naturalSpawnChunkCount = j; NaturalSpawner.SpawnState spawnercreature_d; // moved down +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index fa5ed398ae1c74775e93eb3b80abde324a1d2b9d..8ca98b4b4854294a1659ec2a9f7f55733685d8aa 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -316,7 +316,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + private final ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller.EntityDataController entityDataController; + private final ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller.PoiDataController poiDataController; + private final ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller.ChunkDataController chunkDataController; +- private final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler chunkTaskScheduler; ++ public final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler chunkTaskScheduler; // Lumina - private -> public + private long lastMidTickFailure; + private long tickedBlocksOrFluids; + // Folia - region threading - move to regionized data diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index ad8da6726b4113068b200426ab1ac1e24a061942..00f9e701725c4eb44b4d3179871d5aff8fef3f94 100644 +index ad8da6726b4113068b200426ab1ac1e24a061942..a085af3507147e4d3549f20d4bc5d10328e95ae4 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java @@ -584,8 +584,7 @@ public class ServerPlayerGameMode { @@ -379,7 +405,7 @@ index ad8da6726b4113068b200426ab1ac1e24a061942..00f9e701725c4eb44b4d3179871d5aff if (!flag1) { - InteractionResult enuminteractionresult1 = iblockdata.useItemOn(player.getItemInHand(hand), world, player, hand, hitResult); - -+ ItemInteractionResult iteminteractionresult = (org.leavesmc.lumina.carpet.helpers.BlockRotator.flipBlockWithCactus(iblockdata, world, player, hand, hitResult)) ? ItemInteractionResult.SUCCESS : iblockdata.useItemOn(player.getItemInHand(hand), world, player, hand, hitResult); // Lumina - Porting Carpet's features - cactus block rotation ++ InteractionResult enuminteractionresult1 = (org.leavesmc.lumina.carpet.helpers.BlockRotator.flipBlockWithCactus(iblockdata, world, player, hand, hitResult)) ? InteractionResult.SUCCESS : iblockdata.useItemOn(player.getItemInHand(hand), world, player, hand, hitResult); // Lumina - Porting Carpet's features - cactus block rotation if (enuminteractionresult1.consumesAction()) { CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger(player, blockposition, itemstack1); return enuminteractionresult1; @@ -561,7 +587,7 @@ index 7ed52b887c4d766c23220a8809914d5d80f12ea4..8f8ae3f27be586de1413013db3ffca25 if (j > 0) { diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 1bbce44ea7732764d95147f467da97fde43036a1..a9d879814a051288fe1777d6fe7575f271f6426f 100644 +index 52b8ec3fc7bc18d3f4b3f74db8e8d3165f737ddd..1998d2133d68e9aaa2881688e765bfe0157a39b6 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -185,6 +185,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess @@ -577,6 +603,15 @@ index 1bbce44ea7732764d95147f467da97fde43036a1..a9d879814a051288fe1777d6fe7575f2 // Paper start - Share random for entities to make them more random public static RandomSource SHARED_RANDOM = io.papermc.paper.threadedregions.util.ThreadLocalRandomSource.INSTANCE; // Folia - region threading // Paper start - replace random +@@ -269,7 +275,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + protected int boardingCooldown; + @Nullable + private Entity vehicle; +- private Level level; ++ public Level level; // Lumina - private -> public + public double xo; + public double yo; + public double zo; @@ -5529,6 +5535,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess } @@ -586,7 +621,7 @@ index 1bbce44ea7732764d95147f467da97fde43036a1..a9d879814a051288fe1777d6fe7575f2 if (entityliving instanceof Player entityhuman) { diff --git a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java -index 23b47d90fd35659d9eaa661e808a7f89b29614cf..5110f5a09db25ff3700dd0a1512aa0070741dcac 100644 +index 23b47d90fd35659d9eaa661e808a7f89b29614cf..ce01ad03b00f4bdb5a80a1070259ed1d98e6017e 100644 --- a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java +++ b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java @@ -334,6 +334,19 @@ public class ExperienceOrb extends Entity { @@ -598,7 +633,7 @@ index 23b47d90fd35659d9eaa661e808a7f89b29614cf..5110f5a09db25ff3700dd0a1512aa007 + player.takeXpDelay = 0; + // reducing the count to 1 and leaving vanilla to deal with it + while (this.count > 1) { -+ int remainder = this.repairPlayerItems(player, this.value); ++ int remainder = this.repairPlayerItems(entityplayer, this.value); + if (remainder > 0) { + player.giveExperiencePoints(remainder); + } @@ -879,7 +914,7 @@ index 09edb09cf6beacc8e7960d5d78fe88647525e257..f54fe29bcc1511b94bcb388438206408 @Override diff --git a/src/main/java/net/minecraft/world/entity/monster/Guardian.java b/src/main/java/net/minecraft/world/entity/monster/Guardian.java -index 951f46684623582980901c1ebc1870aa5bcf25a1..1a130d61af7aa7f5304ba106cb4a9f81374442c5 100644 +index 951f46684623582980901c1ebc1870aa5bcf25a1..83d47d0a93fde3a6170aa08e1c1a082c4f4f9e2c 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Guardian.java +++ b/src/main/java/net/minecraft/world/entity/monster/Guardian.java @@ -71,6 +71,28 @@ public class Guardian extends Monster { @@ -891,7 +926,7 @@ index 951f46684623582980901c1ebc1870aa5bcf25a1..1a130d61af7aa7f5304ba106cb4a9f81 + if (!this.level().isClientSide && !this.isRemoved() && org.leavesmc.lumina.carpet.CarpetConfig.configModule().renewableSponges && !((Object)this instanceof ElderGuardian)) { + ElderGuardian elderGuardian = new ElderGuardian(EntityType.ELDER_GUARDIAN ,this.level()); + elderGuardian.moveTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot()); -+ elderGuardian.finalizeSpawn(serverWorld ,this.level().getCurrentDifficultyAt(elderGuardian.blockPosition()), MobSpawnType.CONVERSION, null); ++ elderGuardian.finalizeSpawn(serverWorld ,this.level().getCurrentDifficultyAt(elderGuardian.blockPosition()), EntitySpawnReason.CONVERSION, null); + elderGuardian.setNoAi(this.isNoAi()); + + if (this.hasCustomName()) { @@ -1581,24 +1616,19 @@ index 09ef94ef68f5e2e1943bac32b5fc261cf5562f41..1a7306f109082b3268c9a3b8d5988e2f public boolean isSecondaryUseActive() { diff --git a/src/main/java/net/minecraft/world/item/crafting/Ingredient.java b/src/main/java/net/minecraft/world/item/crafting/Ingredient.java -index 0b0054b3d5d56ba24e1aee0e3ab56ea5b01a82a8..b216e139fe9c3348f12e853be59894d5230e416b 100644 +index 0b0054b3d5d56ba24e1aee0e3ab56ea5b01a82a8..f41ebad315b55075a5324b6b7762ae6f1681e289 100644 --- a/src/main/java/net/minecraft/world/item/crafting/Ingredient.java +++ b/src/main/java/net/minecraft/world/item/crafting/Ingredient.java -@@ -26,7 +26,11 @@ import javax.annotation.Nullable; +@@ -26,7 +26,6 @@ import javax.annotation.Nullable; // CraftBukkit end public final class Ingredient implements StackedContents.IngredientInfo, Predicate { // Paper - Improve exact choice recipe ingredients - -+ // Lumina start - Porting Carpet's features -+ public List> getRecipeStacks() { -+ return Arrays.stream(values).map(Ingredient.Value::getItems).toList(); -+ } -+ // Lumina end - Porting Carpet's features public static final StreamCodec CONTENTS_STREAM_CODEC = ByteBufCodecs.holderSet(Registries.ITEM).map(Ingredient::new, (recipeitemstack) -> { return recipeitemstack.values; }); diff --git a/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java b/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java -index 2483627f807d7a3907f6848a8bc45d7a798e746d..1d8b854ad3c687083896b361572f83ef0038da35 100644 +index 2483627f807d7a3907f6848a8bc45d7a798e746d..159b3965d66983e9c9eba647f1fbdfa5ce01c2ff 100644 --- a/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java +++ b/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java @@ -38,6 +38,12 @@ import org.slf4j.Logger; @@ -1614,36 +1644,8 @@ index 2483627f807d7a3907f6848a8bc45d7a798e746d..1d8b854ad3c687083896b361572f83ef import net.minecraft.server.level.ServerLevel; import net.minecraft.server.packs.resources.ResourceManager; import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener; -@@ -95,6 +101,27 @@ public class RecipeManager extends SimplePreparableReloadListener imp - this.registries = registries; - } - -+ // Lumina start - Porting Carpet's features -+ public List> getAllMatching(RecipeType type, ResourceLocation itemId, RegistryAccess registryAccess) { -+ // quiq cheq -+ RecipeHolder recipe = byName.get(itemId); -+ if (recipe != null && recipe.value().getType().equals(type)) { -+ return List.of(recipe.value()); -+ } -+ if (!byType.containsKey(type)) { -+ // happens when mods add recipe to the registry without updating recipe manager -+ return List.of(); -+ } -+ Collection> typeRecipes = byType.get(type); -+ Registry regs = registryAccess.registryOrThrow(Registries.ITEM); -+ Item item = regs.get(itemId); -+ return typeRecipes.stream() -+ .>map(RecipeHolder::value) -+ .filter(r -> r.getResultItem(registryAccess).getItem() == item) -+ .toList(); -+ } -+ // Lumina end - Porting Carpet's features -+ - @Override - protected RecipeMap prepare(ResourceManager manager, ProfilerFiller profiler) { - SortedMap> sortedmap = new TreeMap(); diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 456f15e44e41d65338c0346594be2b0042b84005..f2a54bab22d8e46616a4f4bda9b5a5988829d8d4 100644 +index 456f15e44e41d65338c0346594be2b0042b84005..955d9073287eda25032a4a3a7de8fee2ffcac8f7 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java @@ -107,7 +107,10 @@ import org.bukkit.event.block.BlockPhysicsEvent; @@ -1658,7 +1660,7 @@ index 456f15e44e41d65338c0346594be2b0042b84005..f2a54bab22d8e46616a4f4bda9b5a598 public static final Codec> RESOURCE_KEY_CODEC = ResourceKey.codec(Registries.DIMENSION); public static final ResourceKey OVERWORLD = ResourceKey.create(Registries.DIMENSION, ResourceLocation.withDefaultNamespace("overworld")); public static final ResourceKey NETHER = ResourceKey.create(Registries.DIMENSION, ResourceLocation.withDefaultNamespace("the_nether")); -@@ -171,6 +174,106 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl +@@ -171,6 +174,105 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl //public final Map explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions // Folia - region threading //public java.util.ArrayDeque redstoneUpdateInfos; // Paper - Faster redstone torch rapid clock removal; Move from Map in BlockRedstoneTorch to here // Folia - region threading @@ -1672,7 +1674,6 @@ index 456f15e44e41d65338c0346594be2b0042b84005..f2a54bab22d8e46616a4f4bda9b5a598 + } + + public List getOtherEntitiesLimited(@org.jetbrains.annotations.Nullable Entity except, AABB box, Predicate predicate, int limit) { -+ this.getProfiler().incrementCounter("getEntities"); // visit + java.util.concurrent.atomic.AtomicInteger checkedEntities = new java.util.concurrent.atomic.AtomicInteger(); + List list = Lists.newArrayList(); + try { @@ -1726,8 +1727,8 @@ index 456f15e44e41d65338c0346594be2b0042b84005..f2a54bab22d8e46616a4f4bda9b5a598 + else { + BlockState blockState_3 = this.getBlockState(blockPos_1); + -+ if (blockState_3 != blockState_2 && (blockState_3.getLightBlock((BlockGetter) this, blockPos_1) != blockState_2.getLightBlock((BlockGetter) this, blockPos_1) || blockState_3.getLightEmission() != blockState_2.getLightEmission() || blockState_3.useShapeForLightOcclusion() || blockState_2.useShapeForLightOcclusion())) { -+ ProfilerFiller profiler = getProfiler(); ++ if (blockState_3 != blockState_2 && (blockState_3.getLightBlock() != blockState_2.getLightBlock() || blockState_3.getLightEmission() != blockState_2.getLightEmission() || blockState_3.useShapeForLightOcclusion() || blockState_2.useShapeForLightOcclusion())) { ++ ProfilerFiller profiler = Profiler.get(); + profiler.push("queueCheckLight"); + this.getChunkSource().getLightEngine().checkBlock(blockPos_1); + profiler.pop(); @@ -1765,7 +1766,7 @@ index 456f15e44e41d65338c0346594be2b0042b84005..f2a54bab22d8e46616a4f4bda9b5a598 public CraftWorld getWorld() { return this.world; } -@@ -1219,13 +1322,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl +@@ -1219,13 +1321,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl } if ((i & 1) != 0) { @@ -1782,7 +1783,7 @@ index 456f15e44e41d65338c0346594be2b0042b84005..f2a54bab22d8e46616a4f4bda9b5a598 // CraftBukkit start diff --git a/src/main/java/net/minecraft/world/level/block/BarrierBlock.java b/src/main/java/net/minecraft/world/level/block/BarrierBlock.java -index c27f78ab7994687fe1c25ec44ffff4388270df6f..f322633e512a7908ba33b2e42f73bbb39ed55cbb 100644 +index c27f78ab7994687fe1c25ec44ffff4388270df6f..0373cfeab1cb7048c4f038c36c2ffabbaf9f5224 100644 --- a/src/main/java/net/minecraft/world/level/block/BarrierBlock.java +++ b/src/main/java/net/minecraft/world/level/block/BarrierBlock.java @@ -24,6 +24,38 @@ import net.minecraft.world.level.material.Fluids; @@ -1796,10 +1797,10 @@ index c27f78ab7994687fe1c25ec44ffff4388270df6f..f322633e512a7908ba33b2e42f73bbb3 + return (shouldPower && direction == Direction.DOWN) ? 15 : 0; + } + -+ public void neighborChanged(BlockState state, net.minecraft.world.level.Level level, BlockPos pos, Block block, BlockPos fromPos, boolean notify) { ++ public void neighborChanged(BlockState state, net.minecraft.world.level.Level level, BlockPos pos, Block block, net.minecraft.world.level.redstone.Orientation fromPos, boolean notify) { + if (org.leavesmc.lumina.carpet.CarpetConfig.configModule().updateSuppressionBlock != -1) { -+ if (fromPos.equals(pos.above())) { -+ BlockState stateAbove = level.getBlockState(fromPos); ++ if (true/*fromPos.equals(pos.above())*/) { // todo neighbour updates don't have source ++ BlockState stateAbove = level.getBlockState(pos.above()); + if (stateAbove.is(Blocks.ACTIVATOR_RAIL) && !stateAbove.getValue(PoweredRailBlock.POWERED)) { + level.scheduleTick(pos, this, 1); + net.minecraft.world.level.redstone.NeighborUpdater updater = level.getNeighborUpdater(); @@ -1825,7 +1826,7 @@ index c27f78ab7994687fe1c25ec44ffff4388270df6f..f322633e512a7908ba33b2e42f73bbb3 @Override public MapCodec codec() { diff --git a/src/main/java/net/minecraft/world/level/block/BuddingAmethystBlock.java b/src/main/java/net/minecraft/world/level/block/BuddingAmethystBlock.java -index 8920855b07a31715327b8102c7faafc9f916825d..d465189343ed1395e247e2a5ef10703d5856900b 100644 +index 8920855b07a31715327b8102c7faafc9f916825d..ee866cd00cd843df66830e84d6fe64487f43f7cc 100644 --- a/src/main/java/net/minecraft/world/level/block/BuddingAmethystBlock.java +++ b/src/main/java/net/minecraft/world/level/block/BuddingAmethystBlock.java @@ -24,6 +24,19 @@ public class BuddingAmethystBlock extends AmethystBlock { @@ -1839,7 +1840,7 @@ index 8920855b07a31715327b8102c7faafc9f916825d..d465189343ed1395e247e2a5ef10703d + // drawback - not controlled via loottables, but hey + if (org.leavesmc.lumina.carpet.CarpetConfig.configModule().movableAmethyst && + stack.getItem() instanceof net.minecraft.world.item.PickaxeItem && -+ net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.SILK_TOUCH, stack) > 0 ++ net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(world.registryAccess().lookupOrThrow(net.minecraft.core.registries.Registries.ENCHANTMENT).getOrThrow(net.minecraft.world.item.enchantment.Enchantments.SILK_TOUCH), stack) > 0 + ) + popResource(world, pos, net.minecraft.world.item.Items.BUDDING_AMETHYST.getDefaultInstance()); + } @@ -2544,9 +2545,18 @@ index c2a0cd1c25b6d775b55f8c3aacca9837e35a5dfd..c18b733032b9ad51a552782d0afce525 double getMinX(); diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java -index f87abb22dd161b2b74401086de80dc95c9ac2dbb..8a2756c00564f02e60213445023832441aa1e384 100644 +index f87abb22dd161b2b74401086de80dc95c9ac2dbb..df78cc9a7011938417c1ec42bdf711746d022040 100644 --- a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java +++ b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java +@@ -63,7 +63,7 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh + private static final Logger LOGGER = LogUtils.getLogger(); + private static final LongSet EMPTY_REFERENCE_SET = new LongOpenHashSet(); + protected final ShortList[] postProcessing; +- private volatile boolean unsaved; ++ volatile boolean unsaved; // Lumina - Porting Carpet's features - private -> package-private + private volatile boolean isLightCorrect; + protected final ChunkPos chunkPos; public final long coordinateKey; public final int locX; public final int locZ; // Paper - cache coordinate key + private long inhabitedTime; @@ -376,6 +376,7 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh public abstract void removeBlockEntity(BlockPos pos); @@ -2985,7 +2995,7 @@ index 0000000000000000000000000000000000000000..0939faf78ea163984a7df2f77579a8c6 + } +} diff --git a/src/main/java/org/leavesmc/lumina/carpet/CarpetConfigModule.java b/src/main/java/org/leavesmc/lumina/carpet/CarpetConfigModule.java -index 8d6de4b557dfb5fdef7d246f15872b9f2d7bdc60..7b2e0c207b9b61b779c415376f02a6d6794ae3f1 100644 +index 8d6de4b557dfb5fdef7d246f15872b9f2d7bdc60..d33d8d096ab8b4fe1dbc870edc64addba1d3fc5f 100644 --- a/src/main/java/org/leavesmc/lumina/carpet/CarpetConfigModule.java +++ b/src/main/java/org/leavesmc/lumina/carpet/CarpetConfigModule.java @@ -36,6 +36,7 @@ public class CarpetConfigModule { @@ -3062,6 +3072,15 @@ index 8d6de4b557dfb5fdef7d246f15872b9f2d7bdc60..7b2e0c207b9b61b779c415376f02a6d6 public static class TNTAngleValidator extends Validator { @Override +@@ -424,7 +404,7 @@ public class CarpetConfigModule { + public boolean validate(String newValue, String oldValue) { + Optional ignoredBlock = MinecraftServer.getServer() + .registryAccess() +- .registryOrThrow(Registries.BLOCK) ++ .lookupOrThrow(Registries.BLOCK) + .getOptional(ResourceLocation.tryParse(newValue)); + if (ignoredBlock.isEmpty()) { + return false; diff --git a/src/main/java/org/leavesmc/lumina/carpet/CarpetServer.java b/src/main/java/org/leavesmc/lumina/carpet/CarpetServer.java new file mode 100644 index 0000000000000000000000000000000000000000..8ea101f28d97b0bc7635b4e3ace42784503835b8 @@ -3209,9 +3228,33 @@ index 0000000000000000000000000000000000000000..8ea101f28d97b0bc7635b4e3ace42784 + // This behaviour will not work in later Carpet versions and the manager won't be registered!""".formatted(ext.getClass().getName())); + // } // TODO +} +diff --git a/src/main/java/org/leavesmc/lumina/carpet/command/PerimeterInfoCommand.java b/src/main/java/org/leavesmc/lumina/carpet/command/PerimeterInfoCommand.java +index f673832378af9529a2eb6fe18c25cda7398a5f61..346d2fa7515f93af17ac9800bb2ceec2ca4e23df 100644 +--- a/src/main/java/org/leavesmc/lumina/carpet/command/PerimeterInfoCommand.java ++++ b/src/main/java/org/leavesmc/lumina/carpet/command/PerimeterInfoCommand.java +@@ -7,6 +7,8 @@ import dev.jorel.commandapi.arguments.LocationType; + import net.minecraft.core.BlockPos; + import net.minecraft.nbt.CompoundTag; + import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.EntitySpawnReason; + import net.minecraft.world.entity.EntityType; + import net.minecraft.world.entity.Mob; + import org.bukkit.Bukkit; +@@ -51,8 +53,8 @@ public class PerimeterInfoCommand { + BlockPos blockPos = toBlockPos(pos); + if (mobId != null) { + nbtTagCompound.putString("id", mobId); +- net.minecraft.world.entity.Entity baseEntity = EntityType.loadEntityRecursive(nbtTagCompound, level, (entity1x) -> { +- entity1x.moveTo(new BlockPos(blockPos.getX(), level.getMinBuildHeight() - 10, blockPos.getZ()), entity1x.getYRot(), entity1x.getXRot()); ++ Entity baseEntity = EntityType.loadEntityRecursive(nbtTagCompound, level, EntitySpawnReason.COMMAND, (entity1x) -> { ++ entity1x.moveTo(new BlockPos(blockPos.getX(), level.getMinY() - 10, blockPos.getZ()), entity1x.getYRot(), entity1x.getXRot()); + return !level.addWithUUID(entity1x) ? null : entity1x; + }); + if (!(baseEntity instanceof Mob)) { diff --git a/src/main/java/org/leavesmc/lumina/carpet/helpers/BlockRotator.java b/src/main/java/org/leavesmc/lumina/carpet/helpers/BlockRotator.java new file mode 100644 -index 0000000000000000000000000000000000000000..ef394c6de77ea5ce51abceed3f66ef71498b2a80 +index 0000000000000000000000000000000000000000..4d5ba0572bdeeb30c15ae04a0523a7812886a6d9 --- /dev/null +++ b/src/main/java/org/leavesmc/lumina/carpet/helpers/BlockRotator.java @@ -0,0 +1,164 @@ @@ -3304,7 +3347,7 @@ index 0000000000000000000000000000000000000000..ef394c6de77ea5ce51abceed3f66ef71 + } + } + // Send block update to the block that just have been rotated. -+ world.neighborChanged(blockpos, block, source.pos()); ++ world.neighborChanged(blockpos, block, null); + + return stack; + } @@ -3399,7 +3442,7 @@ index 0000000000000000000000000000000000000000..d0eb90b1f501d9d702d9d08e09ba7c0e +} diff --git a/src/main/java/org/leavesmc/lumina/carpet/helpers/FertilizableCoral.java b/src/main/java/org/leavesmc/lumina/carpet/helpers/FertilizableCoral.java new file mode 100644 -index 0000000000000000000000000000000000000000..53b275c250d97f5595e27fdc6fc6f24cdd5be029 +index 0000000000000000000000000000000000000000..2a68e7be02613ac207fe4d5c0122cf746a3bac3d --- /dev/null +++ b/src/main/java/org/leavesmc/lumina/carpet/helpers/FertilizableCoral.java @@ -0,0 +1,77 @@ @@ -3459,7 +3502,7 @@ index 0000000000000000000000000000000000000000..53b275c250d97f5595e27fdc6fc6f24c + + MapColor color = blockUnder.getMapColor(worldIn, pos); + BlockState properBlock = blockUnder; -+ HolderSet.Named coralBlocks = worldIn.registryAccess().registryOrThrow(Registries.BLOCK).getTag(BlockTags.CORAL_BLOCKS).orElseThrow(); ++ HolderSet.Named coralBlocks = worldIn.registryAccess().lookupOrThrow(Registries.BLOCK).get(BlockTags.CORAL_BLOCKS).orElseThrow(); + for (Holder block : coralBlocks) { + properBlock = block.value().defaultBlockState(); + if (properBlock.getMapColor(worldIn, pos) == color) { @@ -3482,17 +3525,16 @@ index 0000000000000000000000000000000000000000..53b275c250d97f5595e27fdc6fc6f24c +} diff --git a/src/main/java/org/leavesmc/lumina/carpet/helpers/HopperCounter.java b/src/main/java/org/leavesmc/lumina/carpet/helpers/HopperCounter.java new file mode 100644 -index 0000000000000000000000000000000000000000..520aec415548003720af59aa2c3bad3e2fc3efc2 +index 0000000000000000000000000000000000000000..d11e835f97be89f93f9cfc47cc6b63710229a3f1 --- /dev/null +++ b/src/main/java/org/leavesmc/lumina/carpet/helpers/HopperCounter.java -@@ -0,0 +1,392 @@ +@@ -0,0 +1,389 @@ +package org.leavesmc.lumina.carpet.helpers; + -+import org.leavesmc.lumina.carpet.CarpetServer; -+import org.leavesmc.lumina.carpet.utils.Messenger; +import it.unimi.dsi.fastutil.objects.Object2LongLinkedOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2LongMap; +import net.minecraft.ChatFormatting; ++import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.core.RegistryAccess; +import net.minecraft.core.registries.Registries; @@ -3502,28 +3544,20 @@ index 0000000000000000000000000000000000000000..520aec415548003720af59aa2c3bad3e +import net.minecraft.network.chat.TextColor; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; -+import net.minecraft.world.item.BlockItem; -+import net.minecraft.world.item.DyeColor; -+import net.minecraft.world.item.DyeItem; -+import net.minecraft.world.item.Item; -+import net.minecraft.world.item.ItemStack; -+import net.minecraft.world.item.Items; ++import net.minecraft.world.item.*; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.item.crafting.Recipe; -+import net.minecraft.world.item.crafting.RecipeType; ++import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.AbstractBannerBlock; +import net.minecraft.world.level.block.BeaconBeamBlock; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.material.MapColor; ++import org.leavesmc.lumina.carpet.CarpetServer; ++import org.leavesmc.lumina.carpet.utils.Messenger; ++import org.leavesmc.lumina.carpet.utils.RecipeHelper; + -+import java.util.ArrayList; -+import java.util.Collection; -+import java.util.Collections; -+import java.util.EnumMap; -+import java.util.List; -+import java.util.Locale; -+import java.util.Map; ++import java.util.*; +import java.util.stream.Collectors; + +import static java.util.Map.entry; @@ -3683,7 +3717,7 @@ index 0000000000000000000000000000000000000000..520aec415548003720af59aa2c3bad3e + Item item = e.getKey(); + MutableComponent itemName = Component.translatable(item.getDescriptionId()); + Style itemStyle = itemName.getStyle(); -+ TextColor color = guessColor(item, server.registryAccess()); ++ TextColor color = guessColor(item, server.overworld()); + itemName.setStyle((color != null) ? itemStyle.withColor(color) : itemStyle.withItalic(true)); + long count = e.getLongValue(); + return Messenger.c("g - ", itemName, @@ -3809,13 +3843,13 @@ index 0000000000000000000000000000000000000000..520aec415548003720af59aa2c3bad3e + if (item instanceof DyeItem dye) + return TextColor.fromRgb(appropriateColor(dye.getDyeColor().getMapColor().col)); + Block block = null; -+ final Registry itemRegistry = registryAccess.registryOrThrow(Registries.ITEM); -+ final Registry blockRegistry = registryAccess.registryOrThrow(Registries.BLOCK); ++ final Registry itemRegistry = registryAccess.lookupOrThrow(Registries.ITEM); ++ final Registry blockRegistry = registryAccess.lookupOrThrow(Registries.BLOCK); + ResourceLocation id = itemRegistry.getKey(item); + if (item instanceof BlockItem blockItem) { + block = blockItem.getBlock(); + } else if (blockRegistry.getOptional(id).isPresent()) { -+ block = blockRegistry.get(id); ++ block = blockRegistry.getValue(id); + } + if (block != null) { + if (block instanceof AbstractBannerBlock) @@ -3831,24 +3865,30 @@ index 0000000000000000000000000000000000000000..520aec415548003720af59aa2c3bad3e + * Guesses the item's colour from the item itself. It first calls {@link HopperCounter#fromItem} to see if it has a + * valid colour there, if not just makes a guess, and if that fails just returns null + */ -+ public static TextColor guessColor(Item item, RegistryAccess registryAccess) { ++ public static TextColor guessColor(Item item, Level level) { ++ RegistryAccess registryAccess = level.registryAccess(); + TextColor direct = fromItem(item, registryAccess); -+ if (direct != null) return direct; -+ -+ ResourceLocation id = registryAccess.registryOrThrow(Registries.ITEM).getKey(item); -+ for (RecipeType type : registryAccess.registryOrThrow(Registries.RECIPE_TYPE)) { -+ for (Recipe r : MinecraftServer.getServer().getRecipeManager().getAllMatching(type, id, registryAccess)) { -+ for (Ingredient ingredient : r.getIngredients()) { -+ for (Collection stacks : ingredient.getRecipeStacks()) { -+ for (ItemStack iStak : stacks) { -+ TextColor cand = fromItem(iStak.getItem(), registryAccess); -+ if (cand != null) -+ return cand; -+ } -+ } ++ if (direct != null) { ++ return direct; ++ } ++ if (MinecraftServer.getServer() == null) { ++ return WHITE; ++ } ++ ++ ResourceLocation id = registryAccess.lookupOrThrow(Registries.ITEM).getKey(item); ++ if (id == null) { ++ return null; ++ } ++ ++ for (Recipe r : RecipeHelper.getRecipesForOutput(MinecraftServer.getServer().getRecipeManager(), id, level)) { ++ for (Ingredient ingredient : r.placementInfo().ingredients()) { ++ Optional> match = ingredient.items().filter(stack -> fromItem(stack.value(), registryAccess) != null).findFirst(); ++ if (match.isPresent()) { ++ return fromItem(match.get().value(), registryAccess); + } + } + } ++ + return null; + } + @@ -3880,29 +3920,26 @@ index 0000000000000000000000000000000000000000..520aec415548003720af59aa2c3bad3e +} diff --git a/src/main/java/org/leavesmc/lumina/carpet/helpers/ParticleDisplay.java b/src/main/java/org/leavesmc/lumina/carpet/helpers/ParticleDisplay.java new file mode 100644 -index 0000000000000000000000000000000000000000..ec494f4ee53695c06486d8318a6439729883e7f8 +index 0000000000000000000000000000000000000000..9d9fb04e4112f3c46c36185f00d4ca34fc85ef52 --- /dev/null +++ b/src/main/java/org/leavesmc/lumina/carpet/helpers/ParticleDisplay.java -@@ -0,0 +1,40 @@ +@@ -0,0 +1,35 @@ +package org.leavesmc.lumina.carpet.helpers; + -+import org.jetbrains.annotations.NotNull; -+import org.leavesmc.lumina.carpet.script.utils.ParticleParser; +import net.minecraft.core.particles.ParticleOptions; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.phys.Vec3; ++import org.leavesmc.lumina.carpet.script.utils.ParticleParser; + -+public class ParticleDisplay -+{ -+ public static void drawParticleLine(@NotNull ServerPlayer player, Vec3 from, Vec3 to, String main, String accent, int count, double spread) -+ { ++public class ParticleDisplay { ++ public static void drawParticleLine(ServerPlayer player, Vec3 from, Vec3 to, String main, String accent, int count, double spread) { + ParticleOptions accentParticle = ParticleParser.getEffect(accent, player.server.registryAccess()); + ParticleOptions mainParticle = ParticleParser.getEffect(main, player.server.registryAccess()); + + if (accentParticle != null) player.serverLevel().sendParticles( + player, + accentParticle, -+ true, ++ true, true, + to.x, to.y, to.z, count, + spread, spread, spread, 0.0); + @@ -3910,19 +3947,17 @@ index 0000000000000000000000000000000000000000..ec494f4ee53695c06486d8318a643972 + if (lineLengthSq == 0) return; + + Vec3 incvec = to.subtract(from).normalize();// multiply(50/sqrt(lineLengthSq)); -+ for (Vec3 delta = new Vec3(0.0,0.0,0.0); ++ for (Vec3 delta = new Vec3(0.0, 0.0, 0.0); + delta.lengthSqr() < lineLengthSq; -+ delta = delta.add(incvec.scale(player.level().random.nextFloat()))) -+ { ++ delta = delta.add(incvec.scale(player.level().random.nextFloat()))) { + player.serverLevel().sendParticles( + player, + mainParticle, -+ true, -+ delta.x+from.x, delta.y+from.y, delta.z+from.z, 1, ++ true, true, ++ delta.x + from.x, delta.y + from.y, delta.z + from.z, 1, + 0.0, 0.0, 0.0, 0.0); + } + } -+ +} diff --git a/src/main/java/org/leavesmc/lumina/carpet/helpers/QuasiConnectivity.java b/src/main/java/org/leavesmc/lumina/carpet/helpers/QuasiConnectivity.java new file mode 100644 @@ -4573,7 +4608,7 @@ index 0000000000000000000000000000000000000000..e36e5b3661f780faba5995fe1963c2d9 +} diff --git a/src/main/java/org/leavesmc/lumina/carpet/logging/logHelpers/ExplosionLogHelper.java b/src/main/java/org/leavesmc/lumina/carpet/logging/logHelpers/ExplosionLogHelper.java new file mode 100644 -index 0000000000000000000000000000000000000000..4415d3a342d3936cb0b05df4d5e6d3bd1207e03a +index 0000000000000000000000000000000000000000..3f3e494b66da7cd7a5480285c2488ddac1f3ead3 --- /dev/null +++ b/src/main/java/org/leavesmc/lumina/carpet/logging/logHelpers/ExplosionLogHelper.java @@ -0,0 +1,96 @@ @@ -4652,7 +4687,7 @@ index 0000000000000000000000000000000000000000..4415d3a342d3936cb0b05df4d5e6d3bd + messages.add(c((k.pos.equals(pos)) ? "r - TNT" : "w - ", + Messenger.dblt((k.pos.equals(pos)) ? "r" : "y", k.pos.x, k.pos.y, k.pos.z), "w dV", + Messenger.dblt("d", k.accel.x, k.accel.y, k.accel.z), -+ "w " + regs.registryOrThrow(Registries.ENTITY_TYPE).getKey(k.type).getPath(), (v > 1) ? "l (" + v + ")" : "" ++ "w " + regs.lookupOrThrow(Registries.ENTITY_TYPE).getKey(k.type).getPath(), (v > 1) ? "l (" + v + ")" : "" + )); + }); + } @@ -5309,7 +5344,7 @@ index 0000000000000000000000000000000000000000..cc9b7281eadf2ca97ded226c19559b0d +} diff --git a/src/main/java/org/leavesmc/lumina/carpet/utils/BlockInfo.java b/src/main/java/org/leavesmc/lumina/carpet/utils/BlockInfo.java new file mode 100644 -index 0000000000000000000000000000000000000000..029be60570be67ce8f85df7932e7d6945de43f96 +index 0000000000000000000000000000000000000000..d35a4dbd9ef43be2132c990edc0c7a9a0bf3e7ba --- /dev/null +++ b/src/main/java/org/leavesmc/lumina/carpet/utils/BlockInfo.java @@ -0,0 +1,112 @@ @@ -5318,6 +5353,7 @@ index 0000000000000000000000000000000000000000..029be60570be67ce8f85df7932e7d694 +import java.util.ArrayList; +import java.util.List; + ++import net.minecraft.world.entity.EntitySpawnReason; +import org.leavesmc.lumina.carpet.script.utils.Colors; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Registry; @@ -5325,7 +5361,6 @@ index 0000000000000000000000000000000000000000..029be60570be67ce8f85df7932e7d694 +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.EntityType; -+import net.minecraft.world.entity.MobSpawnType; +import net.minecraft.world.entity.PathfinderMob; +import net.minecraft.world.entity.ai.goal.RandomStrollGoal; +import net.minecraft.world.entity.ai.util.DefaultRandomPos; @@ -5344,7 +5379,7 @@ index 0000000000000000000000000000000000000000..029be60570be67ce8f85df7932e7d694 + BlockState state = world.getBlockState(pos); + Block block = state.getBlock(); + String metastring = ""; -+ final Registry blocks = world.registryAccess().registryOrThrow(Registries.BLOCK); ++ final Registry blocks = world.registryAccess().lookupOrThrow(Registries.BLOCK); + for (net.minecraft.world.level.block.state.properties.Property iproperty : state.getProperties()) + { + metastring += ", "+iproperty.getName() + '='+state.getValue(iproperty); @@ -5390,7 +5425,7 @@ index 0000000000000000000000000000000000000000..029be60570be67ce8f85df7932e7d694 + private static Component wander_chances(BlockPos pos, ServerLevel worldIn) + { + PathfinderMob creature = new ZombifiedPiglin(EntityType.ZOMBIFIED_PIGLIN, worldIn); -+ creature.finalizeSpawn(worldIn, worldIn.getCurrentDifficultyAt(pos), MobSpawnType.NATURAL, null); ++ creature.finalizeSpawn(worldIn, worldIn.getCurrentDifficultyAt(pos), EntitySpawnReason.NATURAL, null); + creature.moveTo(pos, 0.0F, 0.0F); + RandomStrollGoal wander = new RandomStrollGoal(creature, 0.8D); + int success = 0; @@ -5639,10 +5674,10 @@ index 0000000000000000000000000000000000000000..3da1c5a39484ae2e0b394ab02f48e0fa \ No newline at end of file diff --git a/src/main/java/org/leavesmc/lumina/carpet/utils/Messenger.java b/src/main/java/org/leavesmc/lumina/carpet/utils/Messenger.java new file mode 100644 -index 0000000000000000000000000000000000000000..0eb66c8bd216e84e5ad946b9cc4c2507a4fd0702 +index 0000000000000000000000000000000000000000..2e764f32a56b8d353a852901f6c78ddc93c53191 --- /dev/null +++ b/src/main/java/org/leavesmc/lumina/carpet/utils/Messenger.java -@@ -0,0 +1,307 @@ +@@ -0,0 +1,308 @@ +package org.leavesmc.lumina.carpet.utils; + +import net.minecraft.ChatFormatting; @@ -5655,6 +5690,7 @@ index 0000000000000000000000000000000000000000..0eb66c8bd216e84e5ad946b9cc4c2507 +import net.minecraft.network.chat.Style; +import net.minecraft.network.chat.TextColor; +import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.MobCategory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.phys.Vec3; @@ -5885,7 +5921,7 @@ index 0000000000000000000000000000000000000000..0eb66c8bd216e84e5ad946b9cc4c2507 + } + + public static void m(Player player, Object... fields) { -+ player.sendSystemMessage(Messenger.c(fields)); ++ ((ServerPlayer)player).sendSystemMessage(Messenger.c(fields)); + } + + /* @@ -5922,7 +5958,7 @@ index 0000000000000000000000000000000000000000..0eb66c8bd216e84e5ad946b9cc4c2507 + + + public static void send(Player player, Collection lines) { -+ lines.forEach(message -> player.sendSystemMessage(message)); ++ lines.forEach(message -> ((ServerPlayer)player).sendSystemMessage(message)); + } + + public static void send(CommandSourceStack source, Collection lines) { @@ -5936,7 +5972,7 @@ index 0000000000000000000000000000000000000000..0eb66c8bd216e84e5ad946b9cc4c2507 + server.sendSystemMessage(Component.literal(message)); + Component txt = c("gi " + message); + for (Player entityplayer : server.getPlayerList().getPlayers()) { -+ entityplayer.sendSystemMessage(txt); ++ ((ServerPlayer)entityplayer).sendSystemMessage(txt); + } + } + @@ -5945,7 +5981,7 @@ index 0000000000000000000000000000000000000000..0eb66c8bd216e84e5ad946b9cc4c2507 + LOG.error("Message not delivered: " + message.getString()); + server.sendSystemMessage(message); + for (Player entityplayer : server.getPlayerList().getPlayers()) { -+ entityplayer.sendSystemMessage(message); ++ ((ServerPlayer)entityplayer).sendSystemMessage(message); + } + } +} @@ -6015,10 +6051,10 @@ index 0000000000000000000000000000000000000000..7ce683d3ee99fd2236b088a23e588999 +} diff --git a/src/main/java/org/leavesmc/lumina/carpet/utils/PerimeterDiagnostics.java b/src/main/java/org/leavesmc/lumina/carpet/utils/PerimeterDiagnostics.java new file mode 100644 -index 0000000000000000000000000000000000000000..d3668806d79a47fe4bc04a464d6b1a581fcfad0e +index 0000000000000000000000000000000000000000..ddeaf1538643e914ffec3c5c5ba93dbbe373bbc3 --- /dev/null +++ b/src/main/java/org/leavesmc/lumina/carpet/utils/PerimeterDiagnostics.java -@@ -0,0 +1,161 @@ +@@ -0,0 +1,156 @@ +package org.leavesmc.lumina.carpet.utils; + +import java.util.ArrayList; @@ -6027,12 +6063,7 @@ index 0000000000000000000000000000000000000000..d3668806d79a47fe4bc04a464d6b1a58 +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.tags.FluidTags; -+import net.minecraft.world.entity.AgeableMob; -+import net.minecraft.world.entity.EntityType; -+import net.minecraft.world.entity.Mob; -+import net.minecraft.world.entity.MobCategory; -+import net.minecraft.world.entity.MobSpawnType; -+import net.minecraft.world.entity.SpawnPlacements; ++import net.minecraft.world.entity.*; +import net.minecraft.world.entity.ambient.AmbientCreature; +import net.minecraft.world.entity.animal.WaterAnimal; +import net.minecraft.world.entity.monster.Enemy; @@ -6099,8 +6130,8 @@ index 0000000000000000000000000000000000000000..d3668806d79a47fe4bc04a464d6b1a58 + PerimeterDiagnostics diagnostic = new PerimeterDiagnostics(worldserver, ctype, el); + EntityType type = EntityType.ZOMBIE; + if (el != null) type = el.getType(); -+ int minY = worldserver.getMinBuildHeight(); -+ int maxY = worldserver.getMaxBuildHeight(); ++ int minY = worldserver.getMinY(); ++ int maxY = worldserver.getMaxY(); + for (int x = -128; x <= 128; ++x) { + for (int z = -128; z <= 128; ++z) { + if (x * x + z * z > 128 * 128) // cut out a cyllinder first @@ -6173,16 +6204,84 @@ index 0000000000000000000000000000000000000000..d3668806d79a47fe4bc04a464d6b1a58 + + if (SpawnPlacements.isSpawnPositionOk(sle.type, worldServer, pos)) { + el.moveTo(pos.getX() + 0.5F, pos.getY(), pos.getZ() + 0.5F, 0.0F, 0.0F); -+ return el.checkSpawnObstruction(worldServer) && el.checkSpawnRules(worldServer, MobSpawnType.NATURAL) && -+ SpawnPlacements.checkSpawnRules(el.getType(), (ServerLevel) el.getCommandSenderWorld(), MobSpawnType.NATURAL, el.blockPosition(), el.getCommandSenderWorld().random) && ++ return el.checkSpawnObstruction(worldServer) && el.checkSpawnRules(worldServer, EntitySpawnReason.NATURAL) && ++ SpawnPlacements.checkSpawnRules(el.getType(), (ServerLevel) el.getCommandSenderWorld(), EntitySpawnReason.NATURAL, el.blockPosition(), el.getCommandSenderWorld().random) && + worldServer.noCollision(el); // check collision rules once they stop fiddling with them after 1.14.1 + } + return false; + } +} +diff --git a/src/main/java/org/leavesmc/lumina/carpet/utils/RecipeHelper.java b/src/main/java/org/leavesmc/lumina/carpet/utils/RecipeHelper.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7fff9db7d097e5abf3e1c552652e7524683875ed +--- /dev/null ++++ b/src/main/java/org/leavesmc/lumina/carpet/utils/RecipeHelper.java +@@ -0,0 +1,61 @@ ++package org.leavesmc.lumina.carpet.utils; ++ ++import net.minecraft.core.registries.BuiltInRegistries; ++import net.minecraft.resources.ResourceKey; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.util.context.ContextMap; ++import net.minecraft.world.item.crafting.Recipe; ++import net.minecraft.world.item.crafting.RecipeManager; ++import net.minecraft.world.item.crafting.RecipeType; ++import net.minecraft.world.item.crafting.display.RecipeDisplay; ++import net.minecraft.world.item.crafting.display.SlotDisplayContext; ++import net.minecraft.world.level.Level; ++ ++import java.util.ArrayList; ++import java.util.List; ++ ++public class RecipeHelper ++{ ++ public static List> getRecipesForOutput(RecipeManager recipeManager, RecipeType type, ResourceLocation id, Level level) ++ { ++ List> results = new ArrayList<>(); ++ ++ ++ ContextMap context = SlotDisplayContext.fromLevel(level); ++ recipeManager.getRecipes().forEach(r -> { ++ if (r.value().getType() == type) ++ { ++ for (RecipeDisplay recipeDisplay : r.value().display()) ++ { ++ recipeDisplay.result().resolveForStacks(context).forEach(stack -> { ++ if (BuiltInRegistries.ITEM.wrapAsHolder(stack.getItem()).unwrapKey().map(ResourceKey::location).orElseThrow(IllegalStateException::new).equals(id)) ++ { ++ results.add(r.value()); ++ } ++ }); ++ } ++ } ++ }); ++ return results; ++ } ++ ++ public static List> getRecipesForOutput(RecipeManager recipeManager, ResourceLocation id, Level level) ++ { ++ List> results = new ArrayList<>(); ++ ++ ContextMap context = SlotDisplayContext.fromLevel(level); ++ recipeManager.getRecipes().forEach(r -> { ++ for (RecipeDisplay recipeDisplay : r.value().display()) ++ { ++ recipeDisplay.result().resolveForStacks(context).forEach(stack -> { ++ if (BuiltInRegistries.ITEM.wrapAsHolder(stack.getItem()).unwrapKey().map(ResourceKey::location).orElseThrow(IllegalStateException::new).equals(id)) ++ { ++ results.add(r.value()); ++ } ++ }); ++ } ++ ++ }); ++ return results; ++ } ++} +\ No newline at end of file diff --git a/src/main/java/org/leavesmc/lumina/carpet/utils/SpawnOverrides.java b/src/main/java/org/leavesmc/lumina/carpet/utils/SpawnOverrides.java new file mode 100644 -index 0000000000000000000000000000000000000000..89f9d60227768909dbb0cb48ad02c6545afb7c42 +index 0000000000000000000000000000000000000000..54917a2efe2ef5b108c53eb72a0d3a686fa15bf3 --- /dev/null +++ b/src/main/java/org/leavesmc/lumina/carpet/utils/SpawnOverrides.java @@ -0,0 +1,99 @@ @@ -6242,7 +6341,7 @@ index 0000000000000000000000000000000000000000..89f9d60227768909dbb0cb48ad02c654 + + public static WeightedRandomList test(StructureManager structureFeatureManager, LongSet foo, + MobCategory cat, Structure confExisting, BlockPos where) { -+ ResourceLocation resource = structureFeatureManager.registryAccess().registryOrThrow(Registries.STRUCTURE).getKey(confExisting); ++ ResourceLocation resource = structureFeatureManager.registryAccess().lookupOrThrow(Registries.STRUCTURE).getKey(confExisting); + ResourceKey key = ResourceKey.create(Registries.STRUCTURE, resource); + final Pair spawnData = carpetOverrides.get(Pair.of(cat, key)); + if (spawnData == null || !spawnData.getKey().getAsBoolean()) return null; @@ -6264,7 +6363,7 @@ index 0000000000000000000000000000000000000000..89f9d60227768909dbb0cb48ad02c654 + + public static boolean isStructureAtPosition(ServerLevel level, ResourceKey structureKey, BlockPos pos) + { -+ final Structure fortressFeature = level.registryAccess().registryOrThrow(Registries.STRUCTURE).get(structureKey); ++ final Structure fortressFeature = level.registryAccess().lookupOrThrow(Registries.STRUCTURE).getValue(structureKey); + if (fortressFeature == null) { + return false; + } @@ -6287,10 +6386,10 @@ index 0000000000000000000000000000000000000000..89f9d60227768909dbb0cb48ad02c654 +} diff --git a/src/main/java/org/leavesmc/lumina/carpet/utils/SpawnReporter.java b/src/main/java/org/leavesmc/lumina/carpet/utils/SpawnReporter.java new file mode 100644 -index 0000000000000000000000000000000000000000..a2829333c30f90e09941815b360a87526b962083 +index 0000000000000000000000000000000000000000..171b4e2ddf4827b6332debdd508be6a40f4103af --- /dev/null +++ b/src/main/java/org/leavesmc/lumina/carpet/utils/SpawnReporter.java -@@ -0,0 +1,436 @@ +@@ -0,0 +1,430 @@ +package org.leavesmc.lumina.carpet.utils; + +import it.unimi.dsi.fastutil.objects.Object2IntMap; @@ -6304,13 +6403,7 @@ index 0000000000000000000000000000000000000000..a2829333c30f90e09941815b360a8752 +import net.minecraft.server.level.ServerLevel; +import net.minecraft.tags.BlockTags; +import net.minecraft.util.Mth; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.entity.EntityType; -+import net.minecraft.world.entity.LivingEntity; -+import net.minecraft.world.entity.Mob; -+import net.minecraft.world.entity.MobCategory; -+import net.minecraft.world.entity.MobSpawnType; -+import net.minecraft.world.entity.SpawnPlacements; ++import net.minecraft.world.entity.*; +import net.minecraft.world.entity.animal.Ocelot; +import net.minecraft.world.item.DyeColor; +import net.minecraft.world.level.Level; @@ -6330,13 +6423,13 @@ index 0000000000000000000000000000000000000000..a2829333c30f90e09941815b360a8752 +import org.jetbrains.annotations.Nullable; +import org.leavesmc.lumina.carpet.CarpetServer; + -+import static net.minecraft.world.entity.MobCategory.*; -+ +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; + ++import static net.minecraft.world.entity.MobCategory.*; ++ +public class SpawnReporter { + private static final MobCategory[] CACHED_MOBCATEGORY_VALUES = MobCategory.values(); + public static boolean mockSpawns = false; @@ -6658,7 +6751,7 @@ index 0000000000000000000000000000000000000000..a2829333c30f90e09941815b360a8752 + + Mob mob; + try { -+ mob = (Mob) spawnEntry.type.create(worldIn); ++ mob = (Mob) spawnEntry.type.create(worldIn, EntitySpawnReason.NATURAL); + } catch (Exception e) { + CarpetServer.LOGGER.warn("Exception while creating mob for spawn reporter", e); + return rep; @@ -6675,9 +6768,9 @@ index 0000000000000000000000000000000000000000..a2829333c30f90e09941815b360a8752 + + for (int i = 0; i < 20; ++i) { + if ( -+ SpawnPlacements.checkSpawnRules(etype, worldIn, MobSpawnType.NATURAL, pos, worldIn.random) && ++ SpawnPlacements.checkSpawnRules(etype, worldIn, EntitySpawnReason.NATURAL, pos, worldIn.random) && + SpawnPlacements.isSpawnPositionOk(etype, worldIn, pos) && -+ mob.checkSpawnRules(worldIn, MobSpawnType.NATURAL) ++ mob.checkSpawnRules(worldIn, EntitySpawnReason.NATURAL) + // && mob.canSpawn(worldIn) // entity collisions // mostly - except ocelots + ) { + if (etype == EntityType.OCELOT) { @@ -6689,14 +6782,14 @@ index 0000000000000000000000000000000000000000..a2829333c30f90e09941815b360a8752 + willSpawn += 1; + } + } -+ mob.finalizeSpawn(worldIn, worldIn.getCurrentDifficultyAt(mob.blockPosition()), MobSpawnType.NATURAL, null); ++ mob.finalizeSpawn(worldIn, worldIn.getCurrentDifficultyAt(mob.blockPosition()), EntitySpawnReason.NATURAL, null); + // the code invokes onInitialSpawn after getCanSpawHere + fits = fits && worldIn.noCollision(mob); + + killEntity(mob); + + try { -+ mob = (Mob) spawnEntry.type.create(worldIn); ++ mob = (Mob) spawnEntry.type.create(worldIn, EntitySpawnReason.NATURAL); + } catch (Exception e) { + CarpetServer.LOGGER.warn("Exception while creating mob for spawn reporter", e); + return rep; @@ -6705,7 +6798,7 @@ index 0000000000000000000000000000000000000000..a2829333c30f90e09941815b360a8752 + } + + String mobTypeName = mob.getType().getDescription().getString(); -+ // String pack_size = Integer.toString(mob.getMaxSpawnClusterSize());//String.format("%d-%d", animal.minGroupCount, animal.maxGroupCount); ++ //String pack_size = Integer.toString(mob.getMaxSpawnClusterSize());//String.format("%d-%d", animal.minGroupCount, animal.maxGroupCount); + int weight = spawnEntry.getWeight().asInt(); + if (canSpawn) { + String color = (fits && willSpawn > 0) ? "e" : "gi"; @@ -6891,12 +6984,13 @@ index 0000000000000000000000000000000000000000..af4b84fc175a0e75ede7cd4849cd5280 +} diff --git a/src/main/java/org/leavesmc/lumina/carpet/utils/WoolTool.java b/src/main/java/org/leavesmc/lumina/carpet/utils/WoolTool.java new file mode 100644 -index 0000000000000000000000000000000000000000..f8f59c96589643fb054bb1640a2439a35c6950c5 +index 0000000000000000000000000000000000000000..35bee3437502f9dcbefe3fa472c036367fce6bbe --- /dev/null +++ b/src/main/java/org/leavesmc/lumina/carpet/utils/WoolTool.java -@@ -0,0 +1,119 @@ +@@ -0,0 +1,120 @@ +package org.leavesmc.lumina.carpet.utils; + ++import net.minecraft.server.level.ServerPlayer; +import org.leavesmc.lumina.carpet.CarpetConfig; +import org.leavesmc.lumina.carpet.helpers.HopperCounter; + @@ -6971,7 +7065,7 @@ index 0000000000000000000000000000000000000000..f8f59c96589643fb054bb1640a2439a3 + break; + case BROWN: + if (!CarpetConfig.configModule().commandDistance) { -+ CommandSourceStack source = placer.createCommandSourceStack(); ++ CommandSourceStack source = ((ServerPlayer)placer).createCommandSourceStack(); + if (!DistanceCalculator.hasStartingPoint(source) || placer.isShiftKeyDown()) { + DistanceCalculator.setStart(source, Vec3.atLowerCornerOf(pos)); // zero padded pos + } else { diff --git a/patches/server/0043-Customize-carpet-features.patch b/patches/server/0043-Customize-carpet-features.patch index 72c5a6d..95653bd 100644 --- a/patches/server/0043-Customize-carpet-features.patch +++ b/patches/server/0043-Customize-carpet-features.patch @@ -77,7 +77,7 @@ index d9e8da269da4f985109d6cd870b22efea5628f71..bc242e2628d2ec293731f895f9d87738 @Nullable diff --git a/src/main/java/net/minecraft/world/level/block/AbstractCauldronBlock.java b/src/main/java/net/minecraft/world/level/block/AbstractCauldronBlock.java -index e00ab1ed8088a1970249313ed63e09070fc6192d..5d9a296497b5a054d0c3233ad03ec1f1347a1100 100644 +index e00ab1ed8088a1970249313ed63e09070fc6192d..90a35616a4fc011ff1b1e2e2a551778e65433ca1 100644 --- a/src/main/java/net/minecraft/world/level/block/AbstractCauldronBlock.java +++ b/src/main/java/net/minecraft/world/level/block/AbstractCauldronBlock.java @@ -56,9 +56,27 @@ public abstract class AbstractCauldronBlock extends Block { @@ -89,13 +89,13 @@ index e00ab1ed8088a1970249313ed63e09070fc6192d..5d9a296497b5a054d0c3233ad03ec1f1 + return wrapInteractor(cauldronInteraction, state, world, pos, player, hand, stack, hit.getDirection()); // Paper - pass hit direction } -+ private InteractionResult wrapInteractor(CauldronInteraction cauldronBehavior, BlockState blockState, Level world, BlockPos blockPos, Player playerEntity, InteractionHand hand, ItemStack itemStack) { ++ private InteractionResult wrapInteractor(CauldronInteraction cauldronBehavior, BlockState blockState, Level world, BlockPos blockPos, Player playerEntity, InteractionHand hand, ItemStack itemStack, net.minecraft.core.Direction hitDirection) { + int count = -1; + if (org.leavesmc.lumina.carpet.CarpetConfig.configModule().shulkerBoxStackSize > 1 && itemStack.getItem() instanceof net.minecraft.world.item.BlockItem bi && + bi.getBlock() instanceof ShulkerBoxBlock) { + count = itemStack.getCount(); + } -+ InteractionResult result = cauldronBehavior.interact(blockState, world, blockPos, playerEntity, hand, itemStack); ++ InteractionResult result = cauldronBehavior.interact(blockState, world, blockPos, playerEntity, hand, itemStack, hitDirection); + if (count > 0 && result.consumesAction()) { + ItemStack current = playerEntity.getItemInHand(hand); + if (current.getItem() instanceof net.minecraft.world.item.BlockItem bi && bi.getBlock() instanceof ShulkerBoxBlock) { diff --git a/patches/server/0044-Rewrite-nether-portal-find-logic.patch b/patches/server/0044-Rewrite-nether-portal-find-logic.patch index 44fae9a..81f12b1 100644 --- a/patches/server/0044-Rewrite-nether-portal-find-logic.patch +++ b/patches/server/0044-Rewrite-nether-portal-find-logic.patch @@ -5,14 +5,14 @@ Subject: [PATCH] Rewrite nether portal find logic diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/PoiChunk.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/PoiChunk.java -index bbf9d6c1c9525d97160806819a57be03eca290f1..6b20e9df01527f82e31d0536235d613e1c1a08ff 100644 +index bbf9d6c1c9525d97160806819a57be03eca290f1..543f751624a2e50163e5cd68ca145398c04e74dd 100644 --- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/PoiChunk.java +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/poi/PoiChunk.java @@ -192,6 +192,7 @@ public final class PoiChunk { // completely empty, no point in storing this continue; } -+ world.chunkTaskScheduler.scheduleChunkTask(chunkX, chunkZ, () -> { for (net.minecraft.core.BlockPos pos : deserialized.loadedNetherPortalPoi) world.netherPortalPoiManager.addPoi(pos, world.getBlockState(pos)); }, ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.HIGHEST); // Lumina - rewrite nether portal find logic ++ world.chunkTaskScheduler.scheduleChunkTask(chunkX, chunkZ, () -> { for (net.minecraft.core.BlockPos pos : deserialized.loadedNetherPortalPoi) world.netherPortalPoiManager.addPoi(pos, world.getBlockState(pos)); }, ca.spottedleaf.concurrentutil.util.Priority.HIGHEST); // Lumina - rewrite nether portal find logic readAnything = true; ret.sections[sectionY - ret.minSection] = deserialized; @@ -91,7 +91,7 @@ index 137a15e142c6e934804e3f2a072cdac8d40a2ab2..404f27dd09f6f136b51a1b2eeec8da03 return new SectionPos(x, y, z); } diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index fa5ed398ae1c74775e93eb3b80abde324a1d2b9d..b722358325024f997369ac1f14027dfb92b1247b 100644 +index 8ca98b4b4854294a1659ec2a9f7f55733685d8aa..cfa568e169b5e09fed0936ecc00906bfa25c109d 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java @@ -225,6 +225,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @@ -111,7 +111,7 @@ index fa5ed398ae1c74775e93eb3b80abde324a1d2b9d..b722358325024f997369ac1f14027dfb this.prepareWeather(); this.getWorldBorder().setAbsoluteMaxSize(minecraftserver.getAbsoluteMaxWorldSize()); diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index a9d879814a051288fe1777d6fe7575f271f6426f..a8bc8cc3a339aa55090bebff29056864e2d1d366 100644 +index 1998d2133d68e9aaa2881688e765bfe0157a39b6..6e91b6c6b72671ec6fddc5e29b7bed8274a45dc6 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -4604,12 +4604,16 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess @@ -165,7 +165,7 @@ index 827991ee61406bcda3f4794dcc735c0e2e0e09af..d6cfca3a47285acc04f8466f7c95f350 this.records.put(s, poi); this.byType.computeIfAbsent(holder, type -> Sets.newHashSet()).add(poi); diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index f2a54bab22d8e46616a4f4bda9b5a5988829d8d4..d6cc1c6dbb4ae2422f16ae26870a5cdd9fa65009 100644 +index 955d9073287eda25032a4a3a7de8fee2ffcac8f7..cafecd44b1341127e1dd9d98b31b70d10d616113 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java @@ -60,6 +60,7 @@ import net.minecraft.world.level.block.entity.BlockEntity; diff --git a/patches/server/0045-Leaves-Fakeplayer.patch b/patches/server/0045-Leaves-Fakeplayer.patch index 564ba1d..8f97f12 100644 --- a/patches/server/0045-Leaves-Fakeplayer.patch +++ b/patches/server/0045-Leaves-Fakeplayer.patch @@ -48,7 +48,7 @@ index 35772110e9318df46a2729dbc0b5879b290011b7..f26989a44cdda9baabf337d573436c6c Set> set = (Set) playerAdvancements.criterionData.get(this); // Paper - fix AdvancementDataPlayer leak if (set != null && !set.isEmpty()) { diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 3f15e9c90d5ea9eaf009e50383fd9d128327ade5..018f5adcc0ffcf1cbdcf87dcb140c855bfa10965 100644 +index 1350aad84f37671a58a85b73331fb6b849322b28..64d85ba034fcd2c46fa244c7bb95c201f60debe7 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -377,6 +377,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop realPlayers = Lists.newArrayList(); // Leaves - skip + + // CraftBukkit start + public final LevelStorageSource.LevelStorageAccess convertable; +@@ -2323,6 +2324,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe return this.players; } @@ -157,7 +165,7 @@ index b722358325024f997369ac1f14027dfb92b1247b..bc5ccefdc8f4cb11172910864de63361 @Override public void onBlockStateChange(BlockPos pos, BlockState oldBlock, BlockState newBlock) { Optional> optional = PoiTypes.forState(oldBlock); -@@ -2839,6 +2845,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -2839,6 +2846,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe // ServerLevel.this.getChunkSource().addEntity(entity); // Paper - ignore and warn about illegal addEntity calls instead of crashing server; moved down below valid=true if (entity instanceof ServerPlayer entityplayer) { ServerLevel.this.players.add(entityplayer); @@ -169,7 +177,7 @@ index b722358325024f997369ac1f14027dfb92b1247b..bc5ccefdc8f4cb11172910864de63361 ServerLevel.this.updateSleepingPlayerList(); } -@@ -2931,6 +2942,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -2931,6 +2943,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe ServerLevel.this.getChunkSource().removeEntity(entity); if (entity instanceof ServerPlayer entityplayer) { ServerLevel.this.players.remove(entityplayer); @@ -182,7 +190,7 @@ index b722358325024f997369ac1f14027dfb92b1247b..bc5ccefdc8f4cb11172910864de63361 } diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 93d5ad158635a67dfc5966d9482a3432f68b36bc..817460e24c853d06dc73110c5cb9e4d0615464ae 100644 +index 22165b6fca47d4ac43d7f3c70915d6bfe8b78ba3..10e740fb2a76f5b73ce1f11bffdd0a7bbc76374f 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java @@ -233,7 +233,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple @@ -328,7 +336,7 @@ index 8273b710e83dcf29763ffd83a04bee56f93bfa50..ad7fb124517ae0cb352082d7a4fc716b public boolean canBypassPlayerLimit(GameProfile profile) { diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index a8bc8cc3a339aa55090bebff29056864e2d1d366..6e3d3aca61452769d7e3eaa047a897f5cdff9338 100644 +index 6e91b6c6b72671ec6fddc5e29b7bed8274a45dc6..2648d84574590235c15d89e02d5fecbed238c184 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -1587,7 +1587,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess @@ -1042,10 +1050,10 @@ index 0000000000000000000000000000000000000000..4f5e6e5c1b9d8bd38c98e97fd31b3833 +} diff --git a/src/main/java/org/leavesmc/leaves/bot/BotList.java b/src/main/java/org/leavesmc/leaves/bot/BotList.java new file mode 100644 -index 0000000000000000000000000000000000000000..dbd3e897c3f72982297695b93b2cced75082cd7a +index 0000000000000000000000000000000000000000..bb5c55104bf3b91a2100790432e1dd41d7c27c48 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/BotList.java -@@ -0,0 +1,344 @@ +@@ -0,0 +1,299 @@ +package org.leavesmc.leaves.bot; + +import com.google.common.collect.Maps; @@ -1169,6 +1177,8 @@ index 0000000000000000000000000000000000000000..dbd3e897c3f72982297695b93b2cced7 + } + + public ServerBot placeNewBot(ServerBot bot, ServerLevel world, Location location, @Nullable CompoundTag nbt) { ++ Optional optional = Optional.ofNullable(nbt); ++ + bot.isRealPlayer = true; + bot.connection = new ServerBotPacketListenerImpl(this.server, bot); + bot.setServerLevel(world); @@ -1189,7 +1199,8 @@ index 0000000000000000000000000000000000000000..dbd3e897c3f72982297695b93b2cced7 + + bot.supressTrackerForLogin = true; + world.addNewPlayer(bot); -+ this.mountSavedVehicle(bot, world, nbt); ++ bot.loadAndSpawnEnderpearls(optional); ++ bot.loadAndSpawnParentVehicle(optional); + + BotJoinEvent event1 = new BotJoinEvent(bot.getBukkitEntity(), PaperAdventure.asAdventure(Component.translatable("multiplayer.player.joined", bot.getDisplayName())).style(Style.style(NamedTextColor.YELLOW))); + this.server.server.getPluginManager().callEvent(event1); @@ -1206,54 +1217,6 @@ index 0000000000000000000000000000000000000000..dbd3e897c3f72982297695b93b2cced7 + return bot; + } + -+ private void mountSavedVehicle(ServerPlayer player, ServerLevel worldserver1, @Nullable CompoundTag nbt) { -+ Optional optional = Optional.ofNullable(nbt); -+ if (optional.isPresent() && optional.get().contains("RootVehicle", 10)) { -+ CompoundTag nbttagcompound = optional.get().getCompound("RootVehicle"); -+ Entity entity = EntityType.loadEntityRecursive(nbttagcompound.getCompound("Entity"), worldserver1, (entity1) -> { -+ return !worldserver1.addWithUUID(entity1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.MOUNT) ? null : entity1; -+ }); -+ -+ if (entity != null) { -+ UUID uuid; -+ -+ if (nbttagcompound.hasUUID("Attach")) { -+ uuid = nbttagcompound.getUUID("Attach"); -+ } else { -+ uuid = null; -+ } -+ -+ Iterator iterator; -+ Entity entity1; -+ -+ if (entity.getUUID().equals(uuid)) { -+ player.startRiding(entity, true); -+ } else { -+ iterator = entity.getIndirectPassengers().iterator(); -+ -+ while (iterator.hasNext()) { -+ entity1 = iterator.next(); -+ if (entity1.getUUID().equals(uuid)) { -+ player.startRiding(entity1, true); -+ break; -+ } -+ } -+ } -+ -+ if (!player.isPassenger()) { -+ BotList.LOGGER.warn("Couldn't reattach entity to fakeplayer"); -+ entity.discard(); -+ iterator = entity.getIndirectPassengers().iterator(); -+ -+ while (iterator.hasNext()) { -+ entity1 = iterator.next(); -+ entity1.discard(); -+ } -+ } -+ } -+ } -+ } -+ + public boolean removeBot(@NotNull ServerBot bot, @NotNull BotRemoveEvent.RemoveReason reason, @Nullable CommandSender remover, boolean saved) { + return this.removeBot(bot, reason, remover, saved, this.dataStorage); + } @@ -1577,10 +1540,10 @@ index 0000000000000000000000000000000000000000..34af4b59a4b4f9572e45d4e691a6f260 +} diff --git a/src/main/java/org/leavesmc/leaves/bot/ServerBot.java b/src/main/java/org/leavesmc/leaves/bot/ServerBot.java new file mode 100644 -index 0000000000000000000000000000000000000000..00e5c4a7037bb8a52ac68fecc2c1d165b29c27f2 +index 0000000000000000000000000000000000000000..eb5b2f7f2f19ef6419b5ea2a5e4035e88d7498dd --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/ServerBot.java -@@ -0,0 +1,525 @@ +@@ -0,0 +1,522 @@ +package org.leavesmc.leaves.bot; + +import com.google.common.collect.ImmutableMap; @@ -1708,12 +1671,15 @@ index 0000000000000000000000000000000000000000..00e5c4a7037bb8a52ac68fecc2c1d165 + } + + public void sendFakeData(ServerPlayerConnection playerConnection, boolean login) { -+ if (this.tracker == null) { ++ ChunkMap.TrackedEntity entityTracker = null; // TODO ++ // this.serverLevel().getChunkSource().chunkMap.entityMap.get(this.getId()); ++ ++ if (entityTracker == null) { + LOGGER.warn("Fakeplayer cant get entity tracker for " + this.getId()); + return; + } + -+ playerConnection.send(this.getAddEntityPacket()); ++ playerConnection.send(this.getAddEntityPacket(entityTracker.serverEntity)); + if (login) { + Bukkit.getScheduler().runTaskLater(MinecraftInternalPlugin.INSTANCE, () -> playerConnection.send(new ClientboundRotateHeadPacket(this, (byte) ((getYRot() * 256f) / 360f))), 10); + } else { @@ -1736,7 +1702,7 @@ index 0000000000000000000000000000000000000000..00e5c4a7037bb8a52ac68fecc2c1d165 + + @Override + public void die(@NotNull DamageSource damageSource) { -+ boolean flag = this.level().getGameRules().getBoolean(GameRules.RULE_SHOWDEATHMESSAGES); ++ boolean flag = this.serverLevel().getGameRules().getBoolean(GameRules.RULE_SHOWDEATHMESSAGES); + Component defaultMessage = this.getCombatTracker().getDeathMessage(); + + BotDeathEvent event = new BotDeathEvent(this.getBukkitEntity(), PaperAdventure.asAdventure(defaultMessage), flag); @@ -1763,12 +1729,6 @@ index 0000000000000000000000000000000000000000..00e5c4a7037bb8a52ac68fecc2c1d165 + this.sendPacket(new ClientboundPlayerInfoRemovePacket(List.of(this.getUUID()))); + } + -+ @Nullable -+ @Override -+ public Entity changeDimension(@NotNull ServerLevel teleportTarget) { -+ return null; // disable dimension change -+ } -+ + @Override + public void tick() { + if (!this.isAlive()) { @@ -1822,7 +1782,7 @@ index 0000000000000000000000000000000000000000..00e5c4a7037bb8a52ac68fecc2c1d165 + @Override + public void checkFallDamage(double heightDifference, boolean onGround, @NotNull BlockState state, @NotNull BlockPos landedPosition) { + if (onGround && this.fallDistance > 0.0F) { -+ this.onChangedBlock(landedPosition); ++ this.onChangedBlock(this.serverLevel(), landedPosition); + double d1 = this.getAttributeValue(Attributes.SAFE_FALL_DISTANCE); + + if ((double) this.fallDistance > d1 && !state.isAir()) { @@ -1844,7 +1804,7 @@ index 0000000000000000000000000000000000000000..00e5c4a7037bb8a52ac68fecc2c1d165 + double d8 = Math.min(0.2F + f / 15.0F, 2.5D); + int i = (int) (150.0D * d8); + -+ this.serverLevel().sendParticles(this, new BlockParticleOption(ParticleTypes.BLOCK, state), d2, d3, d4, i, 0.0D, 0.0D, 0.0D, 0.15000000596046448D, false); ++ // this.serverLevel().sendParticles(this, new BlockParticleOption(ParticleTypes.BLOCK, state), d2, d3, d4, i, 0.0D, 0.0D, 0.0D, 0.15000000596046448D, false); // TODO + } + } + @@ -2108,10 +2068,10 @@ index 0000000000000000000000000000000000000000..00e5c4a7037bb8a52ac68fecc2c1d165 +} diff --git a/src/main/java/org/leavesmc/leaves/bot/ServerBotGameMode.java b/src/main/java/org/leavesmc/leaves/bot/ServerBotGameMode.java new file mode 100644 -index 0000000000000000000000000000000000000000..068bd6ce5fe4bdc81a10bd1697b4c27480f87f00 +index 0000000000000000000000000000000000000000..b09df8dfe88766b57338fd3284b9a6a0f2c5ecb7 --- /dev/null +++ b/src/main/java/org/leavesmc/leaves/bot/ServerBotGameMode.java -@@ -0,0 +1,138 @@ +@@ -0,0 +1,137 @@ +package org.leavesmc.leaves.bot; + +import net.kyori.adventure.text.Component; @@ -2121,7 +2081,6 @@ index 0000000000000000000000000000000000000000..068bd6ce5fe4bdc81a10bd1697b4c274 +import net.minecraft.server.level.ServerPlayerGameMode; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; -+import net.minecraft.world.ItemInteractionResult; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.UseOnContext; +import net.minecraft.world.level.GameType; @@ -2211,7 +2170,7 @@ index 0000000000000000000000000000000000000000..068bd6ce5fe4bdc81a10bd1697b4c274 + return InteractionResult.FAIL; + } + -+ if (player.getCooldowns().isOnCooldown(stack.getItem())) { ++ if (player.getCooldowns().isOnCooldown(stack)) { + return InteractionResult.PASS; + } + @@ -2225,13 +2184,13 @@ index 0000000000000000000000000000000000000000..068bd6ce5fe4bdc81a10bd1697b4c274 + boolean flag1 = player.isSecondaryUseActive() && flag; + + if (!flag1) { -+ ItemInteractionResult iteminteractionresult = iblockdata.useItemOn(player.getItemInHand(hand), world, player, hand, hitResult); ++ InteractionResult iteminteractionresult = iblockdata.useItemOn(player.getItemInHand(hand), world, player, hand, hitResult); + + if (iteminteractionresult.consumesAction()) { -+ return iteminteractionresult.result(); ++ return iteminteractionresult; + } + -+ if (iteminteractionresult == ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION && hand == InteractionHand.MAIN_HAND) { ++ if (iteminteractionresult == InteractionResult.PASS && hand == InteractionHand.MAIN_HAND) { + enuminteractionresult = iblockdata.useWithoutItem(world, player, hitResult); + if (enuminteractionresult.consumesAction()) { + return enuminteractionresult;