From 6d0821d2b5f42f40c6a18479ad68416c7a508dba Mon Sep 17 00:00:00 2001 From: Pedro <3602279+Doc94@users.noreply.github.com> Date: Sat, 21 Jun 2025 11:54:24 -0400 Subject: [PATCH 01/25] [ci skip] Fix docs for Spawner class and cleanup (#12710) --- .../java/org/bukkit/spawner/BaseSpawner.java | 38 +++++++++---------- .../main/java/org/bukkit/spawner/Spawner.java | 37 +++++++++--------- .../block/CraftCreatureSpawner.java | 1 - 3 files changed, 38 insertions(+), 38 deletions(-) diff --git a/paper-api/src/main/java/org/bukkit/spawner/BaseSpawner.java b/paper-api/src/main/java/org/bukkit/spawner/BaseSpawner.java index 553aa2dcc..4c78c0402 100644 --- a/paper-api/src/main/java/org/bukkit/spawner/BaseSpawner.java +++ b/paper-api/src/main/java/org/bukkit/spawner/BaseSpawner.java @@ -8,13 +8,14 @@ import org.bukkit.block.spawner.SpawnerEntry; import org.bukkit.entity.EntitySnapshot; import org.bukkit.entity.EntityType; import org.bukkit.entity.minecart.SpawnerMinecart; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Represents a basic entity spawner.
* May be a {@link SpawnerMinecart}, {@link CreatureSpawner} or {@link TrialSpawnerConfiguration}. */ +@NullMarked public interface BaseSpawner { /** @@ -23,7 +24,7 @@ public interface BaseSpawner { * @return The creature type or null if it not set. */ @Nullable - public EntityType getSpawnedType(); + EntityType getSpawnedType(); /** * Set the spawner's creature type.
@@ -31,7 +32,7 @@ public interface BaseSpawner { * * @param creatureType The creature type or null to clear. */ - public void setSpawnedType(@Nullable EntityType creatureType); + void setSpawnedType(@Nullable EntityType creatureType); /** * Get the spawner's delay. @@ -40,14 +41,14 @@ public interface BaseSpawner { * * @return The delay. */ - public int getDelay(); + int getDelay(); /** * Set the spawner's delay. * * @param delay The delay. */ - public void setDelay(int delay); + void setDelay(int delay); /** * Get the maximum distance(squared) a player can be in order for this @@ -61,7 +62,7 @@ public interface BaseSpawner { * @return the maximum distance(squared) a player can be in order for this * spawner to be active. */ - public int getRequiredPlayerRange(); + int getRequiredPlayerRange(); /** * Set the maximum distance (squared) a player can be in order for this @@ -73,7 +74,7 @@ public interface BaseSpawner { * @param requiredPlayerRange the maximum distance (squared) a player can be * in order for this spawner to be active. */ - public void setRequiredPlayerRange(int requiredPlayerRange); + void setRequiredPlayerRange(int requiredPlayerRange); /** * Get the radius around which the spawner will attempt to spawn mobs in. @@ -89,7 +90,7 @@ public interface BaseSpawner { * * @return the spawn range */ - public int getSpawnRange(); + int getSpawnRange(); /** * Set the new spawn range. @@ -98,7 +99,7 @@ public interface BaseSpawner { * @param spawnRange the new spawn range * @see #getSpawnRange() */ - public void setSpawnRange(int spawnRange); + void setSpawnRange(int spawnRange); /** * Gets the {@link EntitySnapshot} that will be spawned by this spawner or null @@ -111,7 +112,7 @@ public interface BaseSpawner { * spawner. */ @Nullable - public EntitySnapshot getSpawnedEntity(); + EntitySnapshot getSpawnedEntity(); /** * Sets the entity that will be spawned by this spawner.
@@ -123,7 +124,7 @@ public interface BaseSpawner { * * @param snapshot the entity snapshot or null to clear */ - public void setSpawnedEntity(@Nullable EntitySnapshot snapshot); + void setSpawnedEntity(@Nullable EntitySnapshot snapshot); /** * Sets the {@link SpawnerEntry} that will be spawned by this spawner.
@@ -132,14 +133,14 @@ public interface BaseSpawner { * * @param spawnerEntry the spawner entry to use */ - public void setSpawnedEntity(@NotNull SpawnerEntry spawnerEntry); + void setSpawnedEntity(SpawnerEntry spawnerEntry); /** * Adds a new {@link EntitySnapshot} to the list of entities this spawner can * spawn. *

* The weight will determine how often this entry is chosen to spawn, higher - * weighted entries will spawn more often than lower weighted ones.
+ * weighted entries will spawn more often than lower-weighted ones.
* The {@link SpawnRule} will determine under what conditions this entry can * spawn, passing null will use the default conditions for the given entity. * @@ -147,7 +148,7 @@ public interface BaseSpawner { * @param weight the weight * @param spawnRule the spawn rule for this entity, or null */ - public void addPotentialSpawn(@NotNull EntitySnapshot snapshot, int weight, @Nullable SpawnRule spawnRule); + void addPotentialSpawn(EntitySnapshot snapshot, int weight, @Nullable SpawnRule spawnRule); /** * Adds a new {@link SpawnerEntry} to the list of entities this spawner can @@ -156,7 +157,7 @@ public interface BaseSpawner { * @param spawnerEntry the spawner entry to use * @see #addPotentialSpawn(EntitySnapshot, int, SpawnRule) */ - public void addPotentialSpawn(@NotNull final SpawnerEntry spawnerEntry); + void addPotentialSpawn(final SpawnerEntry spawnerEntry); /** * Sets the list of {@link SpawnerEntry} this spawner can spawn.
@@ -165,7 +166,7 @@ public interface BaseSpawner { * * @param entries the list of entries */ - public void setPotentialSpawns(@NotNull final Collection entries); + void setPotentialSpawns(final Collection entries); /** * Gets a list of potential spawns from this spawner or an empty list if no @@ -177,6 +178,5 @@ public interface BaseSpawner { * entities have been assigned to this spawner * @see #getSpawnedType() */ - @NotNull - public List getPotentialSpawns(); + List getPotentialSpawns(); } diff --git a/paper-api/src/main/java/org/bukkit/spawner/Spawner.java b/paper-api/src/main/java/org/bukkit/spawner/Spawner.java index 640767bd1..592c21390 100644 --- a/paper-api/src/main/java/org/bukkit/spawner/Spawner.java +++ b/paper-api/src/main/java/org/bukkit/spawner/Spawner.java @@ -3,11 +3,14 @@ package org.bukkit.spawner; import org.bukkit.block.CreatureSpawner; import org.bukkit.entity.EntityType; import org.bukkit.entity.minecart.SpawnerMinecart; +import org.bukkit.inventory.ItemStack; +import org.jspecify.annotations.NullMarked; /** * Represents an entity spawner.
* May be a {@link SpawnerMinecart} or a {@link CreatureSpawner}. */ +@NullMarked public interface Spawner extends BaseSpawner { /** @@ -19,7 +22,7 @@ public interface Spawner extends BaseSpawner { * @param delay The delay. */ @Override - public void setDelay(int delay); + void setDelay(int delay); /** * The minimum spawn delay amount (in ticks). @@ -32,7 +35,7 @@ public interface Spawner extends BaseSpawner { * * @return the minimum spawn delay amount */ - public int getMinSpawnDelay(); + int getMinSpawnDelay(); /** * Set the minimum spawn delay amount (in ticks). @@ -40,7 +43,7 @@ public interface Spawner extends BaseSpawner { * @param delay the minimum spawn delay amount * @see #getMinSpawnDelay() */ - public void setMinSpawnDelay(int delay); + void setMinSpawnDelay(int delay); /** * The maximum spawn delay amount (in ticks). @@ -56,7 +59,7 @@ public interface Spawner extends BaseSpawner { * * @return the maximum spawn delay amount */ - public int getMaxSpawnDelay(); + int getMaxSpawnDelay(); /** * Set the maximum spawn delay amount (in ticks). @@ -67,7 +70,7 @@ public interface Spawner extends BaseSpawner { * @param delay the new maximum spawn delay amount * @see #getMaxSpawnDelay() */ - public void setMaxSpawnDelay(int delay); + void setMaxSpawnDelay(int delay); /** * Get how many mobs attempt to spawn. @@ -76,27 +79,27 @@ public interface Spawner extends BaseSpawner { * * @return the current spawn count */ - public int getSpawnCount(); + int getSpawnCount(); /** * Set how many mobs attempt to spawn. * * @param spawnCount the new spawn count */ - public void setSpawnCount(int spawnCount); + void setSpawnCount(int spawnCount); /** - * Set the new maximum amount of similar entities that are allowed to be - * within spawning range of this spawner. + * Get the maximum number of similar entities that are allowed to be + * within the spawning range of this spawner. *
* If more than the maximum number of entities are within range, the spawner * will not spawn and try again with a new {@link #getDelay()}. *
- * Default value is 16. + * Default value is 6. * * @return the maximum number of nearby, similar, entities */ - public int getMaxNearbyEntities(); + int getMaxNearbyEntities(); /** * Set the maximum number of similar entities that are allowed to be within @@ -106,20 +109,19 @@ public interface Spawner extends BaseSpawner { * * @param maxNearbyEntities the maximum number of nearby, similar, entities */ - public void setMaxNearbyEntities(int maxNearbyEntities); + void setMaxNearbyEntities(int maxNearbyEntities); - // Paper start /** * Check if spawner is activated (a player is close enough) * * @return True if a player is close enough to activate it */ - public boolean isActivated(); + boolean isActivated(); /** * Resets the spawn delay timer within the min/max range */ - public void resetTimer(); + void resetTimer(); /** * Sets the {@link EntityType} to {@link EntityType#ITEM} and sets the data to the given @@ -128,9 +130,8 @@ public interface Spawner extends BaseSpawner { * {@link #setSpawnCount(int)} does not dictate the amount of items in the stack spawned, but rather how many * stacks should be spawned. * - * @param itemStack The item to spawn. Must not {@link org.bukkit.Material#isAir be air}. + * @param itemStack The item to spawn. Must not {@link ItemStack#isEmpty() be empty}. * @see #setSpawnedType(EntityType) */ - void setSpawnedItem(org.bukkit.inventory.@org.jetbrains.annotations.NotNull ItemStack itemStack); - // Paper end + void setSpawnedItem(ItemStack itemStack); } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java b/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java index 9f20a8543..74a39d826 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java @@ -8,7 +8,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; -import net.minecraft.core.RegistryAccess; import net.minecraft.nbt.CompoundTag; import net.minecraft.util.InclusiveRange; import net.minecraft.util.ProblemReporter; From cceffe3d985ac6e1580b12bfc04b172a2d543990 Mon Sep 17 00:00:00 2001 From: Jake Potrebic Date: Sat, 21 Jun 2025 11:27:46 -0700 Subject: [PATCH 02/25] Release ItemType and BlockType (#12708) --- .../papermc/paper/registry/RegistryKey.java | 8 +- .../main/java/org/bukkit/block/BlockType.java | 49 ++++------ .../java/org/bukkit/inventory/ItemType.java | 97 ++++++++----------- .../craftbukkit/inventory/CraftItemType.java | 5 - 4 files changed, 60 insertions(+), 99 deletions(-) diff --git a/paper-api/src/main/java/io/papermc/paper/registry/RegistryKey.java b/paper-api/src/main/java/io/papermc/paper/registry/RegistryKey.java index 4050f7135..d26c40e9b 100644 --- a/paper-api/src/main/java/io/papermc/paper/registry/RegistryKey.java +++ b/paper-api/src/main/java/io/papermc/paper/registry/RegistryKey.java @@ -76,16 +76,14 @@ public sealed interface RegistryKey extends Keyed permits RegistryKeyImpl { */ RegistryKey MOB_EFFECT = create("mob_effect"); /** - * @apiNote DO NOT USE + * Built-in registry for block types. + * @see io.papermc.paper.registry.keys.BlockTypeKeys */ - @ApiStatus.Internal RegistryKey BLOCK = create("block"); /** - * @apiNote use preferably only in the context of registry entries. - * @see io.papermc.paper.registry.data + * Built-in registry for item types. * @see io.papermc.paper.registry.keys.ItemTypeKeys */ - @ApiStatus.Experimental // Paper - already required for registry builders RegistryKey ITEM = create("item"); /** * Built-in registry for villager professions. diff --git a/paper-api/src/main/java/org/bukkit/block/BlockType.java b/paper-api/src/main/java/org/bukkit/block/BlockType.java index c12cc7a24..849ad58f8 100644 --- a/paper-api/src/main/java/org/bukkit/block/BlockType.java +++ b/paper-api/src/main/java/org/bukkit/block/BlockType.java @@ -2,9 +2,10 @@ package org.bukkit.block; import java.util.Collection; import java.util.function.Consumer; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.key.KeyPattern; import org.bukkit.Keyed; import org.bukkit.Material; -import org.bukkit.NamespacedKey; import org.bukkit.Registry; import org.bukkit.Translatable; import org.bukkit.World; @@ -82,7 +83,6 @@ import org.bukkit.block.data.type.MangrovePropagule; import org.bukkit.block.data.type.MossyCarpet; import org.bukkit.block.data.type.NoteBlock; import org.bukkit.block.data.type.Observer; -import org.bukkit.block.data.type.PinkPetals; import org.bukkit.block.data.type.Piston; import org.bukkit.block.data.type.PistonHead; import org.bukkit.block.data.type.PitcherCrop; @@ -122,18 +122,14 @@ import org.bukkit.block.data.type.WallHangingSign; import org.bukkit.block.data.type.WallSign; import org.bukkit.block.data.type.WallSkull; import org.bukkit.inventory.ItemType; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Unmodifiable; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** - * While this API is in a public interface, it is not intended for use by - * plugins until further notice. The purpose of these types is to make - * {@link Material} more maintenance friendly, but will in due time be the - * official replacement for the aforementioned enum. Entirely incompatible - * changes may occur. Do not use this API in plugins. + * Represents a block type. */ -@org.jetbrains.annotations.ApiStatus.Experimental // Paper - data component API - already required for data component API +@NullMarked public interface BlockType extends Keyed, Translatable, net.kyori.adventure.translation.Translatable, io.papermc.paper.world.flag.FeatureDependant { // Paper - add translatable & feature flag API /** @@ -150,7 +146,6 @@ public interface BlockType extends Keyed, Translatable, net.kyori.adventure.tran * * @return the BlockData class of this BlockType */ - @NotNull @Override Class getBlockDataClass(); @@ -161,7 +156,6 @@ public interface BlockType extends Keyed, Translatable, net.kyori.adventure.tran * @param consumer consumer to run on new instance before returning * @return new data instance */ - @NotNull B createBlockData(@Nullable Consumer consumer); /** @@ -170,7 +164,6 @@ public interface BlockType extends Keyed, Translatable, net.kyori.adventure.tran * * @return new data instance */ - @NotNull @Override B createBlockData(); @@ -181,7 +174,7 @@ public interface BlockType extends Keyed, Translatable, net.kyori.adventure.tran * @return new block data collection */ @Override - @Unmodifiable @NotNull Collection createBlockDataStates(); + @Unmodifiable Collection createBlockDataStates(); /** * Creates a new {@link BlockData} instance for this block type, with all @@ -192,7 +185,6 @@ public interface BlockType extends Keyed, Translatable, net.kyori.adventure.tran * @return new data instance * @throws IllegalArgumentException if the specified data is not valid */ - @NotNull B createBlockData(@Nullable String data); } @@ -2411,10 +2403,10 @@ public interface BlockType extends Keyed, Translatable, net.kyori.adventure.tran // End generate - BlockType // - @NotNull - private static B getBlockType(@NotNull String key) { + @SuppressWarnings("unchecked") + private static B getBlockType(@KeyPattern.Value final String key) { // Cast instead of using BlockType#typed, since block type can be a mock during testing and would return null - return (B) Registry.BLOCK.getOrThrow(NamespacedKey.minecraft(key)); + return (B) Registry.BLOCK.getOrThrow(Key.key(Key.MINECRAFT_NAMESPACE, key)); } /** @@ -2422,7 +2414,6 @@ public interface BlockType extends Keyed, Translatable, net.kyori.adventure.tran * * @return the typed block type. */ - @NotNull BlockType.Typed typed(); /** @@ -2432,8 +2423,7 @@ public interface BlockType extends Keyed, Translatable, net.kyori.adventure.tran * @param the generic type of the block data to type this block type with. * @return the typed block type. */ - @NotNull - BlockType.Typed typed(@NotNull Class blockDataType); + BlockType.Typed typed(Class blockDataType); /** * Returns true if this BlockType has a corresponding {@link ItemType}. @@ -2447,12 +2437,14 @@ public interface BlockType extends Keyed, Translatable, net.kyori.adventure.tran * Returns the corresponding {@link ItemType} for the given BlockType. *

* If there is no corresponding {@link ItemType} an error will be thrown. + *

This is NOT the same as the {@link ItemType} with the same key, + * but instead is the item associated with this block if this block + * can be represented with an item.

* * @return the corresponding ItemType * @see #hasItemType() * @see BlockData#getPlacementMaterial() */ - @NotNull ItemType getItemType(); /** @@ -2460,7 +2452,6 @@ public interface BlockType extends Keyed, Translatable, net.kyori.adventure.tran * * @return the BlockData class of this BlockType */ - @NotNull Class getBlockDataClass(); /** @@ -2469,7 +2460,6 @@ public interface BlockType extends Keyed, Translatable, net.kyori.adventure.tran * * @return new data instance */ - @NotNull BlockData createBlockData(); /** @@ -2478,7 +2468,7 @@ public interface BlockType extends Keyed, Translatable, net.kyori.adventure.tran * * @return new block data collection */ - @Unmodifiable @NotNull Collection createBlockDataStates(); + @Unmodifiable Collection createBlockDataStates(); /** * Creates a new {@link BlockData} instance for this block type, with all @@ -2489,7 +2479,6 @@ public interface BlockType extends Keyed, Translatable, net.kyori.adventure.tran * @return new data instance * @throws IllegalArgumentException if the specified data is not valid */ - @NotNull BlockData createBlockData(@Nullable String data); /** @@ -2607,7 +2596,7 @@ public interface BlockType extends Keyed, Translatable, net.kyori.adventure.tran * @deprecated use {@link io.papermc.paper.world.flag.FeatureFlagSetHolder#isEnabled(io.papermc.paper.world.flag.FeatureDependant)} */ @Deprecated(forRemoval = true, since = "1.21.1") // Paper - boolean isEnabledByFeature(@NotNull World world); + boolean isEnabledByFeature(World world); /** * Tries to convert this BlockType into a Material @@ -2619,21 +2608,17 @@ public interface BlockType extends Keyed, Translatable, net.kyori.adventure.tran @Deprecated(since = "1.20.6") Material asMaterial(); - // Paper start - add Translatable /** * @deprecated use {@link #translationKey()} and {@link net.kyori.adventure.text.Component#translatable(net.kyori.adventure.translation.Translatable)} */ @Deprecated(forRemoval = true) @Override - @NotNull String getTranslationKey(); - // Paper end - add Translatable + String getTranslationKey(); - // Paper start - hasCollision API /** * Checks if this block type has collision. *

* @return false if this block never has collision, true if it might have collision */ boolean hasCollision(); - // Paper end - hasCollision API } diff --git a/paper-api/src/main/java/org/bukkit/inventory/ItemType.java b/paper-api/src/main/java/org/bukkit/inventory/ItemType.java index 014f0f49f..9fb354d29 100644 --- a/paper-api/src/main/java/org/bukkit/inventory/ItemType.java +++ b/paper-api/src/main/java/org/bukkit/inventory/ItemType.java @@ -1,10 +1,13 @@ package org.bukkit.inventory; import com.google.common.collect.Multimap; +import io.papermc.paper.datacomponent.DataComponentType; +import java.util.Set; import java.util.function.Consumer; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.key.KeyPattern; import org.bukkit.Keyed; import org.bukkit.Material; -import org.bukkit.NamespacedKey; import org.bukkit.Registry; import org.bukkit.Translatable; import org.bukkit.World; @@ -36,17 +39,14 @@ import org.bukkit.inventory.meta.SpawnEggMeta; import org.bukkit.inventory.meta.SuspiciousStewMeta; import org.bukkit.inventory.meta.TropicalFishBucketMeta; import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** - * While this API is in a public interface, it is not intended for use by - * plugins until further notice. The purpose of these types is to make - * {@link Material} more maintenance friendly, but will in due time be the - * official replacement for the aforementioned enum. Entirely incompatible - * changes may occur. Do not use this API in plugins. + * Represents an item type. */ -@ApiStatus.Experimental // Paper - already required for registry builders +@NullMarked public interface ItemType extends Keyed, Translatable, net.kyori.adventure.translation.Translatable, io.papermc.paper.world.flag.FeatureDependant { // Paper - add Translatable & feature flag API /** @@ -54,7 +54,11 @@ public interface ItemType extends Keyed, Translatable, net.kyori.adventure.trans * at compile time. * * @param the generic type of the item meta that represents the item type. + * @apiNote Do not use methods exclusive to this interface unless you are + * fine with them being possibly removed in the future. */ + @ApiStatus.Experimental + @ApiStatus.NonExtendable interface Typed extends ItemType { /** @@ -63,7 +67,7 @@ public interface ItemType extends Keyed, Translatable, net.kyori.adventure.trans * @return the ItemMeta class of this ItemType */ @Override - @NotNull + @ApiStatus.Experimental Class getItemMetaClass(); /** @@ -73,7 +77,7 @@ public interface ItemType extends Keyed, Translatable, net.kyori.adventure.trans * May be null if no intent exists to mutate the item meta at this point. * @return the created and configured item stack. */ - @NotNull + @ApiStatus.Experimental ItemStack createItemStack(@Nullable Consumer metaConfigurator); /** @@ -84,7 +88,7 @@ public interface ItemType extends Keyed, Translatable, net.kyori.adventure.trans * May be null if no intent exists to mutate the item meta at this point. * @return the created and configured item stack. */ - @NotNull + @ApiStatus.Experimental ItemStack createItemStack(int amount, @Nullable Consumer metaConfigurator); } @@ -2923,18 +2927,19 @@ public interface ItemType extends Keyed, Translatable, net.kyori.adventure.trans // End generate - ItemType // - @NotNull - private static M getItemType(@NotNull String key) { + @SuppressWarnings("unchecked") + private static M getItemType(@KeyPattern.Value final String key) { // Cast instead of using ItemType#typed, since item type can be a mock during testing and would return null - return (M) Registry.ITEM.getOrThrow(NamespacedKey.minecraft(key)); + return (M) Registry.ITEM.getOrThrow(Key.key(Key.MINECRAFT_NAMESPACE, key)); } /** * Yields this item type as a typed version of itself with a plain {@link ItemMeta} representing it. * * @return the typed item type. + * @apiNote The Typed interface is experimental and may be removed in future versions. */ - @NotNull + @ApiStatus.Experimental Typed typed(); /** @@ -2943,16 +2948,16 @@ public interface ItemType extends Keyed, Translatable, net.kyori.adventure.trans * @param itemMetaType the class type of the {@link ItemMeta} to type this {@link ItemType} with. * @param the generic type of the item meta to type this item type with. * @return the typed item type. + * @apiNote The Typed interface is experimental and may be removed in future versions. */ - @NotNull - Typed typed(@NotNull Class itemMetaType); + @ApiStatus.Experimental + Typed typed(Class itemMetaType); /** * Constructs a new itemstack with this item type that has the amount 1. * * @return the constructed item stack. */ - @NotNull ItemStack createItemStack(); /** @@ -2961,7 +2966,6 @@ public interface ItemType extends Keyed, Translatable, net.kyori.adventure.trans * @param amount the amount of the item stack. * @return the constructed item stack. */ - @NotNull ItemStack createItemStack(int amount); /** @@ -2976,11 +2980,12 @@ public interface ItemType extends Keyed, Translatable, net.kyori.adventure.trans * Returns the corresponding {@link BlockType} for the given ItemType. *

* If there is no corresponding {@link BlockType} an error will be thrown. + *

This is NOT the same as the {@link BlockType} with the same key, + * but instead is the block associated with this item if this item represents a block.

* * @return the corresponding BlockType * @see #hasBlockType() */ - @NotNull BlockType getBlockType(); /** @@ -2988,7 +2993,6 @@ public interface ItemType extends Keyed, Translatable, net.kyori.adventure.trans * * @return the ItemMeta class of this ItemType */ - @NotNull Class getItemMetaClass(); /** @@ -3064,20 +3068,8 @@ public interface ItemType extends Keyed, Translatable, net.kyori.adventure.trans * * @return the item left behind when crafting, or null if nothing is. */ - @Nullable - ItemType getCraftingRemainingItem(); + @Nullable ItemType getCraftingRemainingItem(); -// /** -// * Get the best suitable slot for this item type. -// * -// * For most items this will be {@link EquipmentSlot#HAND}. -// * -// * @return the best EquipmentSlot for this item type -// */ -// @NotNull -// EquipmentSlot getEquipmentSlot(); - - // Paper start - improve default item attribute API /** * Return an immutable copy of all default {@link Attribute}s and their * {@link AttributeModifier}s. @@ -3089,8 +3081,7 @@ public interface ItemType extends Keyed, Translatable, net.kyori.adventure.trans * @return the immutable {@link Multimap} with the respective default * Attributes and modifiers, or an empty map if no attributes are set. */ - @NotNull @org.jetbrains.annotations.Unmodifiable Multimap getDefaultAttributeModifiers(); - // Paper end - improve default item attribute API + @Unmodifiable Multimap getDefaultAttributeModifiers(); /** * Return an immutable copy of all default {@link Attribute}s and their @@ -3103,8 +3094,7 @@ public interface ItemType extends Keyed, Translatable, net.kyori.adventure.trans * @return the immutable {@link Multimap} with the respective default * Attributes and modifiers, or an empty map if no attributes are set. */ - @NotNull - Multimap getDefaultAttributeModifiers(@NotNull EquipmentSlot slot); + @Unmodifiable Multimap getDefaultAttributeModifiers(EquipmentSlot slot); /** * Get the {@link CreativeCategory} to which this item type belongs. @@ -3112,9 +3102,8 @@ public interface ItemType extends Keyed, Translatable, net.kyori.adventure.trans * @return the creative category. null if does not belong to a category */ - @Nullable @Deprecated(since = "1.20.6", forRemoval = true) - CreativeCategory getCreativeCategory(); + @Nullable CreativeCategory getCreativeCategory(); /** * Gets if the ItemType is enabled by the features in a world. @@ -3124,7 +3113,7 @@ public interface ItemType extends Keyed, Translatable, net.kyori.adventure.trans * @deprecated use {@link io.papermc.paper.world.flag.FeatureFlagSetHolder#isEnabled(io.papermc.paper.world.flag.FeatureDependant)} */ @Deprecated(forRemoval = true, since = "1.21.1") // Paper - boolean isEnabledByFeature(@NotNull World world); + boolean isEnabledByFeature(World world); /** * Tries to convert this ItemType into a Material @@ -3132,38 +3121,33 @@ public interface ItemType extends Keyed, Translatable, net.kyori.adventure.trans * @return the converted Material or null * @deprecated only for internal use */ - @Nullable @Deprecated(since = "1.20.6") - Material asMaterial(); + @Nullable Material asMaterial(); - // Paper start - add Translatable /** * @deprecated use {@link #translationKey()} and {@link net.kyori.adventure.text.Component#translatable(net.kyori.adventure.translation.Translatable)} */ @Deprecated(forRemoval = true) @Override - @NotNull String getTranslationKey(); - // Paper end - add Translatable + String getTranslationKey(); - // Paper start - expand ItemRarity API /** * Returns the item rarity for the item. * * @return the item rarity (or null if none is set) */ @Nullable ItemRarity getItemRarity(); - // Paper end - expand ItemRarity API - // Paper start - data component API + /** * Gets the default value of the data component type for this item type. * * @param type the data component type * @param the value type * @return the default value or {@code null} if there is none - * @see #hasDefaultData(io.papermc.paper.datacomponent.DataComponentType) for DataComponentType.NonValued + * @see #hasDefaultData(DataComponentType) for DataComponentType.NonValued */ - @org.jetbrains.annotations.ApiStatus.Experimental - @Nullable T getDefaultData(io.papermc.paper.datacomponent.DataComponentType.@NotNull Valued type); + @ApiStatus.Experimental + @Nullable T getDefaultData(DataComponentType.Valued type); /** * Checks if the data component type has a default value for this item type. @@ -3172,14 +3156,13 @@ public interface ItemType extends Keyed, Translatable, net.kyori.adventure.trans * @return {@code true} if there is a default value */ @org.jetbrains.annotations.ApiStatus.Experimental - boolean hasDefaultData(io.papermc.paper.datacomponent.@NotNull DataComponentType type); + boolean hasDefaultData(DataComponentType type); /** * Gets the default data component types for this item type. * * @return an immutable set of data component types */ - @org.jetbrains.annotations.ApiStatus.Experimental - java.util.@org.jetbrains.annotations.Unmodifiable @NotNull Set getDefaultDataTypes(); - // Paper end - data component API + @ApiStatus.Experimental + @Unmodifiable Set getDefaultDataTypes(); } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemType.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemType.java index 2b55195d9..261f593aa 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemType.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemType.java @@ -185,11 +185,6 @@ public class CraftItemType extends HolderableBase impl return expectedItem.isEmpty() ? null : CraftItemType.minecraftToBukkitNew(expectedItem.getItem()); } -// @Override -// public EquipmentSlot getEquipmentSlot() { -// return CraftEquipmentSlot.getSlot(EntityInsentient.getEquipmentSlotForItem(CraftItemStack.asNMSCopy(ItemStack.of(this)))); -// } - @Override public Multimap getDefaultAttributeModifiers() { return this.getDefaultAttributeModifiers(sg -> true); From 186e9e331b51ce359411a0a48f326d99af3348c2 Mon Sep 17 00:00:00 2001 From: Strokkur24 Date: Sat, 21 Jun 2025 20:44:07 +0200 Subject: [PATCH 03/25] Relocate CommandMap#registerServerAliases() call to after lifecycle events have been run (#12601) --- .../0016-Moonrise-optimisation-patches.patch | 18 +++++++++--------- ...4-Incremental-chunk-and-player-saving.patch | 6 +++--- .../features/0028-Optimize-Hoppers.patch | 4 ++-- .../server/MinecraftServer.java.patch | 3 ++- .../org/bukkit/craftbukkit/CraftServer.java | 1 - 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/paper-server/patches/features/0016-Moonrise-optimisation-patches.patch b/paper-server/patches/features/0016-Moonrise-optimisation-patches.patch index b5e83ec74..df5268bc5 100644 --- a/paper-server/patches/features/0016-Moonrise-optimisation-patches.patch +++ b/paper-server/patches/features/0016-Moonrise-optimisation-patches.patch @@ -23861,7 +23861,7 @@ index 46de98a6bbbae48c4837e1e588ba198a363d2dde..fd3553bdc1c3cdbf6aa3dc00e0a4987f thread1 -> { DedicatedServer dedicatedServer1 = new DedicatedServer( diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index c716521fb1497dc8a22d827ddb50fc1cc21a05f4..80442494db670fec34df310390ea787fb963eef4 100644 +index f7362a979126c5c0a581c05c0b623cf40b8f0ebd..338ef549efe82c250c74365c1c1071986920c8c9 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java @@ -173,7 +173,7 @@ import net.minecraft.world.phys.Vec2; @@ -23960,7 +23960,7 @@ index c716521fb1497dc8a22d827ddb50fc1cc21a05f4..80442494db670fec34df310390ea787f this.server.getPluginManager().callEvent(new org.bukkit.event.world.WorldLoadEvent(serverLevel.getWorld())); } -@@ -844,6 +915,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop false : this::haveTime); @@ -24045,7 +24045,7 @@ index c716521fb1497dc8a22d827ddb50fc1cc21a05f4..80442494db670fec34df310390ea787f this.tickFrame.end(); profilerFiller.popPush("nextTickWait"); this.mayHaveDelayedTasks = true; -@@ -1339,6 +1426,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper - BlockPhysicsEvent serverLevel.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent serverLevel.updateLagCompensationTick(); // Paper - lag compensation diff --git a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch index fd6b97826..c55551057 100644 --- a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch @@ -172,7 +172,7 @@ if (profiledDuration != null) { profiledDuration.finish(true); } -@@ -364,25 +_,265 @@ +@@ -364,25 +_,266 @@ protected void forceDifficulty() { } @@ -439,6 +439,7 @@ + if (io.papermc.paper.plugin.PluginInitializerManager.instance().pluginRemapper != null) io.papermc.paper.plugin.PluginInitializerManager.instance().pluginRemapper.pluginsEnabled(); // Paper - Remap plugins + io.papermc.paper.command.brigadier.PaperCommands.INSTANCE.setValid(); // Paper - reset invalid state for event fire below + io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner.INSTANCE.callReloadableRegistrarEvent(io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents.COMMANDS, io.papermc.paper.command.brigadier.PaperCommands.INSTANCE, org.bukkit.plugin.Plugin.class, io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.INITIAL); // Paper - call commands event for regular plugins ++ this.server.getCommandMap().registerServerAliases(); // Paper - relocate initial CommandMap#registerServerAliases() call + ((org.bukkit.craftbukkit.help.SimpleHelpMap) this.server.getHelpMap()).initializeCommands(); + this.server.getPluginManager().callEvent(new org.bukkit.event.server.ServerLoadEvent(org.bukkit.event.server.ServerLoadEvent.LoadType.STARTUP)); + this.connection.acceptConnections(); diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java index c1f1b757c..0d955ee43 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -613,7 +613,6 @@ public final class CraftServer implements Server { // Spigot start - Allow vanilla commands to be forced to be the main command this.commandMap.setFallbackCommands(); // Spigot end - this.commandMap.registerServerAliases(); DefaultPermissions.registerCorePermissions(); CraftDefaultPermissions.registerCorePermissions(); if (!io.papermc.paper.configuration.GlobalConfiguration.get().misc.loadPermissionsYmlBeforePlugins) this.loadCustomPermissions(); // Paper From 5edcf6ddf6a3c279fce5a4feca58e9679a140aec Mon Sep 17 00:00:00 2001 From: Owen <23108066+Owen1212055@users.noreply.github.com> Date: Sat, 21 Jun 2025 21:44:07 -0400 Subject: [PATCH 04/25] Cleanup/command dispatching (#12713) --- .../io/papermc/paper/InternalAPIBridge.java | 13 +++ .../paper/command/brigadier/Commands.java | 18 ++++ .../minecraft/commands/Commands.java.patch | 82 ++++--------------- .../commands/PermissionSource.java.patch | 10 ++- .../dedicated/DedicatedServer.java.patch | 21 ++--- .../world/level/BaseCommandBlock.java.patch | 10 ++- .../paper/PaperServerInternalAPIBridge.java | 15 ++++ .../bukkit/BukkitBrigForwardingMap.java | 3 +- .../org/bukkit/craftbukkit/CraftServer.java | 47 +++-------- .../command/VanillaCommandWrapper.java | 2 +- 10 files changed, 97 insertions(+), 124 deletions(-) diff --git a/paper-api/src/main/java/io/papermc/paper/InternalAPIBridge.java b/paper-api/src/main/java/io/papermc/paper/InternalAPIBridge.java index 422fdd93d..65e36495b 100644 --- a/paper-api/src/main/java/io/papermc/paper/InternalAPIBridge.java +++ b/paper-api/src/main/java/io/papermc/paper/InternalAPIBridge.java @@ -1,5 +1,6 @@ package io.papermc.paper; +import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.world.damagesource.CombatEntry; import io.papermc.paper.world.damagesource.FallLocationType; import net.kyori.adventure.util.Services; @@ -11,6 +12,8 @@ import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; +import java.util.function.Predicate; + /** * Static bridge to the server internals. *

@@ -73,5 +76,15 @@ public interface InternalAPIBridge { * @return combat entry */ CombatEntry createCombatEntry(DamageSource damageSource, float damage, @Nullable FallLocationType fallLocationType, float fallDistance); + + /** + * Causes this predicate to be considered restricted. + * Applying this to a command node prevents this command from being executed from an + * unattended context, such as click events. + * + * @param predicate wrapped predicate + * @return wrapped predicate + */ + Predicate restricted(Predicate predicate); } diff --git a/paper-api/src/main/java/io/papermc/paper/command/brigadier/Commands.java b/paper-api/src/main/java/io/papermc/paper/command/brigadier/Commands.java index 866442924..3c2d08662 100644 --- a/paper-api/src/main/java/io/papermc/paper/command/brigadier/Commands.java +++ b/paper-api/src/main/java/io/papermc/paper/command/brigadier/Commands.java @@ -5,6 +5,7 @@ import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.builder.RequiredArgumentBuilder; import com.mojang.brigadier.tree.LiteralCommandNode; +import io.papermc.paper.InternalAPIBridge; import io.papermc.paper.plugin.bootstrap.BootstrapContext; import io.papermc.paper.plugin.bootstrap.PluginBootstrap; import io.papermc.paper.plugin.configuration.PluginMeta; @@ -13,6 +14,7 @@ import io.papermc.paper.plugin.lifecycle.event.registrar.Registrar; import java.util.Collection; import java.util.Collections; import java.util.Set; +import java.util.function.Predicate; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Unmodifiable; import org.jspecify.annotations.NullMarked; @@ -85,6 +87,22 @@ public interface Commands extends Registrar { return RequiredArgumentBuilder.argument(name, argumentType); } + /** + * Creates a restricted {@link Predicate} that wraps the given predicate. + *

+ * A restricted predicate prevents execution in unattended contexts, such as from + * chat click events. A warning is shown on the client before executing the command. + *

+ * This is used by vanilla to prevent invocation of sensitive commands (like op) from + * players without their knowledge. + * + * @param predicate the original predicate to wrap + * @return a new predicate with restricted execution behavior + */ + static Predicate restricted(final Predicate predicate) { + return InternalAPIBridge.get().restricted(predicate); + } + /** * Gets the underlying {@link CommandDispatcher}. * diff --git a/paper-server/patches/sources/net/minecraft/commands/Commands.java.patch b/paper-server/patches/sources/net/minecraft/commands/Commands.java.patch index a3538d58e..1910b261a 100644 --- a/paper-server/patches/sources/net/minecraft/commands/Commands.java.patch +++ b/paper-server/patches/sources/net/minecraft/commands/Commands.java.patch @@ -1,6 +1,13 @@ --- a/net/minecraft/commands/Commands.java +++ b/net/minecraft/commands/Commands.java -@@ -176,6 +_,11 @@ +@@ -170,12 +_,18 @@ + + @Override + public boolean isRestricted(CommandNode node) { ++ if (node.getRequirement() instanceof PermissionSource.RestrictedMarker) return true; // Paper - restricted api + return node.getRequirement() instanceof PermissionCheck permissionCheck && permissionCheck.requiredLevel() > 0; + } + }; private final CommandDispatcher dispatcher = new CommandDispatcher<>(); public Commands(Commands.CommandSelection selection, CommandBuildContext context) { @@ -55,71 +62,20 @@ this.dispatcher.setConsumer(ExecutionCommandSource.resultConsumer()); } -@@ -289,9 +_,41 @@ - return new ParseResults<>(commandContextBuilder, parseResults.getReader(), parseResults.getExceptions()); - } - -+ // CraftBukkit start -+ public void dispatchServerCommand(CommandSourceStack sender, String command) { -+ com.google.common.base.Joiner joiner = com.google.common.base.Joiner.on(" "); -+ if (command.startsWith("/")) { -+ command = command.substring(1); -+ } -+ -+ org.bukkit.event.server.ServerCommandEvent event = new org.bukkit.event.server.ServerCommandEvent(sender.getBukkitSender(), command); -+ org.bukkit.Bukkit.getPluginManager().callEvent(event); -+ if (event.isCancelled()) { -+ return; -+ } -+ command = event.getCommand(); -+ -+ String[] args = command.split(" "); -+ if (args.length == 0) return; // Paper - empty commands shall not be dispatched -+ -+ // Paper - Fix permission levels for command blocks -+ -+ // Handle vanilla commands; // Paper - handled in CommandNode/CommandDispatcher -+ -+ String newCommand = joiner.join(args); -+ this.performPrefixedCommand(sender, newCommand, newCommand); -+ } -+ // CraftBukkit end -+ - public void performPrefixedCommand(CommandSourceStack source, String command) { -+ // CraftBukkit start -+ this.performPrefixedCommand(source, command, command); -+ } -+ -+ public void performPrefixedCommand(CommandSourceStack source, String command, String label) { - command = trimOptionalPrefix(command); -- this.performCommand(this.dispatcher.parse(command, source), command); -+ this.performCommand(this.dispatcher.parse(command, source), command, label); -+ // CraftBukkit end - } - - public static String trimOptionalPrefix(String command) { -@@ -299,9 +_,20 @@ +@@ -299,6 +_,13 @@ } public void performCommand(ParseResults parseResults, String command) { -+ // CraftBukkit start -+ this.performCommand(parseResults, command, command); ++ // Paper start ++ this.performCommand(parseResults, command, false); + } + -+ public void performCommand(ParseResults parseResults, String command, String label) { -+ // CraftBukkit end -+ // Paper start -+ this.performCommand(parseResults, command, label, false); -+ } -+ public void performCommand(ParseResults parseResults, String command, String label, boolean throwCommandError) { ++ public void performCommand(ParseResults parseResults, String command, boolean throwCommandError) { ++ org.spigotmc.AsyncCatcher.catchOp("Cannot perform command async"); + // Paper end CommandSourceStack commandSourceStack = parseResults.getContext().getSource(); Profiler.get().push(() -> "/" + command); -- ContextChain contextChain = finishParsing(parseResults, command, commandSourceStack); -+ ContextChain contextChain = this.finishParsing(parseResults, command, commandSourceStack, label); // CraftBukkit // Paper - Add UnknownCommandEvent - - try { - if (contextChain != null) { + ContextChain contextChain = finishParsing(parseResults, command, commandSourceStack); @@ -313,9 +_,10 @@ ); } @@ -133,12 +89,12 @@ StackTraceElement[] stackTrace = var12.getStackTrace(); for (int i = 0; i < Math.min(stackTrace.length, 3); i++) { -@@ -341,18 +_,22 @@ +@@ -341,13 +_,17 @@ } @Nullable - private static ContextChain finishParsing(ParseResults parseResults, String command, CommandSourceStack source) { -+ private ContextChain finishParsing(ParseResults parseResults, String command, CommandSourceStack source, String label) { // CraftBukkit // Paper - Add UnknownCommandEvent ++ private ContextChain finishParsing(ParseResults parseResults, String command, CommandSourceStack source) { try { validateParseResults(parseResults); return ContextChain.tryFlatten(parseResults.getContext().build(command)) @@ -153,12 +109,6 @@ if (var7.getInput() != null && var7.getCursor() >= 0) { int min = Math.min(var7.getInput().length(), var7.getCursor()); MutableComponent mutableComponent = Component.empty() - .withStyle(ChatFormatting.GRAY) -- .withStyle(style -> style.withClickEvent(new ClickEvent.SuggestCommand("/" + command))); -+ .withStyle(style -> style.withClickEvent(new ClickEvent.SuggestCommand("/" + label))); // CraftBukkit // Paper - if (min > 10) { - mutableComponent.append(CommonComponents.ELLIPSIS); - } @@ -364,7 +_,17 @@ } diff --git a/paper-server/patches/sources/net/minecraft/commands/PermissionSource.java.patch b/paper-server/patches/sources/net/minecraft/commands/PermissionSource.java.patch index d71a268e0..0d8a8570b 100644 --- a/paper-server/patches/sources/net/minecraft/commands/PermissionSource.java.patch +++ b/paper-server/patches/sources/net/minecraft/commands/PermissionSource.java.patch @@ -1,16 +1,18 @@ --- a/net/minecraft/commands/PermissionSource.java +++ b/net/minecraft/commands/PermissionSource.java -@@ -9,9 +_,20 @@ +@@ -9,9 +_,22 @@ return this.hasPermission(2); } - public record Check(@Override int requiredLevel) implements PermissionCheck { -+ public record Check(@Override int requiredLevel, java.util.concurrent.atomic.AtomicReference> vanillaNode) implements PermissionCheck { // Paper -+ // Paper start - Vanilla Command permission checking ++ // Paper start - Vanilla Command permission checking & expose restricted API ++ interface RestrictedMarker { } ++ ++ public record Check(@Override int requiredLevel, java.util.concurrent.atomic.AtomicReference> vanillaNode) implements PermissionCheck { + public Check(int requiredLevel) { + this(requiredLevel, new java.util.concurrent.atomic.AtomicReference<>()); + } -+ // Paper end - Vanilla Command permission checking ++ // Paper end - Vanilla Command permission checking & expose restricted API @Override public boolean test(T source) { + // Paper start - Vanilla Command permission checking diff --git a/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch b/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch index 9e68c7056..faf3ec703 100644 --- a/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch @@ -273,7 +273,7 @@ } @Override -@@ -291,13 +_,23 @@ +@@ -291,12 +_,20 @@ } public void handleConsoleInput(String msg, CommandSourceStack source) { @@ -284,23 +284,19 @@ public void handleConsoleInputs() { - while (!this.consoleInput.isEmpty()) { - ConsoleInput consoleInput = this.consoleInput.remove(0); -- this.getCommands().performPrefixedCommand(consoleInput.source, consoleInput.msg); + // Paper start - Perf: use proper queue -+ ConsoleInput servercommand; -+ while ((servercommand = this.serverCommandQueue.poll()) != null) { ++ ConsoleInput consoleInput; ++ while ((consoleInput = this.serverCommandQueue.poll()) != null) { + // Paper end - Perf: use proper queue + // CraftBukkit start - ServerCommand for preprocessing -+ org.bukkit.event.server.ServerCommandEvent event = new org.bukkit.event.server.ServerCommandEvent(this.console, servercommand.msg); ++ org.bukkit.event.server.ServerCommandEvent event = new org.bukkit.event.server.ServerCommandEvent(this.console, consoleInput.msg); + this.server.getPluginManager().callEvent(event); + if (event.isCancelled()) continue; -+ servercommand = new ConsoleInput(event.getCommand(), servercommand.source); -+ -+ // this.getCommands().performPrefixedCommand(servercommand.source, servercommand.msg); // Called in dispatchServerCommand -+ this.server.dispatchServerCommand(this.console, servercommand); ++ consoleInput = new ConsoleInput(event.getCommand(), consoleInput.source); + // CraftBukkit end + this.getCommands().performPrefixedCommand(consoleInput.source, consoleInput.msg); } } - @@ -430,7 +_,11 @@ @Override public boolean enforceSecureProfile() { @@ -314,7 +310,7 @@ } @Override -@@ -515,14 +_,54 @@ +@@ -515,14 +_,53 @@ @Override public String getPluginNames() { @@ -365,8 +361,7 @@ + if (event.isCancelled()) { + return; + } -+ ConsoleInput serverCommand = new ConsoleInput(event.getCommand(), wrapper); -+ this.server.dispatchServerCommand(event.getSender(), serverCommand); ++ this.getCommands().performPrefixedCommand(wrapper, event.getCommand()); + }); + return rconConsoleSource.getCommandResponse(); + // CraftBukkit end diff --git a/paper-server/patches/sources/net/minecraft/world/level/BaseCommandBlock.java.patch b/paper-server/patches/sources/net/minecraft/world/level/BaseCommandBlock.java.patch index 4d22b3d01..73f2de213 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/BaseCommandBlock.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/BaseCommandBlock.java.patch @@ -12,12 +12,18 @@ public int getSuccessCount() { return this.successCount; -@@ -108,7 +_,7 @@ +@@ -108,7 +_,13 @@ this.successCount++; } }); - server.getCommands().performPrefixedCommand(commandSourceStack, this.command); -+ server.getCommands().dispatchServerCommand(commandSourceStack, this.command); // CraftBukkit ++ // Paper start - ServerCommandEvent ++ org.bukkit.event.server.ServerCommandEvent event = new org.bukkit.event.server.ServerCommandEvent(commandSourceStack.getBukkitSender(), net.minecraft.commands.Commands.trimOptionalPrefix(this.command)); ++ if (!event.callEvent()) { ++ return true; ++ } ++ server.getCommands().performPrefixedCommand(commandSourceStack, event.getCommand()); ++ // Paper end - ServerCommandEvent } catch (Throwable var6) { CrashReport crashReport = CrashReport.forThrowable(var6, "Executing command block"); CrashReportCategory crashReportCategory = crashReport.addCategory("Command to be executed"); diff --git a/paper-server/src/main/java/io/papermc/paper/PaperServerInternalAPIBridge.java b/paper-server/src/main/java/io/papermc/paper/PaperServerInternalAPIBridge.java index d69335626..61468be56 100644 --- a/paper-server/src/main/java/io/papermc/paper/PaperServerInternalAPIBridge.java +++ b/paper-server/src/main/java/io/papermc/paper/PaperServerInternalAPIBridge.java @@ -1,10 +1,12 @@ package io.papermc.paper; +import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.world.damagesource.CombatEntry; import io.papermc.paper.world.damagesource.FallLocationType; import io.papermc.paper.world.damagesource.PaperCombatEntryWrapper; import io.papermc.paper.world.damagesource.PaperCombatTrackerWrapper; import net.minecraft.Optionull; +import net.minecraft.commands.PermissionSource; import net.minecraft.world.damagesource.FallLocation; import org.bukkit.block.Biome; import org.bukkit.craftbukkit.block.CraftBiome; @@ -16,6 +18,7 @@ import org.bukkit.damage.DamageSource; import org.bukkit.entity.LivingEntity; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; +import java.util.function.Predicate; @NullMarked public class PaperServerInternalAPIBridge implements InternalAPIBridge { @@ -71,4 +74,16 @@ public class PaperServerInternalAPIBridge implements InternalAPIBridge { damageSource, damage, fallLocation, fallDistance )); } + + @Override + public Predicate restricted(final Predicate predicate) { + record RestrictedPredicate(Predicate predicate) implements Predicate, PermissionSource.RestrictedMarker { + @Override + public boolean test(final CommandSourceStack commandSourceStack) { + return this.predicate.test(commandSourceStack); + } + } + + return new RestrictedPredicate(predicate); + } } diff --git a/paper-server/src/main/java/io/papermc/paper/command/brigadier/bukkit/BukkitBrigForwardingMap.java b/paper-server/src/main/java/io/papermc/paper/command/brigadier/bukkit/BukkitBrigForwardingMap.java index 4ee648f96..fd349089f 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/brigadier/bukkit/BukkitBrigForwardingMap.java +++ b/paper-server/src/main/java/io/papermc/paper/command/brigadier/bukkit/BukkitBrigForwardingMap.java @@ -20,6 +20,7 @@ import java.util.Spliterator; import java.util.function.Consumer; import java.util.stream.Stream; import org.bukkit.command.Command; +import org.bukkit.craftbukkit.command.VanillaCommandWrapper; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -96,7 +97,7 @@ public class BukkitBrigForwardingMap extends HashMap { public Command put(String key, Command value) { Command old = this.get(key); this.getDispatcher().getRoot().removeCommand(key); // Override previous command - if (value instanceof PluginVanillaCommandWrapper wrapper && wrapper.getName().equals(key)) { + if (value instanceof VanillaCommandWrapper wrapper && wrapper.getName().equals(key)) { // Don't break when some plugin tries to remove and add back a plugin command registered with modern API... this.getDispatcher().getRoot().addChild((CommandNode) wrapper.vanillaCommand); } else { diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java index 0d955ee43..03dfcb466 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -302,7 +302,6 @@ public final class CraftServer implements Server { public CraftDataPackManager dataPackManager; private final CraftServerTickManager serverTickManager; private final CraftServerLinks serverLinks; - public boolean playerCommandState; private boolean printSaveWarning; private CraftIconCache icon; private boolean overrideAllCommandBlockCommands = false; @@ -973,41 +972,13 @@ public final class CraftServer implements Server { return this.playerList; } - // NOTE: Should only be called from DedicatedServer.ah() - public boolean dispatchServerCommand(CommandSender sender, ConsoleInput serverCommand) { - if (sender instanceof Conversable) { - Conversable conversable = (Conversable) sender; - - if (conversable.isConversing()) { - conversable.acceptConversationInput(serverCommand.msg); - return true; - } - } - try { - this.playerCommandState = true; - return this.dispatchCommand(sender, serverCommand.msg); - } catch (Exception ex) { - this.getLogger().log(Level.WARNING, "Unexpected exception while parsing console command \"" + serverCommand.msg + '"', ex); - return false; - } finally { - this.playerCommandState = false; - } - } - @Override - public boolean dispatchCommand(CommandSender sender, String commandLine) { - Preconditions.checkArgument(sender != null, "sender cannot be null"); + public boolean dispatchCommand(CommandSender rawSender, String commandLine) { + Preconditions.checkArgument(rawSender != null, "sender cannot be null"); Preconditions.checkArgument(commandLine != null, "commandLine cannot be null"); org.spigotmc.AsyncCatcher.catchOp("Command Dispatched Async: " + commandLine); // Spigot // Paper - Include command in error message + CommandSourceStack sourceStack = VanillaCommandWrapper.getListener(rawSender); - if (this.commandMap.dispatch(sender, commandLine)) { - return true; - } - - return this.dispatchCommand(VanillaCommandWrapper.getListener(sender), commandLine); - } - - public boolean dispatchCommand(CommandSourceStack sourceStack, String commandLine) { net.minecraft.commands.Commands commands = this.getHandle().getServer().getCommands(); com.mojang.brigadier.CommandDispatcher dispatcher = commands.getDispatcher(); com.mojang.brigadier.ParseResults results = dispatcher.parse(commandLine, sourceStack); @@ -1017,7 +988,12 @@ public final class CraftServer implements Server { Command target = this.commandMap.getCommand(args[0].toLowerCase(java.util.Locale.ENGLISH)); try { - commands.performCommand(results, commandLine, commandLine, true); + if (results.getContext().getNodes().isEmpty()) { + return false; + } + Commands.validateParseResults(results); + commands.performCommand(results, commandLine, true); + return true; } catch (CommandException ex) { new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerCommandException(ex, target, sender, args)).callEvent(); // Paper throw ex; @@ -1026,9 +1002,6 @@ public final class CraftServer implements Server { new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerCommandException(ex, target, sender, args)).callEvent(); // Paper throw new CommandException(msg, ex); } - // Paper end - - return false; } @Override @@ -2608,7 +2581,7 @@ public final class CraftServer implements Server { } public void checkSaveState() { - if (this.playerCommandState || this.printSaveWarning || this.console.autosavePeriod <= 0) { + if (this.printSaveWarning || this.console.autosavePeriod <= 0) { return; } this.printSaveWarning = true; diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java b/paper-server/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java index 7876e09b9..f93af795d 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java @@ -50,7 +50,7 @@ public class VanillaCommandWrapper extends BukkitCommand { // Paper if (!this.testPermission(sender)) return true; CommandSourceStack source = VanillaCommandWrapper.getListener(sender); - this.commands().performPrefixedCommand(source, this.toDispatcher(args, this.getName()), this.toDispatcher(args, commandLabel)); // Paper + this.commands().performPrefixedCommand(source, this.toDispatcher(args, this.getName())); return true; } From 803baf0ba697630802f8b7a85666463e6092e6c0 Mon Sep 17 00:00:00 2001 From: Pedro <3602279+Doc94@users.noreply.github.com> Date: Sun, 22 Jun 2025 13:42:06 -0400 Subject: [PATCH 05/25] Support hidden entities in Waypoints (#12715) --- .../features/0016-Moonrise-optimisation-patches.patch | 11 ++++++----- .../world/waypoints/WaypointTransmitter.java.patch | 10 ++++++++++ 2 files changed, 16 insertions(+), 5 deletions(-) create mode 100644 paper-server/patches/sources/net/minecraft/world/waypoints/WaypointTransmitter.java.patch diff --git a/paper-server/patches/features/0016-Moonrise-optimisation-patches.patch b/paper-server/patches/features/0016-Moonrise-optimisation-patches.patch index df5268bc5..f3695fc52 100644 --- a/paper-server/patches/features/0016-Moonrise-optimisation-patches.patch +++ b/paper-server/patches/features/0016-Moonrise-optimisation-patches.patch @@ -23061,7 +23061,7 @@ index 0000000000000000000000000000000000000000..f1f72a051083b61273202cb4e67ecb11 + private SaveUtil() {} +} diff --git a/io/papermc/paper/FeatureHooks.java b/io/papermc/paper/FeatureHooks.java -index 338dc0fb07cdba5f7350cca332fa3e942c622bfb..e1fe49e4bf014e2405708270efd81bab4e1512da 100644 +index 68e1e2da7a3291c6260898c90373bf21630f3351..b2b67a3b1b4620dc97a32df953d4ad47bbe5e481 100644 --- a/io/papermc/paper/FeatureHooks.java +++ b/io/papermc/paper/FeatureHooks.java @@ -1,6 +1,9 @@ @@ -23286,6 +23286,7 @@ index 338dc0fb07cdba5f7350cca332fa3e942c622bfb..e1fe49e4bf014e2405708270efd81bab } } +\ No newline at end of file diff --git a/io/papermc/paper/command/subcommands/ChunkDebugCommand.java b/io/papermc/paper/command/subcommands/ChunkDebugCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..2dca7afbd93cfbb8686f336fcd3b45dd01fba0fc @@ -24067,7 +24068,7 @@ index f7362a979126c5c0a581c05c0b623cf40b8f0ebd..338ef549efe82c250c74365c1c107198 // CraftBukkit start public boolean isDebugging() { diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java -index 5db176be3bd31eb886a541eeaee922ee30ee9908..5fea5e2e9fc10d348fa3e65cd354ef6a4a717a4d 100644 +index 4488d0a2f05ef07afab0f9a1483f54b21757b29e..98927d4a5fba2a0dcdb147ac10b82c3286ccdc6b 100644 --- a/net/minecraft/server/dedicated/DedicatedServer.java +++ b/net/minecraft/server/dedicated/DedicatedServer.java @@ -391,7 +391,33 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface @@ -31626,7 +31627,7 @@ index 96e8dfb1ff24954656470925a1fc6280fe5e09d9..be6f37f91569c659c609e5e8d38671ca public void animateTick(BlockState state, Level level, BlockPos pos, RandomSource random) { diff --git a/net/minecraft/world/level/block/state/BlockBehaviour.java b/net/minecraft/world/level/block/state/BlockBehaviour.java -index 1c81dc30aabb354d18290d42dfc419d9b1581fbd..834e27ef2f7b342b074ff9e1e390e02f3ca1c399 100644 +index c846d5d47c6488b11930b858da946e636e025294..d4c02b9bb9bfc10484a79ede35985ba35c99bada 100644 --- a/net/minecraft/world/level/block/state/BlockBehaviour.java +++ b/net/minecraft/world/level/block/state/BlockBehaviour.java @@ -413,7 +413,7 @@ public abstract class BlockBehaviour implements FeatureElement { @@ -36675,10 +36676,10 @@ index c634d795644be86ad85395ffa39fbac33bf7418b..66d0a6390febe929ef774b0a78133290 for (SavedTick savedTick : this.pendingTicks) { diff --git a/net/minecraft/world/waypoints/WaypointTransmitter.java b/net/minecraft/world/waypoints/WaypointTransmitter.java -index b579839c03b371d408e3750ec09af7da1d7bc9a0..9b41c62afc861847571ad739d1dd848b8276230c 100644 +index 47382efcd50f29601a6623876be50c4d047336c5..5d1c933dfa862d0733777d305563a89ea7827f07 100644 --- a/net/minecraft/world/waypoints/WaypointTransmitter.java +++ b/net/minecraft/world/waypoints/WaypointTransmitter.java -@@ -31,7 +31,10 @@ public interface WaypointTransmitter extends Waypoint { +@@ -32,7 +32,10 @@ public interface WaypointTransmitter extends Waypoint { } static boolean isChunkVisible(ChunkPos pos, ServerPlayer player) { diff --git a/paper-server/patches/sources/net/minecraft/world/waypoints/WaypointTransmitter.java.patch b/paper-server/patches/sources/net/minecraft/world/waypoints/WaypointTransmitter.java.patch new file mode 100644 index 000000000..f27603540 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/waypoints/WaypointTransmitter.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/world/waypoints/WaypointTransmitter.java ++++ b/net/minecraft/world/waypoints/WaypointTransmitter.java +@@ -20,6 +_,7 @@ + Waypoint.Icon waypointIcon(); + + static boolean doesSourceIgnoreReceiver(LivingEntity entity, ServerPlayer player) { ++ if (!player.getBukkitEntity().canSee(entity.getBukkitEntity())) return true; // Paper - ignore if entity is hidden from player + if (player.isSpectator()) { + return false; + } else if (!entity.isSpectator() && !entity.hasIndirectPassenger(player)) { From 1814d8b47ad8c174ab5c5b5c22a06ffb7de20ab3 Mon Sep 17 00:00:00 2001 From: Riley Park Date: Mon, 23 Jun 2025 14:13:13 -0700 Subject: [PATCH 06/25] build: publish to fill (#12717) --- paper-server/build.gradle.kts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/paper-server/build.gradle.kts b/paper-server/build.gradle.kts index 24aa9d6c2..123d549ab 100644 --- a/paper-server/build.gradle.kts +++ b/paper-server/build.gradle.kts @@ -1,3 +1,4 @@ +import io.papermc.fill.model.BuildChannel import io.papermc.paperweight.attribute.DevBundleOutput import io.papermc.paperweight.util.* import io.papermc.paperweight.util.data.FileEntry @@ -10,6 +11,7 @@ plugins { `maven-publish` idea id("io.papermc.paperweight.core") + id("io.papermc.fill.gradle") version "1.0.3" } val paperMavenPublicUrl = "https://repo.papermc.io/repository/maven-public/" @@ -372,3 +374,20 @@ tasks.registerRunTask("runReobfPaperclip") { classpath(tasks.createReobfPaperclipJar.flatMap { it.outputZip }) mainClass.set(null as String?) } + +fill { + project("paper") + versionFamily(paperweight.minecraftVersion.map { it.split(".", "-").takeWhile { part -> part.toIntOrNull() != null }.take(2).joinToString(".") }) + version(paperweight.minecraftVersion) + + build { + channel = BuildChannel.ALPHA + + downloads { + register("server:default") { + file = tasks.createMojmapPaperclipJar.flatMap { it.outputZip } + nameResolver.set { project, _, version, build -> "$project-$version-$build.jar" } + } + } + } +} From e454fef40e1e1e7a889327d3371fc7b5ff2b68df Mon Sep 17 00:00:00 2001 From: wiicart Date: Mon, 23 Jun 2025 22:50:59 -0400 Subject: [PATCH 07/25] Add support for private constructors in plugin main classes (#12652) --- .../paper/plugin/provider/util/ProviderUtil.java | 11 ++++++++++- .../org/bukkit/plugin/java/PluginClassLoader.java | 12 ++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/paper-api/src/main/java/io/papermc/paper/plugin/provider/util/ProviderUtil.java b/paper-api/src/main/java/io/papermc/paper/plugin/provider/util/ProviderUtil.java index 48a67c1b6..5a1f30f9b 100644 --- a/paper-api/src/main/java/io/papermc/paper/plugin/provider/util/ProviderUtil.java +++ b/paper-api/src/main/java/io/papermc/paper/plugin/provider/util/ProviderUtil.java @@ -4,6 +4,8 @@ import com.destroystokyo.paper.util.SneakyThrow; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; +import java.lang.reflect.Constructor; +import java.lang.reflect.InaccessibleObjectException; /** * An internal utility type that holds logic for loading a provider-like type from a classloaders. @@ -56,7 +58,14 @@ public final class ProviderUtil { throw new ClassCastException("class '%s' does not extend '%s'".formatted(clazz, classType)); } - clazzInstance = pluginClass.getDeclaredConstructor().newInstance(); + final Constructor constructor = pluginClass.getDeclaredConstructor(); + try { + constructor.setAccessible(true); // Allow non-public constructors + } catch (final InaccessibleObjectException | SecurityException ex) { + throw new RuntimeException("Inaccessible constructor"); + } + + clazzInstance = constructor.newInstance(); } catch (final IllegalAccessException exception) { throw new RuntimeException("No public constructor"); } catch (final InstantiationException exception) { diff --git a/paper-api/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java b/paper-api/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java index 37827b19e..8c5a5043a 100644 --- a/paper-api/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java +++ b/paper-api/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java @@ -6,6 +6,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Constructor; +import java.lang.reflect.InaccessibleObjectException; import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; import java.net.URL; @@ -91,13 +92,20 @@ public final class PluginClassLoader extends URLClassLoader implements io.paperm try { pluginConstructor = pluginClass.getDeclaredConstructor(); } catch (NoSuchMethodException ex) { - throw new InvalidPluginException("main class `" + description.getMain() + "' must have a public no-args constructor", ex); + throw new InvalidPluginException("main class `" + description.getMain() + "' must have a no-args constructor", ex); + } + + try { + // Support non-public constructors + pluginConstructor.setAccessible(true); + } catch (InaccessibleObjectException | SecurityException ex) { + throw new InvalidPluginException("main class `" + description.getMain() + "' constructor inaccessible", ex); } try { plugin = pluginConstructor.newInstance(); } catch (IllegalAccessException ex) { - throw new InvalidPluginException("main class `" + description.getMain() + "' constructor must be public", ex); + throw new InvalidPluginException("main class `" + description.getMain() + "' constructor inaccessible", ex); } catch (InstantiationException ex) { throw new InvalidPluginException("main class `" + description.getMain() + "' must not be abstract", ex); } catch (IllegalArgumentException ex) { From d0e808f44c78434fd10b31cf37aac932306f8dd0 Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Tue, 24 Jun 2025 02:09:39 -0700 Subject: [PATCH 08/25] Move player to correct position in vehicle move packet handler We add the change to guarantee that the player position moves in-sync with the vehicle's. As a result, it should be teleporting the player using the new position of the vehicle rather than the old. --- .../server/network/ServerGamePacketListenerImpl.java.patch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch index efbca9819..a1ba9c4a3 100644 --- a/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch @@ -295,7 +295,7 @@ rootVehicle.absSnapTo(d, d1, d2, f, f1); + // CraftBukkit start - fire PlayerMoveEvent TODO: this should be removed. -+ this.player.absSnapTo(x, y, z, this.player.getYRot(), this.player.getXRot()); // Paper - TODO: This breaks alot of stuff ++ this.player.absSnapTo(d, d1, d2, this.player.getYRot(), this.player.getXRot()); // Paper - TODO: This breaks alot of stuff + org.bukkit.entity.Player player = this.getCraftPlayer(); + if (!this.hasMoved) { + this.lastPosX = prevX; From 46b4b0b8d5dde6de7c6d5dc8b36eba101c3156d9 Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Tue, 24 Jun 2025 04:14:24 -0700 Subject: [PATCH 09/25] Improve keepalive ping system Send more keepalives, record all transactions within the last minute. We send more keepalives so that the latency calculation is more accurate. Since we send more keepalives, we track all pending keepalives in case multiple end up in flight. Additionally, replace the latency calculation with a true average over the last 5 seconds of keepalive transactions. --- .../0032-Improve-keepalive-ping-system.patch | 204 ++++++++++++++++++ .../ServerCommonPacketListenerImpl.java.patch | 8 +- 2 files changed, 207 insertions(+), 5 deletions(-) create mode 100644 paper-server/patches/features/0032-Improve-keepalive-ping-system.patch diff --git a/paper-server/patches/features/0032-Improve-keepalive-ping-system.patch b/paper-server/patches/features/0032-Improve-keepalive-ping-system.patch new file mode 100644 index 000000000..a0955e50f --- /dev/null +++ b/paper-server/patches/features/0032-Improve-keepalive-ping-system.patch @@ -0,0 +1,204 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 24 Jun 2025 03:41:38 -0700 +Subject: [PATCH] Improve keepalive ping system + +Send more keepalives, record all transactions within the last minute. +We send more keepalives so that the latency calculation is more +accurate. Since we send more keepalives, we track all pending +keepalives in case multiple end up in flight. + +Additionally, replace the latency calculation with a true +average over the last 5 seconds of keepalive transactions. + +diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java +index 53f038e1b5e7a13a08a0c925c8bd3f8a40868195..f3eca351021c37b64315872d075bd0a84aeee267 100644 +--- a/net/minecraft/server/level/ServerPlayer.java ++++ b/net/minecraft/server/level/ServerPlayer.java +@@ -461,6 +461,70 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + return this.viewDistanceHolder; + } + // Paper end - rewrite chunk system ++ // Paper start - improve keepalives ++ public long lastKeepAliveTx = System.nanoTime(); ++ public static final record KeepAliveResponse(long txTimeNS, long rxTimeNS) { ++ public long latencyNS() { ++ return this.rxTimeNS - this.txTimeNS; ++ } ++ } ++ public static final record PendingKeepAlive(long txTimeNS, long challengeId) {} ++ ++ public final ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue pendingKeepAlives = new ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue<>(); ++ ++ public final PingCalculator pingCalculator1m = new PingCalculator(java.util.concurrent.TimeUnit.MINUTES.toNanos(1L)); ++ public final PingCalculator pingCalculator5s = new PingCalculator(java.util.concurrent.TimeUnit.SECONDS.toNanos(5L)); ++ ++ public static final class PingCalculator { ++ ++ private final long intervalNS; ++ private final ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue responses = new ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue<>(); ++ ++ private long timeSumNS; ++ private int timeSumCount; ++ private volatile long lastAverageNS; ++ ++ public PingCalculator(long intervalNS) { ++ this.intervalNS = intervalNS; ++ } ++ ++ public void update(KeepAliveResponse response) { ++ long currTime = response.txTimeNS; ++ ++ this.responses.add(response); ++ ++ ++this.timeSumCount; ++ this.timeSumNS += response.latencyNS(); ++ ++ // remove out-of-window times ++ KeepAliveResponse removed; ++ while ((removed = this.responses.pollIf((ka) -> (currTime - ka.txTimeNS) > this.intervalNS)) != null) { ++ --this.timeSumCount; ++ this.timeSumNS -= removed.latencyNS(); ++ } ++ ++ this.lastAverageNS = this.timeSumNS / (long)this.timeSumCount; ++ } ++ ++ public int getAvgLatencyMS() { ++ return (int)java.util.concurrent.TimeUnit.NANOSECONDS.toMillis(this.getAvgLatencyNS()); ++ } ++ ++ public long getAvgLatencyNS() { ++ return this.lastAverageNS; ++ } ++ ++ public it.unimi.dsi.fastutil.longs.LongArrayList getAllNS() { ++ it.unimi.dsi.fastutil.longs.LongArrayList ret = new it.unimi.dsi.fastutil.longs.LongArrayList(); ++ ++ for (KeepAliveResponse response : this.responses) { ++ ret.add(response.latencyNS()); ++ } ++ ++ return ret; ++ } ++ } ++ // Paper end - improve keepalives + + public ServerPlayer(MinecraftServer server, ServerLevel level, GameProfile gameProfile, ClientInformation clientInformation) { + super(level, gameProfile); +diff --git a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +index 85e01c3a1536b41a0301a5a6506e058ff9633a4a..43f70a5561d6cc62aaeba6d1e39598ecb382e369 100644 +--- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +@@ -38,12 +38,12 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + protected final MinecraftServer server; + public final Connection connection; // Paper + private final boolean transferred; +- private long keepAliveTime; +- private boolean keepAlivePending; +- private long keepAliveChallenge; ++ //private long keepAliveTime; // Paper - improve keepalives ++ //private boolean keepAlivePending; // Paper - improve keepalives ++ //private long keepAliveChallenge; // Paper - improve keepalives + private long closedListenerTime; + private boolean closed = false; +- private int latency; ++ private volatile int latency; // Paper - improve keepalives - make volatile + private volatile boolean suspendFlushingOnServerThread = false; + // CraftBukkit start + protected final net.minecraft.server.level.ServerPlayer player; +@@ -57,7 +57,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + public ServerCommonPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie cookie, net.minecraft.server.level.ServerPlayer player) { // CraftBukkit + this.server = server; + this.connection = connection; +- this.keepAliveTime = Util.getMillis(); ++ //this.keepAliveTime = Util.getMillis(); // Paper - improve keepalives + this.latency = cookie.latency(); + this.transferred = cookie.transferred(); + // CraftBukkit start - add fields and methods +@@ -120,13 +120,41 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + + @Override + public void handleKeepAlive(ServerboundKeepAlivePacket packet) { +- if (this.keepAlivePending && packet.getId() == this.keepAliveChallenge) { +- int i = (int)(Util.getMillis() - this.keepAliveTime); +- this.latency = (this.latency * 3 + i) / 4; +- this.keepAlivePending = false; +- } else if (!this.isSingleplayerOwner()) { +- this.disconnectAsync(TIMEOUT_DISCONNECTION_MESSAGE, org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT); // Paper - add proper async disconnect ++ // Paper start - improve keepalives ++ long now = System.nanoTime(); ++ net.minecraft.server.level.ServerPlayer.PendingKeepAlive pending = this.player.pendingKeepAlives.peek(); ++ if (pending != null && pending.challengeId() == packet.getId()) { ++ this.player.pendingKeepAlives.remove(pending); ++ ++ net.minecraft.server.level.ServerPlayer.KeepAliveResponse response = new net.minecraft.server.level.ServerPlayer.KeepAliveResponse(pending.txTimeNS(), now); ++ ++ this.player.pingCalculator1m.update(response); ++ this.player.pingCalculator5s.update(response); ++ ++ this.latency = this.player.pingCalculator5s.getAvgLatencyMS(); ++ return; ++ } ++ ++ for (java.util.Iterator itr = this.player.pendingKeepAlives.iterator(); itr.hasNext();) { ++ net.minecraft.server.level.ServerPlayer.PendingKeepAlive ka = itr.next(); ++ if (ka.challengeId() == packet.getId()) { ++ itr.remove(); ++ ++ if (!this.processedDisconnect) { ++ LOGGER.info("Disconnecting " + this.player.getScoreboardName() + " for sending keepalive response (" + packet.getId() + ") out-of-order!"); ++ this.disconnectAsync(TIMEOUT_DISCONNECTION_MESSAGE, org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT); ++ return; ++ } ++ break; ++ } + } ++ ++ if (!this.processedDisconnect) { ++ LOGGER.info("Disconnecting " + this.player.getScoreboardName() + " for sending keepalive response (" + packet.getId() + ") without matching challenge!"); ++ this.disconnectAsync(TIMEOUT_DISCONNECTION_MESSAGE, org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT); ++ return; ++ } ++ // Paper end - improve keepalives + } + + @Override +@@ -247,20 +275,23 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + protected void keepConnectionAlive() { + Profiler.get().push("keepAlive"); + long millis = Util.getMillis(); +- // Paper start - give clients a longer time to respond to pings as per pre 1.12.2 timings +- // This should effectively place the keepalive handling back to "as it was" before 1.12.2 +- final long elapsedTime = millis - this.keepAliveTime; +- if (!this.isSingleplayerOwner() && elapsedTime >= 15000L) { // use vanilla's 15000L between keep alive packets +- if (this.keepAlivePending) { +- if (!this.processedDisconnect && elapsedTime >= KEEPALIVE_LIMIT) { // check keepalive limit, don't fire if already disconnected +- this.disconnect(TIMEOUT_DISCONNECTION_MESSAGE, org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause +- } +- // Paper end - give clients a longer time to respond to pings as per pre 1.12.2 timings +- } else if (this.checkIfClosed(millis)) { +- this.keepAlivePending = true; +- this.keepAliveTime = millis; +- this.keepAliveChallenge = millis; +- this.send(new ClientboundKeepAlivePacket(this.keepAliveChallenge)); ++ // Paper start - improve keepalives ++ if (this.checkIfClosed(millis) && !this.processedDisconnect) { ++ long currTime = System.nanoTime(); ++ ++ if ((currTime - this.player.lastKeepAliveTx) >= java.util.concurrent.TimeUnit.SECONDS.toNanos(1L)) { ++ this.player.lastKeepAliveTx = currTime; ++ ++ net.minecraft.server.level.ServerPlayer.PendingKeepAlive pka = new net.minecraft.server.level.ServerPlayer.PendingKeepAlive(currTime, millis); ++ this.player.pendingKeepAlives.add(pka); ++ this.send(new ClientboundKeepAlivePacket(pka.challengeId())); ++ } ++ ++ net.minecraft.server.level.ServerPlayer.PendingKeepAlive oldest = this.player.pendingKeepAlives.peek(); ++ if (oldest != null && (currTime - oldest.txTimeNS()) > java.util.concurrent.TimeUnit.MILLISECONDS.toNanos(KEEPALIVE_LIMIT)) { ++ LOGGER.warn(this.player.getScoreboardName() + " was kicked due to keepalive timeout!"); ++ this.disconnect(TIMEOUT_DISCONNECTION_MESSAGE, org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT); ++ // Paper end - improve keepalives + } + } + diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch index da18c6208..dac8b4dcd 100644 --- a/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch @@ -1,6 +1,6 @@ --- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -@@ -29,30 +_,67 @@ +@@ -29,14 +_,14 @@ import net.minecraft.util.profiling.Profiler; import org.slf4j.Logger; @@ -15,11 +15,9 @@ - protected final Connection connection; + public final Connection connection; // Paper private final boolean transferred; -- private long keepAliveTime; -+ private long keepAliveTime = Util.getMillis(); // Paper + private long keepAliveTime; private boolean keepAlivePending; - private long keepAliveChallenge; - private long closedListenerTime; +@@ -45,14 +_,51 @@ private boolean closed = false; private int latency; private volatile boolean suspendFlushingOnServerThread = false; From 38c1ddb52abf11c8a72fb91a104e520962d2b53a Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Tue, 24 Jun 2025 04:55:58 -0700 Subject: [PATCH 10/25] Add and use FeatureHooks.getAllEntities The ServerLevel#getAllEntities function only returns entities which are accessible. FeatureHooks#getAllEntities will return all entities, whether or not they are accessible. Use the new hook in the EntityCommand, which allows server admins to inspect entities in unloaded chunks. Use the hook as well for ticking the EntityScheduler. This fixes an issue whether unloaded entities did not have their scheduler ticked. --- .../patches/features/0004-Anti-Xray.patch | 4 ++-- .../0016-Moonrise-optimisation-patches.patch | 20 +++++++++++-------- .../io/papermc/paper/FeatureHooks.java.patch | 7 ++++++- .../server/MinecraftServer.java.patch | 2 +- .../level/entity/EntitySection.java.patch | 15 ++++++++++++++ .../entity/EntitySectionStorage.java.patch | 19 ++++++++++++++++++ .../LevelEntityGetterAdapter.java.patch | 11 ++++++++++ .../command/subcommands/EntityCommand.java | 4 ++-- 8 files changed, 68 insertions(+), 14 deletions(-) create mode 100644 paper-server/patches/sources/net/minecraft/world/level/entity/EntitySection.java.patch create mode 100644 paper-server/patches/sources/net/minecraft/world/level/entity/EntitySectionStorage.java.patch create mode 100644 paper-server/patches/sources/net/minecraft/world/level/entity/LevelEntityGetterAdapter.java.patch diff --git a/paper-server/patches/features/0004-Anti-Xray.patch b/paper-server/patches/features/0004-Anti-Xray.patch index 3a664e4b9..2dbce57f7 100644 --- a/paper-server/patches/features/0004-Anti-Xray.patch +++ b/paper-server/patches/features/0004-Anti-Xray.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Anti-Xray diff --git a/io/papermc/paper/FeatureHooks.java b/io/papermc/paper/FeatureHooks.java -index 55cc0dec4a88baea17f160e95d5d8316e0bb7a50..338dc0fb07cdba5f7350cca332fa3e942c622bfb 100644 +index 811a8e5141f2061a185b53b63d951646141c0c7d..33d3eb510c5844e72bbc382bd24641aae080962d 100644 --- a/io/papermc/paper/FeatureHooks.java +++ b/io/papermc/paper/FeatureHooks.java -@@ -40,20 +40,25 @@ public final class FeatureHooks { +@@ -45,20 +45,25 @@ public final class FeatureHooks { } public static LevelChunkSection createSection(final Registry biomeRegistry, final Level level, final ChunkPos chunkPos, final int chunkSection) { diff --git a/paper-server/patches/features/0016-Moonrise-optimisation-patches.patch b/paper-server/patches/features/0016-Moonrise-optimisation-patches.patch index f3695fc52..7dc38ba47 100644 --- a/paper-server/patches/features/0016-Moonrise-optimisation-patches.patch +++ b/paper-server/patches/features/0016-Moonrise-optimisation-patches.patch @@ -23061,7 +23061,7 @@ index 0000000000000000000000000000000000000000..f1f72a051083b61273202cb4e67ecb11 + private SaveUtil() {} +} diff --git a/io/papermc/paper/FeatureHooks.java b/io/papermc/paper/FeatureHooks.java -index 68e1e2da7a3291c6260898c90373bf21630f3351..b2b67a3b1b4620dc97a32df953d4ad47bbe5e481 100644 +index 33d3eb510c5844e72bbc382bd24641aae080962d..ccf9bd756e4841e8c0413f078842b987324aeb35 100644 --- a/io/papermc/paper/FeatureHooks.java +++ b/io/papermc/paper/FeatureHooks.java @@ -1,6 +1,9 @@ @@ -23074,8 +23074,13 @@ index 68e1e2da7a3291c6260898c90373bf21630f3351..b2b67a3b1b4620dc97a32df953d4ad47 import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; import it.unimi.dsi.fastutil.longs.LongSets; -@@ -31,12 +34,16 @@ import org.bukkit.World; - public final class FeatureHooks { +@@ -32,16 +35,20 @@ public final class FeatureHooks { + + // this includes non-accessible entities + public static Iterable getAllEntities(final net.minecraft.server.level.ServerLevel world) { +- return ((net.minecraft.world.level.entity.LevelEntityGetterAdapter)world.getEntities()).sectionStorage.getAllEntities(); ++ return ((ca.spottedleaf.moonrise.patches.chunk_system.level.entity.EntityLookup)world.getEntities()).getAllMapped(); // Paper - rewrite chunk system + } public static void setPlayerChunkUnloadDelay(final long ticks) { + ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader.setUnloadDelay(ticks); // Paper - rewrite chunk system @@ -23091,7 +23096,7 @@ index 68e1e2da7a3291c6260898c90373bf21630f3351..b2b67a3b1b4620dc97a32df953d4ad47 } public static LevelChunkSection createSection(final Registry biomeRegistry, final Level level, final ChunkPos chunkPos, final int chunkSection) { -@@ -62,111 +69,58 @@ public final class FeatureHooks { +@@ -67,111 +74,58 @@ public final class FeatureHooks { } public static Set getSentChunkKeys(final ServerPlayer player) { @@ -23224,7 +23229,7 @@ index 68e1e2da7a3291c6260898c90373bf21630f3351..b2b67a3b1b4620dc97a32df953d4ad47 org.bukkit.Chunk chunk = null; for (net.minecraft.server.level.Ticket ticket : tickets) { -@@ -186,15 +140,15 @@ public final class FeatureHooks { +@@ -191,15 +145,15 @@ public final class FeatureHooks { } public static int getViewDistance(net.minecraft.server.level.ServerLevel world) { @@ -23243,7 +23248,7 @@ index 68e1e2da7a3291c6260898c90373bf21630f3351..b2b67a3b1b4620dc97a32df953d4ad47 } public static void setViewDistance(net.minecraft.server.level.ServerLevel world, int distance) { -@@ -212,35 +166,31 @@ public final class FeatureHooks { +@@ -217,35 +171,31 @@ public final class FeatureHooks { } public static void setSendViewDistance(net.minecraft.server.level.ServerLevel world, int distance) { @@ -23286,7 +23291,6 @@ index 68e1e2da7a3291c6260898c90373bf21630f3351..b2b67a3b1b4620dc97a32df953d4ad47 } } -\ No newline at end of file diff --git a/io/papermc/paper/command/subcommands/ChunkDebugCommand.java b/io/papermc/paper/command/subcommands/ChunkDebugCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..2dca7afbd93cfbb8686f336fcd3b45dd01fba0fc @@ -23862,7 +23866,7 @@ index 46de98a6bbbae48c4837e1e588ba198a363d2dde..fd3553bdc1c3cdbf6aa3dc00e0a4987f thread1 -> { DedicatedServer dedicatedServer1 = new DedicatedServer( diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index f7362a979126c5c0a581c05c0b623cf40b8f0ebd..338ef549efe82c250c74365c1c1071986920c8c9 100644 +index aea96ab1c5f2dae9f2a19126e8be314d06b99bc3..388bf752fd09745e8c470b5bca4d004708a2d82f 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java @@ -173,7 +173,7 @@ import net.minecraft.world.phys.Vec2; diff --git a/paper-server/patches/sources/io/papermc/paper/FeatureHooks.java.patch b/paper-server/patches/sources/io/papermc/paper/FeatureHooks.java.patch index 97959e16b..6d467d774 100644 --- a/paper-server/patches/sources/io/papermc/paper/FeatureHooks.java.patch +++ b/paper-server/patches/sources/io/papermc/paper/FeatureHooks.java.patch @@ -1,6 +1,6 @@ --- /dev/null +++ b/io/papermc/paper/FeatureHooks.java -@@ -1,0 +_,241 @@ +@@ -1,0 +_,246 @@ +package io.papermc.paper; + +import io.papermc.paper.command.PaperSubcommand; @@ -33,6 +33,11 @@ + +public final class FeatureHooks { + ++ // this includes non-accessible entities ++ public static Iterable getAllEntities(final net.minecraft.server.level.ServerLevel world) { ++ return ((net.minecraft.world.level.entity.LevelEntityGetterAdapter)world.getEntities()).sectionStorage.getAllEntities(); ++ } ++ + public static void setPlayerChunkUnloadDelay(final long ticks) { + } + diff --git a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch index c55551057..ae7e78ecf 100644 --- a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch @@ -992,7 +992,7 @@ + // Paper start - Folia scheduler API + ((io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler) org.bukkit.Bukkit.getGlobalRegionScheduler()).tick(); + getAllLevels().forEach(level -> { -+ for (final net.minecraft.world.entity.Entity entity : level.getEntities().getAll()) { ++ for (final net.minecraft.world.entity.Entity entity : io.papermc.paper.FeatureHooks.getAllEntities(level)) { + if (entity.isRemoved()) { + continue; + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/entity/EntitySection.java.patch b/paper-server/patches/sources/net/minecraft/world/level/entity/EntitySection.java.patch new file mode 100644 index 000000000..ba9179927 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/entity/EntitySection.java.patch @@ -0,0 +1,15 @@ +--- a/net/minecraft/world/level/entity/EntitySection.java ++++ b/net/minecraft/world/level/entity/EntitySection.java +@@ -19,6 +_,12 @@ + this.storage = new ClassInstanceMultiMap<>(entityClazz); + } + ++ // Paper start - support retrieving all entities, regardless of whether they are accessible ++ public void getEntities(java.util.List into) { ++ into.addAll(this.storage); ++ } ++ // Paper end - support retrieving all entities, regardless of whether they are accessible ++ + public void add(T entity) { + this.storage.add(entity); + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/entity/EntitySectionStorage.java.patch b/paper-server/patches/sources/net/minecraft/world/level/entity/EntitySectionStorage.java.patch new file mode 100644 index 000000000..a6c808035 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/entity/EntitySectionStorage.java.patch @@ -0,0 +1,19 @@ +--- a/net/minecraft/world/level/entity/EntitySectionStorage.java ++++ b/net/minecraft/world/level/entity/EntitySectionStorage.java +@@ -34,6 +_,16 @@ + this.intialSectionVisibility = initialSectionVisibility; + } + ++ // Paper start - support retrieving all entities, regardless of whether they are accessible ++ public Iterable getAllEntities() { ++ java.util.List ret = new java.util.ArrayList<>(); ++ for (EntitySection section : this.sections.values()) { ++ section.getEntities(ret); ++ } ++ return ret; ++ } ++ // Paper end - support retrieving all entities, regardless of whether they are accessible ++ + public void forEachAccessibleNonEmptySection(AABB boundingBox, AbortableIterationConsumer> consumer) { + int sectionPosCoord = SectionPos.posToSectionCoord(boundingBox.minX - 2.0); + int sectionPosCoord1 = SectionPos.posToSectionCoord(boundingBox.minY - 4.0); diff --git a/paper-server/patches/sources/net/minecraft/world/level/entity/LevelEntityGetterAdapter.java.patch b/paper-server/patches/sources/net/minecraft/world/level/entity/LevelEntityGetterAdapter.java.patch new file mode 100644 index 000000000..a9e2d66b7 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/entity/LevelEntityGetterAdapter.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/world/level/entity/LevelEntityGetterAdapter.java ++++ b/net/minecraft/world/level/entity/LevelEntityGetterAdapter.java +@@ -8,7 +_,7 @@ + + public class LevelEntityGetterAdapter implements LevelEntityGetter { + private final EntityLookup visibleEntities; +- private final EntitySectionStorage sectionStorage; ++ public final EntitySectionStorage sectionStorage; // Paper - public + + public LevelEntityGetterAdapter(EntityLookup visibleEntities, EntitySectionStorage sectionStorage) { + this.visibleEntities = visibleEntities; diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java index bbd29bcca..e3ddc201b 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java @@ -1,6 +1,7 @@ package io.papermc.paper.command.subcommands; import com.google.common.collect.Maps; +import io.papermc.paper.FeatureHooks; import io.papermc.paper.command.CommandUtil; import io.papermc.paper.command.PaperSubcommand; import java.util.Collections; @@ -9,7 +10,6 @@ import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; - import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.event.HoverEvent; @@ -102,7 +102,7 @@ public final class EntityCommand implements PaperSubcommand { ServerLevel world = ((CraftWorld) bukkitWorld).getHandle(); Map nonEntityTicking = Maps.newHashMap(); ServerChunkCache chunkProviderServer = world.getChunkSource(); - world.getAllEntities().forEach(e -> { + FeatureHooks.getAllEntities(world).forEach(e -> { ResourceLocation key = EntityType.getKey(e.getType()); MutablePair> info = list.computeIfAbsent(key, k -> MutablePair.of(0, Maps.newHashMap())); From 2f083acbedfc895a8fe611a8be4bc5723d84b4e1 Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Tue, 24 Jun 2025 05:10:19 -0700 Subject: [PATCH 11/25] Ensure player entity schedulers are ticked when they are dead If the player dies, then they are removed from the world and as a result are not present in the world entity map. To guarantee that the player entity scheduler is ticked, we can tick all schedulers for players in the server player list, and then skip all players we find in the world entity map. This problem is not present on Folia since Folia must guarantee that the player remains in the world. --- ...0006-Optimize-Collision-to-not-load-chunks.patch | 8 ++++---- .../0016-Moonrise-optimisation-patches.patch | 6 +++--- .../patches/features/0028-Optimize-Hoppers.patch | 4 ++-- .../net/minecraft/server/MinecraftServer.java.patch | 13 +++++++++++-- .../net/minecraft/world/entity/Entity.java.patch | 2 +- 5 files changed, 21 insertions(+), 12 deletions(-) diff --git a/paper-server/patches/features/0006-Optimize-Collision-to-not-load-chunks.patch b/paper-server/patches/features/0006-Optimize-Collision-to-not-load-chunks.patch index 359fd9036..af170c120 100644 --- a/paper-server/patches/features/0006-Optimize-Collision-to-not-load-chunks.patch +++ b/paper-server/patches/features/0006-Optimize-Collision-to-not-load-chunks.patch @@ -14,17 +14,17 @@ movement will load only the chunk the player enters anyways and avoids loading massive amounts of surrounding chunks due to large AABB lookups. diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index 7453ddb09f349b7836f966573e4933646a75cba6..58eda0d6426f30cda604f4120f1ddb012316c108 100644 +index 23dfc87db1d5e90099270627197abc0f787a4393..27a01fd28ea565221768f31df02f0a2ddf242fce 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java -@@ -229,6 +229,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - // Paper end - Share random for entities to make them more random +@@ -230,6 +230,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess public @Nullable org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason; // Paper - Entity#getEntitySpawnReason + private volatile @Nullable org.bukkit.craftbukkit.entity.CraftEntity bukkitEntity; // Paper - Folia schedulers - volatile + public boolean collisionLoadChunks = false; // Paper - private @Nullable org.bukkit.craftbukkit.entity.CraftEntity bukkitEntity; public org.bukkit.craftbukkit.entity.CraftEntity getBukkitEntity() { + if (this.bukkitEntity == null) { diff --git a/net/minecraft/world/level/BlockCollisions.java b/net/minecraft/world/level/BlockCollisions.java index ed6e4f9fd0c7ad1219e66bc1cb4038191dd6edd8..45a20dbb935b12d429153463dba5d6fd3385dd7a 100644 --- a/net/minecraft/world/level/BlockCollisions.java diff --git a/paper-server/patches/features/0016-Moonrise-optimisation-patches.patch b/paper-server/patches/features/0016-Moonrise-optimisation-patches.patch index 7dc38ba47..afe87b7c9 100644 --- a/paper-server/patches/features/0016-Moonrise-optimisation-patches.patch +++ b/paper-server/patches/features/0016-Moonrise-optimisation-patches.patch @@ -23866,7 +23866,7 @@ index 46de98a6bbbae48c4837e1e588ba198a363d2dde..fd3553bdc1c3cdbf6aa3dc00e0a4987f thread1 -> { DedicatedServer dedicatedServer1 = new DedicatedServer( diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index aea96ab1c5f2dae9f2a19126e8be314d06b99bc3..388bf752fd09745e8c470b5bca4d004708a2d82f 100644 +index 75aba65cbe1a943f21c7464ff9465e64f63e8e5b..32475c0958fd7e0f1f9b494b0cc78a4a718d12b8 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java @@ -173,7 +173,7 @@ import net.minecraft.world.phys.Vec2; @@ -24058,7 +24058,7 @@ index aea96ab1c5f2dae9f2a19126e8be314d06b99bc3..388bf752fd09745e8c470b5bca4d0047 return true; } else { boolean ret = false; // Paper - force execution of all worlds, do not just bias the first -@@ -2469,6 +2557,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper - BlockPhysicsEvent serverLevel.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent serverLevel.updateLagCompensationTick(); // Paper - lag compensation diff --git a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch index ae7e78ecf..a26cf1104 100644 --- a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch @@ -984,16 +984,25 @@ ObjectArrayList list = new ObjectArrayList<>(min); int randomInt = Mth.nextInt(this.random, 0, players.size() - min); -@@ -1040,17 +_,66 @@ +@@ -1040,17 +_,75 @@ protected void tickChildren(BooleanSupplier hasTimeLeft) { ProfilerFiller profilerFiller = Profiler.get(); this.getPlayerList().getPlayers().forEach(serverPlayer1 -> serverPlayer1.connection.suspendFlushing()); + this.server.getScheduler().mainThreadHeartbeat(); // CraftBukkit + // Paper start - Folia scheduler API + ((io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler) org.bukkit.Bukkit.getGlobalRegionScheduler()).tick(); ++ for (ServerPlayer player : this.playerList.players) { ++ if (!this.playerList.players.contains(player)) { ++ continue; ++ } ++ final org.bukkit.craftbukkit.entity.CraftEntity bukkit = player.getBukkitEntityRaw(); ++ if (bukkit != null) { ++ bukkit.taskScheduler.executeTick(); ++ } ++ } + getAllLevels().forEach(level -> { + for (final net.minecraft.world.entity.Entity entity : io.papermc.paper.FeatureHooks.getAllEntities(level)) { -+ if (entity.isRemoved()) { ++ if (entity.isRemoved() || entity instanceof ServerPlayer) { + continue; + } + final org.bukkit.craftbukkit.entity.CraftEntity bukkit = entity.getBukkitEntityRaw(); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch index 742ae285d..a5c67c10e 100644 --- a/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch @@ -85,7 +85,7 @@ + // Paper end - Share random for entities to make them more random + public @Nullable org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason; // Paper - Entity#getEntitySpawnReason + -+ private @Nullable org.bukkit.craftbukkit.entity.CraftEntity bukkitEntity; ++ private volatile @Nullable org.bukkit.craftbukkit.entity.CraftEntity bukkitEntity; // Paper - Folia schedulers - volatile + + public org.bukkit.craftbukkit.entity.CraftEntity getBukkitEntity() { + if (this.bukkitEntity == null) { From 7c90c7c4970b44b8bcaebd1b3f4f124e9757dc3c Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Tue, 24 Jun 2025 07:19:33 -0700 Subject: [PATCH 12/25] Optimise EntityScheduler ticking The vast majority of the time, there are no tasks scheduled to the EntityScheduler. We can avoid iterating the entire entity list by tracking which schedulers have any tasks scheduled. --- ...033-Optimise-EntityScheduler-ticking.patch | 83 +++++++++++++++++ .../threadedregions/EntityScheduler.java | 92 +++++++++++++++++++ 2 files changed, 175 insertions(+) create mode 100644 paper-server/patches/features/0033-Optimise-EntityScheduler-ticking.patch diff --git a/paper-server/patches/features/0033-Optimise-EntityScheduler-ticking.patch b/paper-server/patches/features/0033-Optimise-EntityScheduler-ticking.patch new file mode 100644 index 000000000..6a1ba697e --- /dev/null +++ b/paper-server/patches/features/0033-Optimise-EntityScheduler-ticking.patch @@ -0,0 +1,83 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 24 Jun 2025 07:05:51 -0700 +Subject: [PATCH] Optimise EntityScheduler ticking + +The vast majority of the time, there are no tasks scheduled to +the EntityScheduler. We can avoid iterating the entire entity list +by tracking which schedulers have any tasks scheduled. + +diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java +index 5f2deeb5cc01d8bbeb7449bd4e59c466b3dfdf57..82824ae7ffbced513a8bcace684af94916135e84 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/server/ServerEntityLookup.java +@@ -96,6 +96,7 @@ public final class ServerEntityLookup extends EntityLookup { + if (entity instanceof ThrownEnderpearl enderpearl) { + this.addEnderPearl(CoordinateUtils.getChunkKey(enderpearl.chunkPosition())); + } ++ entity.registerScheduler(); // Paper - optimise Folia entity scheduler + } + + @Override +diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java +index 0a260fdf6b198a8ab52e60bf6db2fb5eab719c48..b8d864b9a05ba2822b6610a2ebd4ef5d2d96bd9a 100644 +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -1654,33 +1654,21 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop serverPlayer1.connection.suspendFlushing()); + this.server.getScheduler().mainThreadHeartbeat(); // CraftBukkit +- // Paper start - Folia scheduler API +- ((io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler) org.bukkit.Bukkit.getGlobalRegionScheduler()).tick(); +- for (ServerPlayer player : this.playerList.players) { +- if (!this.playerList.players.contains(player)) { ++ // Paper start - optimise Folia entity scheduler ++ for (io.papermc.paper.threadedregions.EntityScheduler scheduler : this.entitySchedulerTickList.getAllSchedulers()) { ++ if (scheduler.isRetired()) { + continue; + } +- final org.bukkit.craftbukkit.entity.CraftEntity bukkit = player.getBukkitEntityRaw(); +- if (bukkit != null) { +- bukkit.taskScheduler.executeTick(); +- } ++ ++ scheduler.executeTick(); + } +- getAllLevels().forEach(level -> { +- for (final net.minecraft.world.entity.Entity entity : io.papermc.paper.FeatureHooks.getAllEntities(level)) { +- if (entity.isRemoved() || entity instanceof ServerPlayer) { +- continue; +- } +- final org.bukkit.craftbukkit.entity.CraftEntity bukkit = entity.getBukkitEntityRaw(); +- if (bukkit != null) { +- bukkit.taskScheduler.executeTick(); +- } +- } +- }); +- // Paper end - Folia scheduler API ++ // Paper end - optimise Folia entity scheduler + io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CALLBACK_MANAGER.handleQueue(this.tickCount); // Paper + profilerFiller.push("commandFunctions"); + this.getFunctions().tick(); +diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java +index 45cdbea0bbf12697ffd1fb2193c2eafe34142ea9..81413ac0de7b3c7a72bc606fe5ae6fb4ae7055e3 100644 +--- a/net/minecraft/world/entity/Entity.java ++++ b/net/minecraft/world/entity/Entity.java +@@ -5175,6 +5175,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + this.getBukkitEntity().taskScheduler.retire(); + } + // Paper end - Folia schedulers ++ // Paper start - optimise Folia entity scheduler ++ public final void registerScheduler() { ++ this.getBukkitEntity().taskScheduler.registerTo(net.minecraft.server.MinecraftServer.getServer().entitySchedulerTickList); ++ } ++ // Paper end - optimise Folia entity scheduler + + @Override + public void setLevelCallback(EntityInLevelCallback levelCallback) { diff --git a/paper-server/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java b/paper-server/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java index c03608fec..12a135ff0 100644 --- a/paper-server/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java +++ b/paper-server/src/main/java/io/papermc/paper/threadedregions/EntityScheduler.java @@ -1,6 +1,7 @@ package io.papermc.paper.threadedregions; import ca.spottedleaf.concurrentutil.util.Validate; +import ca.spottedleaf.moonrise.common.list.ReferenceList; import ca.spottedleaf.moonrise.common.util.TickThread; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import net.minecraft.world.entity.Entity; @@ -9,6 +10,8 @@ import org.bukkit.craftbukkit.entity.CraftEntity; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.StampedLock; import java.util.function.Consumer; /** @@ -43,6 +46,8 @@ public final class EntityScheduler { private static final long RETIRED_TICK_COUNT = -1L; private final Object stateLock = new Object(); private final Long2ObjectOpenHashMap> oneTimeDelayed = new Long2ObjectOpenHashMap<>(); + private EntitySchedulerTickList scheduledList; + private boolean insideScheduledList; private final ArrayDeque currentlyExecuting = new ArrayDeque<>(); @@ -50,6 +55,51 @@ public final class EntityScheduler { this.entity = Validate.notNull(entity); } + // must own state lock + private boolean hasTasks() { + return !this.currentlyExecuting.isEmpty() || !this.oneTimeDelayed.isEmpty(); + } + + public void registerTo(final EntitySchedulerTickList newTickList) { + synchronized (this.stateLock) { + final EntitySchedulerTickList prevList = this.scheduledList; + if (prevList == newTickList) { + return; + } + this.scheduledList = newTickList; + + // make sure tasks scheduled before registration can be ticked + if (prevList == null && this.hasTasks()) { + this.insideScheduledList = true; + } + + // transfer to new list + if (this.insideScheduledList) { + if (prevList != null) { + prevList.remove(this); + } + if (newTickList != null) { + newTickList.add(this); + } else { + // retired + this.insideScheduledList = false; + } + } + } + } + + /** + * Returns whether this scheduler is retired. + * + *

+ * Note: This should only be invoked on the owning thread for the entity. + *

+ * @return whether this scheduler is retired. + */ + public boolean isRetired() { + return this.tickCount == RETIRED_TICK_COUNT; + } + /** * Retires the scheduler, preventing new tasks from being scheduled and invoking the retired callback * on all currently scheduled tasks. @@ -66,6 +116,7 @@ public final class EntityScheduler { throw new IllegalStateException("Already retired"); } this.tickCount = RETIRED_TICK_COUNT; + this.registerTo(null); } final Entity thisEntity = this.entity.getHandleRaw(); @@ -127,6 +178,11 @@ public final class EntityScheduler { this.oneTimeDelayed.computeIfAbsent(this.tickCount + Math.max(1L, delay), (final long keyInMap) -> { return new ArrayList<>(); }).add(task); + + if (!this.insideScheduledList && this.scheduledList != null) { + this.scheduledList.add(this); + this.insideScheduledList = true; + } } return true; @@ -147,6 +203,12 @@ public final class EntityScheduler { throw new IllegalStateException("Ticking retired scheduler"); } ++this.tickCount; + + if (this.scheduledList != null && !this.hasTasks()) { + this.scheduledList.remove(this); + this.insideScheduledList = false; + } + if (this.oneTimeDelayed.isEmpty()) { toRun = null; } else { @@ -178,4 +240,34 @@ public final class EntityScheduler { } } } + + public static final class EntitySchedulerTickList { + + private static final EntityScheduler[] ENTITY_SCHEDULER_ARRAY = new EntityScheduler[0]; + + private final ReferenceList entitySchedulers = new ReferenceList<>(ENTITY_SCHEDULER_ARRAY); + + public boolean add(final EntityScheduler scheduler) { + synchronized (this) { + return this.entitySchedulers.add(scheduler); + } + } + + public void remove(final EntityScheduler scheduler) { + synchronized (this) { + this.entitySchedulers.remove(scheduler); + } + } + + public EntityScheduler[] getAllSchedulers() { + EntityScheduler[] ret = new EntityScheduler[this.entitySchedulers.size()]; + synchronized (this) { + if (ret.length != this.entitySchedulers.size()) { + ret = new EntityScheduler[this.entitySchedulers.size()]; + } + System.arraycopy(this.entitySchedulers.getRawDataUnchecked(), 0, ret, 0, this.entitySchedulers.size()); + return ret; + } + } + } } From aa6ee44a7ae18c7659d3a2aab897f43675fb459c Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Tue, 24 Jun 2025 08:14:29 -0700 Subject: [PATCH 13/25] Re-add global region scheduler tick erroneously removed in last commit --- .../features/0033-Optimise-EntityScheduler-ticking.patch | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/paper-server/patches/features/0033-Optimise-EntityScheduler-ticking.patch b/paper-server/patches/features/0033-Optimise-EntityScheduler-ticking.patch index 6a1ba697e..adfb05f60 100644 --- a/paper-server/patches/features/0033-Optimise-EntityScheduler-ticking.patch +++ b/paper-server/patches/features/0033-Optimise-EntityScheduler-ticking.patch @@ -20,10 +20,10 @@ index 5f2deeb5cc01d8bbeb7449bd4e59c466b3dfdf57..82824ae7ffbced513a8bcace684af949 @Override diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index 0a260fdf6b198a8ab52e60bf6db2fb5eab719c48..b8d864b9a05ba2822b6610a2ebd4ef5d2d96bd9a 100644 +index 0a260fdf6b198a8ab52e60bf6db2fb5eab719c48..52fa5112cd90ba766c94512a02401dd3aee82cc9 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java -@@ -1654,33 +1654,21 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop Date: Tue, 24 Jun 2025 15:26:37 -0400 Subject: [PATCH 14/25] Fix #12722 (#12726) Use our getRegistryAccess to null check against world --- .../org/bukkit/craftbukkit/block/CraftBlockEntityState.java | 2 +- .../org/bukkit/craftbukkit/block/CraftCreatureSpawner.java | 4 ++-- .../bukkit/craftbukkit/entity/CraftMinecartMobSpawner.java | 6 ++++++ .../bukkit/craftbukkit/spawner/PaperSharedSpawnerLogic.java | 2 ++ 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java b/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java index 7232b560a..5d4faad9d 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java @@ -74,7 +74,7 @@ public abstract class CraftBlockEntityState extends Craft this.loadData(state.getSnapshotNBT()); } - private RegistryAccess getRegistryAccess() { + public RegistryAccess getRegistryAccess() { LevelAccessor worldHandle = this.getWorldHandle(); return (worldHandle != null) ? worldHandle.registryAccess() : CraftRegistry.getMinecraftRegistry(); } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java b/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java index 74a39d826..b824e8527 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftCreatureSpawner.java @@ -50,7 +50,7 @@ public class CraftCreatureSpawner extends CraftBlockEntityState "spawner@" + getLocation(), LOGGER)) { - ValueInput valueInput = TagValueInput.create(scopedCollector, this.getInternalWorld().registryAccess(), spawnData.entityToSpawn()); + ValueInput valueInput = TagValueInput.create(scopedCollector, this.getRegistryAccess(), spawnData.entityToSpawn()); Optional> type = net.minecraft.world.entity.EntityType.by(valueInput); return type.map(CraftEntityType::minecraftToBukkit).orElse(null); } @@ -182,7 +182,7 @@ public class CraftCreatureSpawner extends CraftBlockEntityState "spawner@" + getLocation(), LOGGER)) { - ValueInput valueInput = TagValueInput.create(scopedCollector, this.getInternalWorld().registryAccess(), spawnData.getEntityToSpawn()); + ValueInput valueInput = TagValueInput.create(scopedCollector, this.getRegistryAccess(), spawnData.getEntityToSpawn()); Optional> type = net.minecraft.world.entity.EntityType.by(valueInput); return type.map(CraftEntityType::minecraftToBukkit).map(CraftEntityType::bukkitToString).orElse(null); diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartMobSpawner.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartMobSpawner.java index c353cb4ad..ab1a4dcea 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartMobSpawner.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartMobSpawner.java @@ -4,6 +4,7 @@ import com.google.common.base.Preconditions; import java.util.Collection; import java.util.List; import java.util.Optional; +import net.minecraft.core.RegistryAccess; import net.minecraft.util.ProblemReporter; import net.minecraft.util.RandomSource; import net.minecraft.util.random.WeightedList; @@ -186,6 +187,11 @@ public class CraftMinecartMobSpawner extends CraftMinecart implements SpawnerMin return this.getHandle().level(); } + @Override + public RegistryAccess getRegistryAccess() { + return this.getHandle().registryAccess(); + } + @Override public net.minecraft.core.BlockPos getInternalPosition() { return this.getHandle().blockPosition(); diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/spawner/PaperSharedSpawnerLogic.java b/paper-server/src/main/java/org/bukkit/craftbukkit/spawner/PaperSharedSpawnerLogic.java index 11c799042..13807ed94 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/spawner/PaperSharedSpawnerLogic.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/spawner/PaperSharedSpawnerLogic.java @@ -34,6 +34,8 @@ public interface PaperSharedSpawnerLogic extends Spawner { Level getInternalWorld(); + RegistryAccess getRegistryAccess(); + BlockPos getInternalPosition(); default boolean isActivated() { return this.getSpawner().isNearPlayer(this.getInternalWorld(), this.getInternalPosition()); From 0caf75f8392e4ad4c4c7189d1b1f4922b18133b6 Mon Sep 17 00:00:00 2001 From: Owen <23108066+Owen1212055@users.noreply.github.com> Date: Wed, 25 Jun 2025 14:16:03 -0400 Subject: [PATCH 15/25] Fix #12721 (#12725) * Fix #12721 Track when knots are created and only fire the event then * Use mutable boolean + check if actually spawned --- .../LeashFenceKnotEntity.java.patch | 22 +++++++++++++++++++ .../minecraft/world/item/LeadItem.java.patch | 20 ++++++++++------- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/paper-server/patches/sources/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java.patch index 51f18a929..65331b404 100644 --- a/paper-server/patches/sources/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java.patch @@ -27,3 +27,25 @@ } } +@@ -120,7 +_,12 @@ + return this.level().getBlockState(this.pos).is(BlockTags.FENCES); + } + ++ // Paper start - Track if a knot was created + public static LeashFenceKnotEntity getOrCreateKnot(Level level, BlockPos pos) { ++ return getOrCreateKnot(level, pos, null); ++ } ++ public static LeashFenceKnotEntity getOrCreateKnot(Level level, BlockPos pos, @Nullable org.apache.commons.lang3.mutable.MutableBoolean created) { ++ // Paper end - Track if a knot was created + int x = pos.getX(); + int y = pos.getY(); + int z = pos.getZ(); +@@ -134,7 +_,7 @@ + } + + LeashFenceKnotEntity leashFenceKnotEntity1 = new LeashFenceKnotEntity(level, pos); +- level.addFreshEntity(leashFenceKnotEntity1); ++ if (level.addFreshEntity(leashFenceKnotEntity1) && created != null) { created.setTrue(); } // Paper - Track if a knot was created + return leashFenceKnotEntity1; + } + diff --git a/paper-server/patches/sources/net/minecraft/world/item/LeadItem.java.patch b/paper-server/patches/sources/net/minecraft/world/item/LeadItem.java.patch index c9fcda66a..632e53402 100644 --- a/paper-server/patches/sources/net/minecraft/world/item/LeadItem.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/item/LeadItem.java.patch @@ -1,6 +1,6 @@ --- a/net/minecraft/world/item/LeadItem.java +++ b/net/minecraft/world/item/LeadItem.java -@@ -26,24 +_,43 @@ +@@ -26,24 +_,46 @@ if (blockState.is(BlockTags.FENCES)) { Player player = context.getPlayer(); if (!level.isClientSide && player != null) { @@ -22,15 +22,19 @@ + for (java.util.Iterator iterator = list.iterator(); iterator.hasNext();) { // Paper - use iterator to remove + Leashable leashable = iterator.next(); // Paper - use iterator to remove if (leashFenceKnotEntity == null) { - leashFenceKnotEntity = LeashFenceKnotEntity.getOrCreateKnot(level, pos); +- leashFenceKnotEntity = LeashFenceKnotEntity.getOrCreateKnot(level, pos); + // CraftBukkit start - fire HangingPlaceEvent -+ org.bukkit.inventory.EquipmentSlot hand = org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(interactionHand); -+ org.bukkit.event.hanging.HangingPlaceEvent event = new org.bukkit.event.hanging.HangingPlaceEvent((org.bukkit.entity.Hanging) leashFenceKnotEntity.getBukkitEntity(), player != null ? (org.bukkit.entity.Player) player.getBukkitEntity() : null, org.bukkit.craftbukkit.block.CraftBlock.at(level, pos), org.bukkit.block.BlockFace.SELF, hand); -+ level.getCraftServer().getPluginManager().callEvent(event); ++ org.apache.commons.lang3.mutable.MutableBoolean created = new org.apache.commons.lang3.mutable.MutableBoolean(false); ++ leashFenceKnotEntity = LeashFenceKnotEntity.getOrCreateKnot(level, pos, created); ++ if (created.booleanValue()) { ++ org.bukkit.inventory.EquipmentSlot hand = org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(interactionHand); ++ org.bukkit.event.hanging.HangingPlaceEvent event = new org.bukkit.event.hanging.HangingPlaceEvent((org.bukkit.entity.Hanging) leashFenceKnotEntity.getBukkitEntity(), player != null ? (org.bukkit.entity.Player) player.getBukkitEntity() : null, org.bukkit.craftbukkit.block.CraftBlock.at(level, pos), org.bukkit.block.BlockFace.SELF, hand); ++ level.getCraftServer().getPluginManager().callEvent(event); + -+ if (event.isCancelled()) { -+ leashFenceKnotEntity.discard(null); // CraftBukkit - add Bukkit remove cause -+ return InteractionResult.PASS; ++ if (event.isCancelled()) { ++ leashFenceKnotEntity.discard(null); // CraftBukkit - add Bukkit remove cause ++ return InteractionResult.PASS; ++ } + } + // CraftBukkit end leashFenceKnotEntity.playPlacementSound(); From bee287927c5d688570635f3a7ca6b57d298a4382 Mon Sep 17 00:00:00 2001 From: Kezz Date: Wed, 25 Jun 2025 20:16:18 +0100 Subject: [PATCH 16/25] Adventure 4.23.0 (#12690) --- paper-api/build.gradle.kts | 2 +- .../main/java/org/bukkit/SoundCategory.java | 2 +- paper-server/build.gradle.kts | 2 +- .../ServerGamePacketListenerImpl.java.patch | 14 +++- .../paper/adventure/AdventureCodecs.java | 68 ++++++++++++------- .../paper/adventure/PaperAdventure.java | 17 +++-- .../providers/ClickCallbackProviderImpl.java | 38 ++++++++--- .../paper/command/CallbackCommand.java | 35 ---------- .../papermc/paper/command/PaperCommands.java | 1 - .../org/bukkit/craftbukkit/CraftWorld.java | 15 ++-- .../command/ServerCommandSender.java | 16 ++--- .../craftbukkit/entity/CraftEntity.java | 17 ++--- .../craftbukkit/entity/CraftPlayer.java | 20 +++--- .../paper/adventure/AdventureCodecsTest.java | 39 +++++++---- 14 files changed, 151 insertions(+), 135 deletions(-) delete mode 100644 paper-server/src/main/java/io/papermc/paper/command/CallbackCommand.java diff --git a/paper-api/build.gradle.kts b/paper-api/build.gradle.kts index 82d300591..db243429f 100644 --- a/paper-api/build.gradle.kts +++ b/paper-api/build.gradle.kts @@ -11,7 +11,7 @@ java { val annotationsVersion = "26.0.2" // Keep in sync with paper-server adventure-text-serializer-ansi dep -val adventureVersion = "4.21.0" +val adventureVersion = "4.23.0" val bungeeCordChatVersion = "1.21-R0.2-deprecated+build.21" val slf4jVersion = "2.0.16" val log4jVersion = "2.24.1" diff --git a/paper-api/src/main/java/org/bukkit/SoundCategory.java b/paper-api/src/main/java/org/bukkit/SoundCategory.java index 44fed7472..f4c6af9d6 100644 --- a/paper-api/src/main/java/org/bukkit/SoundCategory.java +++ b/paper-api/src/main/java/org/bukkit/SoundCategory.java @@ -37,7 +37,7 @@ public enum SoundCategory implements Sound.Source.Provider { case PLAYERS -> Sound.Source.PLAYER; case AMBIENT -> Sound.Source.AMBIENT; case VOICE -> Sound.Source.VOICE; - case UI -> throw new UnsupportedOperationException("Waiting on adventure release for the UI sound source"); // todo adventure + case UI -> Sound.Source.UI; }; } } diff --git a/paper-server/build.gradle.kts b/paper-server/build.gradle.kts index 123d549ab..3ea718cdb 100644 --- a/paper-server/build.gradle.kts +++ b/paper-server/build.gradle.kts @@ -134,7 +134,7 @@ dependencies { implementation("org.jline:jline-terminal-ffm:3.27.1") // use ffm on java 22+ implementation("org.jline:jline-terminal-jni:3.27.1") // fall back to jni on java 21 implementation("net.minecrell:terminalconsoleappender:1.3.0") - implementation("net.kyori:adventure-text-serializer-ansi:4.21.0") // Keep in sync with adventureVersion from Paper-API build file + implementation("net.kyori:adventure-text-serializer-ansi:4.23.0") // Keep in sync with adventureVersion from Paper-API build file runtimeConfiguration(sourceSets.main.map { it.runtimeClasspath }) /* diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch index a1ba9c4a3..43c85fd93 100644 --- a/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch @@ -2610,7 +2610,7 @@ if (!this.receivedMovementThisTick) { this.player.setKnownMovement(Vec3.ZERO); } -@@ -2078,4 +_,17 @@ +@@ -2078,4 +_,29 @@ interface EntityInteraction { InteractionResult run(ServerPlayer player, Entity entity, InteractionHand hand); } @@ -2627,4 +2627,16 @@ + return event; + } + // Paper end - Add fail move event ++ ++ // Paper start - Implement click callbacks with custom click action ++ @Override ++ public void handleCustomClickAction(final net.minecraft.network.protocol.common.ServerboundCustomClickActionPacket packet) { ++ super.handleCustomClickAction(packet); ++ if (packet.id().equals(io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CLICK_CALLBACK_RESOURCE_LOCATION)) { ++ packet.payload().ifPresent(tag -> ++ io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CALLBACK_MANAGER.tryRunCallback(this.player.getBukkitEntity(), tag) ++ ); ++ } ++ } ++ // Paper end - Implement click callbacks with custom click action } diff --git a/paper-server/src/main/java/io/papermc/paper/adventure/AdventureCodecs.java b/paper-server/src/main/java/io/papermc/paper/adventure/AdventureCodecs.java index 2af7fb86d..dbc03f9e0 100644 --- a/paper-server/src/main/java/io/papermc/paper/adventure/AdventureCodecs.java +++ b/paper-server/src/main/java/io/papermc/paper/adventure/AdventureCodecs.java @@ -1,5 +1,6 @@ package io.papermc.paper.adventure; +import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.datafixers.util.Either; import com.mojang.serialization.Codec; import com.mojang.serialization.DataResult; @@ -14,6 +15,7 @@ import java.util.function.Function; import java.util.function.Predicate; import com.mojang.serialization.codecs.RecordCodecBuilder; import net.kyori.adventure.key.Key; +import net.kyori.adventure.nbt.api.BinaryTagHolder; import net.kyori.adventure.text.BlockNBTComponent; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.EntityNBTComponent; @@ -37,6 +39,9 @@ import net.kyori.adventure.text.format.TextDecoration; import net.minecraft.commands.arguments.selector.SelectorPattern; import net.minecraft.core.UUIDUtil; import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.Tag; +import net.minecraft.nbt.TagParser; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.chat.ComponentSerialization; import net.minecraft.network.chat.contents.KeybindContents; @@ -44,6 +49,7 @@ import net.minecraft.network.chat.contents.ScoreContents; import net.minecraft.network.chat.contents.TranslatableContents; import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceLocation; import net.minecraft.util.ExtraCodecs; import net.minecraft.util.StringRepresentable; import net.minecraft.world.item.Item; @@ -63,7 +69,14 @@ import static net.kyori.adventure.text.TranslationArgument.numeric; @DefaultQualifier(NonNull.class) public final class AdventureCodecs { - + public static final Codec BINARY_TAG_HOLDER_CODEC = ExtraCodecs.NBT.flatComapMap(tag -> BinaryTagHolder.encode(tag, PaperAdventure.NBT_CODEC), api -> { + try { + final Tag tag = api.get(PaperAdventure.NBT_CODEC); + return DataResult.success(tag); + } catch (CommandSyntaxException e) { + return DataResult.error(e::getMessage); + } + }); public static final Codec COMPONENT_CODEC = recursive("adventure Component", AdventureCodecs::createCodec); public static final StreamCodec STREAM_COMPONENT_CODEC = ByteBufCodecs.fromCodecWithRegistriesTrusted(COMPONENT_CODEC); @@ -89,27 +102,37 @@ public final class AdventureCodecs { return Key.parseable(s) ? DataResult.success(Key.key(s)) : DataResult.error(() -> "Cannot convert " + s + " to adventure Key"); }, Key::asString); + static final Function TEXT_PAYLOAD_EXTRACTOR = a -> ((ClickEvent.Payload.Text) a.payload()).value(); + /* * Click */ static final MapCodec OPEN_URL_CODEC = mapCodec((instance) -> instance.group( - ExtraCodecs.UNTRUSTED_URI.fieldOf("url").forGetter(a -> URI.create(!a.value().contains("://") ? "https://" + a.value() : a.value())) + ExtraCodecs.UNTRUSTED_URI.fieldOf("url").forGetter(a -> { + final String url = ((ClickEvent.Payload.Text) a.payload()).value(); + return URI.create(!url.contains("://") ? "https://" + url : url); + } + ) ).apply(instance, (url) -> ClickEvent.openUrl(url.toString()))); static final MapCodec OPEN_FILE_CODEC = mapCodec((instance) -> instance.group( - Codec.STRING.fieldOf("path").forGetter(ClickEvent::value) + Codec.STRING.fieldOf("path").forGetter(TEXT_PAYLOAD_EXTRACTOR) ).apply(instance, ClickEvent::openFile)); static final MapCodec RUN_COMMAND_CODEC = mapCodec((instance) -> instance.group( - ExtraCodecs.CHAT_STRING.fieldOf("command").forGetter(ClickEvent::value) + ExtraCodecs.CHAT_STRING.fieldOf("command").forGetter(TEXT_PAYLOAD_EXTRACTOR) ).apply(instance, ClickEvent::runCommand)); static final MapCodec SUGGEST_COMMAND_CODEC = mapCodec((instance) -> instance.group( - ExtraCodecs.CHAT_STRING.fieldOf("command").forGetter(ClickEvent::value) + ExtraCodecs.CHAT_STRING.fieldOf("command").forGetter(TEXT_PAYLOAD_EXTRACTOR) ).apply(instance, ClickEvent::suggestCommand)); static final MapCodec CHANGE_PAGE_CODEC = mapCodec((instance) -> instance.group( - ExtraCodecs.POSITIVE_INT.fieldOf("page").forGetter(a -> Integer.parseInt(a.value())) + ExtraCodecs.POSITIVE_INT.fieldOf("page").forGetter(a -> ((ClickEvent.Payload.Int) a.payload()).integer()) ).apply(instance, ClickEvent::changePage)); static final MapCodec COPY_TO_CLIPBOARD_CODEC = mapCodec((instance) -> instance.group( - Codec.STRING.fieldOf("value").forGetter(ClickEvent::value) + Codec.STRING.fieldOf("value").forGetter(TEXT_PAYLOAD_EXTRACTOR) ).apply(instance, ClickEvent::copyToClipboard)); + static final MapCodec CUSTOM_CODEC = mapCodec((instance) -> instance.group( + KEY_CODEC.fieldOf("id").forGetter(a -> ((ClickEvent.Payload.Custom) a.payload()).key()), + BINARY_TAG_HOLDER_CODEC.fieldOf("payload").forGetter(a -> ((ClickEvent.Payload.Custom) a.payload()).nbt()) + ).apply(instance, ClickEvent::custom)); static final ClickEventType OPEN_URL_CLICK_EVENT_TYPE = new ClickEventType(OPEN_URL_CODEC, "open_url"); static final ClickEventType OPEN_FILE_CLICK_EVENT_TYPE = new ClickEventType(OPEN_FILE_CODEC, "open_file"); @@ -117,7 +140,8 @@ public final class AdventureCodecs { static final ClickEventType SUGGEST_COMMAND_CLICK_EVENT_TYPE = new ClickEventType(SUGGEST_COMMAND_CODEC, "suggest_command"); static final ClickEventType CHANGE_PAGE_CLICK_EVENT_TYPE = new ClickEventType(CHANGE_PAGE_CODEC, "change_page"); static final ClickEventType COPY_TO_CLIPBOARD_CLICK_EVENT_TYPE = new ClickEventType(COPY_TO_CLIPBOARD_CODEC, "copy_to_clipboard"); - static final Codec CLICK_EVENT_TYPE_CODEC = StringRepresentable.fromValues(() -> new ClickEventType[]{OPEN_URL_CLICK_EVENT_TYPE, OPEN_FILE_CLICK_EVENT_TYPE, RUN_COMMAND_CLICK_EVENT_TYPE, SUGGEST_COMMAND_CLICK_EVENT_TYPE, CHANGE_PAGE_CLICK_EVENT_TYPE, COPY_TO_CLIPBOARD_CLICK_EVENT_TYPE}); + static final ClickEventType CUSTOM_CLICK_EVENT_TYPE = new ClickEventType(CUSTOM_CODEC, "custom"); + static final Codec CLICK_EVENT_TYPE_CODEC = StringRepresentable.fromValues(() -> new ClickEventType[]{OPEN_URL_CLICK_EVENT_TYPE, OPEN_FILE_CLICK_EVENT_TYPE, RUN_COMMAND_CLICK_EVENT_TYPE, SUGGEST_COMMAND_CLICK_EVENT_TYPE, CHANGE_PAGE_CLICK_EVENT_TYPE, COPY_TO_CLIPBOARD_CLICK_EVENT_TYPE, CUSTOM_CLICK_EVENT_TYPE}); record ClickEventType(MapCodec codec, String id) implements StringRepresentable { @Override @@ -126,23 +150,17 @@ public final class AdventureCodecs { } } - private static final Function GET_CLICK_EVENT_TYPE = he -> { - if (he.action() == ClickEvent.Action.OPEN_URL) { - return OPEN_URL_CLICK_EVENT_TYPE; - } else if (he.action() == ClickEvent.Action.OPEN_FILE) { - return OPEN_FILE_CLICK_EVENT_TYPE; - } else if (he.action() == ClickEvent.Action.RUN_COMMAND) { - return RUN_COMMAND_CLICK_EVENT_TYPE; - } else if (he.action() == ClickEvent.Action.SUGGEST_COMMAND) { - return SUGGEST_COMMAND_CLICK_EVENT_TYPE; - } else if (he.action() == ClickEvent.Action.CHANGE_PAGE) { - return CHANGE_PAGE_CLICK_EVENT_TYPE; - } else if (he.action() == ClickEvent.Action.COPY_TO_CLIPBOARD) { - return COPY_TO_CLIPBOARD_CLICK_EVENT_TYPE; - } else { - throw new IllegalStateException(); - } - }; + private static final Function GET_CLICK_EVENT_TYPE = + he -> switch (he.action()) { + case OPEN_URL -> OPEN_URL_CLICK_EVENT_TYPE; + case OPEN_FILE -> OPEN_FILE_CLICK_EVENT_TYPE; + case RUN_COMMAND -> RUN_COMMAND_CLICK_EVENT_TYPE; + case SUGGEST_COMMAND -> SUGGEST_COMMAND_CLICK_EVENT_TYPE; + case CHANGE_PAGE -> CHANGE_PAGE_CLICK_EVENT_TYPE; + case COPY_TO_CLIPBOARD -> COPY_TO_CLIPBOARD_CLICK_EVENT_TYPE; + case SHOW_DIALOG -> throw new UnsupportedOperationException(); // todo: dialog codec with dialog "api" + case CUSTOM -> CUSTOM_CLICK_EVENT_TYPE; + }; static final Codec CLICK_EVENT_CODEC = CLICK_EVENT_TYPE_CODEC.dispatch("action", GET_CLICK_EVENT_TYPE, ClickEventType::codec); diff --git a/paper-server/src/main/java/io/papermc/paper/adventure/PaperAdventure.java b/paper-server/src/main/java/io/papermc/paper/adventure/PaperAdventure.java index 32b378681..75b46a9fd 100644 --- a/paper-server/src/main/java/io/papermc/paper/adventure/PaperAdventure.java +++ b/paper-server/src/main/java/io/papermc/paper/adventure/PaperAdventure.java @@ -35,8 +35,6 @@ import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.plain.PlainComponentSerializer; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import net.kyori.adventure.translation.GlobalTranslator; -import net.kyori.adventure.translation.TranslationRegistry; -import net.kyori.adventure.translation.Translator; import net.kyori.adventure.util.Codec; import net.minecraft.ChatFormatting; import net.minecraft.commands.CommandSourceStack; @@ -78,16 +76,16 @@ import static java.util.Objects.requireNonNull; public final class PaperAdventure { private static final Pattern LOCALIZATION_PATTERN = Pattern.compile("%(?:(\\d+)\\$)?s"); public static final ComponentFlattener FLATTENER = ComponentFlattener.basic().toBuilder() + .nestingLimit(30) // todo: should this be configurable? a system property or config value? .complexMapper(TranslatableComponent.class, (translatable, consumer) -> { - if (!Language.getInstance().has(translatable.key())) { - for (final Translator source : GlobalTranslator.translator().sources()) { - if (source instanceof TranslationRegistry registry && registry.contains(translatable.key())) { - consumer.accept(GlobalTranslator.render(translatable, Locale.US)); - return; - } + final Language language = Language.getInstance(); + final @Nullable String fallback = translatable.fallback(); + if (!language.has(translatable.key()) && (fallback == null || !language.has(fallback))) { + if (GlobalTranslator.translator().canTranslate(translatable.key(), Locale.US)) { + consumer.accept(GlobalTranslator.render(translatable, Locale.US)); + return; } } - final @Nullable String fallback = translatable.fallback(); final @NotNull String translated = Language.getInstance().getOrDefault(translatable.key(), fallback != null ? fallback : translatable.key()); final Matcher matcher = LOCALIZATION_PATTERN.matcher(translated); @@ -379,6 +377,7 @@ public final class PaperAdventure { case PLAYER -> SoundSource.PLAYERS; case AMBIENT -> SoundSource.AMBIENT; case VOICE -> SoundSource.VOICE; + case UI -> SoundSource.UI; }; } diff --git a/paper-server/src/main/java/io/papermc/paper/adventure/providers/ClickCallbackProviderImpl.java b/paper-server/src/main/java/io/papermc/paper/adventure/providers/ClickCallbackProviderImpl.java index 40a7ab04f..28a3817d4 100644 --- a/paper-server/src/main/java/io/papermc/paper/adventure/providers/ClickCallbackProviderImpl.java +++ b/paper-server/src/main/java/io/papermc/paper/adventure/providers/ClickCallbackProviderImpl.java @@ -1,24 +1,33 @@ package io.papermc.paper.adventure.providers; +import io.papermc.paper.adventure.PaperAdventure; import java.util.HashMap; import java.util.Map; +import java.util.Queue; import java.util.UUID; +import java.util.concurrent.ConcurrentLinkedQueue; import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.key.Key; import net.kyori.adventure.text.event.ClickCallback; import net.kyori.adventure.text.event.ClickEvent; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.Tag; +import net.minecraft.resources.ResourceLocation; import org.jetbrains.annotations.NotNull; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; - @SuppressWarnings("UnstableApiUsage") // permitted provider public class ClickCallbackProviderImpl implements ClickCallback.Provider { + private static final Key CLICK_CALLBACK_KEY = Key.key("paper", "click_callback"); + private static final String ID_KEY = "id"; + public static final ResourceLocation CLICK_CALLBACK_RESOURCE_LOCATION = PaperAdventure.asVanilla(CLICK_CALLBACK_KEY); public static final CallbackManager CALLBACK_MANAGER = new CallbackManager(); @Override public @NotNull ClickEvent create(final @NotNull ClickCallback callback, final ClickCallback.@NotNull Options options) { - return ClickEvent.runCommand("/paper:callback " + CALLBACK_MANAGER.addCallback(callback, options)); + final CompoundTag tag = new CompoundTag(); + tag.putString(ID_KEY, CALLBACK_MANAGER.addCallback(callback, options).toString()); + return ClickEvent.custom(CLICK_CALLBACK_KEY, PaperAdventure.asBinaryTagHolder(tag)); } public static final class CallbackManager { @@ -48,12 +57,21 @@ public class ClickCallbackProviderImpl implements ClickCallback.Provider { } } - public void runCallback(final @NotNull Audience audience, final UUID id) { - final StoredCallback callback = this.callbacks.get(id); - if (callback != null && callback.valid()) { //TODO Message if expired/invalid? - callback.takeUse(); - callback.callback.accept(audience); - } + public void tryRunCallback(final @NotNull Audience audience, final Tag tag) { + tag.asCompound().flatMap(t -> t.getString(ID_KEY)).ifPresent(s -> { + final UUID id; + try { + id = UUID.fromString(s); + } catch (final IllegalArgumentException ignored) { + return; + } + + final StoredCallback callback = this.callbacks.get(id); + if (callback != null && callback.valid()) { + callback.takeUse(); + callback.callback.accept(audience); + } + }); } } diff --git a/paper-server/src/main/java/io/papermc/paper/command/CallbackCommand.java b/paper-server/src/main/java/io/papermc/paper/command/CallbackCommand.java deleted file mode 100644 index fa4202abd..000000000 --- a/paper-server/src/main/java/io/papermc/paper/command/CallbackCommand.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.papermc.paper.command; - -import io.papermc.paper.adventure.providers.ClickCallbackProviderImpl; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.framework.qual.DefaultQualifier; -import java.util.UUID; - -@DefaultQualifier(NonNull.class) -public class CallbackCommand extends Command { - - protected CallbackCommand(final String name) { - super(name); - this.description = "ClickEvent callback"; - this.usageMessage = "/callback "; - } - - @Override - public boolean execute(final CommandSender sender, final String commandLabel, final String[] args) { - if (args.length != 1) { - return false; - } - - final UUID id; - try { - id = UUID.fromString(args[0]); - } catch (final IllegalArgumentException ignored) { - return false; - } - - ClickCallbackProviderImpl.CALLBACK_MANAGER.runCallback(sender, id); - return true; - } -} diff --git a/paper-server/src/main/java/io/papermc/paper/command/PaperCommands.java b/paper-server/src/main/java/io/papermc/paper/command/PaperCommands.java index f3f466ee5..eeefb915d 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/PaperCommands.java +++ b/paper-server/src/main/java/io/papermc/paper/command/PaperCommands.java @@ -23,7 +23,6 @@ public final class PaperCommands { public static void registerCommands(final MinecraftServer server) { COMMANDS.put("paper", new PaperCommand("paper")); - COMMANDS.put("callback", new CallbackCommand("callback")); COMMANDS.put("mspt", new MSPTCommand("mspt")); COMMANDS.forEach((s, command) -> { diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index 7990382df..68351e476 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -29,6 +29,7 @@ import java.util.UUID; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.stream.Collectors; +import net.kyori.adventure.pointer.PointersSupplier; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.core.HolderSet; @@ -157,6 +158,10 @@ import org.jetbrains.annotations.Nullable; public class CraftWorld extends CraftRegionAccessor implements World { public static final int CUSTOM_DIMENSION_OFFSET = 10; private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry(); + private static final PointersSupplier POINTERS_SUPPLIER = PointersSupplier.builder() + .resolving(net.kyori.adventure.identity.Identity.NAME, World::getName) + .resolving(net.kyori.adventure.identity.Identity.UUID, World::getUID) + .build(); private final ServerLevel world; private WorldBorder worldBorder; @@ -168,7 +173,6 @@ public class CraftWorld extends CraftRegionAccessor implements World { private final BlockMetadataStore blockMetadata = new BlockMetadataStore(this); private final Object2IntOpenHashMap spawnCategoryLimit = new Object2IntOpenHashMap<>(); private final CraftPersistentDataContainer persistentDataContainer = new CraftPersistentDataContainer(CraftWorld.DATA_TYPE_REGISTRY); - private net.kyori.adventure.pointer.Pointers adventure$pointers; // Paper - implement pointers // Paper start - void damage configuration private boolean voidDamageEnabled; private float voidDamageAmount; @@ -2481,14 +2485,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { // Paper start - implement pointers @Override public net.kyori.adventure.pointer.Pointers pointers() { - if (this.adventure$pointers == null) { - this.adventure$pointers = net.kyori.adventure.pointer.Pointers.builder() - .withDynamic(net.kyori.adventure.identity.Identity.NAME, this::getName) - .withDynamic(net.kyori.adventure.identity.Identity.UUID, this::getUID) - .build(); - } - - return this.adventure$pointers; + return POINTERS_SUPPLIER.view(this); } // Paper end } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java b/paper-server/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java index 36e478b22..b540472c0 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java @@ -2,6 +2,7 @@ package org.bukkit.craftbukkit.command; import java.util.Set; import java.util.UUID; +import net.kyori.adventure.pointer.PointersSupplier; import org.bukkit.Bukkit; import org.bukkit.Server; import org.bukkit.command.CommandSender; @@ -12,8 +13,12 @@ import org.bukkit.permissions.PermissionAttachmentInfo; import org.bukkit.plugin.Plugin; public abstract class ServerCommandSender implements CommandSender { + private static final PointersSupplier POINTERS_SUPPLIER = PointersSupplier.builder() + .resolving(net.kyori.adventure.identity.Identity.DISPLAY_NAME, ServerCommandSender::name) + .resolving(net.kyori.adventure.permission.PermissionChecker.POINTER, serverCommandSender -> serverCommandSender::permissionValue) + .build(); + public final PermissibleBase perm; - private net.kyori.adventure.pointer.Pointers adventure$pointers; protected ServerCommandSender() { this.perm = new PermissibleBase(this); @@ -126,13 +131,6 @@ public abstract class ServerCommandSender implements CommandSender { @Override public net.kyori.adventure.pointer.Pointers pointers() { - if (this.adventure$pointers == null) { - this.adventure$pointers = net.kyori.adventure.pointer.Pointers.builder() - .withDynamic(net.kyori.adventure.identity.Identity.DISPLAY_NAME, this::name) - .withStatic(net.kyori.adventure.permission.PermissionChecker.POINTER, this::permissionValue) - .build(); - } - - return this.adventure$pointers; + return POINTERS_SUPPLIER.view(this); } } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java index a41f481a1..b38073628 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java @@ -14,6 +14,7 @@ import java.util.Set; import java.util.UUID; import io.papermc.paper.entity.LookAnchor; import java.util.concurrent.CompletableFuture; +import net.kyori.adventure.pointer.PointersSupplier; import net.kyori.adventure.util.TriState; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.chat.Component; @@ -85,13 +86,17 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { private static PermissibleBase perm; private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry(); + static final PointersSupplier POINTERS_SUPPLIER = PointersSupplier.builder() + .resolving(net.kyori.adventure.identity.Identity.DISPLAY_NAME, org.bukkit.entity.Entity::name) + .resolving(net.kyori.adventure.identity.Identity.UUID, org.bukkit.entity.Entity::getUniqueId) + .resolving(net.kyori.adventure.permission.PermissionChecker.POINTER, entity1 -> entity1::permissionValue) + .build(); protected final CraftServer server; protected Entity entity; private final EntityType entityType; private EntityDamageEvent lastDamageEvent; private final CraftPersistentDataContainer persistentDataContainer = new CraftPersistentDataContainer(CraftEntity.DATA_TYPE_REGISTRY); - protected net.kyori.adventure.pointer.Pointers adventure$pointers; // Paper - implement pointers // Paper start - Folia shedulers public final io.papermc.paper.threadedregions.EntityScheduler taskScheduler = new io.papermc.paper.threadedregions.EntityScheduler(this); private final io.papermc.paper.threadedregions.scheduler.FoliaEntityScheduler apiScheduler = new io.papermc.paper.threadedregions.scheduler.FoliaEntityScheduler(this); @@ -670,15 +675,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { @Override public net.kyori.adventure.pointer.Pointers pointers() { - if (this.adventure$pointers == null) { - this.adventure$pointers = net.kyori.adventure.pointer.Pointers.builder() - .withDynamic(net.kyori.adventure.identity.Identity.DISPLAY_NAME, this::name) - .withDynamic(net.kyori.adventure.identity.Identity.UUID, this::getUniqueId) - .withStatic(net.kyori.adventure.permission.PermissionChecker.POINTER, this::permissionValue) - .build(); - } - - return this.adventure$pointers; + return POINTERS_SUPPLIER.view(this); } @Override diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java index 144623d4e..3e18819ae 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java @@ -44,6 +44,8 @@ import java.util.concurrent.CompletableFuture; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; +import net.kyori.adventure.identity.Identity; +import net.kyori.adventure.pointer.PointersSupplier; import net.kyori.adventure.util.TriState; import net.md_5.bungee.api.chat.BaseComponent; import net.minecraft.advancements.AdvancementProgress; @@ -213,6 +215,12 @@ import org.jspecify.annotations.Nullable; @DelegateDeserialization(CraftOfflinePlayer.class) public class CraftPlayer extends CraftHumanEntity implements Player { + private static final PointersSupplier POINTERS_SUPPLIER = PointersSupplier.builder() + .parent(CraftEntity.POINTERS_SUPPLIER) + .resolving(Identity.NAME, Player::getName) + .resolving(Identity.DISPLAY_NAME, Player::displayName) + .resolving(Identity.LOCALE, Player::locale) + .build(); private long firstPlayed = 0; private long lastPlayed = 0; @@ -3283,17 +3291,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { @Override public net.kyori.adventure.pointer.Pointers pointers() { - if (this.adventure$pointers == null) { - this.adventure$pointers = net.kyori.adventure.pointer.Pointers.builder() - .withDynamic(net.kyori.adventure.identity.Identity.DISPLAY_NAME, this::displayName) - .withDynamic(net.kyori.adventure.identity.Identity.NAME, this::getName) - .withDynamic(net.kyori.adventure.identity.Identity.UUID, this::getUniqueId) - .withStatic(net.kyori.adventure.permission.PermissionChecker.POINTER, this::permissionValue) - .withDynamic(net.kyori.adventure.identity.Identity.LOCALE, this::locale) - .build(); - } - - return this.adventure$pointers; + return POINTERS_SUPPLIER.view(this); } @Override diff --git a/paper-server/src/test/java/io/papermc/paper/adventure/AdventureCodecsTest.java b/paper-server/src/test/java/io/papermc/paper/adventure/AdventureCodecsTest.java index 42e9b09c8..f8c00e1de 100644 --- a/paper-server/src/test/java/io/papermc/paper/adventure/AdventureCodecsTest.java +++ b/paper-server/src/test/java/io/papermc/paper/adventure/AdventureCodecsTest.java @@ -11,11 +11,14 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.net.URI; import java.util.List; +import java.util.Optional; import java.util.UUID; import java.util.function.Function; import java.util.stream.Stream; import net.kyori.adventure.key.Key; +import net.kyori.adventure.nbt.api.BinaryTagHolder; import net.kyori.adventure.text.BlockNBTComponent; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; @@ -106,23 +109,35 @@ class AdventureCodecsTest { } @ParameterizedTest(name = PARAMETERIZED_NAME) - @EnumSource(value = ClickEvent.Action.class, mode = EnumSource.Mode.EXCLUDE, names = {"OPEN_FILE"}) + @EnumSource(value = ClickEvent.Action.class, mode = EnumSource.Mode.EXCLUDE, names = {"OPEN_FILE", "SHOW_DIALOG", "CUSTOM"}) void testClickEvent(final ClickEvent.Action action) { - final ClickEvent event = ClickEvent.clickEvent(action, action.name().equals("OPEN_URL") ? "https://google.com" : "1337"); - final Tag result = CLICK_EVENT_CODEC.encodeStart(NbtOps.INSTANCE, event).result().orElseThrow(); + final ClickEvent event = switch (action) { + case OPEN_URL -> openUrl("https://google.com"); + case RUN_COMMAND -> ClickEvent.runCommand("/say hello"); + case SUGGEST_COMMAND -> suggestCommand("/suggest hello"); + case CHANGE_PAGE -> ClickEvent.changePage(2); + case COPY_TO_CLIPBOARD -> ClickEvent.copyToClipboard("clipboard content"); + case CUSTOM -> ClickEvent.custom(key("test"), BinaryTagHolder.binaryTagHolder("3")); + case SHOW_DIALOG, OPEN_FILE -> throw new IllegalArgumentException(); + }; + final Tag result = CLICK_EVENT_CODEC.encodeStart(NbtOps.INSTANCE, event).result().orElseThrow(() -> new RuntimeException("Failed to encode ClickEvent: " + event)); final net.minecraft.network.chat.ClickEvent nms = net.minecraft.network.chat.ClickEvent.CODEC.decode(NbtOps.INSTANCE, result).result().orElseThrow().getFirst(); assertEquals(event.action().toString(), nms.action().getSerializedName()); switch (nms) { - case net.minecraft.network.chat.ClickEvent.OpenUrl(java.net.URI uri) -> - assertEquals(event.value(), uri.toString()); + case net.minecraft.network.chat.ClickEvent.OpenUrl(URI uri) -> + assertEquals(((ClickEvent.Payload.Text) event.payload()).value(), uri.toString()); case net.minecraft.network.chat.ClickEvent.SuggestCommand(String command) -> - assertEquals(event.value(), command); + assertEquals(((ClickEvent.Payload.Text) event.payload()).value(), command); case net.minecraft.network.chat.ClickEvent.RunCommand(String command) -> - assertEquals(event.value(), command); + assertEquals(((ClickEvent.Payload.Text) event.payload()).value(), command); case net.minecraft.network.chat.ClickEvent.CopyToClipboard(String value) -> - assertEquals(event.value(), value); + assertEquals(((ClickEvent.Payload.Text) event.payload()).value(), value); case net.minecraft.network.chat.ClickEvent.ChangePage(int page) -> - assertEquals(event.value(), String.valueOf(page)); + assertEquals(((ClickEvent.Payload.Int) event.payload()).integer(), page); + case net.minecraft.network.chat.ClickEvent.Custom(ResourceLocation id, Optional payload) -> { + assertEquals(((ClickEvent.Payload.Custom) event.payload()).key().toString(), id.toString()); + assertEquals(((ClickEvent.Payload.Custom) event.payload()).nbt(), payload.orElseThrow().asString()); + } default -> throw new AssertionError("Unexpected ClickEvent type: " + nms.getClass()); } } @@ -294,10 +309,10 @@ class AdventureCodecsTest { .clickEvent(openUrl("https://github.com")) .build(), style() - .hoverEvent(HoverEvent.showEntity(HoverEvent.ShowEntity.showEntity( - Key.key(Key.MINECRAFT_NAMESPACE, "pig"), + .hoverEvent(showEntity(HoverEvent.ShowEntity.showEntity( + key(Key.MINECRAFT_NAMESPACE, "pig"), UUID.randomUUID(), - Component.text("Dolores", TextColor.color(0x0a1ab9)) + text("Dolores", color(0x0a1ab9)) ))) .build() ); From 692e93a91f48924cc46a908f6f1e097815ed373e Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Wed, 25 Jun 2025 12:23:05 -0700 Subject: [PATCH 17/25] Fix MC-299110 When converting entity/poi data (SimpleRegionStorage), we need to insert the new data version so that force upgrading will write the data back with the correct version. --- .../features/0015-Rewrite-dataconverter-system.patch | 12 +++++++----- .../0016-Moonrise-optimisation-patches.patch | 5 +++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/paper-server/patches/features/0015-Rewrite-dataconverter-system.patch b/paper-server/patches/features/0015-Rewrite-dataconverter-system.patch index e9a7bb812..e83312ee7 100644 --- a/paper-server/patches/features/0015-Rewrite-dataconverter-system.patch +++ b/paper-server/patches/features/0015-Rewrite-dataconverter-system.patch @@ -32481,7 +32481,7 @@ index 0000000000000000000000000000000000000000..b028017b9c44821a8a313a04e0b10f5d + } +} diff --git a/ca/spottedleaf/moonrise/paper/PaperHooks.java b/ca/spottedleaf/moonrise/paper/PaperHooks.java -index 0c611598a6912b692a7639301811ee557e2304f1..6f65564f6a6e99d6549de9a23a031b1f4a8a9798 100644 +index e4f0653c575c33b1ef8160b6c88e29ee9fb44508..b6a34796d33f1593301c3a67a013fa7e812cb329 100644 --- a/ca/spottedleaf/moonrise/paper/PaperHooks.java +++ b/ca/spottedleaf/moonrise/paper/PaperHooks.java @@ -211,6 +211,43 @@ public final class PaperHooks extends BaseChunkSystemHooks implements PlatformHo @@ -32542,7 +32542,7 @@ index 6536dc08c80170f5679acedd65cd2b9f6ad3fb3a..294cd15a796ad25823c8ccf98fbfae46 return structureTemplate.save(new CompoundTag()); } diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index 794a01ee70a2a30d91550f5265f774ba73828cf9..c716521fb1497dc8a22d827ddb50fc1cc21a05f4 100644 +index 2d597e50dcd957bd566c4da384fac5f36b5362f7..75aba65cbe1a943f21c7464ff9465e64f63e8e5b 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java @@ -305,6 +305,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop Date: Wed, 25 Jun 2025 15:51:26 -0400 Subject: [PATCH 18/25] Don't mutate the position of Items for MC-4 Fix (#12702) --- .../protocol/game/VecDeltaCodec.java.patch | 13 +------------ .../net/minecraft/world/entity/Entity.java.patch | 12 +----------- .../world/entity/item/ItemEntity.java.patch | 15 +++++++++++++++ .../paper/configuration/GlobalConfiguration.java | 2 +- .../configuration/RemovedConfigurations.java | 1 + 5 files changed, 19 insertions(+), 24 deletions(-) diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/game/VecDeltaCodec.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/VecDeltaCodec.java.patch index 2f0544231..fff6b8efc 100644 --- a/paper-server/patches/sources/net/minecraft/network/protocol/game/VecDeltaCodec.java.patch +++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/VecDeltaCodec.java.patch @@ -1,6 +1,6 @@ --- a/net/minecraft/network/protocol/game/VecDeltaCodec.java +++ b/net/minecraft/network/protocol/game/VecDeltaCodec.java -@@ -5,16 +_,16 @@ +@@ -5,7 +_,7 @@ public class VecDeltaCodec { private static final double TRUNCATION_STEPS = 4096.0; @@ -9,14 +9,3 @@ @VisibleForTesting static long encode(double value) { -- return Math.round(value * 4096.0); -+ return Math.round(value * 4096.0); // Paper - Fix MC-4; diff on change - } - - @VisibleForTesting - static double decode(long value) { -- return value / 4096.0; -+ return value / 4096.0; // Paper - Fix MC-4; diff on change - } - - public Vec3 decode(long x, long y, long z) { diff --git a/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch index a5c67c10e..6bf39741d 100644 --- a/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch @@ -1786,7 +1786,7 @@ } public void addDeltaMovement(Vec3 addend) { -@@ -3661,9 +_,45 @@ +@@ -3661,9 +_,35 @@ return this.getZ((2.0 * this.random.nextDouble() - 1.0) * scale); } @@ -1815,16 +1815,6 @@ + return; + } + // Paper end - Block invalid positions and bounding box -+ // Paper start - Fix MC-4 -+ if (this instanceof ItemEntity) { -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.fixEntityPositionDesync) { -+ // encode/decode from VecDeltaCodec todo computation changed? -+ x = Mth.lfloor(x * 4096.0) * (1 / 4096.0); -+ y = Mth.lfloor(y * 4096.0) * (1 / 4096.0); -+ z = Mth.lfloor(z * 4096.0) * (1 / 4096.0); -+ } -+ } -+ // Paper end - Fix MC-4 if (this.position.x != x || this.position.y != y || this.position.z != z) { + synchronized (this.posLock) { // Paper - detailed watchdog information this.position = new Vec3(x, y, z); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch index f3eb51339..f48b45fe5 100644 --- a/paper-server/patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/item/ItemEntity.java.patch @@ -24,6 +24,21 @@ } public ItemEntity(Level level, double posX, double posY, double posZ, ItemStack itemStack, double deltaX, double deltaY, double deltaZ) { +@@ -79,6 +_,14 @@ + this.bobOffs = other.bobOffs; + } + ++ // Paper start - Require item entities to send their location precisely (Fixes MC-4) ++ { ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.sendFullPosForItemEntities) { ++ this.setRequiresPrecisePosition(true); ++ } ++ } ++ // Paper end - Require item entities to send their location precisely (Fixes MC-4) ++ + @Override + public boolean dampensVibrations() { + return this.getItem().is(ItemTags.DAMPENS_VIBRATIONS); @@ -116,7 +_,7 @@ @Override public void tick() { diff --git a/paper-server/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/paper-server/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java index c2b53adb5..17247f177 100644 --- a/paper-server/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java +++ b/paper-server/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java @@ -343,7 +343,7 @@ public class GlobalConfiguration extends ConfigurationPart { } } public int maxJoinsPerTick = 5; - public boolean fixEntityPositionDesync = true; + public boolean sendFullPosForItemEntities = false; public boolean loadPermissionsYmlBeforePlugins = true; @Constraints.Min(4) public int regionFileCacheSize = 256; diff --git a/paper-server/src/main/java/io/papermc/paper/configuration/RemovedConfigurations.java b/paper-server/src/main/java/io/papermc/paper/configuration/RemovedConfigurations.java index 7e4b27a6e..2ea421954 100644 --- a/paper-server/src/main/java/io/papermc/paper/configuration/RemovedConfigurations.java +++ b/paper-server/src/main/java/io/papermc/paper/configuration/RemovedConfigurations.java @@ -82,6 +82,7 @@ interface RemovedConfigurations { path("unsupported-settings", "allow-grindstone-overstacking"), path("unsupported-settings", "allow-tripwire-disarming-exploits"), path("commands", "fix-target-selector-tag-completion"), + path("misc", "fix-entity-position-desync") }; } From aa6cd74c037d3607f92deec485e133324b80272a Mon Sep 17 00:00:00 2001 From: Pedro <3602279+Doc94@users.noreply.github.com> Date: Wed, 25 Jun 2025 16:09:11 -0400 Subject: [PATCH 19/25] Remove unnecesary item check for ServerboundPlayerActionPacket RELEASE_USE_ITEM (#12668) --- .../server/network/ServerGamePacketListenerImpl.java.patch | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch index 43c85fd93..70b6b96e5 100644 --- a/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch @@ -1146,8 +1146,7 @@ return; case RELEASE_USE_ITEM: -- this.player.releaseUsingItem(); -+ if (this.player.getUseItem() == this.player.getItemInHand(this.player.getUsedItemHand())) this.player.releaseUsingItem(); // Paper - validate use item before processing release + this.player.releaseUsingItem(); + if (io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.updateEquipmentOnPlayerActions) this.player.detectEquipmentUpdates(); // Paper - Force update attributes. return; case START_DESTROY_BLOCK: From c9e89f49b0ed47899823be6cf95e17035056ac65 Mon Sep 17 00:00:00 2001 From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> Date: Wed, 25 Jun 2025 22:11:18 +0200 Subject: [PATCH 20/25] Expose arrow velocity in EntityShootBowEvent for mobs (#12688) --- .../monster/AbstractSkeleton.java.patch | 29 ++++++++++++++----- .../entity/monster/Illusioner.java.patch | 27 ++++++++++++----- 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/AbstractSkeleton.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/AbstractSkeleton.java.patch index 4b66486e6..bb54fbf08 100644 --- a/paper-server/patches/sources/net/minecraft/world/entity/monster/AbstractSkeleton.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/AbstractSkeleton.java.patch @@ -43,23 +43,36 @@ if (this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) { LocalDate localDate = LocalDate.now(); int i = localDate.get(ChronoField.DAY_OF_MONTH); -@@ -196,9 +_,19 @@ +@@ -188,7 +_,8 @@ + + @Override + public void performRangedAttack(LivingEntity target, float distanceFactor) { +- ItemStack itemInHand = this.getItemInHand(ProjectileUtil.getWeaponHoldingHand(this, Items.BOW)); ++ net.minecraft.world.InteractionHand hand = ProjectileUtil.getWeaponHoldingHand(this, Items.BOW); // Paper - call EntityShootBowEvent ++ ItemStack itemInHand = this.getItemInHand(hand); // Paper - call EntityShootBowEvent + ItemStack projectile = this.getProjectile(itemInHand); + AbstractArrow arrow = this.getArrow(projectile, distanceFactor, itemInHand); + double d = target.getX() - this.getX(); +@@ -196,9 +_,21 @@ double d2 = target.getZ() - this.getZ(); double squareRoot = Math.sqrt(d * d + d2 * d2); if (this.level() instanceof ServerLevel serverLevel) { -+ // CraftBukkit start -+ org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(this, this.getMainHandItem(), arrow.getPickupItem(), arrow, net.minecraft.world.InteractionHand.MAIN_HAND, distanceFactor, true); // Paper - improve entity shoot bow event, add arrow stack to event +- Projectile.spawnProjectileUsingShoot( ++ Projectile.Delayed delayedEntity = Projectile.spawnProjectileUsingShootDelayed( // Paper - delayed + arrow, serverLevel, projectile, d, d1 + squareRoot * 0.2F, d2, 1.6F, 14 - serverLevel.getDifficulty().getId() * 4 + ); ++ ++ // Paper start - call EntityShootBowEvent ++ org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(this, itemInHand, arrow.getPickupItem(), arrow, hand, distanceFactor, true); + if (event.isCancelled()) { + event.getProjectile().remove(); + return; + } + + if (event.getProjectile() == arrow.getBukkitEntity()) { -+ // CraftBukkit end - Projectile.spawnProjectileUsingShoot( - arrow, serverLevel, projectile, d, d1 + squareRoot * 0.2F, d2, 1.6F, 14 - serverLevel.getDifficulty().getId() * 4 - ); -+ } // CraftBukkit ++ delayedEntity.spawn(); ++ } ++ // Paper end - call EntityShootBowEvent } this.playSound(SoundEvents.SKELETON_SHOOT, 1.0F, 1.0F / (this.getRandom().nextFloat() * 0.4F + 0.8F)); diff --git a/paper-server/patches/sources/net/minecraft/world/entity/monster/Illusioner.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/monster/Illusioner.java.patch index fd0641de0..59562e47c 100644 --- a/paper-server/patches/sources/net/minecraft/world/entity/monster/Illusioner.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/monster/Illusioner.java.patch @@ -1,22 +1,35 @@ --- a/net/minecraft/world/entity/monster/Illusioner.java +++ b/net/minecraft/world/entity/monster/Illusioner.java -@@ -180,9 +_,19 @@ +@@ -172,7 +_,8 @@ + + @Override + public void performRangedAttack(LivingEntity target, float distanceFactor) { +- ItemStack itemInHand = this.getItemInHand(ProjectileUtil.getWeaponHoldingHand(this, Items.BOW)); ++ net.minecraft.world.InteractionHand hand = ProjectileUtil.getWeaponHoldingHand(this, Items.BOW); // Paper - call EntityShootBowEvent ++ ItemStack itemInHand = this.getItemInHand(hand); // Paper - call EntityShootBowEvent + ItemStack projectile = this.getProjectile(itemInHand); + AbstractArrow mobArrow = ProjectileUtil.getMobArrow(this, projectile, distanceFactor, itemInHand); + double d = target.getX() - this.getX(); +@@ -180,9 +_,21 @@ double d2 = target.getZ() - this.getZ(); double squareRoot = Math.sqrt(d * d + d2 * d2); if (this.level() instanceof ServerLevel serverLevel) { -+ // Paper start - EntityShootBowEvent -+ org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(this, this.getMainHandItem(), mobArrow.getPickupItem(), mobArrow, target.getUsedItemHand(), distanceFactor, true); +- Projectile.spawnProjectileUsingShoot( ++ Projectile.Delayed delayedEntity = Projectile.spawnProjectileUsingShootDelayed( // Paper - delayed + mobArrow, serverLevel, projectile, d, d1 + squareRoot * 0.2F, d2, 1.6F, 14 - serverLevel.getDifficulty().getId() * 4 + ); ++ ++ // Paper start - call EntityShootBowEvent ++ org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(this, itemInHand, mobArrow.getPickupItem(), mobArrow, hand, distanceFactor, true); + if (event.isCancelled()) { + event.getProjectile().remove(); + return; + } + + if (event.getProjectile() == mobArrow.getBukkitEntity()) { - Projectile.spawnProjectileUsingShoot( - mobArrow, serverLevel, projectile, d, d1 + squareRoot * 0.2F, d2, 1.6F, 14 - serverLevel.getDifficulty().getId() * 4 - ); ++ delayedEntity.spawn(); + } -+ // Paper end - EntityShootBowEvent ++ // Paper end - call EntityShootBowEvent } this.playSound(SoundEvents.SKELETON_SHOOT, 1.0F, 1.0F / (this.getRandom().nextFloat() * 0.4F + 0.8F)); From 7ec3174a332e4a51868685e2d948533e5335e418 Mon Sep 17 00:00:00 2001 From: Nassim Jahnke Date: Wed, 25 Jun 2025 22:14:20 +0200 Subject: [PATCH 21/25] Jump out of experimental phase --- paper-server/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paper-server/build.gradle.kts b/paper-server/build.gradle.kts index 3ea718cdb..7624ab328 100644 --- a/paper-server/build.gradle.kts +++ b/paper-server/build.gradle.kts @@ -381,7 +381,7 @@ fill { version(paperweight.minecraftVersion) build { - channel = BuildChannel.ALPHA + channel = BuildChannel.STABLE downloads { register("server:default") { From 4e1a2555be7ff1e6960714ee7fbabc13fa25d4c2 Mon Sep 17 00:00:00 2001 From: Nassim Jahnke Date: Wed, 25 Jun 2025 22:42:38 +0200 Subject: [PATCH 22/25] Update try catch for command handling Fixes #12729 --- .../sources/net/minecraft/commands/Commands.java.patch | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/paper-server/patches/sources/net/minecraft/commands/Commands.java.patch b/paper-server/patches/sources/net/minecraft/commands/Commands.java.patch index 1910b261a..b430d59a4 100644 --- a/paper-server/patches/sources/net/minecraft/commands/Commands.java.patch +++ b/paper-server/patches/sources/net/minecraft/commands/Commands.java.patch @@ -76,11 +76,15 @@ CommandSourceStack commandSourceStack = parseResults.getContext().getSource(); Profiler.get().push(() -> "/" + command); ContextChain contextChain = finishParsing(parseResults, command, commandSourceStack); -@@ -313,9 +_,10 @@ +@@ -312,10 +_,13 @@ + ) ); } - } catch (Exception var12) { -+ if (throwCommandError) throw var12; // Paper +- } catch (Exception var12) { ++ // Paper start ++ } catch (Throwable var12) { // always gracefully handle it, no matter how bad:tm: ++ if (throwCommandError) throw var12; // rethrow directly if requested ++ // Paper end MutableComponent mutableComponent = Component.literal(var12.getMessage() == null ? var12.getClass().getName() : var12.getMessage()); - if (LOGGER.isDebugEnabled()) { - LOGGER.error("Command exception: /{}", command, var12); From e382e6872ca9613c26697909ebeb7f0f6902614c Mon Sep 17 00:00:00 2001 From: Nassim Jahnke Date: Thu, 26 Jun 2025 16:58:05 +0200 Subject: [PATCH 23/25] Fix 0 yield on explosion events Fixes #12745 That cooked is mildly cooked and should be fixed differently, e.g. a custom loot context param, but reverting the recent change in this line works for now --- .../minecraft/world/level/block/state/BlockBehaviour.java.patch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/state/BlockBehaviour.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/state/BlockBehaviour.java.patch index 4b4f6939c..2cb6e4913 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/block/state/BlockBehaviour.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/block/state/BlockBehaviour.java.patch @@ -30,7 +30,7 @@ - builder.withParameter(LootContextParams.EXPLOSION_RADIUS, explosion.radius()); + // CraftBukkit start - add yield + if (explosion instanceof net.minecraft.world.level.ServerExplosion serverExplosion && serverExplosion.yield < 1.0F) { -+ builder.withParameter(LootContextParams.EXPLOSION_RADIUS, serverExplosion.yield == 0 ? 0 : 1.0F / serverExplosion.yield); ++ builder.withParameter(LootContextParams.EXPLOSION_RADIUS, 1.0F / serverExplosion.yield); + // CraftBukkit end } From 35b2c6ece4a8e9a2f4b9d3c21dc06d7b94dcc604 Mon Sep 17 00:00:00 2001 From: Shane Freeder Date: Thu, 26 Jun 2025 19:44:29 +0100 Subject: [PATCH 24/25] Use dropped item for stats info (#12747) We are already using the dropped stack to determine the type, we might as well also use it for the count, given that plugins can already mutate the type, might as well let them mess with the amount. --- .../features/0003-Entity-Activation-Range-2.0.patch | 12 ++++++------ .../0016-Moonrise-optimisation-patches.patch | 12 ++++++------ .../0033-Optimise-EntityScheduler-ticking.patch | 4 ++-- .../minecraft/server/level/ServerPlayer.java.patch | 3 ++- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/paper-server/patches/features/0003-Entity-Activation-Range-2.0.patch b/paper-server/patches/features/0003-Entity-Activation-Range-2.0.patch index bb84f6562..2cabaa3c6 100644 --- a/paper-server/patches/features/0003-Entity-Activation-Range-2.0.patch +++ b/paper-server/patches/features/0003-Entity-Activation-Range-2.0.patch @@ -354,7 +354,7 @@ index 0000000000000000000000000000000000000000..ae2bb9a73106febfe5f0d090abd4252b + } +} diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java -index 7b5ed00c9b2f22af5cbc44171192d674936dc7d7..5fe3c9a159908785e08fa874982bc1a82283bb1d 100644 +index d51645b115780dac9ff6010806e8bd62dedc8e9f..3faf1b5556c55f3468182f75b2dbff4aaa3aae3a 100644 --- a/net/minecraft/server/level/ChunkMap.java +++ b/net/minecraft/server/level/ChunkMap.java @@ -4,7 +4,6 @@ import com.google.common.collect.ImmutableList; @@ -484,7 +484,7 @@ index c70a58f5f633fa8e255f74c42f5e87c96b7b013a..ec20a5a6d7c8f65abda528fec36bec7b public void tick() { super.tick(); diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index f961540a00bfb5e1c8eb0e739f8ae535e9eee8f3..7453ddb09f349b7836f966573e4933646a75cba6 100644 +index 75f81a6bc156a6455a616b8de0d7701fd2255a2d..b3d951670b3a097d04cfe347d7df496b1d0a0e09 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java @@ -409,6 +409,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess @@ -661,10 +661,10 @@ index 3f780276be6766ef253c50212e06fd93a96b0caa..7e70c7bee633c54497d1cd2854dd60f4 } } diff --git a/net/minecraft/world/entity/item/ItemEntity.java b/net/minecraft/world/entity/item/ItemEntity.java -index 548d7c8dc517da6c4db86b11f579ae63e6db56cf..a29860af4c37b2b45df49f9ba18f7e38921dfb02 100644 +index fca31bbab8e7830933ceffcf992ff56ccc84414c..51804b611f469f2ab53e455e8c633b867b00cc88 100644 --- a/net/minecraft/world/entity/item/ItemEntity.java +++ b/net/minecraft/world/entity/item/ItemEntity.java -@@ -121,6 +121,29 @@ public class ItemEntity extends Entity implements TraceableEntity { +@@ -129,6 +129,29 @@ public class ItemEntity extends Entity implements TraceableEntity { return 0.04; } @@ -695,7 +695,7 @@ index 548d7c8dc517da6c4db86b11f579ae63e6db56cf..a29860af4c37b2b45df49f9ba18f7e38 public void tick() { if (this.getItem().isEmpty()) { diff --git a/net/minecraft/world/entity/npc/Villager.java b/net/minecraft/world/entity/npc/Villager.java -index b0607f4a9b35570b319423c7876bb904d5154e8e..98c8653647dc52059d8becfe38a74d4e62edf08f 100644 +index ef8347329b440833b45a54be2b6e4204ac0a425e..43f16df230f87a43e249a58fc10ef2da517f22ee 100644 --- a/net/minecraft/world/entity/npc/Villager.java +++ b/net/minecraft/world/entity/npc/Villager.java @@ -269,11 +269,35 @@ public class Villager extends AbstractVillager implements ReputationEventHandler @@ -838,7 +838,7 @@ index 52acc72841f0c6980f5f3f8ef21d0b29dd472ce3..41a6ec508a10a49a37539d2f10171d15 + } diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java -index d26e4d85d8fd8bd4f0c7de30b50a2ce370b37bf5..bab28e7afb7b1249d40631aabff16fc18cf95ea0 100644 +index 06069d3ac598f5f12feab038de4f1199794298f6..980eaba27ce2616c1573a4760cf4acc2dd251190 100644 --- a/net/minecraft/world/level/Level.java +++ b/net/minecraft/world/level/Level.java @@ -143,6 +143,12 @@ public abstract class Level implements LevelAccessor, UUIDLookup, AutoCl diff --git a/paper-server/patches/features/0016-Moonrise-optimisation-patches.patch b/paper-server/patches/features/0016-Moonrise-optimisation-patches.patch index 0f72fcdc4..ebd69473a 100644 --- a/paper-server/patches/features/0016-Moonrise-optimisation-patches.patch +++ b/paper-server/patches/features/0016-Moonrise-optimisation-patches.patch @@ -27691,7 +27691,7 @@ index 49008b4cbaead8a66a93d2b0d4b50b335a6c3eed..f9c96bbdc54e68b9216b7f8662bfae03 } diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java -index 69dbff3f62e3a9ba7090156380f842bb44deebf8..c0b74d408340101bc3aac4cb4b7232c5cc78b08a 100644 +index 6f7f92cc43c56a7453b289f205502d089474ef6d..b390ba657b8b880e431c84e9dd948ac9c84af2fd 100644 --- a/net/minecraft/server/level/ServerPlayer.java +++ b/net/minecraft/server/level/ServerPlayer.java @@ -193,7 +193,7 @@ import net.minecraft.world.scores.Team; @@ -28728,7 +28728,7 @@ index 8cc5c0716392ba06501542ff5cbe71ee43979e5d..09fd99c9cbd23b5f3c899bfb00c9b896 + // Paper end - block counting } diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index 27a01fd28ea565221768f31df02f0a2ddf242fce..45cdbea0bbf12697ffd1fb2193c2eafe34142ea9 100644 +index 06d07f93e42edcfdd7d69df0b52efe2a58e7dfc1..da880f52920b1101f23ef94f3fd0dbdea218c373 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java @@ -147,7 +147,7 @@ import net.minecraft.world.waypoints.WaypointTransmitter; @@ -29436,7 +29436,7 @@ index 27a01fd28ea565221768f31df02f0a2ddf242fce..45cdbea0bbf12697ffd1fb2193c2eafe if (!checkPosition(this, x, y, z)) { return; } -@@ -4828,6 +5135,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -4818,6 +5125,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess @Override public final void setRemoved(Entity.RemovalReason removalReason, @Nullable org.bukkit.event.entity.EntityRemoveEvent.Cause cause) { // CraftBukkit - add Bukkit remove cause @@ -29449,7 +29449,7 @@ index 27a01fd28ea565221768f31df02f0a2ddf242fce..45cdbea0bbf12697ffd1fb2193c2eafe org.bukkit.craftbukkit.event.CraftEventFactory.callEntityRemoveEvent(this, cause); // CraftBukkit final boolean alreadyRemoved = this.removalReason != null; // Paper - Folia schedulers if (this.removalReason == null) { -@@ -4838,7 +5151,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -4828,7 +5141,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess this.stopRiding(); } @@ -29458,7 +29458,7 @@ index 27a01fd28ea565221768f31df02f0a2ddf242fce..45cdbea0bbf12697ffd1fb2193c2eafe this.levelCallback.onRemove(removalReason); this.onRemoval(removalReason); // Paper start - Folia schedulers -@@ -4872,7 +5185,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -4862,7 +5175,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess public boolean shouldBeSaved() { return (this.removalReason == null || this.removalReason.shouldSave()) && !this.isPassenger() @@ -31632,7 +31632,7 @@ index 96e8dfb1ff24954656470925a1fc6280fe5e09d9..be6f37f91569c659c609e5e8d38671ca public void animateTick(BlockState state, Level level, BlockPos pos, RandomSource random) { diff --git a/net/minecraft/world/level/block/state/BlockBehaviour.java b/net/minecraft/world/level/block/state/BlockBehaviour.java -index c846d5d47c6488b11930b858da946e636e025294..d4c02b9bb9bfc10484a79ede35985ba35c99bada 100644 +index 1c81dc30aabb354d18290d42dfc419d9b1581fbd..834e27ef2f7b342b074ff9e1e390e02f3ca1c399 100644 --- a/net/minecraft/world/level/block/state/BlockBehaviour.java +++ b/net/minecraft/world/level/block/state/BlockBehaviour.java @@ -413,7 +413,7 @@ public abstract class BlockBehaviour implements FeatureElement { diff --git a/paper-server/patches/features/0033-Optimise-EntityScheduler-ticking.patch b/paper-server/patches/features/0033-Optimise-EntityScheduler-ticking.patch index adfb05f60..ac688a653 100644 --- a/paper-server/patches/features/0033-Optimise-EntityScheduler-ticking.patch +++ b/paper-server/patches/features/0033-Optimise-EntityScheduler-ticking.patch @@ -67,10 +67,10 @@ index 0a260fdf6b198a8ab52e60bf6db2fb5eab719c48..52fa5112cd90ba766c94512a02401dd3 profilerFiller.push("commandFunctions"); this.getFunctions().tick(); diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index 45cdbea0bbf12697ffd1fb2193c2eafe34142ea9..81413ac0de7b3c7a72bc606fe5ae6fb4ae7055e3 100644 +index da880f52920b1101f23ef94f3fd0dbdea218c373..3d2c0a4d3a1f9d3e5cc6cd0cdb988ae1205de821 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java -@@ -5175,6 +5175,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -5165,6 +5165,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess this.getBukkitEntity().taskScheduler.retire(); } // Paper end - Folia schedulers diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch index 4e9066f0c..382b39f8c 100644 --- a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch @@ -1334,7 +1334,8 @@ if (traceItem) { - ItemStack itemStack = itemEntity != null ? itemEntity.getItem() : ItemStack.EMPTY; if (!itemStack.isEmpty()) { - this.awardStat(Stats.ITEM_DROPPED.get(itemStack.getItem()), droppedItem.getCount()); +- this.awardStat(Stats.ITEM_DROPPED.get(itemStack.getItem()), droppedItem.getCount()); ++ this.awardStat(Stats.ITEM_DROPPED.get(itemStack.getItem()), itemStack.getCount()); // Paper - use size from dropped item this.awardStat(Stats.DROP); } } From bd79e20c6627faadad184bd2e6dbea31b35d32d0 Mon Sep 17 00:00:00 2001 From: FlorianMichael Date: Thu, 26 Jun 2025 21:17:20 +0200 Subject: [PATCH 25/25] [ci skip] PluginManager#getPlugin and PluginManager#isPluginEnabled are case-insensitive (#12723) --- paper-api/src/main/java/org/bukkit/plugin/PluginManager.java | 4 ++-- .../src/main/java/org/bukkit/plugin/SimplePluginManager.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/paper-api/src/main/java/org/bukkit/plugin/PluginManager.java b/paper-api/src/main/java/org/bukkit/plugin/PluginManager.java index 47153dee6..215accd4e 100644 --- a/paper-api/src/main/java/org/bukkit/plugin/PluginManager.java +++ b/paper-api/src/main/java/org/bukkit/plugin/PluginManager.java @@ -29,7 +29,7 @@ public interface PluginManager extends io.papermc.paper.plugin.PermissionManager /** * Checks if the given plugin is loaded and returns it when applicable *

- * Please note that the name of the plugin is case-sensitive + * Please note that the name of the plugin is case-insensitive * * @param name Name of the plugin to check * @return Plugin if it exists, otherwise null @@ -48,7 +48,7 @@ public interface PluginManager extends io.papermc.paper.plugin.PermissionManager /** * Checks if the given plugin is enabled or not *

- * Please note that the name of the plugin is case-sensitive. + * Please note that the name of the plugin is case-insensitive. * * @param name Name of the plugin to check * @return true if the plugin is enabled, otherwise false diff --git a/paper-api/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/paper-api/src/main/java/org/bukkit/plugin/SimplePluginManager.java index 001465eed..dfc2d7640 100644 --- a/paper-api/src/main/java/org/bukkit/plugin/SimplePluginManager.java +++ b/paper-api/src/main/java/org/bukkit/plugin/SimplePluginManager.java @@ -464,7 +464,7 @@ public final class SimplePluginManager implements PluginManager { /** * Checks if the given plugin is loaded and returns it when applicable *

- * Please note that the name of the plugin is case-sensitive + * Please note that the name of the plugin is case-insensitive * * @param name Name of the plugin to check * @return Plugin if it exists, otherwise null @@ -486,7 +486,7 @@ public final class SimplePluginManager implements PluginManager { /** * Checks if the given plugin is enabled or not *

- * Please note that the name of the plugin is case-sensitive. + * Please note that the name of the plugin is case-insensitive. * * @param name Name of the plugin to check * @return true if the plugin is enabled, otherwise false